From 4ad2c84fcea1e70d821346737159e4f82a86121e Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 9 Oct 2023 22:18:51 +0000 Subject: [PATCH 001/786] [CI] Updating repo.json for 1.0.2.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index f0732bc..a4b92b9 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.2.1", - "TestingAssemblyVersion": "1.0.2.1", + "AssemblyVersion": "1.0.2.2", + "TestingAssemblyVersion": "1.0.2.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.2.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.2.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.2.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.2.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.2.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.2.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 09493984c070a2c42adc71b9c9bba7e7643fb756 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Tue, 10 Oct 2023 21:41:12 +0200 Subject: [PATCH 002/786] Disable window sounds in GenericPopupWindow --- Glamourer/Gui/GenericPopupWindow.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index 7a201e8..0c891f1 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -24,6 +24,7 @@ public class GenericPopupWindow : Window | ImGuiWindowFlags.NoTitleBar, true) { _config = config; + DisableWindowSounds = true; IsOpen = true; } From 277b26cc92f568cc922c1e3fbdd84c6b144e27d8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 11 Oct 2023 17:22:46 +0200 Subject: [PATCH 003/786] Add a quick design bar. --- Glamourer/Configuration.cs | 44 +-- Glamourer/Events/DesignChanged.cs | 8 +- Glamourer/Gui/ConvenienceRevertButtons.cs | 62 ----- Glamourer/Gui/DesignCombo.cs | 179 ++++++++++++ Glamourer/Gui/DesignQuickBar.cs | 255 ++++++++++++++++++ Glamourer/Gui/GlamourerWindowSystem.cs | 3 +- Glamourer/Gui/MainWindow.cs | 39 +-- .../Gui/Tabs/AutomationTab/DesignCombo.cs | 109 -------- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 8 +- Glamourer/Gui/Tabs/SettingsTab.cs | 46 +++- Glamourer/Services/ServiceManager.cs | 3 +- 11 files changed, 533 insertions(+), 223 deletions(-) delete mode 100644 Glamourer/Gui/ConvenienceRevertButtons.cs create mode 100644 Glamourer/Gui/DesignCombo.cs create mode 100644 Glamourer/Gui/DesignQuickBar.cs delete mode 100644 Glamourer/Gui/Tabs/AutomationTab/DesignCombo.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 6aaa11c..f7607b5 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Dalamud.Configuration; +using Dalamud.Game.ClientState.Keys; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Gui; @@ -18,25 +19,30 @@ namespace Glamourer; public class Configuration : IPluginConfiguration, ISavable { - public bool Enabled { get; set; } = true; - public bool UseRestrictedGearProtection { get; set; } = false; - public bool OpenFoldersByDefault { get; set; } = false; - public bool AutoRedrawEquipOnChanges { get; set; } = false; - public bool EnableAutoDesigns { get; set; } = true; - public bool IncognitoMode { get; set; } = false; - public bool UnlockDetailMode { 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 MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; - public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); + public bool Enabled { get; set; } = true; + public bool UseRestrictedGearProtection { get; set; } = false; + public bool OpenFoldersByDefault { get; set; } = false; + public bool AutoRedrawEquipOnChanges { get; set; } = false; + public bool EnableAutoDesigns { get; set; } = true; + public bool IncognitoMode { get; set; } = false; + public bool UnlockDetailMode { 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 ShowDesignQuickBar { get; set; } = false; + public bool LockDesignQuickBar { get; set; } = false; + public bool ShowQuickBarInTabs { get; set; } = true; + + public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.D, ModifierHotkey.Control, ModifierHotkey.Shift); + public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; + public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index c06eb53..9c8e189 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -1,5 +1,6 @@ using System; using Glamourer.Designs; +using Glamourer.Gui; using OtterGui.Classes; namespace Glamourer.Events; @@ -76,14 +77,17 @@ public sealed class DesignChanged : EventWrapper + AutoDesignManager = 1, + /// DesignFileSystem = 0, /// DesignFileSystemSelector = -1, - /// - AutoDesignManager = 1, + /// + DesignCombo = -2, } public DesignChanged() diff --git a/Glamourer/Gui/ConvenienceRevertButtons.cs b/Glamourer/Gui/ConvenienceRevertButtons.cs deleted file mode 100644 index bf8bd11..0000000 --- a/Glamourer/Gui/ConvenienceRevertButtons.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Numerics; -using Dalamud.Interface; -using Glamourer.Automation; -using Glamourer.Events; -using Glamourer.Interop; -using Glamourer.State; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; - -namespace Glamourer.Gui; - -public class ConvenienceRevertButtons -{ - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly ObjectManager _objects; - private readonly Configuration _config; - - - public ConvenienceRevertButtons(StateManager stateManager, AutoDesignApplier autoDesignApplier, ObjectManager objects, - Configuration config) - { - _stateManager = stateManager; - _autoDesignApplier = autoDesignApplier; - _objects = objects; - _config = config; - } - - public void DrawButtons(float yPos) - { - _objects.Update(); - var (playerIdentifier, playerData) = _objects.PlayerData; - - string? error = null; - if (!playerIdentifier.IsValid || !playerData.Valid) - error = "No player character available."; - - if (!_stateManager.TryGetValue(playerIdentifier, out var state)) - error = "The player character was not modified by Glamourer yet."; - else if (state.IsLocked) - error = "The state of the player character is currently locked."; - - var buttonSize = new Vector2(ImGui.GetFrameHeight()); - var spacing = ImGui.GetStyle().ItemInnerSpacing; - ImGui.SetCursorPos(new Vector2(ImGui.GetWindowContentRegionMax().X - 2 * buttonSize.X - spacing.X, yPos - 1)); - - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.RedoAlt.ToIconString(), buttonSize, - error ?? "Revert the player character to its game state.", error != null, true)) - _stateManager.ResetState(state, StateChanged.Source.Manual); - - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.SyncAlt.ToIconString(), buttonSize, - error ?? "Revert the player character to its automation state.", error != null && _config.EnableAutoDesigns, true)) - foreach (var actor in playerData.Objects) - { - _autoDesignApplier.ReapplyAutomation(actor, playerIdentifier, state); - _stateManager.ReapplyState(actor); - } - } -} diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs new file mode 100644 index 0000000..a60a69b --- /dev/null +++ b/Glamourer/Gui/DesignCombo.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Dalamud.Interface.Utility; +using Glamourer.Automation; +using Glamourer.Customization; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.Services; +using ImGuiNET; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Log; +using OtterGui.Widgets; + +namespace Glamourer.Gui; + +public abstract class DesignComboBase : FilterComboCache>, IDisposable +{ + private readonly Configuration _config; + private readonly DesignChanged _designChanged; + protected readonly TabSelected TabSelected; + protected float InnerWidth; + + protected DesignComboBase(Func>> generator, Logger log, DesignChanged designChanged, + TabSelected tabSelected, Configuration config) + : base(generator, log) + { + _designChanged = designChanged; + TabSelected = tabSelected; + _config = config; + _designChanged.Subscribe(OnDesignChange, DesignChanged.Priority.DesignCombo); + } + + public bool Incognito + => _config.IncognitoMode; + + void IDisposable.Dispose() + => _designChanged.Unsubscribe(OnDesignChange); + + protected override bool DrawSelectable(int globalIdx, bool selected) + { + var ret = base.DrawSelectable(globalIdx, selected); + var (design, path) = Items[globalIdx]; + if (path.Length > 0 && design.Name != path) + { + var start = ImGui.GetItemRectMin(); + var pos = start.X + ImGui.CalcTextSize(design.Name).X; + var maxSize = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X; + var remainingSpace = maxSize - pos; + var requiredSize = ImGui.CalcTextSize(path).X + ImGui.GetStyle().ItemInnerSpacing.X; + var offset = remainingSpace - requiredSize; + if (ImGui.GetScrollMaxY() == 0) + offset -= ImGui.GetStyle().ItemInnerSpacing.X; + + if (offset < ImGui.GetStyle().ItemSpacing.X) + ImGuiUtil.HoverTooltip(path); + else + ImGui.GetWindowDrawList().AddText(start with { X = pos + offset }, + ImGui.GetColorU32(ImGuiCol.TextDisabled), path); + } + + return ret; + } + + protected bool Draw(Design? currentDesign, string? label, float width) + { + InnerWidth = 400 * ImGuiHelpers.GlobalScale; + CurrentSelectionIdx = Math.Max(Items.IndexOf(p => currentDesign == p.Item1), 0); + CurrentSelection = Items[CurrentSelectionIdx]; + var name = label ?? "Select Design Here..."; + var ret = Draw("##design", name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()) + && CurrentSelection != null; + + if (currentDesign != null) + { + if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) + TabSelected.Invoke(MainWindow.TabType.Designs, currentDesign); + ImGuiUtil.HoverTooltip("Control + Right-Click to move to design."); + } + + return ret; + } + + protected override string ToString(Tuple obj) + => obj.Item1.Name.Text; + + protected override float GetFilterWidth() + => InnerWidth - 2 * ImGui.GetStyle().FramePadding.X; + + protected override bool IsVisible(int globalIndex, LowerString filter) + { + var (design, path) = Items[globalIndex]; + return filter.IsContained(path) || design.Name.Lower.Contains(filter.Lower); + } + + private void OnDesignChange(DesignChanged.Type type, Design design, object? data = null) + { + switch (type) + { + case DesignChanged.Type.Created: + case DesignChanged.Type.Renamed: + Cleanup(); + break; + case DesignChanged.Type.Deleted: + Cleanup(); + if (CurrentSelection?.Item1 == design) + { + CurrentSelectionIdx = -1; + CurrentSelection = null; + } + + break; + } + } +} + +public sealed class DesignCombo : DesignComboBase +{ + public DesignCombo(DesignManager designs, DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, TabSelected tabSelected, + Configuration config) + : base( + () => designs.Designs + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .OrderBy(d => d.Item2) + .ToList(), log, designChanged, tabSelected, config) + { } + + public Design? Design + => CurrentSelection?.Item1; + + public void Draw(float width) + => Draw(Design, (Incognito ? Design?.Incognito : Design?.Name.Text) ?? string.Empty, width); +} + +public sealed class RevertDesignCombo : DesignComboBase, IDisposable +{ + public const int RevertDesignIndex = -1228; + public readonly Design RevertDesign; + private readonly AutoDesignManager _autoDesignManager; + + public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, + ItemManager items, CustomizationService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, + Configuration config) + : this(designs, fileSystem, tabSelected, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, config) + { } + + private RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, + Design revertDesign, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, Configuration config) + : base(() => designs.Designs + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .OrderBy(d => d.Item2) + .Prepend(new Tuple(revertDesign, string.Empty)) + .ToList(), log, designChanged, tabSelected, config) + { + RevertDesign = revertDesign; + _autoDesignManager = autoDesignManager; + } + + + public void Draw(AutoDesignSet set, AutoDesign? design, int autoDesignIndex) + { + if (!Draw(design?.Design, design?.Name(Incognito), ImGui.GetContentRegionAvail().X)) + return; + + if (autoDesignIndex >= 0) + _autoDesignManager.ChangeDesign(set, autoDesignIndex, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1); + else + _autoDesignManager.AddDesign(set, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1); + } + + private static Design CreateRevertDesign(CustomizationService customize, ItemManager items) + => new(customize, items) + { + Index = RevertDesignIndex, + Name = AutoDesign.RevertName, + ApplyCustomize = CustomizeFlagExtensions.AllRelevant, + }; +} diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs new file mode 100644 index 0000000..fd9976f --- /dev/null +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -0,0 +1,255 @@ +using System; +using System.Numerics; +using Dalamud.Game.ClientState.Keys; +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface.Windowing; +using Dalamud.Plugin.Services; +using Glamourer.Automation; +using Glamourer.Events; +using Glamourer.Interop; +using Glamourer.Interop.Structs; +using Glamourer.State; +using ImGuiNET; +using OtterGui; +using OtterGui.Classes; +using Penumbra.GameData.Actors; + +namespace Glamourer.Gui; + +public class DesignQuickBar : Window, IDisposable +{ + private ImGuiWindowFlags GetFlags + => _config.LockDesignQuickBar + ? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoBackground + : ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing; + + private readonly Configuration _config; + private readonly DesignCombo _designCombo; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly ObjectManager _objects; + private readonly IKeyState _keyState; + private readonly ImRaii.Style _windowPadding = new(); + private DateTime _keyboardToggle = DateTime.UnixEpoch; + + public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState, + ObjectManager objects, AutoDesignApplier autoDesignApplier) + : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration) + { + _config = config; + _designCombo = designCombo; + _stateManager = stateManager; + _keyState = keyState; + _objects = objects; + _autoDesignApplier = autoDesignApplier; + IsOpen = _config.ShowDesignQuickBar; + DisableWindowSounds = true; + Size = Vector2.Zero; + } + + public void Dispose() + => _windowPadding.Dispose(); + + public override void PreOpenCheck() + { + CheckHotkeys(); + IsOpen = _config.ShowDesignQuickBar; + } + + public override void PreDraw() + { + Flags = GetFlags; + Size = new Vector2(12 * ImGui.GetFrameHeight(), ImGui.GetFrameHeight()); + + _windowPadding.Push(ImGuiStyleVar.WindowPadding, new Vector2(ImGuiHelpers.GlobalScale * 4)); + } + + public override void PostDraw() + => _windowPadding.Dispose(); + + + public override void Draw() + => Draw(ImGui.GetContentRegionAvail().X); + + 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); + var contentRegion = width; + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + var comboSize = contentRegion - 3 * buttonSize.X - 3 * spacing.X; + _designCombo.Draw(comboSize); + PrepareButtons(); + ImGui.SameLine(); + DrawApplyButton(buttonSize); + ImGui.SameLine(); + DrawRevertButton(buttonSize); + ImGui.SameLine(); + DrawRevertAutomationButton(buttonSize); + } + + private ActorIdentifier _playerIdentifier; + private ActorData _playerData; + private ActorState? _playerState; + + private ActorData _targetData; + private ActorIdentifier _targetIdentifier; + private ActorState? _targetState; + + private void PrepareButtons() + { + _objects.Update(); + (_playerIdentifier, _playerData) = _objects.PlayerData; + (_targetIdentifier, _targetData) = _objects.TargetData; + if (!_stateManager.TryGetValue(_playerIdentifier, out _playerState)) + _playerState = null; + if (!_stateManager.TryGetValue(_targetIdentifier, out _targetState)) + _targetState = null; + } + + private void DrawApplyButton(Vector2 size) + { + var design = _designCombo.Design; + var available = 0; + var tooltip = string.Empty; + if (design == null) + { + tooltip = "No design selected."; + } + else + { + if (_playerIdentifier.IsValid && _playerData.Valid) + { + available |= 1; + tooltip = $"Left-Click: Apply {(_config.IncognitoMode ? design.Incognito : design.Name)} to yourself."; + } + + if (_targetIdentifier.IsValid && _targetData.Valid) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Apply {(_config.IncognitoMode ? design.Incognito : design.Name)} to {_targetIdentifier}."; + } + + if (available == 0) + tooltip = "Neither player character nor target available."; + } + + + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.PlayCircle, size, tooltip, available); + if (!clicked) + return; + + if (state == null && !_stateManager.GetOrCreate(id, data.Objects[0], out state)) + { + Glamourer.Messager.NotificationMessage($"Could not apply {design!.Incognito} to {id.Incognito(null)}: Failed to create state."); + return; + } + + var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); + using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize); + _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); + } + + public void DrawRevertButton(Vector2 buttonSize) + { + var available = 0; + var tooltip = string.Empty; + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false }) + { + available |= 1; + tooltip = "Left-Click: Revert the player character to their game state."; + } + + if (_targetIdentifier.IsValid && _targetState is { IsLocked: false }) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Revert {_targetIdentifier} to their game state."; + } + + if (available == 0) + tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.RedoAlt, buttonSize, tooltip, available); + if (clicked) + _stateManager.ResetState(state!, StateChanged.Source.Manual); + } + + public void DrawRevertAutomationButton(Vector2 buttonSize) + { + var available = 0; + var tooltip = string.Empty; + if (!_config.EnableAutoDesigns) + { + tooltip = "Automation is not enabled, you can not reset to automation state."; + } + else + { + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) + { + available |= 1; + tooltip = "Left-Click: Revert the player character to their automation state."; + } + + if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Revert {_targetIdentifier} to their automation state."; + } + + if (available == 0) + tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + } + + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, tooltip, available); + if (!clicked) + { } + else + { + foreach (var actor in data.Objects) + { + _autoDesignApplier.ReapplyAutomation(actor, id, state!); + _stateManager.ReapplyState(actor); + } + } + } + + private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip, + int available) + { + ImGuiUtil.DrawDisabledButton(icon.ToIconString(), buttonSize, tooltip, available == 0, true); + if ((available & 1) == 1 && ImGui.IsItemClicked(ImGuiMouseButton.Left)) + return (true, _playerIdentifier, _playerData, _playerState); + if ((available & 2) == 2 && ImGui.IsItemClicked(ImGuiMouseButton.Right)) + return (true, _targetIdentifier, _targetData, _targetState); + + return (false, ActorIdentifier.Invalid, ActorData.Invalid, null); + } + + private void CheckHotkeys() + { + if (_keyboardToggle > DateTime.UtcNow || !CheckKeyState(_config.ToggleQuickDesignBar, false)) + return; + + _keyboardToggle = DateTime.UtcNow.AddMilliseconds(500); + _config.ShowDesignQuickBar = !_config.ShowDesignQuickBar; + _config.Save(); + } + + public bool CheckKeyState(ModifiableHotkey key, bool noKey) + { + if (key.Hotkey == VirtualKey.NO_KEY) + return noKey; + + return _keyState[key.Hotkey] && key.Modifier1.IsActive() && key.Modifier2.IsActive(); + } +} diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 8858c9e..95a1042 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -13,7 +13,7 @@ public class GlamourerWindowSystem : IDisposable private readonly PenumbraChangedItemTooltip _penumbraTooltip; public GlamourerWindowSystem(UiBuilder uiBuilder, MainWindow ui, GenericPopupWindow popups, PenumbraChangedItemTooltip penumbraTooltip, - Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog) + Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog, DesignQuickBar quick) { _uiBuilder = uiBuilder; _ui = ui; @@ -22,6 +22,7 @@ public class GlamourerWindowSystem : IDisposable _windowSystem.AddWindow(popups); _windowSystem.AddWindow(unlocksTab); _windowSystem.AddWindow(changelog.Changelog); + _windowSystem.AddWindow(quick); _uiBuilder.Draw += _windowSystem.Draw; _uiBuilder.OpenConfigUi += _ui.Toggle; _uiBuilder.DisableCutsceneUiHide = !config.HideWindowInCutscene; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 1a68018..4b58979 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -1,6 +1,6 @@ using System; using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Glamourer.Designs; @@ -30,10 +30,10 @@ public class MainWindow : Window, IDisposable Messages = 6, } - private readonly Configuration _config; - private readonly TabSelected _event; - private readonly ConvenienceRevertButtons _convenienceButtons; - private readonly ITab[] _tabs; + private readonly Configuration _config; + private readonly DesignQuickBar _quickBar; + private readonly TabSelected _event; + private readonly ITab[] _tabs; public readonly SettingsTab Settings; public readonly ActorTab Actors; @@ -46,8 +46,7 @@ public class MainWindow : Window, IDisposable public TabType SelectTab = TabType.None; public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs, - DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, ConvenienceRevertButtons convenienceButtons, - MessagesTab messages) + DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar) : base(GetLabel()) { pi.UiBuilder.DisableGposeUiHide = true; @@ -56,16 +55,16 @@ public class MainWindow : Window, IDisposable MinimumSize = new Vector2(700, 675), MaximumSize = ImGui.GetIO().DisplaySize, }; - Settings = settings; - Actors = actors; - Designs = designs; - Automation = automation; - Debug = debugTab; - Unlocks = unlocks; - _event = @event; - _convenienceButtons = convenienceButtons; - Messages = messages; - _config = config; + Settings = settings; + Actors = actors; + Designs = designs; + Automation = automation; + Debug = debugTab; + Unlocks = unlocks; + _event = @event; + Messages = messages; + _quickBar = quickBar; + _config = config; _tabs = new ITab[] { settings, @@ -93,7 +92,11 @@ public class MainWindow : Window, IDisposable _config.Save(); } - _convenienceButtons.DrawButtons(yPos); + if (_config.ShowQuickBarInTabs) + { + ImGui.SetCursorPos(new Vector2(ImGui.GetWindowContentRegionMax().X - 10 * ImGui.GetFrameHeight(), yPos - ImGuiHelpers.GlobalScale)); + _quickBar.Draw(); + } } private ReadOnlySpan ToLabel(TabType type) diff --git a/Glamourer/Gui/Tabs/AutomationTab/DesignCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/DesignCombo.cs deleted file mode 100644 index 998270b..0000000 --- a/Glamourer/Gui/Tabs/AutomationTab/DesignCombo.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Linq; -using Dalamud.Interface.Utility; -using Glamourer.Automation; -using Glamourer.Customization; -using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.Services; -using ImGuiNET; -using OtterGui; -using OtterGui.Classes; -using OtterGui.Log; -using OtterGui.Widgets; - -namespace Glamourer.Gui.Tabs.AutomationTab; - -public sealed class DesignCombo : FilterComboCache<(Design, string)> -{ - public const int RevertDesignIndex = -1228; - public readonly Design RevertDesign; - - private readonly AutoDesignManager _manager; - private readonly TabSelected _tabSelected; - private float _innerWidth; - - public DesignCombo(AutoDesignManager manager, DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, - ItemManager items, CustomizationService customize, Logger log) - : this(manager, designs, fileSystem, tabSelected, CreateRevertDesign(customize, items), log) - { } - - private DesignCombo(AutoDesignManager manager, DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, - Design revertDesign, Logger log) - : base(() => designs.Designs.Select(d => (d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)).OrderBy(d => d.Item2) - .Prepend((revertDesign, string.Empty)).ToList(), log) - { - _manager = manager; - _tabSelected = tabSelected; - RevertDesign = revertDesign; - } - - protected override bool DrawSelectable(int globalIdx, bool selected) - { - var ret = base.DrawSelectable(globalIdx, selected); - var (design, path) = Items[globalIdx]; - if (path.Length > 0 && design.Name != path) - { - var start = ImGui.GetItemRectMin(); - var pos = start.X + ImGui.CalcTextSize(design.Name).X; - var maxSize = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X; - var remainingSpace = maxSize - pos; - var requiredSize = ImGui.CalcTextSize(path).X + ImGui.GetStyle().ItemInnerSpacing.X; - var offset = remainingSpace - requiredSize; - if (ImGui.GetScrollMaxY() == 0) - offset -= ImGui.GetStyle().ItemInnerSpacing.X; - - if (offset < ImGui.GetStyle().ItemSpacing.X) - ImGuiUtil.HoverTooltip(path); - else - ImGui.GetWindowDrawList().AddText(start with { X = pos + offset }, - ImGui.GetColorU32(ImGuiCol.TextDisabled), path); - } - - return ret; - } - - protected override float GetFilterWidth() - => _innerWidth - 2 * ImGui.GetStyle().FramePadding.X; - - public void Draw(AutoDesignSet set, AutoDesign? design, int autoDesignIndex, bool incognito) - { - _innerWidth = 400 * ImGuiHelpers.GlobalScale; - CurrentSelectionIdx = Math.Max(Items.IndexOf(p => design?.Design == p.Item1), 0); - CurrentSelection = Items[CurrentSelectionIdx]; - var name = design?.Name(incognito) ?? "Select Design Here..."; - if (Draw("##design", name, string.Empty, ImGui.GetContentRegionAvail().X, - ImGui.GetTextLineHeightWithSpacing()) - && CurrentSelection.Item1 != null) - { - if (autoDesignIndex >= 0) - _manager.ChangeDesign(set, autoDesignIndex, CurrentSelection.Item1 == RevertDesign ? null : CurrentSelection.Item1); - else - _manager.AddDesign(set, CurrentSelection.Item1 == RevertDesign ? null : CurrentSelection.Item1); - } - - if (design?.Design != null) - { - if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) - _tabSelected.Invoke(MainWindow.TabType.Designs, design.Design); - ImGuiUtil.HoverTooltip("Control + Right-Click to move to design."); - } - } - - protected override string ToString((Design, string) obj) - => obj.Item1.Name.Text; - - protected override bool IsVisible(int globalIndex, LowerString filter) - { - var (design, path) = Items[globalIndex]; - return filter.IsContained(path) || design.Name.Lower.Contains(filter.Lower); - } - - private static Design CreateRevertDesign(CustomizationService customize, ItemManager items) - => new(customize, items) - { - Index = RevertDesignIndex, - Name = AutoDesign.RevertName, - ApplyCustomize = CustomizeFlagExtensions.AllRelevant, - }; -} diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index a1c1fcf..3a0f437 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -30,7 +30,7 @@ public class SetPanel private readonly CustomizationService _customizations; private readonly Configuration _config; - private readonly DesignCombo _designCombo; + private readonly RevertDesignCombo _designCombo; private readonly JobGroupCombo _jobGroupCombo; private readonly IdentifierDrawer _identifierDrawer; @@ -39,7 +39,7 @@ public class SetPanel private Action? _endAction; - public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, DesignCombo designCombo, + public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, RevertDesignCombo designCombo, CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer, Configuration config) { _selector = selector; @@ -209,7 +209,7 @@ public class SetPanel ImGui.Selectable($"#{idx + 1:D2}"); DrawDragDrop(Selection, idx); ImGui.TableNextColumn(); - _designCombo.Draw(Selection, design, idx, _selector.IncognitoMode); + _designCombo.Draw(Selection, design, idx); DrawDragDrop(Selection, idx); if (singleRow) { @@ -237,7 +237,7 @@ public class SetPanel ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted("New"); ImGui.TableNextColumn(); - _designCombo.Draw(Selection, null, -1, _selector.IncognitoMode); + _designCombo.Draw(Selection, null, -1); ImGui.TableNextRow(); _endAction?.Invoke(); diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index ff55b14..a604860 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -1,9 +1,12 @@ using System; +using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; +using Dalamud.Game.ClientState.Keys; using Dalamud.Interface; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; +using Dalamud.Plugin.Services; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -18,6 +21,7 @@ namespace Glamourer.Gui.Tabs; public class SettingsTab : ITab { + private readonly VirtualKey[] _validKeys; private readonly Configuration _config; private readonly DesignFileSystemSelector _selector; private readonly StateListener _stateListener; @@ -30,7 +34,7 @@ public class SettingsTab : ITab public SettingsTab(Configuration config, DesignFileSystemSelector selector, StateListener stateListener, CodeService codeService, PenumbraAutoRedraw autoRedraw, ContextMenuService contextMenuService, UiBuilder uiBuilder, - GlamourerChangelog changelog, FunModule funModule) + GlamourerChangelog changelog, FunModule funModule, IKeyState keys) { _config = config; _selector = selector; @@ -41,6 +45,7 @@ public class SettingsTab : ITab _uiBuilder = uiBuilder; _changelog = changelog; _funModule = funModule; + _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); } public ReadOnlySpan Label @@ -99,11 +104,23 @@ public class SettingsTab : ITab if (!ImGui.CollapsingHeader("Interface")) return; - 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); - 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); + Checkbox("Show Quick Design Bar", + "Show a bar separate from the main window that allows you to quickly apply designs or revert your character and target.", + _config.ShowDesignQuickBar, v => _config.ShowDesignQuickBar = v); + Checkbox("Lock Quick Design Bar", "Prevent the quick design bar from being moved and lock it in place.", _config.LockDesignQuickBar, + v => _config.LockDesignQuickBar = v); + if (Widget.ModifiableKeySelector("Hotkey to Toggle Quick Design Bar", "Set a hotkey that opens or closes the quick design bar.", + 100 * ImGuiHelpers.GlobalScale, + _config.ToggleQuickDesignBar, v => _config.ToggleQuickDesignBar = v, _validKeys)) + _config.Save(); + Checkbox("Show Quick Design Bar in Main Window", + "Show the quick design bar in the tab selection part of the main window, too.", + _config.ShowQuickBarInTabs, v => _config.ShowQuickBarInTabs = v); + + ImGui.Dummy(Vector2.Zero); + ImGui.Separator(); + ImGui.Dummy(Vector2.Zero); + Checkbox("Enable Game Context Menus", "Whether to show a Try On via Glamourer button on context menus for equippable items.", _config.EnableGameContextMenu, v => { @@ -120,14 +137,29 @@ public class SettingsTab : ITab _config.HideWindowInCutscene = v; _uiBuilder.DisableCutsceneUiHide = !v; }); + + ImGui.Dummy(Vector2.Zero); + ImGui.Separator(); + ImGui.Dummy(Vector2.Zero); + + Checkbox("Smaller Equip Display", "Use single-line display without icons and small dye buttons instead of double-line display.", + _config.SmallEquip, v => _config.SmallEquip = v); + 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); if (Widget.DoubleModifierSelector("Design Deletion Modifier", "A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale, _config.DeleteDesignModifier, v => _config.DeleteDesignModifier = v)) _config.Save(); - DrawFolderSortType(); Checkbox("Auto-Open Design Folders", "Have design folders open or closed as their default state after launching.", _config.OpenFoldersByDefault, v => _config.OpenFoldersByDefault = v); + DrawFolderSortType(); + + ImGui.Dummy(Vector2.Zero); + ImGui.Separator(); + ImGui.Dummy(Vector2.Zero); + Checkbox("Show all Application Rule Checkboxes for Automation", "Show multiple separate application rule checkboxes for automated designs, instead of a single box for enabling or disabling.", _config.ShowAllAutomatedApplicationRules, v => _config.ShowAllAutomatedApplicationRules = v); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index a848f96..2552f44 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -131,6 +131,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() @@ -142,7 +143,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton(); private static IServiceCollection AddApi(this IServiceCollection services) => services.AddSingleton() From 6dbac4e0843040037e7c99d53cd6737b30b80523 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 11 Oct 2023 17:24:26 +0200 Subject: [PATCH 004/786] Disable sounds for changelog windows. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index a9dac59..62dedf1 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit a9dac59d36e25064ebd9cd17d519bfac91acc17e +Subproject commit 62dedf1fe7c9ee6fd7a0ee342c59f8e976468feb From ba81d585d437733cfc65fde83ddf25d0087f7dcb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 11 Oct 2023 22:43:48 +0200 Subject: [PATCH 005/786] Update ChangeCustomize to call the original by address for Palette+ compatibility. --- Glamourer.GameData/Offsets.cs | 12 ------------ Glamourer/Interop/ChangeCustomizeService.cs | 19 +++++++++++-------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/Glamourer.GameData/Offsets.cs b/Glamourer.GameData/Offsets.cs index a6b5d1f..71151d9 100644 --- a/Glamourer.GameData/Offsets.cs +++ b/Glamourer.GameData/Offsets.cs @@ -1,19 +1,7 @@ namespace Glamourer; -public static class Offsets -{ - public static class Character - { - public const int ClassJobContainer = 0x1A8; - } - - public const byte DrawObjectVisorStateFlag = 0x40; - public const byte DrawObjectVisorToggleFlag = 0x80; -} - public static class Sigs { public const string ChangeJob = "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 80 61"; public const string FlagSlotForUpdate = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A"; - public const string ChangeCustomize = "E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86"; } diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 0b6f2fb..a5a46e6 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -2,7 +2,6 @@ using System.Threading; using Dalamud.Hooking; using Dalamud.Plugin.Services; -using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Customization; using Glamourer.Events; @@ -19,8 +18,9 @@ namespace Glamourer.Interop; /// public unsafe class ChangeCustomizeService : EventWrapper>, ChangeCustomizeService.Priority> { - private readonly PenumbraReloaded _penumbraReloaded; - private readonly IGameInteropProvider _interop; + private readonly PenumbraReloaded _penumbraReloaded; + private readonly IGameInteropProvider _interop; + private readonly delegate* unmanaged[Stdcall] _original; /// Check whether we in a manual customize update, in which case we need to not toggle certain flags. public static readonly ThreadLocal InUpdate = new(() => false); @@ -37,6 +37,7 @@ public unsafe class ChangeCustomizeService : EventWrapper _changeCustomizeHook; public bool UpdateCustomize(Model model, CustomizeData customize) @@ -71,7 +71,7 @@ public unsafe class ChangeCustomizeService : EventWrapper(new Customize(*(CustomizeData*)data)); - Invoke(this, (Model)human, customize); - ((Customize*)data)->Load(customize.Value); + if (!InUpdate.Value) + { + var customize = new Ref(new Customize(*(CustomizeData*)data)); + Invoke(this, (Model)human, customize); + ((Customize*)data)->Load(customize.Value); + } return _changeCustomizeHook.Original(human, data, skipEquipment); } } From 1b0c4326804fbd1cbfe46aa4aceb6b5b0cab1e51 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 12 Oct 2023 01:06:32 +0200 Subject: [PATCH 006/786] 1.0.3.0 --- Glamourer/Gui/GlamourerChangelog.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index a03b230..9dd2fda 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -20,6 +20,7 @@ public class GlamourerChangelog Add1_0_0_6(Changelog); Add1_0_1_1(Changelog); Add1_0_2_0(Changelog); + Add1_0_3_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -32,6 +33,23 @@ public class GlamourerChangelog _config.Save(); } + private static void Add1_0_3_0(Changelog log) + => log.NextVersion("Version 1.0.3.0") + .RegisterEntry("Hopefully improved Palette+ compatibility.") + .RegisterHighlight( + "Added a Quick Design Bar, which is a small bar in which you can select your designs and apply them to yourself or your target, or revert them.") + .RegisterEntry("You can toggle visibility of this bar via keybinds, which you can set up in the settings tab.", 1) + .RegisterEntry("You can also lock the bar, and enable or disable an additional, identical bar in the main window.", 1) + .RegisterEntry("Disabled a sound that played on startup when a certain Dalamud setting was enabled.") + .RegisterEntry("Fixed an issue with reading state for Who Am I!?!. (1.0.2.2)") + .RegisterEntry("Fixed an issue where applying gear sets would not always update your dyes. (1.0.2.2)") + .RegisterEntry("Fixed an issue where some errors due to missing null-checks wound up in the log. (1.0.2.2)") + .RegisterEntry("Fixed an issue with hat visibility. (1.0.2.1 and 1.0.2.2)") + .RegisterEntry("Improved some logging. (1.0.2.1)") + .RegisterEntry("Improved notifications when encountering errors while loading automation sets. (1.0.2.1)") + .RegisterEntry("Fixed another issue with monk fist weapons. (1.0.2.1)") + .RegisterEntry("Added missing dot to changelog entry."); + private static void Add1_0_2_0(Changelog log) => log.NextVersion("Version 1.0.2.0") .RegisterHighlight("Added option to favorite items so they appear first in the item selection combos.") From e2ddde81d9f8c5da3408e9e0a8511ddac8db3863 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 11 Oct 2023 23:08:39 +0000 Subject: [PATCH 007/786] [CI] Updating repo.json for 1.0.3.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index a4b92b9..b38f1db 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.2.2", - "TestingAssemblyVersion": "1.0.2.2", + "AssemblyVersion": "1.0.3.0", + "TestingAssemblyVersion": "1.0.3.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.2.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.2.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.2.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a84a66a3444363f096b5ce2b3750a2674409dc44 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 12 Oct 2023 16:55:46 +0200 Subject: [PATCH 008/786] Set no default key combination for Quick Bar --- Glamourer/Configuration.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index f7607b5..8c3b21d 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -39,8 +39,9 @@ public class Configuration : IPluginConfiguration, ISavable public bool ShowDesignQuickBar { get; set; } = false; public bool LockDesignQuickBar { get; set; } = false; public bool ShowQuickBarInTabs { get; set; } = true; + public bool LockMainWindow { get; set; } = false; - public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.D, ModifierHotkey.Control, ModifierHotkey.Shift); + public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); From 27c41cac4971d6063b602f013b4d5b07f003c691 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 12 Oct 2023 16:56:12 +0200 Subject: [PATCH 009/786] Add locking and color options and commands. --- Glamourer/Gui/Colors.cs | 43 ++++++++++++++++------------ Glamourer/Gui/DesignQuickBar.cs | 11 +++++-- Glamourer/Gui/MainWindow.cs | 7 +++++ Glamourer/Gui/Tabs/SettingsTab.cs | 2 ++ Glamourer/Services/CommandService.cs | 32 +++++++++++++++++++-- 5 files changed, 73 insertions(+), 22 deletions(-) diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index a665eee..4b163f9 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using ImGuiNET; namespace Glamourer.Gui; @@ -21,6 +22,9 @@ public enum ColorId FavoriteStarOn, FavoriteStarHovered, FavoriteStarOff, + QuickDesignButton, + QuickDesignFrame, + QuickDesignBg, } public static class Colors @@ -29,24 +33,27 @@ public static class Colors => color switch { // @formatter:off - ColorId.NormalDesign => (0xFFFFFFFF, "Normal Design", "A design with no specific traits." ), - ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ), - ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that does not change equipment or customizations on a character." ), - ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ), - ColorId.ActorAvailable => (0xFF18C018, "Actor Available", "The header in the Actor tab panel if the currently selected actor exists in the game world at least once." ), - ColorId.ActorUnavailable => (0xFF1818C0, "Actor Unavailable", "The Header in the Actor tab panel if the currently selected actor does not exist in the game world." ), - ColorId.FolderExpanded => (0xFFFFF0C0, "Expanded Design Folder", "A design folder that is currently expanded." ), - ColorId.FolderCollapsed => (0xFFFFF0C0, "Collapsed Design Folder", "A design folder that is currently collapsed." ), - ColorId.FolderLine => (0xFFFFF0C0, "Expanded Design Folder Line", "The line signifying which descendants belong to an expanded design folder." ), - ColorId.EnabledAutoSet => (0xFFA0F0A0, "Enabled Automation Set", "An automation set that is currently enabled. Only one set can be enabled for each identifier at once." ), - ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ), - ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ), - ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ), - ColorId.HeaderButtons => (0xFFFFF0C0, "Header Buttons", "The text and border color of buttons in the header, like the Incognito toggle." ), - ColorId.FavoriteStarOn => (0xFF40D0D0, "Favored Item", "The color of the star for favored items and of the border in the unlock overview tab." ), - ColorId.FavoriteStarHovered => (0xFFD040D0, "Favorite Star Hovered", "The color of the star for favored items when it is hovered." ), - ColorId.FavoriteStarOff => (0x20808080, "Favorite Star Outline", "The color of the star for items that are not favored when it is not hovered." ), - _ => (0x00000000, string.Empty, string.Empty ), + ColorId.NormalDesign => (0xFFFFFFFF, "Normal Design", "A design with no specific traits." ), + ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ), + ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that does not change equipment or customizations on a character." ), + ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ), + ColorId.ActorAvailable => (0xFF18C018, "Actor Available", "The header in the Actor tab panel if the currently selected actor exists in the game world at least once." ), + ColorId.ActorUnavailable => (0xFF1818C0, "Actor Unavailable", "The Header in the Actor tab panel if the currently selected actor does not exist in the game world." ), + ColorId.FolderExpanded => (0xFFFFF0C0, "Expanded Design Folder", "A design folder that is currently expanded." ), + ColorId.FolderCollapsed => (0xFFFFF0C0, "Collapsed Design Folder", "A design folder that is currently collapsed." ), + ColorId.FolderLine => (0xFFFFF0C0, "Expanded Design Folder Line", "The line signifying which descendants belong to an expanded design folder." ), + ColorId.EnabledAutoSet => (0xFFA0F0A0, "Enabled Automation Set", "An automation set that is currently enabled. Only one set can be enabled for each identifier at once." ), + ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ), + ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ), + ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ), + ColorId.HeaderButtons => (0xFFFFF0C0, "Header Buttons", "The text and border color of buttons in the header, like the Incognito toggle." ), + ColorId.FavoriteStarOn => (0xFF40D0D0, "Favored Item", "The color of the star for favored items and of the border in the unlock overview tab." ), + ColorId.FavoriteStarHovered => (0xFFD040D0, "Favorite Star Hovered", "The color of the star for favored items when it is hovered." ), + ColorId.FavoriteStarOff => (0x20808080, "Favorite Star Outline", "The color of the star for items that are not favored when it is not hovered." ), + ColorId.QuickDesignButton => (0x900A0A0A, "Quick Design Bar Button Background", "The color of button frames in the quick design bar." ), + ColorId.QuickDesignFrame => (0x90383838, "Quick Design Bar Combo Background", "The color of the combo background in the quick design bar." ), + ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ), + _ => (0x00000000, string.Empty, string.Empty ), // @formatter:on }; diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index fd9976f..3a0fe3f 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -22,7 +22,7 @@ public class DesignQuickBar : Window, IDisposable { private ImGuiWindowFlags GetFlags => _config.LockDesignQuickBar - ? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoBackground + ? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove : ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing; private readonly Configuration _config; @@ -32,6 +32,7 @@ public class DesignQuickBar : Window, IDisposable private readonly ObjectManager _objects; private readonly IKeyState _keyState; private readonly ImRaii.Style _windowPadding = new(); + private readonly ImRaii.Color _windowColor = new(); private DateTime _keyboardToggle = DateTime.UnixEpoch; public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState, @@ -64,10 +65,16 @@ public class DesignQuickBar : Window, IDisposable Size = new Vector2(12 * ImGui.GetFrameHeight(), ImGui.GetFrameHeight()); _windowPadding.Push(ImGuiStyleVar.WindowPadding, new Vector2(ImGuiHelpers.GlobalScale * 4)); + _windowColor.Push(ImGuiCol.WindowBg, ColorId.QuickDesignBg.Value()) + .Push(ImGuiCol.Button, ColorId.QuickDesignButton.Value()) + .Push(ImGuiCol.FrameBg, ColorId.QuickDesignFrame.Value()); } public override void PostDraw() - => _windowPadding.Dispose(); + { + _windowPadding.Dispose(); + _windowColor.Dispose(); + } public override void Draw() diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 4b58979..415fc89 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -79,6 +79,13 @@ public class MainWindow : Window, IDisposable IsOpen = _config.DebugMode; } + public override void PreDraw() + { + Flags = _config.LockMainWindow + ? Flags | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize + : Flags & ~(ImGuiWindowFlags.NoMove |ImGuiWindowFlags.NoResize); + } + public void Dispose() => _event.Unsubscribe(OnTabSelected); diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index a604860..142b48b 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -137,6 +137,8 @@ public class SettingsTab : ITab _config.HideWindowInCutscene = v; _uiBuilder.DisableCutsceneUiHide = !v; }); + Checkbox("Lock Main Window", "Prevent the main window from being moved and lock it in place.", _config.LockMainWindow, + v => _config.LockMainWindow = v); ImGui.Dummy(Vector2.Zero); ImGui.Separator(); diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 924d091..02d4185 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -35,10 +35,11 @@ public class CommandService : IDisposable private readonly DesignManager _designManager; private readonly DesignConverter _converter; private readonly DesignFileSystem _designFileSystem; + private readonly Configuration _config; public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorService actors, ObjectManager objects, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, - DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager) + DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config) { _commands = commands; _mainWindow = mainWindow; @@ -51,6 +52,7 @@ public class CommandService : IDisposable _converter = converter; _designFileSystem = designFileSystem; _autoDesignManager = autoDesignManager; + _config = config; _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); _commands.AddHandler(ApplyCommandString, @@ -64,7 +66,33 @@ public class CommandService : IDisposable } private void OnGlamourer(string command, string arguments) - => _mainWindow.Toggle(); + { + if (arguments.Length > 0) + switch (arguments) + { + case "qdb": + case "quick": + case "bar": + case "designs": + case "design": + case "design bar": + _config.ShowDesignQuickBar = !_config.ShowDesignQuickBar; + _config.Save(); + return; + case "lock": + case "unlock": + _config.LockMainWindow = !_config.LockMainWindow; + _config.Save(); + return; + default: + _chat.Print("Use without argument to toggle the main window."); + _chat.Print(new SeStringBuilder().AddCommand("qdb", "Toggles the quick design bar on or off.").BuiltString); + _chat.Print(new SeStringBuilder().AddCommand("lock", "Toggles the lock of the main window on or off.").BuiltString); + return; + } + + _mainWindow.Toggle(); + } private void OnGlamour(string command, string arguments) { From 909966d41131f3ff83a4cf8b51d6c8c5098c4032 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Oct 2023 15:04:58 +0200 Subject: [PATCH 010/786] Improve color handling. --- Glamourer/Gui/Colors.cs | 2 ++ .../Gui/Customization/CustomizationDrawer.Color.cs | 13 +++++++++++-- .../Gui/Customization/CustomizationDrawer.Icon.cs | 7 +++++-- OtterGui | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index 4b163f9..9206498 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -29,6 +29,8 @@ public enum ColorId public static class Colors { + public const uint SelectedRed = 0xFF2020D0; + public static (uint DefaultColor, string Name, string Description) Data(this ColorId color) => color switch { diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index bd7599b..127d8c2 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -3,7 +3,9 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Customization; using ImGuiNET; +using OtterGui; using OtterGui.Raii; +using Penumbra.GameData; namespace Glamourer.Gui.Customization; @@ -50,10 +52,10 @@ public partial class CustomizationDrawer ImGui.TextUnformatted(custom.Color == 0 ? $"{_currentOption} (NPC)" : _currentOption); } - DrawColorPickerPopup(); + DrawColorPickerPopup(current); } - private void DrawColorPickerPopup() + private void DrawColorPickerPopup(int current) { using var popup = ImRaii.Popup(ColorPickerPopupName, ImGuiWindowFlags.AlwaysAutoResize); if (!popup) @@ -70,6 +72,13 @@ public partial class CustomizationDrawer ImGui.CloseCurrentPopup(); } + if (i == current) + { + var size = ImGui.GetItemRectSize(); + ImGui.GetWindowDrawList() + .AddCircleFilled(ImGui.GetItemRectMin() + size / 2, size.X / 4, ImGuiUtil.ContrastColorBW(custom.Color)); + } + if (i % 8 != 7) ImGui.SameLine(); } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index 31d593d..cabd2d5 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Dalamud.Interface.Utility; using Glamourer.Customization; using ImGuiNET; using OtterGui; @@ -52,10 +53,10 @@ public partial class CustomizationDrawer ImGui.TextUnformatted(label); } - DrawIconPickerPopup(); + DrawIconPickerPopup(current); } - private void DrawIconPickerPopup() + private void DrawIconPickerPopup(int current) { using var popup = ImRaii.Popup(IconSelectorPopup, ImGuiWindowFlags.AlwaysAutoResize); if (!popup) @@ -69,6 +70,8 @@ public partial class CustomizationDrawer var icon = _service.AwaitedService.GetIcon(custom.IconId); using (var _ = ImRaii.Group()) { + using var frameColor = ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed, current == i); + if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) { UpdateValue(custom.Value); diff --git a/OtterGui b/OtterGui index 62dedf1..a4f9b28 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 62dedf1fe7c9ee6fd7a0ee342c59f8e976468feb +Subproject commit a4f9b285c82f84ff0841695c0787dbba93afc59b From 7a94c4ee0758a11f6b7c35f5f773bd8bf1e1548c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Oct 2023 15:05:17 +0200 Subject: [PATCH 011/786] Improve unlocks table. --- Glamourer/Gui/DesignQuickBar.cs | 2 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 119 ++++++------------- Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs | 12 ++ Penumbra.GameData | 2 +- 4 files changed, 49 insertions(+), 86 deletions(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 3a0fe3f..b47c810 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -184,7 +184,7 @@ public class DesignQuickBar : Window, IDisposable if (available == 0) tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; - var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.RedoAlt, buttonSize, tooltip, available); + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); if (clicked) _stateManager.ResetState(state!, StateChanged.Source.Manual); } diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 5ccd037..5ac087f 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Events; using Glamourer.Interop; @@ -35,7 +34,7 @@ public class UnlockTable : Table, IDisposable new ItemIdColumn() { Label = "Item Id..." }, new ModelDataColumn(items) { Label = "Model Data..." }, new JobColumn(jobs) { Label = "Jobs" }, - new LevelColumn() { Label = "Level..." }, + new RequiredLevelColumn() { Label = "Level..." }, new DyableColumn() { Label = "Dye" }, new CrestColumn() { Label = "Crest" }, new TradableColumn() { Label = "Trade" } @@ -43,14 +42,14 @@ public class UnlockTable : Table, IDisposable { _event = @event; Sortable = true; - Flags |= ImGuiTableFlags.Hideable; + Flags |= ImGuiTableFlags.Hideable | ImGuiTableFlags.Reorderable | ImGuiTableFlags.Resizable; _event.Subscribe(OnObjectUnlock, ObjectUnlocked.Priority.UnlockTable); } public void Dispose() => _event.Unsubscribe(OnObjectUnlock); - private sealed class FavoriteColumn : YesNoColumn + private sealed class FavoriteColumn : YesNoColumn { public override float Width => ImGui.GetFrameHeightWithSpacing(); @@ -60,8 +59,9 @@ public class UnlockTable : Table, IDisposable public FavoriteColumn(FavoriteManager favorites, ObjectUnlocked hackEvent) { - _favorites = favorites; - _hackEvent = hackEvent; + _favorites = favorites; + _hackEvent = hackEvent; + Flags |= ImGuiTableColumnFlags.NoResize; } protected override bool GetValue(EquipItem item) @@ -145,8 +145,9 @@ public class UnlockTable : Table, IDisposable public SlotColumn() { - AllFlags = Values.Aggregate((a, b) => a | b); - _filterValue = AllFlags; + Flags &= ~ImGuiTableColumnFlags.NoResize; + AllFlags = Values.Aggregate((a, b) => a | b); + _filterValue = AllFlags; } public override void DrawColumn(EquipItem item, int idx) @@ -225,7 +226,10 @@ public class UnlockTable : Table, IDisposable => 110 * ImGuiHelpers.GlobalScale; public UnlockDateColumn(ItemUnlockManager unlocks) - => _unlocks = unlocks; + { + _unlocks = unlocks; + Flags &= ~ImGuiTableColumnFlags.NoResize; + } public override void DrawColumn(EquipItem item, int idx) { @@ -245,22 +249,17 @@ public class UnlockTable : Table, IDisposable } } - private sealed class ItemIdColumn : ColumnString + private sealed class ItemIdColumn : ColumnNumber { public override float Width => 70 * ImGuiHelpers.GlobalScale; - public override int Compare(EquipItem lhs, EquipItem rhs) - => lhs.ItemId.Id.CompareTo(rhs.ItemId.Id); + public override int ToValue(EquipItem item) + => (int) item.Id.Id; - public override string ToName(EquipItem item) - => item.ItemId.ToString(); - - public override void DrawColumn(EquipItem item, int _) - { - ImGui.AlignTextToFramePadding(); - ImGuiUtil.RightAlign(item.ItemId.ToString()); - } + public ItemIdColumn() + : base(ComparisonMethod.Equal) + { } } private sealed class ModelDataColumn : ColumnString @@ -287,7 +286,7 @@ public class UnlockTable : Table, IDisposable } public override int Compare(EquipItem lhs, EquipItem rhs) - => lhs.Weapon().Value.CompareTo(rhs.Weapon().Value); + => lhs.Weapon().CompareTo(rhs.Weapon()); public override bool FilterFunc(EquipItem item) { @@ -306,7 +305,7 @@ public class UnlockTable : Table, IDisposable } } - private sealed class LevelColumn : ColumnString + private sealed class RequiredLevelColumn : ColumnNumber { public override float Width => 70 * ImGuiHelpers.GlobalScale; @@ -314,11 +313,12 @@ public class UnlockTable : Table, IDisposable public override string ToName(EquipItem item) => item.Level.ToString(); - public override void DrawColumn(EquipItem item, int _) - => ImGuiUtil.RightAlign(item.Level.Value.ToString()); + public override int ToValue(EquipItem item) + => item.Level.Value; - public override int Compare(EquipItem lhs, EquipItem rhs) - => lhs.Level.Value.CompareTo(rhs.Level.Value); + public RequiredLevelColumn() + : base(ComparisonMethod.LessEqual) + { } } @@ -338,11 +338,12 @@ public class UnlockTable : Table, IDisposable public JobColumn(JobService jobs) { - _jobs = jobs; - _values = _jobs.Jobs.Values.Skip(1).Select(j => j.Flag).ToArray(); - _names = _jobs.Jobs.Values.Skip(1).Select(j => j.Abbreviation).ToArray(); - AllFlags = _values.Aggregate((l, r) => l | r); - _filterValue = AllFlags; + _jobs = jobs; + _values = _jobs.Jobs.Values.Skip(1).Select(j => j.Flag).ToArray(); + _names = _jobs.Jobs.Values.Skip(1).Select(j => j.Abbreviation).ToArray(); + AllFlags = _values.Aggregate((l, r) => l | r); + _filterValue = AllFlags; + Flags &= ~ImGuiTableColumnFlags.NoResize; } protected override void SetValue(JobFlag value, bool enable) @@ -383,57 +384,7 @@ public class UnlockTable : Table, IDisposable } } - [Flags] - private enum YesNoFlag - { - Yes = 0x01, - No = 0x02, - }; - - private class YesNoColumn : ColumnFlags - { - public string Tooltip = string.Empty; - - private YesNoFlag _filterValue; - - public override YesNoFlag FilterValue - => _filterValue; - - public YesNoColumn() - { - AllFlags = YesNoFlag.Yes | YesNoFlag.No; - _filterValue = AllFlags; - } - - protected override void SetValue(YesNoFlag value, bool enable) - => _filterValue = enable ? _filterValue | value : _filterValue & ~value; - - protected virtual bool GetValue(EquipItem item) - => false; - - public override float Width - => ImGui.GetFrameHeight() * 2; - - public override bool FilterFunc(EquipItem item) - => GetValue(item) - ? FilterValue.HasFlag(YesNoFlag.Yes) - : FilterValue.HasFlag(YesNoFlag.No); - - public override int Compare(EquipItem lhs, EquipItem rhs) - => GetValue(lhs).CompareTo(GetValue(rhs)); - - public override void DrawColumn(EquipItem item, int idx) - { - using (var font = ImRaii.PushFont(UiBuilder.IconFont)) - { - ImGuiUtil.Center(GetValue(item) ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); - } - - ImGuiUtil.HoverTooltip(Tooltip); - } - } - - private sealed class DyableColumn : YesNoColumn + private sealed class DyableColumn : YesNoColumn { public DyableColumn() => Tooltip = "Whether the item is dyable."; @@ -442,7 +393,7 @@ public class UnlockTable : Table, IDisposable => item.Flags.HasFlag(ItemFlags.IsDyable); } - private sealed class TradableColumn : YesNoColumn + private sealed class TradableColumn : YesNoColumn { public TradableColumn() => Tooltip = "Whether the item is tradable."; @@ -451,7 +402,7 @@ public class UnlockTable : Table, IDisposable => item.Flags.HasFlag(ItemFlags.IsTradable); } - private sealed class CrestColumn : YesNoColumn + private sealed class CrestColumn : YesNoColumn { public CrestColumn() => Tooltip = "Whether a crest can be applied to the item.."; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs index 75f180f..30f7ad3 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs @@ -50,6 +50,7 @@ public class UnlocksTab : Window, ITab _table.Draw(ImGui.GetFrameHeightWithSpacing()); else _overview.Draw(); + _table.Flags |= ImGuiTableFlags.Resizable; } public override void Draw() @@ -64,6 +65,8 @@ public class UnlocksTab : Window, ITab var buttonSize = new Vector2(ImGui.GetContentRegionAvail().X / 2, ImGui.GetFrameHeight()); if (!IsOpen) buttonSize.X -= ImGui.GetFrameHeight() / 2; + if (DetailMode) + buttonSize.X -= ImGui.GetFrameHeight() / 2; if (ImGuiUtil.DrawDisabledButton("Overview Mode", buttonSize, "Show tinted icons of sets of unlocks.", !DetailMode)) DetailMode = false; @@ -72,6 +75,15 @@ public class UnlocksTab : Window, ITab if (ImGuiUtil.DrawDisabledButton("Detailed Mode", buttonSize, "Show all unlockable data as a combined filterable and sortable table.", DetailMode)) DetailMode = true; + + if (DetailMode) + { + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Expand.ToIconString(), new Vector2(ImGui.GetFrameHeight()), + "Restore all columns to their original size.", false, true)) + _table.Flags &= ~ImGuiTableFlags.Resizable; + } + if (!IsOpen) { ImGui.SameLine(); diff --git a/Penumbra.GameData b/Penumbra.GameData index 0c0554e..6a0daf2 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 0c0554e7c482fac35110c3375e890540ee31abb3 +Subproject commit 6a0daf2f309c27c5a260825bce31094987fce3d1 From 27f1fcd422a3a8a430ed38b0e7b7db40fd674558 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 16 Oct 2023 13:09:46 +0000 Subject: [PATCH 012/786] [CI] Updating repo.json for 1.0.3.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index b38f1db..3ac0e9c 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.3.0", - "TestingAssemblyVersion": "1.0.3.0", + "AssemblyVersion": "1.0.3.1", + "TestingAssemblyVersion": "1.0.3.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 0e943b5d1d8a4a50175d2ceeb14ee28362de7082 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 17 Oct 2023 19:49:49 +0200 Subject: [PATCH 013/786] Allow GPose Target to work as Target. --- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- Glamourer/Interop/ObjectManager.cs | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index b1fd1f8..04baa06 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -419,7 +419,7 @@ public class ActorPanel : "The current target can not be manipulated." : "No valid target selected."; if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, - !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0 || _objects.IsInGPose)) + !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) return; var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 2cb843b..53f1ef5 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -449,7 +449,7 @@ public class DesignPanel ? "Apply the current design with its settings to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations." : "The current target can not be manipulated." : "No valid target selected."; - if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, !data.Valid || _objects.IsInGPose)) + if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, !data.Valid)) return; if (_state.GetOrCreate(id, data.Objects[0], out var state)) diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index 15caeee..17f997f 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using Dalamud.Game.ClientState.Objects; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game.Control; using Glamourer.Interop.Structs; using Glamourer.Services; using Penumbra.GameData.Actors; @@ -152,8 +153,8 @@ public class ObjectManager : IReadOnlyDictionary public Actor Player => _objects.GetObjectAddress(0); - public Actor Target - => _targets.Target?.Address ?? nint.Zero; + public unsafe Actor Target + => _clientState.IsGPosing ? TargetSystem.Instance()->GPoseTarget : TargetSystem.Instance()->Target; public Actor Focus => _targets.FocusTarget?.Address ?? nint.Zero; From 8b8b14f2b60dc1781d9114605600a8e01c4600cf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 17 Oct 2023 19:51:17 +0200 Subject: [PATCH 014/786] Show and update BNPCs. --- Glamourer/Gui/Tabs/DebugTab.cs | 37 +++++++++++++++++++++++++++++++++- Penumbra.GameData | 2 +- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index 05e313e..482a7ab 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -730,7 +730,7 @@ public unsafe class DebugTab : ITab disabled.Dispose(); - DrawNameTable("BNPCs", ref _bnpcFilter, _actors.AwaitedService.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Value))); + DrawBnpcTable(); DrawNameTable("ENPCs", ref _enpcFilter, _actors.AwaitedService.Data.ENpcs.Select(kvp => (kvp.Key, kvp.Value))); DrawNameTable("Companions", ref _companionFilter, _actors.AwaitedService.Data.Companions.Select(kvp => (kvp.Key, kvp.Value))); DrawNameTable("Mounts", ref _mountFilter, _actors.AwaitedService.Data.Mounts.Select(kvp => (kvp.Key, kvp.Value))); @@ -738,6 +738,41 @@ public unsafe class DebugTab : ITab DrawNameTable("Worlds", ref _worldFilter, _actors.AwaitedService.Data.Worlds.Select(kvp => ((uint)kvp.Key, kvp.Value))); } + private void DrawBnpcTable() + { + using var _ = ImRaii.PushId(1); + using var tree = ImRaii.TreeNode("BNPCs"); + if (!tree) + return; + + var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref _bnpcFilter, 256); + var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; + using var table = ImRaii.Table("##table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, + new Vector2(-1, 10 * height)); + if (!table) + return; + + if (resetScroll) + ImGui.SetScrollY(0); + ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("3", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableNextColumn(); + var skips = ImGuiClip.GetNecessarySkips(height); + ImGui.TableNextRow(); + var data = _actors.AwaitedService.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Key.ToString("D5"), kvp.Value)); + var remainder = ImGuiClip.FilteredClippedDraw(data, skips, + p => p.Item2.Contains(_bnpcFilter) || p.Item3.Contains(_bnpcFilter, StringComparison.OrdinalIgnoreCase), + p => + { + ImGuiUtil.DrawTableColumn(p.Item2); + ImGuiUtil.DrawTableColumn(p.Item3); + var bnpcs = _items.IdentifierService.AwaitedService.GetBnpcsFromName(p.Item1); + ImGuiUtil.DrawTableColumn(string.Join(", ", bnpcs.Select(b => b.Id.ToString()))); + }); + ImGuiClip.DrawEndDummy(remainder, height); + } + private static void DrawNameTable(string label, ref string filter, IEnumerable<(uint, string)> names) { using var _ = ImRaii.PushId(label); diff --git a/Penumbra.GameData b/Penumbra.GameData index 6a0daf2..4dd261f 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 6a0daf2f309c27c5a260825bce31094987fce3d1 +Subproject commit 4dd261fe837bbe4b799b3ca3c0c8c178197bc48f From 30c5cd0bdba3b60bfed88a893e506913618203c5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 23 Oct 2023 14:43:11 +0200 Subject: [PATCH 015/786] Try to handle transformations a bit better, maybe. --- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 8 ++++- Glamourer/Interop/Structs/Actor.cs | 3 ++ Glamourer/State/FunModule.cs | 21 ++++++------- Glamourer/State/StateListener.cs | 36 ++++++++++++++++------- 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 04baa06..6963053 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -68,7 +68,7 @@ public class ActorPanel private CustomizeFlag CustomizeApplicationFlags => _lockedRedraw ? CustomizeFlagExtensions.AllRelevant & ~CustomizeFlagExtensions.RedrawRequired : CustomizeFlagExtensions.AllRelevant; - public void Draw() + public unsafe void Draw() { using var group = ImRaii.Group(); (_identifier, _data) = _selector.Selection; @@ -114,12 +114,18 @@ public class ActorPanel if (!child || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state)) return; + var transformationId = _actor.IsCharacter ? _actor.AsCharacter->CharacterData.TransformationId : 0; + if (transformationId != 0) + ImGuiUtil.DrawTextButton($"Currently transformed to Transformation {transformationId}.", + -Vector2.UnitX, Colors.SelectedRed); + DrawApplyToSelf(); ImGui.SameLine(); DrawApplyToTarget(); RevertButtons(); + using var disabled = ImRaii.Disabled(transformationId != 0); if (_state.ModelData.IsHuman) DrawHumanPanel(); else diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 644d904..1d1d172 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -45,6 +45,9 @@ public readonly unsafe struct Actor : IEquatable public bool IsGPoseOrCutscene => Index.Index is >= (int)ScreenActor.CutsceneStart and < (int)ScreenActor.CutsceneEnd; + public bool IsTransformed + => AsCharacter->CharacterData.TransformationId != 0; + public ActorIdentifier GetIdentifier(ActorManager actors) => actors.FromObject(AsObject, out _, true, true, false); diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 06ed9ef..afa5b5e 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -92,10 +92,7 @@ public unsafe class FunModule : IDisposable public void ApplyFun(Actor actor, ref CharacterArmor armor, EquipSlot slot) { - if (!actor.IsCharacter || actor.AsObject->ObjectKind is not (byte)ObjectKind.Player) - return; - - if (actor.AsCharacter->CharacterData.ModelCharaId != 0) + if (!ValidFunTarget(actor)) return; if (_config.DisableFestivals == 0 && _festivalSet != null @@ -112,10 +109,7 @@ public unsafe class FunModule : IDisposable public void ApplyFun(Actor actor, Span armor, ref Customize customize) { - if (!actor.IsCharacter || actor.AsObject->ObjectKind is not (byte)ObjectKind.Player) - return; - - if (actor.AsCharacter->CharacterData.ModelCharaId != 0) + if (!ValidFunTarget(actor)) return; if (_config.DisableFestivals == 0 && _festivalSet != null) @@ -140,16 +134,19 @@ public unsafe class FunModule : IDisposable public void ApplyFun(Actor actor, ref CharacterWeapon weapon, EquipSlot slot) { - if (!actor.IsCharacter || actor.AsObject->ObjectKind is not (byte)ObjectKind.Player) - return; - - if (actor.AsCharacter->CharacterData.ModelCharaId != 0) + if (!ValidFunTarget(actor)) return; if (_codes.EnabledWorld) _worldSets.Apply(actor, _rng, ref weapon, slot); } + private static bool ValidFunTarget(Actor actor) + => actor.IsCharacter + && actor.AsObject->ObjectKind is (byte)ObjectKind.Player + && !actor.IsTransformed + && actor.AsCharacter->CharacterData.ModelCharaId == 0; + public void ApplyClown(Span armors) { if (!_codes.EnabledClown) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 6e1d060..791a0f8 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -13,6 +13,7 @@ using Penumbra.GameData.Structs; using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using Glamourer.Structs; namespace Glamourer.State; @@ -116,6 +117,9 @@ public class StateListener : IDisposable /// The base state changed compared to prior state. Change, + + /// Special case for hat stuff. + HatHack, } /// @@ -315,7 +319,6 @@ public class StateListener : IDisposable _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); else apply = true; - break; case UpdateState.NoChange: apply = true; @@ -359,12 +362,20 @@ public class StateListener : IDisposable if (actorArmor.Value != armor.Value) { // Update base data in case hat visibility is off. - if (slot is EquipSlot.Head && armor.Value == 0 && actorArmor.Value != state.BaseData.Armor(EquipSlot.Head).Value) + if (slot is EquipSlot.Head && armor.Value == 0) { - var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant); - state.BaseData.SetItem(EquipSlot.Head, item); - state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stain); - return UpdateState.Change; + if (actor.IsTransformed) + return UpdateState.Transformed; + + if (actorArmor.Value != state.BaseData.Armor(EquipSlot.Head).Value) + { + var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant); + state.BaseData.SetItem(EquipSlot.Head, item); + state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stain); + return UpdateState.Change; + } + + return UpdateState.HatHack; } if (!fistWeapon) @@ -410,12 +421,10 @@ public class StateListener : IDisposable if (apply) armor = state.ModelData.ArmorWithState(slot); - break; // Use current model data. - // Transformed also handles invisible hat state. case UpdateState.NoChange: - case UpdateState.Transformed when slot is EquipSlot.Head && armor.Value is 0: + case UpdateState.HatHack: armor = state.ModelData.ArmorWithState(slot); break; case UpdateState.Transformed: break; @@ -423,8 +432,15 @@ public class StateListener : IDisposable } /// Update base data for a single changed weapon slot. - private UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterWeapon weapon) + private unsafe UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterWeapon weapon) { + if (actor.AsCharacter->CharacterData.TransformationId != 0) + { + var actorWeapon = slot is EquipSlot.MainHand ? actor.GetMainhand() : actor.GetOffhand(); + if (weapon.Value != actorWeapon.Value) + return UpdateState.Transformed; + } + var baseData = state.BaseData.Weapon(slot); var change = UpdateState.NoChange; From c0f19a24e4e7e1f4093c95dbe03a874671b09e44 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 23 Oct 2023 14:47:02 +0200 Subject: [PATCH 016/786] Update API. --- Penumbra.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.Api b/Penumbra.Api index 839cc8f..f9069df 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 839cc8f270abb6a938d71596bef05b4a9b3ab0ea +Subproject commit f9069dfdf1f0a7011c3b0ea7c0be5330c42959dd From 4618e73c25038c7009d7e8c1326328996cdef1f0 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 23 Oct 2023 12:49:34 +0000 Subject: [PATCH 017/786] [CI] Updating repo.json for testing_1.0.3.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 3ac0e9c..92822a6 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.3.1", - "TestingAssemblyVersion": "1.0.3.1", + "TestingAssemblyVersion": "1.0.3.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.0.3.1/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.3.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From bdcc6cb4de2162f48c7fc9aab7fce7d5d03d1c26 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 28 Oct 2023 01:14:31 +0200 Subject: [PATCH 018/786] Handle ItemOffhand and fix weapon plugin load order dependency. --- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 6 +- Glamourer/Interop/WeaponService.cs | 71 +++++++++++++-------- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index ea57515..8eb763e 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -164,8 +164,9 @@ public class PenumbraChangedItemTooltip : IDisposable switch (type) { + case ChangedItemType.ItemOffhand: case ChangedItemType.Item: - if (!_items.ItemService.AwaitedService.TryGetValue(id, EquipSlot.MainHand, out var item)) + if (!_items.ItemService.AwaitedService.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return; CreateTooltip(item, "[Glamourer] ", false); @@ -189,13 +190,14 @@ public class PenumbraChangedItemTooltip : IDisposable switch (type) { case ChangedItemType.Item: + case ChangedItemType.ItemOffhand: if (button is not MouseButton.Right) return; if (!Player(out var state)) return; - if (!_items.ItemService.AwaitedService.TryGetValue(id, EquipSlot.MainHand, out var item)) + if (!_items.ItemService.AwaitedService.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return; ApplyItem(state, item); diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 708377b..caea959 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -1,7 +1,7 @@ using System; +using System.Threading; using Dalamud.Hooking; using Dalamud.Plugin.Services; -using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; using Glamourer.Interop.Structs; @@ -12,20 +12,27 @@ namespace Glamourer.Interop; public unsafe class WeaponService : IDisposable { - private readonly WeaponLoading _event; + private readonly WeaponLoading _event; + private readonly ThreadLocal _inUpdate = new(() => false); + + + private readonly delegate* unmanaged[Stdcall] + _original; + public WeaponService(WeaponLoading @event, IGameInteropProvider interop) { _event = @event; _loadWeaponHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour); + _original = + (delegate* unmanaged[Stdcall] < DrawDataContainer*, uint, ulong, byte, byte, byte, byte, void >) + DrawDataContainer.MemberFunctionPointers.LoadWeapon; _loadWeaponHook.Enable(); } public void Dispose() - { - _loadWeaponHook.Dispose(); - } + => _loadWeaponHook.Dispose(); // Weapons for a specific character are reloaded with this function. // slot is 0 for main hand, 1 for offhand, 2 for combat effects. @@ -42,30 +49,37 @@ public unsafe class WeaponService : IDisposable private void LoadWeaponDetour(DrawDataContainer* drawData, uint slot, ulong weaponValue, byte redrawOnEquality, byte unk2, byte skipGameObject, byte unk4) { - var actor = (Actor)((nint*)drawData)[1]; - var weapon = new CharacterWeapon(weaponValue); - var equipSlot = slot switch + if (!_inUpdate.Value) { - 0 => EquipSlot.MainHand, - 1 => EquipSlot.OffHand, - _ => EquipSlot.Unknown, - }; + var actor = (Actor)((nint*)drawData)[1]; + var weapon = new CharacterWeapon(weaponValue); + var equipSlot = slot switch + { + 0 => EquipSlot.MainHand, + 1 => EquipSlot.OffHand, + _ => EquipSlot.Unknown, + }; - var tmpWeapon = weapon; - // First call the regular function. - if (equipSlot is not EquipSlot.Unknown) - _event.Invoke(actor, equipSlot, ref tmpWeapon); + var tmpWeapon = weapon; + // First call the regular function. + if (equipSlot is not EquipSlot.Unknown) + _event.Invoke(actor, equipSlot, ref tmpWeapon); - _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); - if (tmpWeapon.Value != weapon.Value) - { - if (tmpWeapon.Set.Id == 0) - tmpWeapon.Stain = 0; - _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); + _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); + if (tmpWeapon.Value != weapon.Value) + { + if (tmpWeapon.Set.Id == 0) + tmpWeapon.Stain = 0; + _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); + } + + Glamourer.Log.Excessive( + $"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}"); + } + else + { + _original(drawData, slot, weaponValue, redrawOnEquality, unk2, skipGameObject, unk4); } - - Glamourer.Log.Excessive( - $"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}"); } // Load a specific weapon for a character by its data and slot. @@ -74,16 +88,21 @@ public unsafe class WeaponService : IDisposable switch (slot) { case EquipSlot.MainHand: + _inUpdate.Value = true; _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); + _inUpdate.Value = false; return; case EquipSlot.OffHand: + _inUpdate.Value = true; _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0); + _inUpdate.Value = false; return; case EquipSlot.BothHand: + _inUpdate.Value = true; _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0); + _inUpdate.Value = false; return; - // function can also be called with '2', but does not seem to ever be. } } diff --git a/Penumbra.Api b/Penumbra.Api index f9069df..80f9793 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit f9069dfdf1f0a7011c3b0ea7c0be5330c42959dd +Subproject commit 80f9793ef2ddaa50246b7112fde4d9b2098d8823 diff --git a/Penumbra.GameData b/Penumbra.GameData index 4dd261f..e1a62d8 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 4dd261fe837bbe4b799b3ca3c0c8c178197bc48f +Subproject commit e1a62d8e6b4e1d8c482253ad14850fd3dc372d86 From 81395f761c2a6e8ac685f7d62d413de81a65c63c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 30 Oct 2023 15:05:01 +0100 Subject: [PATCH 019/786] Remove Thaumaturges moccasins from restricted gear. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index e1a62d8..9906903 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit e1a62d8e6b4e1d8c482253ad14850fd3dc372d86 +Subproject commit 990690373f1abdc2cffc03ce569b7d9d6fe9222f From d59779377297f789fdc307d783f1f3b5ecac26af Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 30 Oct 2023 15:05:18 +0100 Subject: [PATCH 020/786] Use local time when resetting festivals. --- Glamourer/State/FunModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index afa5b5e..ac99e1a 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -66,7 +66,7 @@ public unsafe class FunModule : IDisposable } internal void ResetFestival() - => OnDayChange(DateTime.UtcNow.Day, DateTime.UtcNow.Month, DateTime.UtcNow.Year); + => OnDayChange(DateTime.Now.Day, DateTime.Now.Month, DateTime.Now.Year); public FunModule(CodeService codes, CustomizationService customizations, ItemManager items, Configuration config, GenericPopupWindow popupWindow, StateManager stateManager, ObjectManager objects, DesignConverter designConverter, From 7716e4cc2a0f4b59f32b2e06c377ba60cbce07f1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 30 Oct 2023 15:11:18 +0100 Subject: [PATCH 021/786] 1.0.4.0 --- Glamourer/Gui/GlamourerChangelog.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 9dd2fda..14bac50 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -21,6 +21,7 @@ public class GlamourerChangelog Add1_0_1_1(Changelog); Add1_0_2_0(Changelog); Add1_0_3_0(Changelog); + Add1_0_4_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -33,6 +34,19 @@ public class GlamourerChangelog _config.Save(); } + private static void Add1_0_4_0(Changelog log) + => log.NextVersion("Version 1.0.4.0") + .RegisterEntry("The GPose target is now used for target-dependent functionality in GPose.") + .RegisterEntry("Fixed a few issues with transformations, especially their weapons and head gear.") + .RegisterEntry( + "Previewing Offhand Models for both-handed weapons via right click is now possible (may need to wait for a not-yet released Penumbra update).") + .RegisterEntry("Updated the known list of Battle NPCs.") + .RegisterEntry("Removed another technically unrestricted item from restricted item list.") + .RegisterEntry("Use local time for discerning the current day on start-up instead of UTC-time.") + .RegisterEntry("Improved the Unlocks Table with additional info. (1.0.3.1)") + .RegisterEntry("Added position locking option and more color options. (1.0.3.1)") + .RegisterEntry("Removed the default key combination for toggling the quick bar. (1.0.3.1)"); + private static void Add1_0_3_0(Changelog log) => log.NextVersion("Version 1.0.3.0") .RegisterEntry("Hopefully improved Palette+ compatibility.") From 66cb6ffaadf308086f61b1a04b4dc9bcc35a7bb0 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 30 Oct 2023 14:15:28 +0000 Subject: [PATCH 022/786] [CI] Updating repo.json for 1.0.4.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 92822a6..db5078c 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.3.1", - "TestingAssemblyVersion": "1.0.3.2", + "AssemblyVersion": "1.0.4.0", + "TestingAssemblyVersion": "1.0.4.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.3.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.3.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From b7cd6dfe2d992aec6dc48acd9b36860dd4dc129c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 31 Oct 2023 11:29:42 +0100 Subject: [PATCH 023/786] Fix log spam on nonexistent models. --- Glamourer/State/FunModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index ac99e1a..9f30cac 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -98,7 +98,7 @@ public unsafe class FunModule : IDisposable if (_config.DisableFestivals == 0 && _festivalSet != null || _codes.EnabledWorld && actor.Index != 0) { - armor = actor.Model.GetArmor(slot); + armor = actor.Model.Valid ? actor.Model.GetArmor(slot) : actor.GetArmor(slot); } else { From 53b9aa938744a3bb0db4ab34d47cb25460383349 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 31 Oct 2023 11:30:24 +0100 Subject: [PATCH 024/786] Make popups not appear when the player is busy. --- Glamourer/Gui/GenericPopupWindow.cs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index 0c891f1..f1aa9fe 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -1,6 +1,8 @@ using System.Numerics; +using Dalamud.Game.ClientState.Conditions; using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; +using Dalamud.Plugin.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -10,9 +12,11 @@ namespace Glamourer.Gui; public class GenericPopupWindow : Window { private readonly Configuration _config; + private readonly ICondition _condition; + private readonly IClientState _state; public bool OpenFestivalPopup { get; internal set; } = false; - public GenericPopupWindow(Configuration config) + public GenericPopupWindow(Configuration config, IClientState state, ICondition condition) : base("Glamourer Popups", ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoDecoration @@ -23,14 +27,16 @@ public class GenericPopupWindow : Window | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoTitleBar, true) { - _config = config; + _config = config; + _state = state; + _condition = condition; DisableWindowSounds = true; - IsOpen = true; + IsOpen = true; } public override void Draw() { - if (OpenFestivalPopup) + if (OpenFestivalPopup && CheckFestivalPopupConditions()) { ImGui.OpenPopup("FestivalPopup"); OpenFestivalPopup = false; @@ -39,6 +45,18 @@ public class GenericPopupWindow : Window DrawFestivalPopup(); } + private bool CheckFestivalPopupConditions() + => !_state.IsPvPExcludingDen + && !_condition[ConditionFlag.InCombat] + && !_condition[ConditionFlag.BoundByDuty] + && !_condition[ConditionFlag.WatchingCutscene] + && !_condition[ConditionFlag.WatchingCutscene78] + && !_condition[ConditionFlag.BoundByDuty95] + && !_condition[ConditionFlag.BoundByDuty56] + && !_condition[ConditionFlag.InDeepDungeon] + && !_condition[ConditionFlag.PlayingLordOfVerminion] + && !_condition[ConditionFlag.ChocoboRacing]; + private void DrawFestivalPopup() { From 5e22946666268d6f094f4135d4158584ce020d74 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 31 Oct 2023 11:24:22 +0000 Subject: [PATCH 025/786] [CI] Updating repo.json for 1.0.4.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index db5078c..125a2b2 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.4.0", - "TestingAssemblyVersion": "1.0.4.0", + "AssemblyVersion": "1.0.4.1", + "TestingAssemblyVersion": "1.0.4.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From d1b6ec21596c31bbdda1405da68f99021e067e0a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 31 Oct 2023 12:27:34 +0100 Subject: [PATCH 026/786] Refix fix. --- Glamourer/State/FunModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 9f30cac..77a990f 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -98,7 +98,7 @@ public unsafe class FunModule : IDisposable if (_config.DisableFestivals == 0 && _festivalSet != null || _codes.EnabledWorld && actor.Index != 0) { - armor = actor.Model.Valid ? actor.Model.GetArmor(slot) : actor.GetArmor(slot); + armor = actor.Model.Valid ? actor.Model.GetArmor(slot) : armor; } else { From 9f970a1920b64a79b8909c2a662a832e31461f13 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 31 Oct 2023 11:30:09 +0000 Subject: [PATCH 027/786] [CI] Updating repo.json for 1.0.4.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 125a2b2..938c1a9 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.4.1", - "TestingAssemblyVersion": "1.0.4.1", + "AssemblyVersion": "1.0.4.2", + "TestingAssemblyVersion": "1.0.4.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 900953c249cdf28c6609c80e929f885a3e3fcb3c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 31 Oct 2023 12:31:58 +0100 Subject: [PATCH 028/786] Give Witches panties. --- Glamourer/State/FunEquipSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/State/FunEquipSet.cs b/Glamourer/State/FunEquipSet.cs index c2618b0..720eb9e 100644 --- a/Glamourer/State/FunEquipSet.cs +++ b/Glamourer/State/FunEquipSet.cs @@ -70,7 +70,7 @@ internal class FunEquipSet public static readonly FunEquipSet Halloween = new ( - new Group(0316, 1, 0316, 1, 0316, 1, 0279, 1, 0316, 1), // Witch + new Group(0316, 1, 0316, 1, 0316, 1, 0049, 6, 0316, 1), // Witch new Group(6047, 1, 6047, 1, 6047, 1, 6047, 1, 6047, 1), // Werewolf new Group(6148, 1, 6148, 1, 6148, 1, 6148, 1, 6148, 1), // Wake Doctor new Group(6117, 1, 6117, 1, 6117, 1, 6117, 1, 6117, 1), // Clown From 3d23923c526aa8d3a3d246ce7b19c1c81d3c71be Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 31 Oct 2023 11:34:20 +0000 Subject: [PATCH 029/786] [CI] Updating repo.json for 1.0.4.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 938c1a9..8c1279e 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.4.2", - "TestingAssemblyVersion": "1.0.4.2", + "AssemblyVersion": "1.0.4.3", + "TestingAssemblyVersion": "1.0.4.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 6ad9b562398b7ea513f5eba408510cf5ef7e2674 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 3 Nov 2023 15:24:27 +0100 Subject: [PATCH 030/786] Fix lack of redraw after GPose. --- Glamourer/State/StateApplier.cs | 1 + Penumbra.GameData | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 88a97e3..a32449e 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -79,6 +79,7 @@ public class StateApplier { var mdlCustomize = (Customize*)&mdl.AsHuman->Customize; mdlCustomize->Load(customize); + _penumbra.RedrawObject(actor, RedrawType.AfterGPose); } else { diff --git a/Penumbra.GameData b/Penumbra.GameData index 9906903..d5c27fb 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 990690373f1abdc2cffc03ce569b7d9d6fe9222f +Subproject commit d5c27fb2af24e6756eb0eabe69a95eefed46160b From 4328f5d680f9106e966c47f1dac3c4359bd3679b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Nov 2023 17:30:27 +0100 Subject: [PATCH 031/786] Make colors favoritable. --- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 21 ++--- .../Gui/Equipment/GlamourerColorCombo.cs | 52 +++++++++++ Glamourer/Gui/UiHelpers.cs | 23 +++++ Glamourer/Unlocks/FavoriteManager.cs | 86 ++++++++++++++++--- OtterGui | 2 +- 5 files changed, 161 insertions(+), 23 deletions(-) create mode 100644 Glamourer/Gui/Equipment/GlamourerColorCombo.cs diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 75ca0ab..3b33f8a 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -14,7 +14,6 @@ using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using OtterGui.Widgets; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -26,7 +25,7 @@ public class EquipmentDrawer private const float DefaultWidth = 280; private readonly ItemManager _items; - private readonly FilterComboColors _stainCombo; + private readonly GlamourerColorCombo _stainCombo; private readonly StainData _stainData; private readonly ItemCombo[] _itemCombo; private readonly Dictionary _weaponCombo; @@ -39,17 +38,15 @@ public class EquipmentDrawer private float _requiredComboWidth; public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures, - Configuration config, - GPoseService gPose) + Configuration config, GPoseService gPose) { - _items = items; - _codes = codes; - _textures = textures; - _config = config; - _gPose = gPose; - _stainData = items.Stains; - _stainCombo = new FilterComboColors(DefaultWidth - 20, - _stainData.Data.Prepend(new KeyValuePair(0, ("None", 0, false))), Glamourer.Log); + _items = items; + _codes = codes; + _textures = textures; + _config = config; + _gPose = gPose; + _stainData = items.Stains; + _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); _weaponCombo = new Dictionary(FullEquipTypeExtensions.WeaponTypes.Count * 2); foreach (var type in Enum.GetValues()) diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs new file mode 100644 index 0000000..5cdb6d4 --- /dev/null +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using Glamourer.Unlocks; +using ImGuiNET; +using OtterGui.Widgets; +using Penumbra.GameData.Data; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Equipment; + +public sealed class GlamourerColorCombo : FilterComboColors +{ + private readonly FavoriteManager _favorites; + + public GlamourerColorCombo(float comboWidth, StainData stains, FavoriteManager favorites) + : base(comboWidth, CreateFunc(stains, favorites), Glamourer.Log) + => _favorites = favorites; + + protected override bool DrawSelectable(int globalIdx, bool selected) + { + using (var space = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(4, 0))) + { + if (globalIdx == 0) + { + using var font = ImRaii.PushFont(UiBuilder.IconFont); + ImGui.Dummy(ImGui.CalcTextSize(FontAwesomeIcon.Star.ToIconString())); + } + else + { + UiHelpers.DrawFavoriteStar(_favorites, (StainId)Items[globalIdx].Key); + } + + ImGui.SameLine(); + } + + var buttonWidth = ImGui.GetContentRegionAvail().X; + var totalWidth = ImGui.GetContentRegionMax().X; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(buttonWidth / 2 / totalWidth, 0.5f)); + + return base.DrawSelectable(globalIdx, selected); + } + + private static Func>> CreateFunc(StainData stains, + FavoriteManager favorites) + => () => stains.Data.Select(kvp => (kvp, favorites.Contains((StainId)kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp) + .Prepend(new KeyValuePair(0, ("None", 0, false))).ToList(); +} diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 3bb4c3a..8299e43 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -143,4 +143,27 @@ public static class UiHelpers return false; } + + public static bool DrawFavoriteStar(FavoriteManager favorites, StainId stain) + { + var favorite = favorites.Contains(stain); + var hovering = ImGui.IsMouseHoveringRect(ImGui.GetCursorScreenPos(), + ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetFrameHeight())); + + using var font = ImRaii.PushFont(UiBuilder.IconFont); + using var c = ImRaii.PushColor(ImGuiCol.Text, + hovering ? ColorId.FavoriteStarHovered.Value() : favorite ? ColorId.FavoriteStarOn.Value() : ColorId.FavoriteStarOff.Value()); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString()); + if (ImGui.IsItemClicked()) + { + if (favorite) + favorites.Remove(stain); + else + favorites.TryAdd(stain); + return true; + } + + return false; + } } diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index 4cd98c8..a96ae35 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -5,7 +5,6 @@ using System.Linq; using Dalamud.Interface.Internal.Notifications; using Glamourer.Services; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.GameData.Structs; @@ -13,8 +12,10 @@ namespace Glamourer.Unlocks; public class FavoriteManager : ISavable { - private readonly SaveService _saveService; - private readonly HashSet _favorites = new(); + private const int CurrentVersion = 1; + private readonly SaveService _saveService; + private readonly HashSet _favorites = new(); + private readonly HashSet _favoriteColors = new(); public FavoriteManager(SaveService saveService) { @@ -30,9 +31,23 @@ public class FavoriteManager : ISavable try { - var text = File.ReadAllText(file); - var array = JsonConvert.DeserializeObject(text) ?? Array.Empty(); - _favorites.UnionWith(array.Select(i => (ItemId)i)); + var text = File.ReadAllText(file); + if (text.StartsWith('[')) + { + LoadV0(text); + } + else + { + var load = JsonConvert.DeserializeObject(text); + switch (load.Version) + { + case 1: + _favorites.UnionWith(load.FavoriteItems.Select(i => (ItemId)i)); + _favoriteColors.UnionWith(load.FavoriteColors.Select(i => (StainId)i)); + break; + default: throw new Exception($"Unknown Version {load.Version}"); + } + } } catch (Exception ex) { @@ -40,6 +55,13 @@ public class FavoriteManager : ISavable } } + private void LoadV0(string text) + { + var array = JsonConvert.DeserializeObject(text) ?? Array.Empty(); + _favorites.UnionWith(array.Select(i => (ItemId)i)); + Save(); + } + public string ToFilename(FilenameService fileNames) => fileNames.FavoriteFile; @@ -52,10 +74,20 @@ public class FavoriteManager : ISavable { Formatting = Formatting.Indented, }; + j.WriteStartObject(); + j.WritePropertyName(nameof(LoadStruct.Version)); + j.WriteValue(CurrentVersion); + j.WritePropertyName(nameof(LoadStruct.FavoriteItems)); j.WriteStartArray(); foreach (var item in _favorites) j.WriteValue(item.Id); j.WriteEndArray(); + j.WritePropertyName(nameof(LoadStruct.FavoriteColors)); + j.WriteStartArray(); + foreach (var stain in _favoriteColors) + j.WriteValue(stain.Id); + j.WriteEndArray(); + j.WriteEndObject(); } public bool TryAdd(EquipItem item) @@ -70,6 +102,18 @@ public class FavoriteManager : ISavable return true; } + public bool TryAdd(Stain stain) + => TryAdd(stain.RowIndex); + + public bool TryAdd(StainId stain) + { + if (stain.Id == 0 || !_favoriteColors.Add(stain)) + return false; + + Save(); + return true; + } + public bool Remove(EquipItem item) => Remove(item.ItemId); @@ -82,15 +126,37 @@ public class FavoriteManager : ISavable return true; } - public IEnumerator GetEnumerator() - => _favorites.GetEnumerator(); + public bool Remove(Stain stain) + => Remove(stain.RowIndex); - public int Count - => _favorites.Count; + public bool Remove(StainId stain) + { + if (!_favoriteColors.Remove(stain)) + return false; + + Save(); + return true; + } public bool Contains(EquipItem item) => _favorites.Contains(item.ItemId); + public bool Contains(Stain stain) + => _favoriteColors.Contains(stain.RowIndex); + public bool Contains(ItemId item) => _favorites.Contains(item); + + public bool Contains(StainId stain) + => _favoriteColors.Contains(stain); + + private struct LoadStruct + { + public int Version = CurrentVersion; + public uint[] FavoriteItems = Array.Empty(); + public byte[] FavoriteColors = Array.Empty(); + + public LoadStruct() + { } + } } diff --git a/OtterGui b/OtterGui index a4f9b28..b09bbcc 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit a4f9b285c82f84ff0841695c0787dbba93afc59b +Subproject commit b09bbcc276363bc994d90b641871e6280898b6e5 From eb6e665147beb7354eee68ae227e55b74dd856f0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Nov 2023 18:21:13 +0100 Subject: [PATCH 032/786] Fix exception throwing on no saved designs. Allow mousewheel scrolling of the combo. --- Glamourer/Gui/DesignCombo.cs | 55 ++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index a60a69b..690344d 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -21,6 +21,7 @@ public abstract class DesignComboBase : FilterComboCache>, private readonly DesignChanged _designChanged; protected readonly TabSelected TabSelected; protected float InnerWidth; + private Design? _currentDesign; protected DesignComboBase(Func>> generator, Logger log, DesignChanged designChanged, TabSelected tabSelected, Configuration config) @@ -63,11 +64,17 @@ public abstract class DesignComboBase : FilterComboCache>, return ret; } + protected override int UpdateCurrentSelected(int currentSelected) + { + CurrentSelectionIdx = Items.IndexOf(p => _currentDesign == p.Item1); + CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : null; + return CurrentSelectionIdx; + } + protected bool Draw(Design? currentDesign, string? label, float width) { - InnerWidth = 400 * ImGuiHelpers.GlobalScale; - CurrentSelectionIdx = Math.Max(Items.IndexOf(p => currentDesign == p.Item1), 0); - CurrentSelection = Items[CurrentSelectionIdx]; + _currentDesign = currentDesign; + InnerWidth = 400 * ImGuiHelpers.GlobalScale; var name = label ?? "Select Design Here..."; var ret = Draw("##design", name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()) && CurrentSelection != null; @@ -79,6 +86,7 @@ public abstract class DesignComboBase : FilterComboCache>, ImGuiUtil.HoverTooltip("Control + Right-Click to move to design."); } + _currentDesign = null; return ret; } @@ -106,8 +114,8 @@ public abstract class DesignComboBase : FilterComboCache>, Cleanup(); if (CurrentSelection?.Item1 == design) { - CurrentSelectionIdx = -1; - CurrentSelection = null; + CurrentSelectionIdx = Items.Count > 0 ? 0 : -1; + CurrentSelection = Items[CurrentSelectionIdx]; } break; @@ -117,20 +125,43 @@ public abstract class DesignComboBase : FilterComboCache>, public sealed class DesignCombo : DesignComboBase { + private readonly DesignManager _manager; + public DesignCombo(DesignManager designs, DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, TabSelected tabSelected, Configuration config) - : base( - () => designs.Designs - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) - .OrderBy(d => d.Item2) - .ToList(), log, designChanged, tabSelected, config) - { } + : base(() => designs.Designs + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .OrderBy(d => d.Item2) + .ToList(), log, designChanged, tabSelected, config) + { + _manager = designs; + if (designs.Designs.Count == 0) + return; + + CurrentSelection = Items[0]; + CurrentSelectionIdx = 0; + } public Design? Design => CurrentSelection?.Item1; public void Draw(float width) - => Draw(Design, (Incognito ? Design?.Incognito : Design?.Name.Text) ?? string.Empty, width); + { + Draw(Design, (Incognito ? Design?.Incognito : Design?.Name.Text) ?? string.Empty, width); + if (ImGui.IsItemHovered() && _manager.Designs.Count > 1) + { + var mouseWheel = -(int)ImGui.GetIO().MouseWheel % _manager.Designs.Count; + CurrentSelectionIdx = mouseWheel switch + { + < 0 when CurrentSelectionIdx < 0 => _manager.Designs.Count - 1 + mouseWheel, + < 0 => (CurrentSelectionIdx + _manager.Designs.Count + mouseWheel) % _manager.Designs.Count, + > 0 when CurrentSelectionIdx < 0 => mouseWheel, + > 0 => (CurrentSelectionIdx + mouseWheel) % _manager.Designs.Count, + _ => CurrentSelectionIdx, + }; + CurrentSelection = Items[CurrentSelectionIdx]; + } + } } public sealed class RevertDesignCombo : DesignComboBase, IDisposable From a3583dd5f1a7436f47c6adde21262e834ee56e35 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Nov 2023 18:21:26 +0100 Subject: [PATCH 033/786] Remove Enabled Config. --- Glamourer/Configuration.cs | 1 - Glamourer/Glamourer.cs | 2 ++ Glamourer/Gui/Tabs/SettingsTab.cs | 9 +++------ Glamourer/State/StateListener.cs | 30 ++---------------------------- 4 files changed, 7 insertions(+), 35 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 8c3b21d..ff4eb45 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -19,7 +19,6 @@ namespace Glamourer; public class Configuration : IPluginConfiguration, ISavable { - public bool Enabled { get; set; } = true; public bool UseRestrictedGearProtection { get; set; } = false; public bool OpenFoldersByDefault { get; set; } = false; public bool AutoRedrawEquipOnChanges { get; set; } = false; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 31942da..2ac26ce 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -3,6 +3,7 @@ using Dalamud.Plugin; using Glamourer.Gui; using Glamourer.Interop; using Glamourer.Services; +using Glamourer.State; using Microsoft.Extensions.DependencyInjection; using OtterGui.Classes; using OtterGui.Log; @@ -31,6 +32,7 @@ public class Glamourer : IDalamudPlugin { _services = ServiceManager.CreateProvider(pluginInterface, Log); Messager = _services.GetRequiredService(); + _services.GetRequiredService(); // Initialize State Listener. _services.GetRequiredService(); // initialize ui. _services.GetRequiredService(); // initialize commands. _services.GetRequiredService(); diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index 142b48b..7b86912 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -24,7 +24,6 @@ public class SettingsTab : ITab private readonly VirtualKey[] _validKeys; private readonly Configuration _config; private readonly DesignFileSystemSelector _selector; - private readonly StateListener _stateListener; private readonly CodeService _codeService; private readonly PenumbraAutoRedraw _autoRedraw; private readonly ContextMenuService _contextMenuService; @@ -32,13 +31,11 @@ public class SettingsTab : ITab private readonly GlamourerChangelog _changelog; private readonly FunModule _funModule; - public SettingsTab(Configuration config, DesignFileSystemSelector selector, StateListener stateListener, - CodeService codeService, PenumbraAutoRedraw autoRedraw, ContextMenuService contextMenuService, UiBuilder uiBuilder, - GlamourerChangelog changelog, FunModule funModule, IKeyState keys) + public SettingsTab(Configuration config, DesignFileSystemSelector selector, CodeService codeService, PenumbraAutoRedraw autoRedraw, + ContextMenuService contextMenuService, UiBuilder uiBuilder, GlamourerChangelog changelog, FunModule funModule, IKeyState keys) { _config = config; _selector = selector; - _stateListener = stateListener; _codeService = codeService; _autoRedraw = autoRedraw; _contextMenuService = contextMenuService; @@ -59,11 +56,11 @@ public class SettingsTab : ITab if (!child) return; - Checkbox("Enabled", "Enable main functionality of keeping and applying state.", _stateListener.Enabled, _stateListener.Enable); Checkbox("Enable Auto Designs", "Enable the application of designs associated to characters to be applied automatically.", _config.EnableAutoDesigns, v => _config.EnableAutoDesigns = v); ImGui.NewLine(); ImGui.NewLine(); + ImGui.NewLine(); using (var child2 = ImRaii.Child("SettingsChild")) { diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 791a0f8..3fdf4f7 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -13,7 +13,6 @@ using Penumbra.GameData.Structs; using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; -using Glamourer.Structs; namespace Glamourer.State; @@ -49,12 +48,6 @@ public class StateListener : IDisposable private ActorState? _creatingState; private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty; - public bool Enabled - { - get => _config.Enabled; - set => Enable(value); - } - public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config, SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, @@ -81,30 +74,11 @@ public class StateListener : IDisposable _changeCustomizeService = changeCustomizeService; _customizations = customizations; _condition = condition; - - if (Enabled) - Subscribe(); - } - - public void Enable(bool value) - { - if (value == Enabled) - return; - - _config.Enabled = value; - _config.Save(); - - if (value) - Subscribe(); - else - Unsubscribe(); + Subscribe(); } void IDisposable.Dispose() - { - if (Enabled) - Unsubscribe(); - } + => Unsubscribe(); /// The result of updating the base state of an ActorState. private enum UpdateState From 36f6c48f7ace8fb9d0ca14c0ffbf2e65906a2feb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Nov 2023 18:30:01 +0100 Subject: [PATCH 034/786] Allow filtering designs for contained items. --- Glamourer/Designs/DesignData.cs | 15 +++++++++++++++ .../Tabs/DesignTab/DesignFileSystemSelector.cs | 7 ++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 1364720..0bf66ee 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -3,6 +3,7 @@ using System.Buffers.Text; using System.Runtime.CompilerServices; using Glamourer.Customization; using Glamourer.Services; +using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String.Functions; @@ -39,6 +40,20 @@ public unsafe struct DesignData public DesignData() { } + public bool ContainsName(LowerString name) + => name.IsContained(_nameHead) + || name.IsContained(_nameBody) + || name.IsContained(_nameHands) + || name.IsContained(_nameLegs) + || name.IsContained(_nameFeet) + || name.IsContained(_nameEars) + || name.IsContained(_nameNeck) + || name.IsContained(_nameWrists) + || name.IsContained(_nameRFinger) + || name.IsContained(_nameLFinger) + || name.IsContained(_nameMainhand) + || name.IsContained(_nameOffhand); + public readonly StainId Stain(EquipSlot slot) { var index = slot.ToIndex(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 114a527..71a74a5 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -173,7 +173,8 @@ public sealed class DesignFileSystemSelector : FileSystemSelector filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 2), 't' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 3), 'T' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 3), + 'i' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4), + 'I' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4), _ => (new LowerString(filterValue), 0), }, _ => (new LowerString(filterValue), 0), @@ -259,6 +263,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector !design.Name.Contains(_designFilter), 2 => !design.AssociatedMods.Any(kvp => _designFilter.IsContained(kvp.Key.Name)), 3 => !design.Tags.Any(_designFilter.IsContained), + 4 => !design.DesignData.ContainsName(_designFilter), _ => false, // Should never happen }; } From 859c7380800cd812942978b0593f64bdaac8220f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Nov 2023 19:00:33 +0100 Subject: [PATCH 035/786] Allow undoing overwriting designs. --- Glamourer/Designs/DesignManager.cs | 28 ++++++++++++++++----- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 24 +++++++++++++++++- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 8628c85..c67dd88 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -19,12 +19,13 @@ namespace Glamourer.Designs; public class DesignManager { - private readonly CustomizationService _customizations; - private readonly ItemManager _items; - private readonly HumanModelList _humans; - private readonly SaveService _saveService; - private readonly DesignChanged _event; - private readonly List _designs = new(); + private readonly CustomizationService _customizations; + private readonly ItemManager _items; + private readonly HumanModelList _humans; + private readonly SaveService _saveService; + private readonly DesignChanged _event; + private readonly List _designs = new(); + private readonly Dictionary _undoStore = new(); public IReadOnlyList Designs => _designs; @@ -83,6 +84,10 @@ public class DesignManager _event.Invoke(DesignChanged.Type.ReloadedAll, null!); } + /// Whether an Undo for the given design is possible. + public bool CanUndo(Design? design) + => design != null && _undoStore.ContainsKey(design.Identifier); + /// Create a new temporary design without adding it to the manager. public DesignBase CreateTemporary() => new(_items); @@ -463,6 +468,7 @@ public class DesignManager /// Apply an entire design based on its appliance rules piece by piece. public void ApplyDesign(Design design, DesignBase other) { + _undoStore[design.Identifier] = design.DesignData; if (other.DoApplyWetness()) design.DesignData.SetIsWet(other.DesignData.IsWet()); if (other.DoApplyHatVisible()) @@ -503,6 +509,16 @@ public class DesignManager ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand)); } + public void UndoDesignChange(Design design) + { + if (!_undoStore.Remove(design.Identifier, out var otherData)) + return; + + var other = CreateTemporary(); + other.DesignData = otherData; + ApplyDesign(design, other); + } + private void MigrateOldDesigns() { if (!File.Exists(_saveService.FileNames.MigrationDesignFile)) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 53f1ef5..013d35d 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -84,6 +84,16 @@ public class DesignPanel Disabled = _selector.Selected?.WriteProtected() ?? true, }; + private HeaderDrawer.Button UndoButton() + => new() + { + Description = "Undo the last change if you accidentally overwrote your design with a different one.", + Icon = FontAwesomeIcon.Undo, + OnClick = UndoOverwrite, + Visible = _selector.Selected != null, + Disabled = !_manager.CanUndo(_selector.Selected), + }; + private HeaderDrawer.Button ExportToClipboardButton() => new() { @@ -95,7 +105,7 @@ public class DesignPanel private void DrawHeader() => HeaderDrawer.Draw(SelectionName, 0, ImGui.GetColorU32(ImGuiCol.FrameBg), - 2, SetFromClipboardButton(), ExportToClipboardButton(), LockButton(), + 3, SetFromClipboardButton(), UndoButton(), ExportToClipboardButton(), LockButton(), HeaderDrawer.Button.IncognitoButton(_selector.IncognitoMode, v => _selector.IncognitoMode = v)); private string SelectionName @@ -411,6 +421,18 @@ public class DesignPanel } } + private void UndoOverwrite() + { + try + { + _manager.UndoDesignChange(_selector.Selected!); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {_selector.Selected!.Name}.", NotificationType.Error, false); + } + } + private void ExportToClipboard() { try From 823a8606d32acc408eb1556edb4c2fafc147a657 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Nov 2023 23:12:26 +0100 Subject: [PATCH 036/786] Add /glamour help text. --- Glamourer/Services/CommandService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 02d4185..0cb4eab 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -86,6 +86,7 @@ public class CommandService : IDisposable return; default: _chat.Print("Use without argument to toggle the main window."); + _chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer").AddText(" for application commands.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("qdb", "Toggles the quick design bar on or off.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("lock", "Toggles the lock of the main window on or off.").BuiltString); return; From 8ba6a9fa33288cf66e147929722cae4b88d6e18e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 11 Nov 2023 13:56:38 +0100 Subject: [PATCH 037/786] Open main window when selecting tab. --- Glamourer/Gui/MainWindow.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 415fc89..3cd6d79 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -83,7 +83,7 @@ public class MainWindow : Window, IDisposable { Flags = _config.LockMainWindow ? Flags | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize - : Flags & ~(ImGuiWindowFlags.NoMove |ImGuiWindowFlags.NoResize); + : Flags & ~(ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); } public void Dispose() @@ -154,7 +154,10 @@ public class MainWindow : Window, IDisposable } private void OnTabSelected(TabType type, Design? _) - => SelectTab = type; + { + SelectTab = type; + IsOpen = true; + } private static string GetLabel() => Glamourer.Version.Length == 0 From 879b8e49a0839fbb42a2fc7f6ac231ebae1d841f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 11 Nov 2023 14:00:29 +0100 Subject: [PATCH 038/786] Update BNPC Data. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index d5c27fb..545aab1 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d5c27fb2af24e6756eb0eabe69a95eefed46160b +Subproject commit 545aab1f007158a5d53bc6a7d73b6b2992deb9b3 From 09b0db977ff79aacc05a334f3ed0933fe9d13ce4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 11 Nov 2023 14:16:21 +0100 Subject: [PATCH 039/786] 1.0.5.0 --- Glamourer/Gui/GlamourerChangelog.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 14bac50..a06eb09 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -22,6 +22,7 @@ public class GlamourerChangelog Add1_0_2_0(Changelog); Add1_0_3_0(Changelog); Add1_0_4_0(Changelog); + Add1_0_5_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -34,6 +35,20 @@ public class GlamourerChangelog _config.Save(); } + private static void Add1_0_5_0(Changelog log) + => log.NextVersion("Version 1.0.5.0") + .RegisterHighlight("Dyes are can now be favorited the same way equipment pieces can.") + .RegisterHighlight("The quick design bar combo can now be scrolled through via mousewheel when hovering over the combo without opening it.") + .RegisterEntry("Control-Rightclicking the quick design bar now not only jumps to the corresponding design, but also opens the main window if it is not currently open.") + .RegisterHighlight("You can now filter for designs containing specific items by using \"i:partial item name\".") + .RegisterEntry("When overwriting a saved designs data entirely from clipboard, you can now undo this change and restore the prior design data once via a button top-left.") + .RegisterEntry("Removed the \"Enabled\" checkbox in the settings since it was barely doing anything but breaking Glamourer.") + .RegisterEntry("If you want to disable Glamourers state-tracking and hooking, you will need to disable the entire Plugin via Dalamud now.", 1) + .RegisterEntry("Added a reference to \"/glamour\" in the \"/glamourer help\" section.") + .RegisterEntry("Updated BNPC Data with new crowd-sourced data from the gubal library.") + .RegisterEntry("Fixed an issue with the quick design bar when no designs are saved.") + .RegisterEntry("Fixed a problem with characters not redrawing after leaving GPose even if necessary."); + private static void Add1_0_4_0(Changelog log) => log.NextVersion("Version 1.0.4.0") .RegisterEntry("The GPose target is now used for target-dependent functionality in GPose.") From 1891957670140e2ec7c7e53b7ef078664a85ab94 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 11 Nov 2023 13:18:43 +0000 Subject: [PATCH 040/786] [CI] Updating repo.json for 1.0.5.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 8c1279e..8692055 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.4.3", - "TestingAssemblyVersion": "1.0.4.3", + "AssemblyVersion": "1.0.5.0", + "TestingAssemblyVersion": "1.0.5.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.4.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.5.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.5.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.5.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 108cfbd828d7b44140dd5320080097861eddf132 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 11 Nov 2023 21:09:17 +0100 Subject: [PATCH 041/786] Add option to open window at game start instead of coupling it with debug mode. --- Glamourer/Configuration.cs | 1 + Glamourer/Gui/MainWindow.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab.cs | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index ff4eb45..4490fa0 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -39,6 +39,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool LockDesignQuickBar { get; set; } = false; public bool ShowQuickBarInTabs { get; set; } = true; public bool LockMainWindow { get; set; } = false; + public bool OpenWindowAtStart { get; set; } = false; public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 3cd6d79..e26ea50 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -76,7 +76,7 @@ public class MainWindow : Window, IDisposable debugTab, }; _event.Subscribe(OnTabSelected, TabSelected.Priority.MainWindow); - IsOpen = _config.DebugMode; + IsOpen = _config.OpenWindowAtStart; } public override void PreDraw() diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index 7b86912..d690a42 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -136,7 +136,8 @@ public class SettingsTab : ITab }); Checkbox("Lock Main Window", "Prevent the main window from being moved and lock it in place.", _config.LockMainWindow, v => _config.LockMainWindow = v); - + Checkbox("Open Main Window at Game Start", "Whether the main Glamourer window should be open or closed after launching the game.", + _config.OpenWindowAtStart, v => _config.OpenWindowAtStart = v); ImGui.Dummy(Vector2.Zero); ImGui.Separator(); ImGui.Dummy(Vector2.Zero); From 8f60688e44a04c518b1205aa2bf47ce26adfb4db Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 12 Nov 2023 13:46:15 +0100 Subject: [PATCH 042/786] Fix design color display on customizations. --- Glamourer/Designs/DesignData.cs | 2 +- .../DesignTab/DesignFileSystemSelector.cs | 30 +++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 0bf66ee..0fd96b2 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -40,7 +40,7 @@ public unsafe struct DesignData public DesignData() { } - public bool ContainsName(LowerString name) + public readonly bool ContainsName(LowerString name) => name.IsContained(_nameHead) || name.IsContained(_nameBody) || name.IsContained(_nameHands) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 71a74a5..05e7bf3 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -7,6 +7,7 @@ using Dalamud.Plugin.Services; using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; +using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -19,11 +20,12 @@ namespace Glamourer.Gui.Tabs.DesignTab; public sealed class DesignFileSystemSelector : FileSystemSelector { - private readonly DesignManager _designManager; - private readonly DesignChanged _event; - private readonly Configuration _config; - private readonly DesignConverter _converter; - private readonly TabSelected _selectionEvent; + private readonly DesignManager _designManager; + private readonly DesignChanged _event; + private readonly Configuration _config; + private readonly DesignConverter _converter; + private readonly TabSelected _selectionEvent; + private readonly CustomizationService _customizationService; private string? _clipboardText; private Design? _cloneDesign; @@ -48,14 +50,15 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Combined wrapper for handling all filters and setting state. private bool ApplyFiltersAndState(DesignFileSystem.Leaf leaf, out DesignState state) { - var applyEquip = leaf.Value.ApplyEquip != 0; - var applyCustomize = (leaf.Value.ApplyCustomize & ~(CustomizeFlag.BodyType | CustomizeFlag.Race)) != 0; + var applyEquip = leaf.Value.ApplyEquip != 0; + var list = _customizationService.AwaitedService.GetList(leaf.Value.DesignData.Customize.Clan, leaf.Value.DesignData.Customize.Gender); + var applyCustomize = leaf.Value.ApplyCustomize.FixApplication(list) != 0; state.Color = (applyEquip, applyCustomize) switch { From c4bb24a6ec4d41104ace5cdcf61c9e359d4987f3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 12 Nov 2023 13:46:36 +0100 Subject: [PATCH 043/786] No border around quick design window. --- Glamourer/Gui/DesignQuickBar.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index b47c810..97cb68c 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -64,7 +64,8 @@ public class DesignQuickBar : Window, IDisposable Flags = GetFlags; Size = new Vector2(12 * ImGui.GetFrameHeight(), ImGui.GetFrameHeight()); - _windowPadding.Push(ImGuiStyleVar.WindowPadding, new Vector2(ImGuiHelpers.GlobalScale * 4)); + _windowPadding.Push(ImGuiStyleVar.WindowPadding, new Vector2(ImGuiHelpers.GlobalScale * 4)) + .Push(ImGuiStyleVar.WindowBorderSize, 0); _windowColor.Push(ImGuiCol.WindowBg, ColorId.QuickDesignBg.Value()) .Push(ImGuiCol.Button, ColorId.QuickDesignButton.Value()) .Push(ImGuiCol.FrameBg, ColorId.QuickDesignFrame.Value()); From 053998e5e4719c77b19d363d4b6540a5b617e322 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 15 Nov 2023 18:37:03 +0100 Subject: [PATCH 044/786] Fix text color for customizations on locked designs. --- .../CustomizationDrawer.Simple.cs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index a43ae9e..3e15cad 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -94,24 +94,26 @@ public partial class CustomizationDrawer using var _ = SetId(index); using var bigGroup = ImRaii.Group(); - using var disabled = ImRaii.Disabled(_locked); - if (indexedBy1) + using (var disabled = ImRaii.Disabled(_locked)) { - ListCombo1(); - ImGui.SameLine(); - ListInputInt1(); - } - else - { - ListCombo0(); - ImGui.SameLine(); - ListInputInt0(); - } + if (indexedBy1) + { + ListCombo1(); + ImGui.SameLine(); + ListInputInt1(); + } + else + { + ListCombo0(); + ImGui.SameLine(); + ListInputInt0(); + } - if (_withApply) - { - ImGui.SameLine(); - ApplyCheckbox(); + if (_withApply) + { + ImGui.SameLine(); + ApplyCheckbox(); + } } ImGui.SameLine(); From 2afa5734f7d3dd15afe38cc7ce33c91ff61ef99f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 15 Nov 2023 18:37:54 +0100 Subject: [PATCH 045/786] Fix display of customization names in application rules. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 013d35d..3bc73af 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -244,7 +244,7 @@ public class DesignPanel foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable)) { var apply = _selector.Selected!.DoApplyCustomize(index); - if (ImGui.Checkbox($"Apply {index.ToDefaultName()}", ref apply)) + if (ImGui.Checkbox($"Apply {set.Option(index)}", ref apply)) _manager.ChangeApplyCustomize(_selector.Selected!, index, apply); } } From f55d25b088d167d55fa26ea85d55ee392c28ed67 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 15 Nov 2023 18:38:42 +0100 Subject: [PATCH 046/786] Improve tri-state checkboxes. --- Glamourer/Gui/Colors.cs | 6 ++++++ Glamourer/Gui/UiHelpers.cs | 42 +++++++++++++++++++++----------------- OtterGui | 2 +- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index 9206498..873ec38 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -25,6 +25,9 @@ public enum ColorId QuickDesignButton, QuickDesignFrame, QuickDesignBg, + TriStateCheck, + TriStateCross, + TriStateNeutral, } public static class Colors @@ -55,6 +58,9 @@ public static class Colors ColorId.QuickDesignButton => (0x900A0A0A, "Quick Design Bar Button Background", "The color of button frames in the quick design bar." ), ColorId.QuickDesignFrame => (0x90383838, "Quick Design Bar Combo Background", "The color of the combo background in the quick design bar." ), ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ), + ColorId.TriStateCheck => (0xFF00D000, "Checkmark in Tri-State Checkboxes", "The color of the checkmark indicating positive change in tri-state checkboxes." ), + ColorId.TriStateCross => (0xFF0000D0, "Cross in Tri-State Checkboxes", "The color of the cross indicating negative change in tri-state checkboxes." ), + ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes" ), _ => (0x00000000, string.Empty, string.Empty ), // @formatter:on }; diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 8299e43..eb1f1c2 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -9,6 +9,7 @@ using Glamourer.Unlocks; using ImGuiNET; using Lumina.Misc; using OtterGui; +using OtterGui.Classes; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -71,15 +72,26 @@ public static class UiHelpers } public static DataChange DrawMetaToggle(string label, bool currentValue, bool currentApply, out bool newValue, - out bool newApply, - bool locked) + out bool newApply, bool locked) { - var flags = currentApply ? currentValue ? 3 : 0 : 2; - bool ret; + var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); using (var disabled = ImRaii.Disabled(locked)) { - ret = ImGui.CheckboxFlags("##" + label, ref flags, 3); + if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw("##" + label, flags, out flags)) + { + (newValue, newApply) = flags switch + { + -1 => (false, true), + 0 => (true, false), + _ => (true, true), + }; + } + else + { + newValue = currentValue; + newApply = currentApply; + } } ImGuiUtil.HoverTooltip($"This attribute will be {(currentApply ? currentValue ? "enabled." : "disabled." : "kept as is.")}"); @@ -87,21 +99,13 @@ public static class UiHelpers ImGui.SameLine(); ImGui.TextUnformatted(label); - if (ret) + return (currentApply != newApply, currentValue != newValue) switch { - (newValue, newApply, var change) = (currentValue, currentApply) switch - { - (false, false) => (false, true, DataChange.ApplyItem), - (false, true) => (true, true, DataChange.Item), - (true, false) => (false, false, DataChange.Item), // Should not happen - (true, true) => (false, false, DataChange.Item | DataChange.ApplyItem), - }; - return change; - } - - newValue = currentValue; - newApply = currentApply; - return DataChange.None; + (true, true) => DataChange.ApplyItem | DataChange.Item, + (true, false) => DataChange.ApplyItem, + (false, true) => DataChange.Item, + _ => DataChange.None, + }; } public static (EquipFlag, CustomizeFlag) ConvertKeysToFlags() diff --git a/OtterGui b/OtterGui index b09bbcc..f55733a 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit b09bbcc276363bc994d90b641871e6280898b6e5 +Subproject commit f55733a96fdc9f82c9bbf8272ca6366079aa8e32 From 68327d3563b6460d263bbceed316f657105d3c9d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 16 Nov 2023 17:32:55 +0100 Subject: [PATCH 047/786] Do a bunch of refactoring regarding available application options. --- Glamourer/Automation/AutoDesign.cs | 2 +- Glamourer/Automation/AutoDesignApplier.cs | 10 +- Glamourer/Designs/Design.cs | 2 +- Glamourer/Designs/DesignBase.cs | 201 ++++++++++-------- Glamourer/Designs/DesignConverter.cs | 21 +- Glamourer/Designs/DesignManager.cs | 52 +++-- .../CustomizationDrawer.GenderRace.cs | 1 - Glamourer/Gui/Tabs/DebugTab.cs | 2 +- .../DesignTab/DesignFileSystemSelector.cs | 9 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 13 +- Glamourer/State/FunModule.cs | 3 +- Glamourer/State/StateManager.cs | 2 +- 12 files changed, 168 insertions(+), 150 deletions(-) diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 2004652..7beda18 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -31,7 +31,7 @@ public class AutoDesign public string Name(bool incognito) => Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text; - public ref DesignData GetDesignData(ActorState state) + public ref readonly DesignData GetDesignData(ActorState state) => ref Design == null ? ref state.BaseData : ref Design.DesignData; public bool Revert diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index f68c3c1..92cd2b0 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -89,7 +89,8 @@ public class AutoDesignApplier : IDisposable { if (_jobChangeMainhand.TryGetValue(current.Type, out var data)) { - Glamourer.Log.Verbose($"Changing Mainhand from {_jobChangeState.ModelData.Weapon(EquipSlot.MainHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); + Glamourer.Log.Verbose( + $"Changing Mainhand from {_jobChangeState.ModelData.Weapon(EquipSlot.MainHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); _state.ChangeItem(_jobChangeState, EquipSlot.MainHand, data.Item1, data.Item2); weapon.Value = _jobChangeState.ModelData.Weapon(EquipSlot.MainHand); } @@ -98,7 +99,8 @@ public class AutoDesignApplier : IDisposable { if (_jobChangeOffhand.TryGetValue(current.Type, out var data)) { - Glamourer.Log.Verbose($"Changing Offhand from {_jobChangeState.ModelData.Weapon(EquipSlot.OffHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); + Glamourer.Log.Verbose( + $"Changing Offhand from {_jobChangeState.ModelData.Weapon(EquipSlot.OffHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); _state.ChangeItem(_jobChangeState, EquipSlot.OffHand, data.Item1, data.Item2); weapon.Value = _jobChangeState.ModelData.Weapon(EquipSlot.OffHand); } @@ -277,8 +279,8 @@ public class AutoDesignApplier : IDisposable if (design.ApplicationType is 0) continue; - ref var data = ref design.GetDesignData(state); - var source = design.Revert ? StateChanged.Source.Game : StateChanged.Source.Fixed; + ref readonly var data = ref design.GetDesignData(state); + var source = design.Revert ? StateChanged.Source.Game : StateChanged.Source.Fixed; if (!data.IsHuman) continue; diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index e6593a9..9901e81 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -15,7 +15,7 @@ public sealed class Design : DesignBase, ISavable { #region Data internal Design(CustomizationService customize, ItemManager items) - : base(items) + : base(customize, items) { } internal Design(DesignBase other) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 9fe18ff..cb40279 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -16,20 +16,37 @@ public class DesignBase { public const int FileVersion = 1; - internal DesignBase(ItemManager items) + private DesignData _designData = new(); + + /// For read-only information about the actual design. + public ref readonly DesignData DesignData + => ref _designData; + + /// To make it clear that something is edited here. + public ref DesignData GetDesignDataRef() + => ref _designData; + + internal DesignBase(CustomizationService customize, ItemManager items) { - DesignData.SetDefaultEquipment(items); + _designData.SetDefaultEquipment(items); + CustomizationSet = SetCustomizationSet(customize); } internal DesignBase(DesignBase clone) { - DesignData = clone.DesignData; - ApplyCustomize = clone.ApplyCustomize & CustomizeFlagExtensions.AllRelevant; - ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; - _designFlags = clone._designFlags & (DesignFlags)0x0F; + _designData = clone._designData; + CustomizationSet = clone.CustomizationSet; + ApplyCustomize = clone.ApplyCustomizeRaw; + ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; + _designFlags = clone._designFlags & (DesignFlags)0x0F; } - internal DesignData DesignData = new(); + /// Ensure that the customization set is updated when the design data changes. + internal void SetDesignData(CustomizationService customize, in DesignData other) + { + _designData = other; + CustomizationSet = SetCustomizationSet(customize); + } #region Application Data @@ -43,9 +60,30 @@ public class DesignBase WriteProtected = 0x10, } - internal CustomizeFlag ApplyCustomize = CustomizeFlagExtensions.AllRelevant; - internal EquipFlag ApplyEquip = EquipFlagExtensions.All; - private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; + private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; + public CustomizationSet CustomizationSet { get; private set; } + + internal CustomizeFlag ApplyCustomize + { + get => _applyCustomize.FixApplication(CustomizationSet); + set => _applyCustomize = value & CustomizeFlagExtensions.AllRelevant; + } + + internal CustomizeFlag ApplyCustomizeRaw + => _applyCustomize; + + internal EquipFlag ApplyEquip = EquipFlagExtensions.All; + private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; + + public bool SetCustomize(CustomizationService customizationService, Customize customize) + { + if (customize.Equals(_designData.Customize)) + return false; + + _designData.Customize.Load(customize); + CustomizationSet = customizationService.AwaitedService.GetList(customize.Clan, customize.Gender); + return true; + } public bool DoApplyHatVisible() => _designFlags.HasFlag(DesignFlags.ApplyHatVisible); @@ -119,7 +157,7 @@ public class DesignBase => ApplyEquip.HasFlag(slot.ToStainFlag()); public bool DoApplyCustomize(CustomizeIndex idx) - => idx is not CustomizeIndex.Race and not CustomizeIndex.BodyType && ApplyCustomize.HasFlag(idx.ToFlag()); + => ApplyCustomize.HasFlag(idx.ToFlag()); internal bool SetApplyEquip(EquipSlot slot, bool value) { @@ -143,20 +181,14 @@ public class DesignBase internal bool SetApplyCustomize(CustomizeIndex idx, bool value) { - var newValue = value ? ApplyCustomize | idx.ToFlag() : ApplyCustomize & ~idx.ToFlag(); - if (newValue == ApplyCustomize) + var newValue = value ? _applyCustomize | idx.ToFlag() : _applyCustomize & ~idx.ToFlag(); + if (newValue == _applyCustomize) return false; - ApplyCustomize = newValue; + _applyCustomize = newValue; return true; } - public void FixCustomizeApplication(CustomizationService service, CustomizeFlag flags) - => FixCustomizeApplication(service.AwaitedService.GetList(DesignData.Customize.Clan, DesignData.Customize.Gender), flags); - - public void FixCustomizeApplication(CustomizationSet set, CustomizeFlag flags) - => ApplyCustomize = flags.FixApplication(set); - internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags) => new(this, equipFlags, customizeFlags); @@ -170,7 +202,7 @@ public class DesignBase { _design = d; _oldEquipFlags = d.ApplyEquip; - _oldCustomizeFlags = d.ApplyCustomize; + _oldCustomizeFlags = d.ApplyCustomizeRaw; d.ApplyEquip &= equipFlags; d.ApplyCustomize &= customizeFlags; } @@ -182,6 +214,11 @@ public class DesignBase } } + private CustomizationSet SetCustomizationSet(CustomizationService customize) + => !_designData.IsHuman + ? customize.AwaitedService.GetList(SubRace.Midlander, Gender.Male) + : customize.AwaitedService.GetList(_designData.Customize.Clan, _designData.Customize.Gender); + #endregion #region Serialization @@ -209,22 +246,22 @@ public class DesignBase }; var ret = new JObject(); - if (DesignData.IsHuman) + if (_designData.IsHuman) { foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) { - var item = DesignData.Item(slot); - var stain = DesignData.Stain(slot); + var item = _designData.Item(slot); + var stain = _designData.Stain(slot); ret[slot.ToString()] = Serialize(item.Id, stain, DoApplyEquip(slot), DoApplyStain(slot)); } - ret["Hat"] = new QuadBool(DesignData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply"); - ret["Visor"] = new QuadBool(DesignData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply"); - ret["Weapon"] = new QuadBool(DesignData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply"); + ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply"); + ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply"); + ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply"); } else { - ret["Array"] = DesignData.WriteEquipmentBytesBase64(); + ret["Array"] = _designData.WriteEquipmentBytesBase64(); } return ret; @@ -234,17 +271,17 @@ public class DesignBase { var ret = new JObject() { - ["ModelId"] = DesignData.ModelId, + ["ModelId"] = _designData.ModelId, }; - var customize = DesignData.Customize; - if (DesignData.IsHuman) + var customize = _designData.Customize; + if (_designData.IsHuman) foreach (var idx in Enum.GetValues()) { ret[idx.ToString()] = new JObject() { ["Value"] = customize[idx].Value, - ["Apply"] = DoApplyCustomize(idx), + ["Apply"] = ApplyCustomizeRaw.HasFlag(idx.ToFlag()), }; } else @@ -252,7 +289,7 @@ public class DesignBase ret["Wetness"] = new JObject() { - ["Value"] = DesignData.IsWet(), + ["Value"] = _designData.IsWet(), ["Apply"] = DoApplyWetness(), }; @@ -275,7 +312,7 @@ public class DesignBase private static DesignBase LoadDesignV1Base(CustomizationService customizations, ItemManager items, JObject json) { - var ret = new DesignBase(items); + var ret = new DesignBase(customizations, items); LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true); LoadEquip(items, json["Equipment"], ret, "Temporary Design", true); return ret; @@ -285,16 +322,16 @@ public class DesignBase { if (equip == null) { - design.DesignData.SetDefaultEquipment(items); + design._designData.SetDefaultEquipment(items); Glamourer.Messager.NotificationMessage("The loaded design does not contain any equipment data, reset to default.", NotificationType.Warning); return; } - if (!design.DesignData.IsHuman) + if (!design._designData.IsHuman) { var textArray = equip["Array"]?.ToObject() ?? string.Empty; - design.DesignData.SetEquipmentBytesFromBase64(textArray); + design._designData.SetEquipmentBytesFromBase64(textArray); return; } @@ -319,8 +356,8 @@ public class DesignBase PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); - design.DesignData.SetItem(slot, item); - design.DesignData.SetStain(slot, stain); + design._designData.SetItem(slot, item); + design._designData.SetStain(slot, stain); design.SetApplyEquip(slot, apply); design.SetApplyStain(slot, applyStain); } @@ -336,10 +373,10 @@ public class DesignBase PrintWarning(items.ValidateWeapons(id, idOff, out var main, out var off, allowUnknown)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); PrintWarning(items.ValidateStain(stainOff, out stainOff, allowUnknown)); - design.DesignData.SetItem(EquipSlot.MainHand, main); - design.DesignData.SetItem(EquipSlot.OffHand, off); - design.DesignData.SetStain(EquipSlot.MainHand, stain); - design.DesignData.SetStain(EquipSlot.OffHand, stainOff); + design._designData.SetItem(EquipSlot.MainHand, main); + design._designData.SetItem(EquipSlot.OffHand, off); + design._designData.SetStain(EquipSlot.MainHand, stain); + design._designData.SetStain(EquipSlot.OffHand, stainOff); design.SetApplyEquip(EquipSlot.MainHand, apply); design.SetApplyEquip(EquipSlot.OffHand, applyOff); design.SetApplyStain(EquipSlot.MainHand, applyStain); @@ -347,15 +384,15 @@ public class DesignBase } var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse); design.SetApplyHatVisible(metaValue.Enabled); - design.DesignData.SetHatVisible(metaValue.ForcedValue); + design._designData.SetHatVisible(metaValue.ForcedValue); metaValue = QuadBool.FromJObject(equip["Weapon"], "Show", "Apply", QuadBool.NullFalse); design.SetApplyWeaponVisible(metaValue.Enabled); - design.DesignData.SetWeaponVisible(metaValue.ForcedValue); + design._designData.SetWeaponVisible(metaValue.ForcedValue); metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse); design.SetApplyVisorToggle(metaValue.Enabled); - design.DesignData.SetVisor(metaValue.ForcedValue); + design._designData.SetVisor(metaValue.ForcedValue); } protected static void LoadCustomize(CustomizationService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman, @@ -363,9 +400,9 @@ public class DesignBase { if (json == null) { - design.DesignData.ModelId = 0; - design.DesignData.IsHuman = true; - design.DesignData.Customize = Customize.Default; + design._designData.ModelId = 0; + design._designData.IsHuman = true; + design.SetCustomize(customizations, Customize.Default); Glamourer.Messager.NotificationMessage("The loaded design does not contain any customization data, reset to default.", NotificationType.Warning); return; @@ -380,21 +417,22 @@ public class DesignBase } var wetness = QuadBool.FromJObject(json["Wetness"], "Value", "Apply", QuadBool.NullFalse); - design.DesignData.SetIsWet(wetness.ForcedValue); + design._designData.SetIsWet(wetness.ForcedValue); design.SetApplyWetness(wetness.Enabled); - design.DesignData.ModelId = json["ModelId"]?.ToObject() ?? 0; - PrintWarning(customizations.ValidateModelId(design.DesignData.ModelId, out design.DesignData.ModelId, out design.DesignData.IsHuman)); - if (design.DesignData.ModelId != 0 && forbidNonHuman) + design._designData.ModelId = json["ModelId"]?.ToObject() ?? 0; + PrintWarning(customizations.ValidateModelId(design._designData.ModelId, out design._designData.ModelId, out design._designData.IsHuman)); + if (design._designData.ModelId != 0 && forbidNonHuman) { PrintWarning("Model IDs different from 0 are not currently allowed, reset model id to 0."); - design.DesignData.ModelId = 0; - design.DesignData.IsHuman = true; + design._designData.ModelId = 0; + design._designData.IsHuman = true; } - else if (!design.DesignData.IsHuman) + else if (!design._designData.IsHuman) { var arrayText = json["Array"]?.ToObject() ?? string.Empty; - design.DesignData.Customize.LoadBase64(arrayText); + design._designData.Customize.LoadBase64(arrayText); + design.CustomizationSet = design.SetCustomizationSet(customizations); return; } @@ -403,42 +441,32 @@ public class DesignBase PrintWarning(customizations.ValidateClan(clan, race, out race, out clan)); var gender = (Gender)((json[CustomizeIndex.Gender.ToString()]?["Value"]?.ToObject() ?? 0) + 1); PrintWarning(customizations.ValidateGender(race, gender, out gender)); - design.DesignData.Customize.Race = race; - design.DesignData.Customize.Clan = clan; - design.DesignData.Customize.Gender = gender; + design._designData.Customize.Race = race; + design._designData.Customize.Clan = clan; + design._designData.Customize.Gender = gender; + design.CustomizationSet = design.SetCustomizationSet(customizations); design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject() ?? false); - - var set = customizations.AwaitedService.GetList(clan, gender); + var set = design.CustomizationSet; foreach (var idx in CustomizationExtensions.AllBasic) { - if (set.IsAvailable(idx)) - { - var tok = json[idx.ToString()]; - var data = (CustomizeValue)(tok?["Value"]?.ToObject() ?? 0); - PrintWarning(CustomizationService.ValidateCustomizeValue(set, design.DesignData.Customize.Face, idx, data, out data, - allowUnknown)); - var apply = tok?["Apply"]?.ToObject() ?? false; - design.DesignData.Customize[idx] = data; - design.SetApplyCustomize(idx, apply); - } - else - { - design.DesignData.Customize[idx] = CustomizeValue.Zero; - design.SetApplyCustomize(idx, false); - } + var tok = json[idx.ToString()]; + var data = (CustomizeValue)(tok?["Value"]?.ToObject() ?? 0); + PrintWarning(CustomizationService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data, + allowUnknown)); + var apply = tok?["Apply"]?.ToObject() ?? false; + design._designData.Customize[idx] = data; + design.SetApplyCustomize(idx, apply); } - - design.FixCustomizeApplication(set, design.ApplyCustomize); } - public void MigrateBase64(ItemManager items, HumanModelList humans, string base64) + public void MigrateBase64(CustomizationService customize, ItemManager items, HumanModelList humans, string base64) { try { - DesignData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags, + _designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags, out var writeProtected, out var applyHat, out var applyVisor, out var applyWeapon); ApplyEquip = equipFlags; @@ -448,6 +476,7 @@ public class DesignBase SetApplyVisorToggle(applyVisor); SetApplyWeaponVisible(applyWeapon); SetApplyWetness(true); + CustomizationSet = SetCustomizationSet(customize); } catch (Exception ex) { @@ -455,15 +484,5 @@ public class DesignBase } } - public void RemoveInvalidCustomize(CustomizationService customizations) - { - var set = customizations.AwaitedService.GetList(DesignData.Customize.Clan, DesignData.Customize.Gender); - foreach (var idx in CustomizationExtensions.AllBasic.Where(i => !set.IsAvailable(i))) - { - DesignData.Customize[idx] = CustomizeValue.Zero; - SetApplyCustomize(idx, false); - } - } - #endregion } diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 22bf287..d8f2b0a 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -60,13 +60,13 @@ public class DesignConverter public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags) { var design = _designs.CreateTemporary(); - design.ApplyEquip = equipFlags & EquipFlagExtensions.All; + design.ApplyEquip = equipFlags & EquipFlagExtensions.All; + design.ApplyCustomize = customizeFlags; design.SetApplyHatVisible(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand)); design.SetApplyWetness(true); - design.DesignData = state.ModelData; - design.FixCustomizeApplication(_customize, customizeFlags); + design.SetDesignData(_customize, state.ModelData); return design; } @@ -90,7 +90,7 @@ public class DesignConverter case 2: case 4: ret = _designs.CreateTemporary(); - ret.MigrateBase64(_items, _humans, base64); + ret.MigrateBase64(_customize, _items, _humans, base64); break; case 3: { @@ -122,15 +122,8 @@ public class DesignConverter return null; } - if (!customize) - { - ret.ApplyCustomize = 0; - ret.SetApplyWetness(false); - } - else - { - ret.FixCustomizeApplication(_customize, ret.ApplyCustomize); - } + ret.SetApplyWetness(customize); + ret.ApplyCustomize = customize ? CustomizeFlagExtensions.AllRelevant : 0; if (!equip) { @@ -152,7 +145,7 @@ public class DesignConverter private static string ShareBackwardCompatible(JObject jObject, DesignBase design) { - var oldBase64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomize, + var oldBase64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, design.DoApplyHatVisible(), design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected(), 1f); var oldBytes = System.Convert.FromBase64String(oldBase64); var json = jObject.ToString(Formatting.None); diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index c67dd88..1084fb0 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -90,7 +90,7 @@ public class DesignManager /// Create a new temporary design without adding it to the manager. public DesignBase CreateTemporary() - => new(_items); + => new(_customizations, _items); /// Create a new design of a given name. public Design CreateEmpty(string name, bool handlePath) @@ -275,6 +275,7 @@ public class DesignManager public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value) { var oldValue = design.DesignData.Customize[idx]; + switch (idx) { case CustomizeIndex.Race: @@ -282,21 +283,29 @@ public class DesignManager Glamourer.Log.Error("Somehow race or body type was changed in a design. This should not happen."); return; case CustomizeIndex.Clan: - if (_customizations.ChangeClan(ref design.DesignData.Customize, (SubRace)value.Value) == 0) + { + var customize = new Customize(design.DesignData.Customize.Data.Clone()); + if (_customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0) + return; + if (!design.SetCustomize(_customizations, customize)) return; - design.RemoveInvalidCustomize(_customizations); break; + } case CustomizeIndex.Gender: - if (_customizations.ChangeGender(ref design.DesignData.Customize, (Gender)(value.Value + 1)) == 0) + { + var customize = new Customize(design.DesignData.Customize.Data.Clone()); + if (_customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0) + return; + if (!design.SetCustomize(_customizations, customize)) return; - design.RemoveInvalidCustomize(_customizations); break; + } default: if (!_customizations.IsCustomizationValid(design.DesignData.Customize.Clan, design.DesignData.Customize.Gender, design.DesignData.Customize.Face, idx, value) - || !design.DesignData.Customize.Set(idx, value)) + || !design.GetDesignDataRef().Customize.Set(idx, value)) return; break; @@ -311,8 +320,6 @@ public class DesignManager /// Change whether to apply a specific customize value. public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value) { - var set = _customizations.AwaitedService.GetList(design.DesignData.Customize.Clan, design.DesignData.Customize.Gender); - value &= set.IsAvailable(idx) || idx is CustomizeIndex.Clan or CustomizeIndex.Gender; if (!design.SetApplyCustomize(idx, value)) return; @@ -329,7 +336,7 @@ public class DesignManager return; var old = design.DesignData.Item(slot); - if (!design.DesignData.SetItem(slot, item)) + if (!design.GetDesignDataRef().SetItem(slot, item)) return; design.LastEdit = DateTimeOffset.UtcNow; @@ -358,7 +365,8 @@ public class DesignManager return; } - if (!(design.DesignData.SetItem(EquipSlot.MainHand, item) | design.DesignData.SetItem(EquipSlot.OffHand, newOff))) + if (!(design.GetDesignDataRef().SetItem(EquipSlot.MainHand, item) + | design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff))) return; design.LastEdit = DateTimeOffset.UtcNow; @@ -372,7 +380,7 @@ public class DesignManager if (!_items.IsOffhandValid(currentOff.Type, item.ItemId, out item)) return; - if (!design.DesignData.SetItem(EquipSlot.OffHand, item)) + if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item)) return; design.LastEdit = DateTimeOffset.UtcNow; @@ -404,7 +412,7 @@ public class DesignManager return; var oldStain = design.DesignData.Stain(slot); - if (!design.DesignData.SetStain(slot, stain)) + if (!design.GetDesignDataRef().SetStain(slot, stain)) return; design.LastEdit = DateTimeOffset.UtcNow; @@ -430,10 +438,10 @@ public class DesignManager { var change = metaIndex switch { - ActorState.MetaIndex.Wetness => design.DesignData.SetIsWet(value), - ActorState.MetaIndex.HatState => design.DesignData.SetHatVisible(value), - ActorState.MetaIndex.VisorState => design.DesignData.SetVisor(value), - ActorState.MetaIndex.WeaponState => design.DesignData.SetWeaponVisible(value), + ActorState.MetaIndex.Wetness => design.GetDesignDataRef().SetIsWet(value), + ActorState.MetaIndex.HatState => design.GetDesignDataRef().SetHatVisible(value), + ActorState.MetaIndex.VisorState => design.GetDesignDataRef().SetVisor(value), + ActorState.MetaIndex.WeaponState => design.GetDesignDataRef().SetWeaponVisible(value), _ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null), }; if (!change) @@ -470,13 +478,13 @@ public class DesignManager { _undoStore[design.Identifier] = design.DesignData; if (other.DoApplyWetness()) - design.DesignData.SetIsWet(other.DesignData.IsWet()); + design.GetDesignDataRef().SetIsWet(other.DesignData.IsWet()); if (other.DoApplyHatVisible()) - design.DesignData.SetHatVisible(other.DesignData.IsHatVisible()); + design.GetDesignDataRef().SetHatVisible(other.DesignData.IsHatVisible()); if (other.DoApplyVisorToggle()) - design.DesignData.SetVisor(other.DesignData.IsVisorToggled()); + design.GetDesignDataRef().SetVisor(other.DesignData.IsVisorToggled()); if (other.DoApplyWeaponVisible()) - design.DesignData.SetWeaponVisible(other.DesignData.IsWeaponVisible()); + design.GetDesignDataRef().SetWeaponVisible(other.DesignData.IsWeaponVisible()); if (design.DesignData.IsHuman) { @@ -515,7 +523,7 @@ public class DesignManager return; var other = CreateTemporary(); - other.DesignData = otherData; + other.SetDesignData(_customizations, otherData); ApplyDesign(design, other); } @@ -545,7 +553,7 @@ public class DesignManager Identifier = CreateNewGuid(), Name = actualName, }; - design.MigrateBase64(_items, _humans, base64); + design.MigrateBase64(_customizations, _items, _humans, base64); if (!oldDesigns.Any(d => d.Name == design.Name && d.CreationDate == design.CreationDate)) { Add(design, $"Migrated old design to {design.Identifier}."); diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index 9cfe301..5528f44 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Specialized; using System.Linq; using Dalamud.Interface; using Glamourer.Customization; diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index 482a7ab..3b07b33 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -962,7 +962,7 @@ public unsafe class DebugTab : ITab continue; DrawDesign(design); - var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomize, + var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, design.DoApplyHatVisible(), design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected()); using var font = ImRaii.PushFont(UiBuilder.MonoFont); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 05e7bf3..0127cdb 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -4,7 +4,6 @@ using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Services; @@ -119,6 +118,9 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Combined wrapper for handling all filters and setting state. private bool ApplyFiltersAndState(DesignFileSystem.Leaf leaf, out DesignState state) { - var applyEquip = leaf.Value.ApplyEquip != 0; - var list = _customizationService.AwaitedService.GetList(leaf.Value.DesignData.Customize.Clan, leaf.Value.DesignData.Customize.Gender); - var applyCustomize = leaf.Value.ApplyCustomize.FixApplication(list) != 0; + var applyEquip = leaf.Value.ApplyEquip != 0; + var applyCustomize = leaf.Value.ApplyCustomize != 0; state.Color = (applyEquip, applyCustomize) switch { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 3bc73af..64324d2 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -193,7 +193,7 @@ public class DesignPanel if (!ImGui.CollapsingHeader(header)) return; - if (_customizationDrawer.Draw(_selector.Selected!.DesignData.Customize, _selector.Selected.ApplyCustomize, + if (_customizationDrawer.Draw(_selector.Selected!.DesignData.Customize, _selector.Selected.ApplyCustomizeRaw, _selector.Selected!.WriteProtected(), false)) foreach (var idx in Enum.GetValues()) { @@ -217,18 +217,15 @@ public class DesignPanel using (var group1 = ImRaii.Group()) { - var set = _customizationService.AwaitedService.GetList(_selector.Selected!.DesignData.Customize.Clan, - _selector.Selected!.DesignData.Customize.Gender); - var all = CustomizationExtensions.All.Where(set.IsAvailable).Select(c => c.ToFlag()).Aggregate((a, b) => a | b) - | CustomizeFlag.Clan - | CustomizeFlag.Gender; - var flags = (_selector.Selected!.ApplyCustomize & all) == 0 ? 0 : (_selector.Selected!.ApplyCustomize & all) == all ? 3 : 1; + var set = _selector.Selected!.CustomizationSet; + var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; + var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) { var newFlags = flags == 3; _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, newFlags); _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, newFlags); - foreach (var index in CustomizationExtensions.AllBasic.Where(set.IsAvailable)) + foreach (var index in CustomizationExtensions.AllBasic) _manager.ChangeApplyCustomize(_selector.Selected!, index, newFlags); } diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 77a990f..b570d45 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -249,8 +249,7 @@ public unsafe class FunModule : IDisposable try { var tmp = _designManager.CreateTemporary(); - tmp.DesignData = _stateManager.FromActor(actor, true, true); - tmp.FixCustomizeApplication(_customizations, CustomizeFlagExtensions.AllRelevant); + tmp.SetDesignData(_customizations, _stateManager.FromActor(actor, true, true)); var data = _designConverter.ShareBase64(tmp); ImGui.SetClipboardText(data); Glamourer.Messager.NotificationMessage($"Copied current actual design of {actor.Utf8Name} to clipboard.", NotificationType.Info, diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 4a85112..5157c60 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -368,7 +368,7 @@ public class StateManager : IReadOnlyDictionary }; } - if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.DesignData.GetEquipmentPtr(), source, + if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(), source, out var oldModelId, key)) return; From ec7a53bee228c89e4c690c5a3fc7cf2ae520f281 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 16 Nov 2023 18:21:50 +0100 Subject: [PATCH 048/786] Improve (hopefully) some handling of options and stuff. Remove localization because wonky. --- .../Customization/CustomName.cs | 7 ---- .../Customization/CustomizationOptions.cs | 37 +++++++++---------- .../Customization/CustomizeIndex.cs | 6 +-- .../CustomizationDrawer.GenderRace.cs | 4 +- .../Customization/CustomizationDrawer.Icon.cs | 31 ++++++++++++++-- .../Gui/Customization/CustomizationDrawer.cs | 2 +- .../DesignTab/DesignFileSystemSelector.cs | 1 + 7 files changed, 52 insertions(+), 36 deletions(-) diff --git a/Glamourer.GameData/Customization/CustomName.cs b/Glamourer.GameData/Customization/CustomName.cs index 6bff835..040c476 100644 --- a/Glamourer.GameData/Customization/CustomName.cs +++ b/Glamourer.GameData/Customization/CustomName.cs @@ -3,13 +3,6 @@ // Localization from the game files directly. public enum CustomName { - Clan = 0, - Gender, - Reverse, - OddEyes, - IrisSmall, - IrisLarge, - IrisSize, MidlanderM, HighlanderM, WildwoodM, diff --git a/Glamourer.GameData/Customization/CustomizationOptions.cs b/Glamourer.GameData/Customization/CustomizationOptions.cs index a3e04f5..fdbac65 100644 --- a/Glamourer.GameData/Customization/CustomizationOptions.cs +++ b/Glamourer.GameData/Customization/CustomizationOptions.cs @@ -72,6 +72,7 @@ public partial class CustomizationOptions foreach (var gender in Genders) _customizationSets[ToIndex(race, gender)] = tmp.GetSet(race, gender); } + tmp.SetNpcData(_customizationSets); } @@ -85,13 +86,6 @@ public partial class CustomizationOptions void Set(CustomName id, Lumina.Text.SeString? s, string def) => _names[(int)id] = s?.ToDalamudString().TextValue ?? def; - Set(CustomName.Clan, tmp.Lobby.GetRow(102)?.Text, "Clan"); - Set(CustomName.Gender, tmp.Lobby.GetRow(103)?.Text, "Gender"); - Set(CustomName.Reverse, tmp.Lobby.GetRow(2135)?.Text, "Reverse"); - Set(CustomName.OddEyes, tmp.Lobby.GetRow(2125)?.Text, "Odd Eyes"); - Set(CustomName.IrisSmall, tmp.Lobby.GetRow(1076)?.Text, "Small"); - Set(CustomName.IrisLarge, tmp.Lobby.GetRow(1075)?.Text, "Large"); - Set(CustomName.IrisSize, tmp.Lobby.GetRow(244)?.Text, "Iris Size"); Set(CustomName.MidlanderM, subRace.GetRow((int)SubRace.Midlander)?.Masculine, SubRace.Midlander.ToName()); Set(CustomName.MidlanderF, subRace.GetRow((int)SubRace.Midlander)?.Feminine, SubRace.Midlander.ToName()); Set(CustomName.HighlanderM, subRace.GetRow((int)SubRace.Highlander)?.Masculine, SubRace.Highlander.ToName()); @@ -184,10 +178,10 @@ public partial class CustomizationOptions { _options = options; _cmpFile = new CmpFile(gameData, log); - _customizeSheet = gameData.GetExcelSheet()!; - _bnpcCustomize = gameData.GetExcelSheet()!; - _enpcBase = gameData.GetExcelSheet()!; - Lobby = gameData.GetExcelSheet()!; + _customizeSheet = gameData.GetExcelSheet(ClientLanguage.English)!; + _bnpcCustomize = gameData.GetExcelSheet(ClientLanguage.English)!; + _enpcBase = gameData.GetExcelSheet(ClientLanguage.English)!; + Lobby = gameData.GetExcelSheet(ClientLanguage.English)!; var tmp = gameData.Excel.GetType().GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)? .MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[] { @@ -401,20 +395,25 @@ public partial class CustomizationOptions return c.ToDefaultName(); } - // Facial Features and Tattoos is created by combining two strings. - if (c is >= CustomizeIndex.FacialFeature1 and <= CustomizeIndex.LegacyTattoo) - return - $"{Lobby.GetRow(1741)?.Text.ToDalamudString().ToString() ?? "Facial Features"} & {Lobby.GetRow(1742)?.Text.ToDalamudString().ToString() ?? "Tattoos"}"; - // Otherwise all is normal, get the menu name or if it does not work the default name. var textRow = Lobby.GetRow(menu.Value.Id); return textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName(); }).ToArray(); // Add names for both eye colors. - nameArray[(int)CustomizeIndex.EyeColorLeft] = nameArray[(int)CustomizeIndex.EyeColorRight]; - nameArray[(int)CustomizeIndex.EyeColorRight] = _options.GetName(CustomName.OddEyes); - + nameArray[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorLeft.ToDefaultName(); + nameArray[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.EyeColorRight.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature1] = CustomizeIndex.FacialFeature1.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature2] = CustomizeIndex.FacialFeature2.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature3] = CustomizeIndex.FacialFeature3.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature4] = CustomizeIndex.FacialFeature4.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature5] = CustomizeIndex.FacialFeature5.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature6] = CustomizeIndex.FacialFeature6.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature7] = CustomizeIndex.FacialFeature7.ToDefaultName(); + nameArray[(int)CustomizeIndex.LegacyTattoo] = CustomizeIndex.LegacyTattoo.ToDefaultName(); + nameArray[(int)CustomizeIndex.SmallIris] = CustomizeIndex.SmallIris.ToDefaultName(); + nameArray[(int)CustomizeIndex.Lipstick] = CustomizeIndex.Lipstick.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacePaintReversed] = CustomizeIndex.FacePaintReversed.ToDefaultName(); set.OptionName = nameArray; } diff --git a/Glamourer.GameData/Customization/CustomizeIndex.cs b/Glamourer.GameData/Customization/CustomizeIndex.cs index 1c03dd1..38aca21 100644 --- a/Glamourer.GameData/Customization/CustomizeIndex.cs +++ b/Glamourer.GameData/Customization/CustomizeIndex.cs @@ -111,14 +111,14 @@ public static class CustomizationExtensions CustomizeIndex.Clan => "Clan", CustomizeIndex.Face => "Head Style", CustomizeIndex.Hairstyle => "Hair Style", - CustomizeIndex.Highlights => "Highlights", + CustomizeIndex.Highlights => "Enable Highlights", CustomizeIndex.SkinColor => "Skin Color", - CustomizeIndex.EyeColorRight => "Right Eye Color", + CustomizeIndex.EyeColorRight => "Left Eye", // inverted due to compatibility fuckup. CustomizeIndex.HairColor => "Hair Color", CustomizeIndex.HighlightsColor => "Highlights Color", CustomizeIndex.TattooColor => "Tattoo Color", CustomizeIndex.Eyebrows => "Eyebrow Style", - CustomizeIndex.EyeColorLeft => "Left Eye Color", + CustomizeIndex.EyeColorLeft => "Right Eye", // inverted due to compatibility fuckup. CustomizeIndex.EyeShape => "Small Pupils", CustomizeIndex.Nose => "Nose Style", CustomizeIndex.Jaw => "Jaw Style", diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index 5528f44..7866bda 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -17,8 +17,6 @@ public partial class CustomizationDrawer ImGui.SameLine(); using var group = ImRaii.Group(); DrawRaceCombo(); - var gender = _service.AwaitedService.GetName(CustomName.Gender); - var clan = _service.AwaitedService.GetName(CustomName.Clan); if (_withApply) { using var disabled = ImRaii.Disabled(_locked); @@ -33,7 +31,7 @@ public partial class CustomizationDrawer } ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted($"{gender} & {clan}"); + ImGui.TextUnformatted("Gender & Clan"); } private void DrawGenderSelector() diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index cabd2d5..e4c15d4 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System; +using System.Numerics; using Dalamud.Interface.Utility; using Glamourer.Customization; using ImGuiNET; @@ -113,8 +114,32 @@ public partial class CustomizationDrawer ImGui.SameLine(); ApplyCheckbox(CustomizeIndex.FacialFeature4); } + else + { + ImGui.Dummy(new Vector2(ImGui.GetFrameHeight())); + } + + var oldValue = _customize.Data.At(_currentIndex.ToByteAndMask().ByteIdx); + var tmp = (int)oldValue; + ImGui.SetNextItemWidth(_inputIntSize); + if (ImGui.InputInt("##text", ref tmp, 1, 1)) + { + tmp = Math.Clamp(tmp, 0, byte.MaxValue); + if (tmp != oldValue) + { + _customize.Data.Set(_currentIndex.ToByteAndMask().ByteIdx, (byte)tmp); + var changes = (byte)tmp ^ oldValue; + Changed |= ((changes & 0x01) == 0x01 ? CustomizeFlag.FacialFeature1 : 0) + | ((changes & 0x02) == 0x02 ? CustomizeFlag.FacialFeature2 : 0) + | ((changes & 0x04) == 0x04 ? CustomizeFlag.FacialFeature3 : 0) + | ((changes & 0x08) == 0x08 ? CustomizeFlag.FacialFeature4 : 0) + | ((changes & 0x10) == 0x10 ? CustomizeFlag.FacialFeature5 : 0) + | ((changes & 0x20) == 0x20 ? CustomizeFlag.FacialFeature6 : 0) + | ((changes & 0x40) == 0x40 ? CustomizeFlag.FacialFeature7 : 0) + | ((changes & 0x80) == 0x80 ? CustomizeFlag.LegacyTattoo : 0); + } + } - PercentageInputInt(); if (_set.DataByValue(CustomizeIndex.Face, _customize.Face, out _, _customize.Face) < 0) { ImGui.SameLine(); @@ -127,7 +152,7 @@ public partial class CustomizationDrawer ImGui.AlignTextToFramePadding(); using (var _ = ImRaii.Enabled()) { - ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo)); + ImGui.TextUnformatted("Facial Features & Tattoos"); } if (_withApply) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 364b469..0f76b62 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -200,7 +200,7 @@ public partial class CustomizationDrawer : IDisposable private void UpdateSizes() { _spacing = ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemInnerSpacing.X }; - _iconSize = new Vector2(ImGui.GetTextLineHeight() * 2 + ImGui.GetStyle().ItemSpacing.Y + 2 * ImGui.GetStyle().FramePadding.Y); + _iconSize = new Vector2(ImGui.GetTextLineHeight() * 2 + _spacing.Y + 2 * ImGui.GetStyle().FramePadding.Y); _framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding; _inputIntSize = 2 * _framedIconSize.X + 1 * _spacing.X; _inputIntSizeNoButtons = _inputIntSize - 2 * _spacing.X - 2 * ImGui.GetFrameHeight(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 0127cdb..1de6ebd 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -118,6 +118,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Date: Thu, 16 Nov 2023 19:43:04 +0100 Subject: [PATCH 049/786] Add support for custom colors for design display. --- Glamourer/Designs/Design.cs | 8 +- Glamourer/Designs/DesignColors.cs | 303 ++++++++++++++++++ Glamourer/Designs/DesignData.cs | 1 - Glamourer/Designs/DesignFileSystem.cs | 1 - Glamourer/Designs/DesignManager.cs | 13 + Glamourer/Events/DesignChanged.cs | 3 + Glamourer/Gui/DesignCombo.cs | 24 +- .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 60 +++- .../DesignTab/DesignFileSystemSelector.cs | 29 +- Glamourer/Gui/Tabs/SettingsTab.cs | 26 +- Glamourer/Services/BackupService.cs | 1 + Glamourer/Services/FilenameService.cs | 2 + Glamourer/Services/ServiceManager.cs | 6 +- 13 files changed, 438 insertions(+), 39 deletions(-) create mode 100644 Glamourer/Designs/DesignColors.cs diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 9901e81..5c106e3 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Dalamud.Interface.Internal.Notifications; +using Glamourer.Customization; +using Glamourer.Gui; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; @@ -14,6 +16,7 @@ namespace Glamourer.Designs; public sealed class Design : DesignBase, ISavable { #region Data + internal Design(CustomizationService customize, ItemManager items) : base(customize, items) { } @@ -40,7 +43,8 @@ public sealed class Design : DesignBase, ISavable public string Description { get; internal set; } = string.Empty; public string[] Tags { get; internal set; } = Array.Empty(); public int Index { get; internal set; } - public SortedList AssociatedMods { get; private set; } = new(); + public string Color { get; internal set; } = string.Empty; + public SortedList AssociatedMods { get; private set; } = new(); public string Incognito => Identifier.ToString()[..8]; @@ -59,6 +63,7 @@ public sealed class Design : DesignBase, ISavable ["LastEdit"] = LastEdit, ["Name"] = Name.Text, ["Description"] = Description, + ["Color"] = Color, ["Tags"] = JArray.FromObject(Tags), ["WriteProtected"] = WriteProtected(), ["Equipment"] = SerializeEquipment(), @@ -131,6 +136,7 @@ public sealed class Design : DesignBase, ISavable LoadCustomize(customizations, json["Customize"], design, design.Name, true, false); LoadEquip(items, json["Equipment"], design, design.Name, true); LoadMods(json["Mods"], design); + design.Color = json["Color"]?.ToObject() ?? string.Empty; return design; } diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs new file mode 100644 index 0000000..dc36e78 --- /dev/null +++ b/Glamourer/Designs/DesignColors.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Utility.Raii; +using Glamourer.Gui; +using Glamourer.Services; +using ImGuiNET; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OtterGui; +using OtterGui.Classes; + +namespace Glamourer.Designs; + +public class DesignColorUi +{ + private readonly DesignColors _colors; + private readonly DesignManager _designs; + private readonly Configuration _config; + + private string _newName = string.Empty; + + public DesignColorUi(DesignColors colors, DesignManager designs, Configuration config) + { + _colors = colors; + _designs = designs; + _config = config; + } + + public void Draw() + { + using var table = ImRaii.Table("designColors", 3, ImGuiTableFlags.RowBg); + if (!table) + return; + + var changeString = string.Empty; + uint? changeValue = null; + + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + ImGui.TableSetupColumn("##Delete", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); + ImGui.TableSetupColumn("##Select", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); + ImGui.TableSetupColumn("Color Name", ImGuiTableColumnFlags.WidthStretch); + + ImGui.TableHeadersRow(); + + ImGui.TableNextColumn(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Recycle.ToIconString(), buttonSize, + "Revert the color used for missing design colors to its default.", _colors.MissingColor == DesignColors.MissingColorDefault, + true)) + { + changeString = DesignColors.MissingColorName; + changeValue = DesignColors.MissingColorDefault; + } + + ImGui.TableNextColumn(); + if (DrawColorButton(DesignColors.MissingColorName, _colors.MissingColor, out var newColor)) + { + changeString = DesignColors.MissingColorName; + changeValue = newColor; + } + + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); + ImGui.TextUnformatted(DesignColors.MissingColorName); + ImGuiUtil.HoverTooltip("This color is used when the color specified in a design is not available."); + + + var disabled = !_config.DeleteDesignModifier.IsActive(); + var tt = "Delete this color. This does not remove it from designs using it."; + if (disabled) + tt += $"\nHold {_config.DeleteDesignModifier} to delete."; + + foreach (var ((name, color), idx) in _colors.WithIndex()) + { + using var id = ImRaii.PushId(idx); + ImGui.TableNextColumn(); + + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, tt, disabled, true)) + { + changeString = name; + changeValue = null; + } + + ImGui.TableNextColumn(); + if (DrawColorButton(name, color, out newColor)) + { + changeString = name; + changeValue = newColor; + } + + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); + ImGui.TextUnformatted(name); + } + + ImGui.TableNextColumn(); + (tt, disabled) = _newName.Length == 0 + ? ("Specify a name for a new color first.", true) + : _newName is DesignColors.MissingColorName or DesignColors.AutomaticName + ? ($"You can not use the name {DesignColors.MissingColorName} or {DesignColors.AutomaticName}, choose a different one.", true) + : _colors.ContainsKey(_newName) + ? ($"The color {_newName} already exists, please choose a different name.", true) + : ($"Add a new color {_newName} to your list.", false); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, disabled, true)) + { + changeString = _newName; + changeValue = 0xFFFFFFFF; + } + + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputTextWithHint("##newDesignColor", "New Color Name...", ref _newName, 64, ImGuiInputTextFlags.EnterReturnsTrue)) + { + changeString = _newName; + changeValue = 0xFFFFFFFF; + } + + + if (changeString.Length > 0) + { + if (!changeValue.HasValue) + _colors.DeleteColor(changeString); + else + _colors.SetColor(changeString, changeValue.Value); + } + } + + public static bool DrawColorButton(string tooltip, uint color, out uint newColor) + { + var vec = ImGui.ColorConvertU32ToFloat4(color); + if (!ImGui.ColorEdit4(tooltip, ref vec, ImGuiColorEditFlags.AlphaPreviewHalf | ImGuiColorEditFlags.NoInputs)) + { + ImGuiUtil.HoverTooltip(tooltip); + newColor = color; + return false; + } + ImGuiUtil.HoverTooltip(tooltip); + + newColor = ImGui.ColorConvertFloat4ToU32(vec); + return newColor != color; + } +} + +public class DesignColors : ISavable, IReadOnlyDictionary +{ + public const string AutomaticName = "Automatic"; + public const string MissingColorName = "Missing Color"; + public const uint MissingColorDefault = 0xFF0000D0; + + private readonly SaveService _saveService; + private readonly Dictionary _colors = new(); + public uint MissingColor { get; private set; } = MissingColorDefault; + + public event Action? ColorChanged; + + public DesignColors(SaveService saveService) + { + _saveService = saveService; + Load(); + } + + public uint GetColor(Design design) + { + if (design.Color.Length == 0) + return AutoColor(design); + + return TryGetValue(design.Color, out var color) ? color : MissingColor; + } + + public void SetColor(string key, uint newColor) + { + if (key.Length == 0) + return; + + if (key is MissingColorName && MissingColor != newColor) + { + MissingColor = newColor; + SaveAndInvoke(); + return; + } + + if (_colors.TryAdd(key, newColor)) + { + SaveAndInvoke(); + return; + } + + _colors.TryGetValue(key, out var color); + _colors[key] = newColor; + + if (color != newColor) + SaveAndInvoke(); + } + + private void SaveAndInvoke() + { + ColorChanged?.Invoke(); + _saveService.DelaySave(this, TimeSpan.FromSeconds(2)); + } + + public void DeleteColor(string key) + { + if (_colors.Remove(key)) + SaveAndInvoke(); + } + + public string ToFilename(FilenameService fileNames) + => fileNames.DesignColorFile; + + public void Save(StreamWriter writer) + { + var jObj = new JObject + { + ["Version"] = 1, + ["MissingColor"] = MissingColor, + ["Definitions"] = JToken.FromObject(_colors), + }; + writer.Write(jObj.ToString(Formatting.Indented)); + } + + private void Load() + { + _colors.Clear(); + var file = _saveService.FileNames.DesignColorFile; + if (!File.Exists(file)) + return; + + try + { + var text = File.ReadAllText(file); + var jObj = JObject.Parse(text); + var version = jObj["Version"]?.ToObject() ?? 0; + switch (version) + { + case 1: + { + var dict = jObj["Definitions"]?.ToObject>() ?? new Dictionary(); + _colors.EnsureCapacity(dict.Count); + foreach (var kvp in dict) + _colors.Add(kvp.Key, kvp.Value); + MissingColor = jObj["MissingColor"]?.ToObject() ?? MissingColorDefault; + break; + } + default: throw new Exception($"Unknown Version {version}"); + } + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, "Could not read design color file.", NotificationType.Error); + } + } + + public IEnumerator> GetEnumerator() + => _colors.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _colors.Count; + + public bool ContainsKey(string key) + => _colors.ContainsKey(key); + + public bool TryGetValue(string key, out uint value) + { + if (_colors.TryGetValue(key, out value)) + { + if (value == 0) + value = ImGui.GetColorU32(ImGuiCol.Text); + return true; + } + + return false; + } + + public static uint AutoColor(DesignBase design) + { + var customize = design.ApplyCustomize == 0; + var equip = design.ApplyEquip == 0; + return (customize, equip) switch + { + (true, true) => ColorId.StateDesign.Value(), + (true, false) => ColorId.EquipmentDesign.Value(), + (false, true) => ColorId.CustomizationDesign.Value(), + (false, false) => ColorId.NormalDesign.Value(), + }; + } + + public uint this[string key] + => _colors[key]; + + public IEnumerable Keys + => _colors.Keys; + + public IEnumerable Values + => _colors.Values; +} diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 0fd96b2..4a24f59 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -1,5 +1,4 @@ using System; -using System.Buffers.Text; using System.Runtime.CompilerServices; using Glamourer.Customization; using Glamourer.Services; diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index 960c460..db727e8 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Text.RegularExpressions; using Dalamud.Interface.Internal.Notifications; using Glamourer.Events; -using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 1084fb0..b8cd9a2 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -189,6 +189,19 @@ public class DesignManager _event.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription); } + public void ChangeColor(Design design, string newColor) + { + var oldColor = design.Color; + if (oldColor == newColor) + return; + + design.Color = newColor; + design.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(design); + Glamourer.Log.Debug($"Changed color of design {design.Identifier}."); + _event.Invoke(DesignChanged.Type.ChangedColor, design, oldColor); + } + /// Add a new tag to a design. The tags remain sorted. public void AddTag(Design design, string tag) { diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 9c8e189..55956f0 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -32,6 +32,9 @@ public sealed class DesignChanged : EventWrapper An existing design had its description changed. Data is the prior description [string]. ChangedDescription, + /// An existing design had its associated color changed. Data is the prior color [string]. + ChangedColor, + /// An existing design had a new tag added. Data is the new tag and the index it was added at [(string, int)]. AddedTag, diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 690344d..49359b2 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; using Glamourer.Automation; using Glamourer.Customization; using Glamourer.Designs; @@ -19,17 +20,19 @@ public abstract class DesignComboBase : FilterComboCache>, { private readonly Configuration _config; private readonly DesignChanged _designChanged; + private readonly DesignColors _designColors; protected readonly TabSelected TabSelected; protected float InnerWidth; private Design? _currentDesign; protected DesignComboBase(Func>> generator, Logger log, DesignChanged designChanged, - TabSelected tabSelected, Configuration config) + TabSelected tabSelected, Configuration config, DesignColors designColors) : base(generator, log) { _designChanged = designChanged; TabSelected = tabSelected; _config = config; + _designColors = designColors; _designChanged.Subscribe(OnDesignChange, DesignChanged.Priority.DesignCombo); } @@ -41,8 +44,13 @@ public abstract class DesignComboBase : FilterComboCache>, protected override bool DrawSelectable(int globalIdx, bool selected) { - var ret = base.DrawSelectable(globalIdx, selected); var (design, path) = Items[globalIdx]; + bool ret; + using (var color = ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(design))) + { + ret = base.DrawSelectable(globalIdx, selected); + } + if (path.Length > 0 && design.Name != path) { var start = ImGui.GetItemRectMin(); @@ -128,11 +136,11 @@ public sealed class DesignCombo : DesignComboBase private readonly DesignManager _manager; public DesignCombo(DesignManager designs, DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, TabSelected tabSelected, - Configuration config) + Configuration config, DesignColors designColors) : base(() => designs.Designs .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) .OrderBy(d => d.Item2) - .ToList(), log, designChanged, tabSelected, config) + .ToList(), log, designChanged, tabSelected, config, designColors) { _manager = designs; if (designs.Designs.Count == 0) @@ -170,19 +178,19 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable public readonly Design RevertDesign; private readonly AutoDesignManager _autoDesignManager; - public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, + public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, ItemManager items, CustomizationService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, Configuration config) - : this(designs, fileSystem, tabSelected, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, config) + : this(designs, fileSystem, tabSelected, designColors, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, config) { } - private RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, + private RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, Design revertDesign, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, Configuration config) : base(() => designs.Designs .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) .OrderBy(d => d.Item2) .Prepend(new Tuple(revertDesign, string.Empty)) - .ToList(), log, designChanged, tabSelected, config) + .ToList(), log, designChanged, tabSelected, config, designColors) { RevertDesign = revertDesign; _autoDesignManager = autoDesignManager; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 38fd5f8..2b0cac6 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Linq; using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; @@ -13,12 +14,35 @@ using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.DesignTab; +public sealed class DesignColorCombo : FilterComboCache +{ + private readonly DesignColors _designColors; + + public DesignColorCombo(DesignColors designColors) + : base(designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName), Glamourer.Log) + => _designColors = designColors; + + protected override bool DrawSelectable(int globalIdx, bool selected) + { + var key = Items[globalIdx]; + var color = globalIdx == 0 ? 0 : _designColors[key]; + using var c = ImRaii.PushColor(ImGuiCol.Text, color, color != 0); + var ret = base.DrawSelectable(globalIdx, selected); + if (globalIdx == 0) + ImGuiUtil.HoverTooltip( + "The automatic color uses the colors dependent on the design state, as defined in the regular color definitions."); + return ret; + } +} + public class DesignDetailTab { private readonly SaveService _saveService; private readonly DesignFileSystemSelector _selector; private readonly DesignFileSystem _fileSystem; private readonly DesignManager _manager; + private readonly DesignColors _colors; + private readonly DesignColorCombo _colorCombo; private readonly TagButtons _tagButtons = new(); private string? _newPath; @@ -29,12 +53,15 @@ public class DesignDetailTab private Design? _changeDesign; private DesignFileSystem.Leaf? _changeLeaf; - public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem) + public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem, + DesignColors colors) { _saveService = saveService; _selector = selector; _manager = manager; _fileSystem = fileSystem; + _colors = colors; + _colorCombo = new DesignColorCombo(_colors); } public void Draw() @@ -89,7 +116,8 @@ public class DesignDetailTab } catch (Exception ex) { - Glamourer.Messager.NotificationMessage(ex, $"Could not open file {fileName}.", $"Could not open file {fileName}", NotificationType.Warning); + Glamourer.Messager.NotificationMessage(ex, $"Could not open file {fileName}.", $"Could not open file {fileName}", + NotificationType.Warning); } } @@ -117,6 +145,34 @@ public class DesignDetailTab Glamourer.Messager.NotificationMessage(ex, ex.Message, "Could not rename or move design", NotificationType.Error); } + ImGuiUtil.DrawFrameColumn("Color"); + var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color; + ImGui.TableNextColumn(); + if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design. Right-Click to revert to automatic coloring.", + width.X - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight()) + && _colorCombo.CurrentSelection != null) + { + colorName = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection; + _manager.ChangeColor(_selector.Selected!, colorName); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + _manager.ChangeColor(_selector.Selected!, string.Empty); + + if (_colors.TryGetValue(_selector.Selected!.Color, out var currentColor)) + { + ImGui.SameLine(); + if (DesignColorUi.DrawColorButton($"Color associated with {_selector.Selected!.Color}", currentColor, out var newColor)) + _colors.SetColor(_selector.Selected!.Color, newColor); + } + else if (_selector.Selected!.Color.Length != 0) + { + ImGui.SameLine(); + var size = new Vector2(ImGui.GetFrameHeight()); + using var font = ImRaii.PushFont(UiBuilder.IconFont); + ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor); + ImGuiUtil.HoverTooltip("The color associated with this design does not exist."); + } + ImGuiUtil.DrawFrameColumn("Creation Date"); ImGui.TableNextColumn(); ImGuiUtil.DrawTextButton(_selector.Selected!.CreationDate.LocalDateTime.ToString("F"), width, 0); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 1de6ebd..7960ad1 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -25,6 +25,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector base.SelectedLeaf; - public struct DesignState - { - public ColorId Color; - } + public record struct DesignState(uint Color) + { } public DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, IKeyState keyState, DesignChanged @event, - Configuration config, DesignConverter converter, TabSelected selectionEvent, Logger log, CustomizationService customizationService) + Configuration config, DesignConverter converter, TabSelected selectionEvent, Logger log, CustomizationService customizationService, + DesignColors designColors) : base(fileSystem, keyState, log, allowMultipleSelection: true) { _designManager = designManager; @@ -58,8 +58,10 @@ public sealed class DesignFileSystemSelector : FileSystemSelector SortMode @@ -121,7 +124,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Combined wrapper for handling all filters and setting state. private bool ApplyFiltersAndState(DesignFileSystem.Leaf leaf, out DesignState state) { - var applyEquip = leaf.Value.ApplyEquip != 0; - var applyCustomize = leaf.Value.ApplyCustomize != 0; - - state.Color = (applyEquip, applyCustomize) switch - { - (false, false) => ColorId.StateDesign, - (false, true) => ColorId.CustomizationDesign, - (true, false) => ColorId.EquipmentDesign, - (true, true) => ColorId.NormalDesign, - }; - + state = new DesignState(_designColors.GetColor(leaf.Value)); return ApplyStringFilters(leaf, leaf.Value); } diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index d690a42..dc20f78 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -7,6 +7,7 @@ using Dalamud.Interface; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; +using Glamourer.Designs; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -30,9 +31,11 @@ public class SettingsTab : ITab private readonly UiBuilder _uiBuilder; private readonly GlamourerChangelog _changelog; private readonly FunModule _funModule; + private readonly DesignColorUi _designColorUi; public SettingsTab(Configuration config, DesignFileSystemSelector selector, CodeService codeService, PenumbraAutoRedraw autoRedraw, - ContextMenuService contextMenuService, UiBuilder uiBuilder, GlamourerChangelog changelog, FunModule funModule, IKeyState keys) + ContextMenuService contextMenuService, UiBuilder uiBuilder, GlamourerChangelog changelog, FunModule funModule, IKeyState keys, + DesignColorUi designColorUi) { _config = config; _selector = selector; @@ -42,6 +45,7 @@ public class SettingsTab : ITab _uiBuilder = uiBuilder; _changelog = changelog; _funModule = funModule; + _designColorUi = designColorUi; _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); } @@ -177,12 +181,22 @@ public class SettingsTab : ITab if (!ImGui.CollapsingHeader("Colors")) return; - foreach (var color in Enum.GetValues()) + using (var tree = ImRaii.TreeNode("Custom Design Colors")) { - var (defaultColor, name, description) = color.Data(); - var currentColor = _config.Colors.TryGetValue(color, out var current) ? current : defaultColor; - if (Widget.ColorPicker(name, description, currentColor, c => _config.Colors[color] = c, defaultColor)) - _config.Save(); + if (tree) + _designColorUi.Draw(); + } + + using (var tree = ImRaii.TreeNode("Color Settings")) + { + if (tree) + foreach (var color in Enum.GetValues()) + { + var (defaultColor, name, description) = color.Data(); + var currentColor = _config.Colors.TryGetValue(color, out var current) ? current : defaultColor; + if (Widget.ColorPicker(name, description, currentColor, c => _config.Colors[color] = c, defaultColor)) + _config.Save(); + } } ImGui.NewLine(); diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs index dfccb2a..ea71319 100644 --- a/Glamourer/Services/BackupService.cs +++ b/Glamourer/Services/BackupService.cs @@ -35,6 +35,7 @@ public class BackupService new(fileNames.UnlockFileCustomize), new(fileNames.UnlockFileItems), new(fileNames.FavoriteFile), + new(fileNames.DesignColorFile), }; list.AddRange(fileNames.Designs()); diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index 7299d32..607bcb3 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -16,6 +16,7 @@ public class FilenameService public readonly string UnlockFileCustomize; public readonly string UnlockFileItems; public readonly string FavoriteFile; + public readonly string DesignColorFile; public FilenameService(DalamudPluginInterface pi) { @@ -28,6 +29,7 @@ public class FilenameService UnlockFileItems = Path.Combine(ConfigDirectory, "unlocks_items.json"); DesignDirectory = Path.Combine(ConfigDirectory, "designs"); FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json"); + DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json"); } diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 2552f44..eefa761 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -106,7 +106,8 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); private static IServiceCollection AddState(this IServiceCollection services) => services.AddSingleton() @@ -143,7 +144,8 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); private static IServiceCollection AddApi(this IServiceCollection services) => services.AddSingleton() From 7b939c81d0af2c92afde9b273cd756d43c080e00 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 16 Nov 2023 19:54:26 +0100 Subject: [PATCH 050/786] Allow filtering for associated color. --- Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 7960ad1..22fe597 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -24,7 +24,6 @@ public sealed class DesignFileSystemSelector : FileSystemSelector filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 3), 'i' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4), 'I' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4), + 'c' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 5), + 'C' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 5), _ => (new LowerString(filterValue), 0), }, _ => (new LowerString(filterValue), 0), @@ -273,6 +273,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector !design.AssociatedMods.Any(kvp => _designFilter.IsContained(kvp.Key.Name)), 3 => !design.Tags.Any(_designFilter.IsContained), 4 => !design.DesignData.ContainsName(_designFilter), + 5 => !_designFilter.IsContained(design.Color.Length == 0 ? DesignColors.AutomaticName : design.Color), _ => false, // Should never happen }; } From 98c793eafc7481e1519d181d51201867b78599a3 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 16 Nov 2023 18:56:14 +0000 Subject: [PATCH 051/786] [CI] Updating repo.json for testing_1.0.5.1 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 8692055..8e76663 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.5.0", - "TestingAssemblyVersion": "1.0.5.0", + "TestingAssemblyVersion": "1.0.5.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.0.5.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.5.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.5.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.5.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From f514f79fe9c42b64e32b11c7174f266497d088ff Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 16 Nov 2023 20:55:41 +0100 Subject: [PATCH 052/786] Fix dumb. --- Glamourer/Designs/DesignBase.cs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index cb40279..4ef567e 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -34,7 +34,7 @@ public class DesignBase internal DesignBase(DesignBase clone) { - _designData = clone._designData; + _designData = clone._designData; CustomizationSet = clone.CustomizationSet; ApplyCustomize = clone.ApplyCustomizeRaw; ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; @@ -44,7 +44,7 @@ public class DesignBase /// Ensure that the customization set is updated when the design data changes. internal void SetDesignData(CustomizationService customize, in DesignData other) { - _designData = other; + _designData = other; CustomizationSet = SetCustomizationSet(customize); } @@ -216,7 +216,7 @@ public class DesignBase private CustomizationSet SetCustomizationSet(CustomizationService customize) => !_designData.IsHuman - ? customize.AwaitedService.GetList(SubRace.Midlander, Gender.Male) + ? customize.AwaitedService.GetList(SubRace.Midlander, Gender.Male) : customize.AwaitedService.GetList(_designData.Customize.Clan, _designData.Customize.Gender); #endregion @@ -400,8 +400,8 @@ public class DesignBase { if (json == null) { - design._designData.ModelId = 0; - design._designData.IsHuman = true; + design._designData.ModelId = 0; + design._designData.IsHuman = true; design.SetCustomize(customizations, Customize.Default); Glamourer.Messager.NotificationMessage("The loaded design does not contain any customization data, reset to default.", NotificationType.Warning); @@ -421,7 +421,8 @@ public class DesignBase design.SetApplyWetness(wetness.Enabled); design._designData.ModelId = json["ModelId"]?.ToObject() ?? 0; - PrintWarning(customizations.ValidateModelId(design._designData.ModelId, out design._designData.ModelId, out design._designData.IsHuman)); + PrintWarning(customizations.ValidateModelId(design._designData.ModelId, out design._designData.ModelId, + out design._designData.IsHuman)); if (design._designData.ModelId != 0 && forbidNonHuman) { PrintWarning("Model IDs different from 0 are not currently allowed, reset model id to 0."); @@ -444,7 +445,7 @@ public class DesignBase design._designData.Customize.Race = race; design._designData.Customize.Clan = clan; design._designData.Customize.Gender = gender; - design.CustomizationSet = design.SetCustomizationSet(customizations); + design.CustomizationSet = design.SetCustomizationSet(customizations); design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject() ?? false); @@ -454,8 +455,9 @@ public class DesignBase { var tok = json[idx.ToString()]; var data = (CustomizeValue)(tok?["Value"]?.ToObject() ?? 0); - PrintWarning(CustomizationService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data, - allowUnknown)); + if (set.IsAvailable(idx)) + PrintWarning(CustomizationService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data, + allowUnknown)); var apply = tok?["Apply"]?.ToObject() ?? false; design._designData.Customize[idx] = data; design.SetApplyCustomize(idx, apply); From 9c8e9f5ead508b4ac0bd1bdd7cf609ec9ca7c072 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 16 Nov 2023 19:58:18 +0000 Subject: [PATCH 053/786] [CI] Updating repo.json for testing_1.0.5.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 8e76663..705d4da 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.5.0", - "TestingAssemblyVersion": "1.0.5.1", + "TestingAssemblyVersion": "1.0.5.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.0.5.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.5.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.5.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.5.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From b4b104f91990909c78319352aeb2a78a5fa0542e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 17 Nov 2023 17:09:29 +0100 Subject: [PATCH 054/786] Add Ephemeral Config. --- Glamourer/Configuration.cs | 55 +++++++------- Glamourer/EphemeralConfig.cs | 73 +++++++++++++++++++ Glamourer/Gui/DesignCombo.cs | 23 +++--- Glamourer/Gui/DesignQuickBar.cs | 16 ++-- Glamourer/Gui/GlamourerChangelog.cs | 28 +++++-- Glamourer/Gui/MainWindow.cs | 8 +- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 4 +- .../Gui/Tabs/AutomationTab/SetSelector.cs | 6 +- .../DesignTab/DesignFileSystemSelector.cs | 7 +- Glamourer/Gui/Tabs/SettingsTab.cs | 27 +++++-- Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs | 4 +- Glamourer/Services/CommandService.cs | 8 +- Glamourer/Services/ConfigMigrationService.cs | 23 +++++- Glamourer/Services/FilenameService.cs | 4 +- Glamourer/Services/ServiceManager.cs | 1 + 15 files changed, 202 insertions(+), 85 deletions(-) create mode 100644 Glamourer/EphemeralConfig.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 4490fa0..841fe68 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -19,40 +19,34 @@ namespace Glamourer; public class Configuration : IPluginConfiguration, ISavable { - 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 IncognitoMode { get; set; } = false; - public bool UnlockDetailMode { 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 ShowDesignQuickBar { get; set; } = false; - public bool LockDesignQuickBar { get; set; } = false; - public bool ShowQuickBarInTabs { get; set; } = true; - public bool LockMainWindow { get; set; } = false; - public bool OpenWindowAtStart { get; set; } = false; + [JsonIgnore] + public readonly EphemeralConfig Ephemeral; - public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); - public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; - public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); - - public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; - 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 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; [JsonConverter(typeof(SortModeConverter))] [JsonProperty(Order = int.MaxValue)] public ISortMode SortMode { get; set; } = ISortMode.FoldersFirst; - public List<(string Code, bool Enabled)> Codes { get; set; } = new(); + public List<(string Code, bool Enabled)> Codes { get; set; } = []; #if DEBUG public bool DebugMode { get; set; } = true; @@ -68,9 +62,10 @@ public class Configuration : IPluginConfiguration, ISavable [JsonIgnore] private readonly SaveService _saveService; - public Configuration(SaveService saveService, ConfigMigrationService migrator) + public Configuration(SaveService saveService, ConfigMigrationService migrator, EphemeralConfig ephemeral) { _saveService = saveService; + Ephemeral = ephemeral; Load(migrator); } @@ -120,7 +115,7 @@ public class Configuration : IPluginConfiguration, ISavable public static class Constants { - public const int CurrentVersion = 4; + public const int CurrentVersion = 5; public static readonly ISortMode[] ValidSortModes = { diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/EphemeralConfig.cs new file mode 100644 index 0000000..349a021 --- /dev/null +++ b/Glamourer/EphemeralConfig.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; +using Dalamud.Interface.Internal.Notifications; +using Glamourer.Gui; +using Glamourer.Services; +using Newtonsoft.Json; +using OtterGui.Classes; +using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; + +namespace Glamourer; + +public class EphemeralConfig : ISavable +{ + public int Version { get; set; } = Configuration.Constants.CurrentVersion; + public bool IncognitoMode { get; set; } = false; + public bool UnlockDetailMode { get; set; } = true; + public bool ShowDesignQuickBar { get; set; } = false; + public bool LockDesignQuickBar { get; set; } = false; + public bool LockMainWindow { get; set; } = false; + public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; + public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; + + + [JsonIgnore] + private readonly SaveService _saveService; + + public EphemeralConfig(SaveService saveService) + { + _saveService = saveService; + Load(); + } + + public void Save() + => _saveService.DelaySave(this, TimeSpan.FromSeconds(5)); + + public void Load() + { + static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs) + { + Glamourer.Log.Error( + $"Error parsing ephemeral Configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}"); + errorArgs.ErrorContext.Handled = true; + } + + if (!File.Exists(_saveService.FileNames.EphemeralConfigFile)) + return; + + try + { + var text = File.ReadAllText(_saveService.FileNames.EphemeralConfigFile); + JsonConvert.PopulateObject(text, this, new JsonSerializerSettings + { + Error = HandleDeserializationError, + }); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, + "Error reading ephemeral Configuration, reverting to default.", + "Error reading ephemeral Configuration", NotificationType.Error); + } + } + + public string ToFilename(FilenameService fileNames) + => fileNames.EphemeralConfigFile; + + public void Save(StreamWriter writer) + { + using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }; + var serializer = new JsonSerializer { Formatting = Formatting.Indented }; + serializer.Serialize(jWriter, this); + } +} diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 49359b2..19d73c4 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -18,15 +18,15 @@ namespace Glamourer.Gui; public abstract class DesignComboBase : FilterComboCache>, IDisposable { - private readonly Configuration _config; - private readonly DesignChanged _designChanged; - private readonly DesignColors _designColors; - protected readonly TabSelected TabSelected; - protected float InnerWidth; - private Design? _currentDesign; + private readonly EphemeralConfig _config; + private readonly DesignChanged _designChanged; + private readonly DesignColors _designColors; + protected readonly TabSelected TabSelected; + protected float InnerWidth; + private Design? _currentDesign; protected DesignComboBase(Func>> generator, Logger log, DesignChanged designChanged, - TabSelected tabSelected, Configuration config, DesignColors designColors) + TabSelected tabSelected, EphemeralConfig config, DesignColors designColors) : base(generator, log) { _designChanged = designChanged; @@ -136,7 +136,7 @@ public sealed class DesignCombo : DesignComboBase private readonly DesignManager _manager; public DesignCombo(DesignManager designs, DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, TabSelected tabSelected, - Configuration config, DesignColors designColors) + EphemeralConfig config, DesignColors designColors) : base(() => designs.Designs .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) .OrderBy(d => d.Item2) @@ -180,12 +180,13 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, ItemManager items, CustomizationService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, - Configuration config) - : this(designs, fileSystem, tabSelected, designColors, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, config) + EphemeralConfig config) + : this(designs, fileSystem, tabSelected, designColors, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, + config) { } private RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, - Design revertDesign, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, Configuration config) + Design revertDesign, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, EphemeralConfig config) : base(() => designs.Designs .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) .OrderBy(d => d.Item2) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 97cb68c..e6f5eac 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -21,7 +21,7 @@ namespace Glamourer.Gui; public class DesignQuickBar : Window, IDisposable { private ImGuiWindowFlags GetFlags - => _config.LockDesignQuickBar + => _config.Ephemeral.LockDesignQuickBar ? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove : ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing; @@ -45,7 +45,7 @@ public class DesignQuickBar : Window, IDisposable _keyState = keyState; _objects = objects; _autoDesignApplier = autoDesignApplier; - IsOpen = _config.ShowDesignQuickBar; + IsOpen = _config.Ephemeral.ShowDesignQuickBar; DisableWindowSounds = true; Size = Vector2.Zero; } @@ -56,7 +56,7 @@ public class DesignQuickBar : Window, IDisposable public override void PreOpenCheck() { CheckHotkeys(); - IsOpen = _config.ShowDesignQuickBar; + IsOpen = _config.Ephemeral.ShowDesignQuickBar; } public override void PreDraw() @@ -133,7 +133,7 @@ public class DesignQuickBar : Window, IDisposable if (_playerIdentifier.IsValid && _playerData.Valid) { available |= 1; - tooltip = $"Left-Click: Apply {(_config.IncognitoMode ? design.Incognito : design.Name)} to yourself."; + tooltip = $"Left-Click: Apply {(_config.Ephemeral.IncognitoMode ? design.Incognito : design.Name)} to yourself."; } if (_targetIdentifier.IsValid && _targetData.Valid) @@ -141,7 +141,7 @@ public class DesignQuickBar : Window, IDisposable if (available != 0) tooltip += '\n'; available |= 2; - tooltip += $"Right-Click: Apply {(_config.IncognitoMode ? design.Incognito : design.Name)} to {_targetIdentifier}."; + tooltip += $"Right-Click: Apply {(_config.Ephemeral.IncognitoMode ? design.Incognito : design.Name)} to {_targetIdentifier}."; } if (available == 0) @@ -248,9 +248,9 @@ public class DesignQuickBar : Window, IDisposable if (_keyboardToggle > DateTime.UtcNow || !CheckKeyState(_config.ToggleQuickDesignBar, false)) return; - _keyboardToggle = DateTime.UtcNow.AddMilliseconds(500); - _config.ShowDesignQuickBar = !_config.ShowDesignQuickBar; - _config.Save(); + _keyboardToggle = DateTime.UtcNow.AddMilliseconds(500); + _config.Ephemeral.ShowDesignQuickBar = !_config.Ephemeral.ShowDesignQuickBar; + _config.Ephemeral.Save(); } public bool CheckKeyState(ModifiableHotkey key, bool noKey) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index a06eb09..7e66fcc 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -26,24 +26,36 @@ public class GlamourerChangelog } private (int, ChangeLogDisplayType) ConfigData() - => (_config.LastSeenVersion, _config.ChangeLogDisplayType); + => (_config.Ephemeral.LastSeenVersion, _config.ChangeLogDisplayType); private void Save(int version, ChangeLogDisplayType type) { - _config.LastSeenVersion = version; - _config.ChangeLogDisplayType = type; - _config.Save(); + if (_config.Ephemeral.LastSeenVersion != version) + { + _config.Ephemeral.LastSeenVersion = version; + _config.Ephemeral.Save(); + } + + if (_config.ChangeLogDisplayType != type) + { + _config.ChangeLogDisplayType = type; + _config.Save(); + } } private static void Add1_0_5_0(Changelog log) => log.NextVersion("Version 1.0.5.0") .RegisterHighlight("Dyes are can now be favorited the same way equipment pieces can.") - .RegisterHighlight("The quick design bar combo can now be scrolled through via mousewheel when hovering over the combo without opening it.") - .RegisterEntry("Control-Rightclicking the quick design bar now not only jumps to the corresponding design, but also opens the main window if it is not currently open.") + .RegisterHighlight( + "The quick design bar combo can now be scrolled through via mousewheel when hovering over the combo without opening it.") + .RegisterEntry( + "Control-Rightclicking the quick design bar now not only jumps to the corresponding design, but also opens the main window if it is not currently open.") .RegisterHighlight("You can now filter for designs containing specific items by using \"i:partial item name\".") - .RegisterEntry("When overwriting a saved designs data entirely from clipboard, you can now undo this change and restore the prior design data once via a button top-left.") + .RegisterEntry( + "When overwriting a saved designs data entirely from clipboard, you can now undo this change and restore the prior design data once via a button top-left.") .RegisterEntry("Removed the \"Enabled\" checkbox in the settings since it was barely doing anything but breaking Glamourer.") - .RegisterEntry("If you want to disable Glamourers state-tracking and hooking, you will need to disable the entire Plugin via Dalamud now.", 1) + .RegisterEntry( + "If you want to disable Glamourers state-tracking and hooking, you will need to disable the entire Plugin via Dalamud now.", 1) .RegisterEntry("Added a reference to \"/glamour\" in the \"/glamourer help\" section.") .RegisterEntry("Updated BNPC Data with new crowd-sourced data from the gubal library.") .RegisterEntry("Fixed an issue with the quick design bar when no designs are saved.") diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index e26ea50..d4e19b2 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -81,7 +81,7 @@ public class MainWindow : Window, IDisposable public override void PreDraw() { - Flags = _config.LockMainWindow + Flags = _config.Ephemeral.LockMainWindow ? Flags | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize : Flags & ~(ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); } @@ -94,9 +94,9 @@ public class MainWindow : Window, IDisposable var yPos = ImGui.GetCursorPosY(); if (TabBar.Draw("##tabs", ImGuiTabBarFlags.None, ToLabel(SelectTab), out var currentTab, () => { }, _tabs)) { - SelectTab = TabType.None; - _config.SelectedTab = FromLabel(currentTab); - _config.Save(); + SelectTab = TabType.None; + _config.Ephemeral.SelectedTab = FromLabel(currentTab); + _config.Ephemeral.Save(); } if (_config.ShowQuickBarInTabs) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 334b78a..b8d1ba4 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -15,13 +15,13 @@ namespace Glamourer.Gui.Tabs.ActorTab; public class ActorSelector { - private readonly Configuration _config; + private readonly EphemeralConfig _config; private readonly ObjectManager _objects; private readonly ActorService _actors; private ActorIdentifier _identifier = ActorIdentifier.Invalid; - public ActorSelector(ObjectManager objects, ActorService actors, Configuration config) + public ActorSelector(ObjectManager objects, ActorService actors, EphemeralConfig config) { _objects = objects; _actors = actors; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 2aaf120..5ca3adf 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -31,11 +31,11 @@ public class SetSelector : IDisposable public bool IncognitoMode { - get => _config.IncognitoMode; + get => _config.Ephemeral.IncognitoMode; set { - _config.IncognitoMode = value; - _config.Save(); + _config.Ephemeral.IncognitoMode = value; + _config.Ephemeral.Save(); } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 22fe597..e9d182a 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -6,7 +6,6 @@ using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; using Glamourer.Designs; using Glamourer.Events; -using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -32,11 +31,11 @@ public sealed class DesignFileSystemSelector : FileSystemSelector _config.IncognitoMode; + get => _config.Ephemeral.IncognitoMode; set { - _config.IncognitoMode = value; - _config.Save(); + _config.Ephemeral.IncognitoMode = value; + _config.Ephemeral.Save(); } } diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index dc20f78..f8395fe 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -105,11 +105,11 @@ public class SettingsTab : ITab if (!ImGui.CollapsingHeader("Interface")) return; - Checkbox("Show Quick Design Bar", + EphemeralCheckbox("Show Quick Design Bar", "Show a bar separate from the main window that allows you to quickly apply designs or revert your character and target.", - _config.ShowDesignQuickBar, v => _config.ShowDesignQuickBar = v); - Checkbox("Lock Quick Design Bar", "Prevent the quick design bar from being moved and lock it in place.", _config.LockDesignQuickBar, - v => _config.LockDesignQuickBar = v); + _config.Ephemeral.ShowDesignQuickBar, v => _config.Ephemeral.ShowDesignQuickBar = v); + EphemeralCheckbox("Lock Quick Design Bar", "Prevent the quick design bar from being moved and lock it in place.", _config.Ephemeral.LockDesignQuickBar, + v => _config.Ephemeral.LockDesignQuickBar = v); if (Widget.ModifiableKeySelector("Hotkey to Toggle Quick Design Bar", "Set a hotkey that opens or closes the quick design bar.", 100 * ImGuiHelpers.GlobalScale, _config.ToggleQuickDesignBar, v => _config.ToggleQuickDesignBar = v, _validKeys)) @@ -138,8 +138,8 @@ public class SettingsTab : ITab _config.HideWindowInCutscene = v; _uiBuilder.DisableCutsceneUiHide = !v; }); - Checkbox("Lock Main Window", "Prevent the main window from being moved and lock it in place.", _config.LockMainWindow, - v => _config.LockMainWindow = v); + EphemeralCheckbox("Lock Main Window", "Prevent the main window from being moved and lock it in place.", _config.Ephemeral.LockMainWindow, + v => _config.Ephemeral.LockMainWindow = v); Checkbox("Open Main Window at Game Start", "Whether the main Glamourer window should be open or closed after launching the game.", _config.OpenWindowAtStart, v => _config.OpenWindowAtStart = v); ImGui.Dummy(Vector2.Zero); @@ -285,6 +285,21 @@ public class SettingsTab : ITab ImGuiUtil.LabeledHelpMarker(label, tooltip); } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void EphemeralCheckbox(string label, string tooltip, bool current, Action setter) + { + using var id = ImRaii.PushId(label); + var tmp = current; + if (ImGui.Checkbox(string.Empty, ref tmp) && tmp != current) + { + setter(tmp); + _config.Ephemeral.Save(); + } + + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker(label, tooltip); + } + /// Different supported sort modes as a combo. private void DrawFolderSortType() { diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs index 30f7ad3..d7b8162 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs @@ -11,11 +11,11 @@ namespace Glamourer.Gui.Tabs.UnlocksTab; public class UnlocksTab : Window, ITab { - private readonly Configuration _config; + private readonly EphemeralConfig _config; private readonly UnlockOverview _overview; private readonly UnlockTable _table; - public UnlocksTab(Configuration config, UnlockOverview overview, UnlockTable table) + public UnlocksTab(EphemeralConfig config, UnlockOverview overview, UnlockTable table) : base("Unlocked Equipment") { _config = config; diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 0cb4eab..6435568 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -76,13 +76,13 @@ public class CommandService : IDisposable case "designs": case "design": case "design bar": - _config.ShowDesignQuickBar = !_config.ShowDesignQuickBar; - _config.Save(); + _config.Ephemeral.ShowDesignQuickBar = !_config.Ephemeral.ShowDesignQuickBar; + _config.Ephemeral.Save(); return; case "lock": case "unlock": - _config.LockMainWindow = !_config.LockMainWindow; - _config.Save(); + _config.Ephemeral.LockMainWindow = !_config.Ephemeral.LockMainWindow; + _config.Ephemeral.Save(); return; default: _chat.Print("Use without argument to toggle the main window."); diff --git a/Glamourer/Services/ConfigMigrationService.cs b/Glamourer/Services/ConfigMigrationService.cs index bc75a17..3be7c29 100644 --- a/Glamourer/Services/ConfigMigrationService.cs +++ b/Glamourer/Services/ConfigMigrationService.cs @@ -25,7 +25,7 @@ public class ConfigMigrationService public void Migrate(Configuration config) { - _config = config; + _config = config; if (config.Version >= Configuration.Constants.CurrentVersion || !File.Exists(_saveService.FileNames.ConfigFile)) { AddColors(config, false); @@ -35,9 +35,28 @@ public class ConfigMigrationService _data = JObject.Parse(File.ReadAllText(_saveService.FileNames.ConfigFile)); MigrateV1To2(); MigrateV2To4(); + MigrateV4To5(); AddColors(config, true); } + // Ephemeral Config. + private void MigrateV4To5() + { + if (_config.Version > 4) + return; + + _config.Ephemeral.IncognitoMode = _data["IncognitoMode"]?.ToObject() ?? _config.Ephemeral.IncognitoMode; + _config.Ephemeral.UnlockDetailMode = _data["UnlockDetailMode"]?.ToObject() ?? _config.Ephemeral.UnlockDetailMode; + _config.Ephemeral.ShowDesignQuickBar = _data["ShowDesignQuickBar"]?.ToObject() ?? _config.Ephemeral.ShowDesignQuickBar; + _config.Ephemeral.LockDesignQuickBar = _data["LockDesignQuickBar"]?.ToObject() ?? _config.Ephemeral.LockDesignQuickBar; + _config.Ephemeral.LockMainWindow = _data["LockMainWindow"]?.ToObject() ?? _config.Ephemeral.LockMainWindow; + _config.Ephemeral.SelectedTab = _data["SelectedTab"]?.ToObject() ?? _config.Ephemeral.SelectedTab; + _config.Ephemeral.LastSeenVersion = _data["LastSeenVersion"]?.ToObject() ?? _config.Ephemeral.LastSeenVersion; + _config.Version = 5; + _config.Ephemeral.Version = 5; + _config.Ephemeral.Save(); + } + private void MigrateV1To2() { if (_config.Version > 1) @@ -58,7 +77,7 @@ public class ConfigMigrationService { if (_config.Version > 4) return; - + _config.Version = 4; _config.Codes = _config.Codes.DistinctBy(c => c.Code).ToList(); } diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index 607bcb3..2ca573f 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -17,6 +17,7 @@ public class FilenameService public readonly string UnlockFileItems; public readonly string FavoriteFile; public readonly string DesignColorFile; + public readonly string EphemeralConfigFile; public FilenameService(DalamudPluginInterface pi) { @@ -29,7 +30,8 @@ public class FilenameService UnlockFileItems = Path.Combine(ConfigDirectory, "unlocks_items.json"); DesignDirectory = Path.Combine(ConfigDirectory, "designs"); FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json"); - DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json"); + DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json"); + EphemeralConfigFile = Path.Combine(ConfigDirectory, "ephemeral_config.json"); } diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index eefa761..0d38099 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -57,6 +57,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); From 0583cc5bfcdaf7912459760380488cb45ba63057 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 18 Nov 2023 13:16:33 +0100 Subject: [PATCH 055/786] Allow filtering for None in certain cases. --- .../DesignTab/DesignFileSystemSelector.cs | 62 +++++++++++-------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index e9d182a..b323b63 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -18,12 +18,12 @@ namespace Glamourer.Gui.Tabs.DesignTab; public sealed class DesignFileSystemSelector : FileSystemSelector { - private readonly DesignManager _designManager; - private readonly DesignChanged _event; - private readonly Configuration _config; - private readonly DesignConverter _converter; - private readonly TabSelected _selectionEvent; - private readonly DesignColors _designColors; + private readonly DesignManager _designManager; + private readonly DesignChanged _event; + private readonly Configuration _config; + private readonly DesignConverter _converter; + private readonly TabSelected _selectionEvent; + private readonly DesignColors _designColors; private string? _clipboardText; private Design? _cloneDesign; @@ -49,12 +49,12 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Appropriately identify and set the string filter and its type. @@ -228,10 +229,10 @@ public sealed class DesignFileSystemSelector : FileSystemSelector filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1), 'N' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 1), - 'm' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 2), - 'M' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 2), - 't' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 3), - 'T' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 3), + 'm' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 2), + 'M' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 2), + 't' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3), + 'T' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3), 'i' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4), 'I' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 4), 'c' => filterValue.Length == 2 ? (LowerString.Empty, -1) : (new LowerString(filterValue[2..]), 5), @@ -244,6 +245,15 @@ public sealed class DesignFileSystemSelector : FileSystemSelector /// The overwritten filter method also computes the state. /// Folders have default state and are filtered out on the direct string instead of the other options. @@ -266,14 +276,16 @@ public sealed class DesignFileSystemSelector : FileSystemSelector false, - 0 => !(_designFilter.IsContained(leaf.FullName()) || design.Name.Contains(_designFilter)), - 1 => !design.Name.Contains(_designFilter), - 2 => !design.AssociatedMods.Any(kvp => _designFilter.IsContained(kvp.Key.Name)), - 3 => !design.Tags.Any(_designFilter.IsContained), - 4 => !design.DesignData.ContainsName(_designFilter), - 5 => !_designFilter.IsContained(design.Color.Length == 0 ? DesignColors.AutomaticName : design.Color), - _ => false, // Should never happen + -1 => false, + 0 => !(_designFilter.IsContained(leaf.FullName()) || design.Name.Contains(_designFilter)), + 1 => !design.Name.Contains(_designFilter), + 2 => !design.AssociatedMods.Any(kvp => _designFilter.IsContained(kvp.Key.Name)), + 3 => !design.Tags.Any(_designFilter.IsContained), + 4 => !design.DesignData.ContainsName(_designFilter), + 5 => !_designFilter.IsContained(design.Color.Length == 0 ? DesignColors.AutomaticName : design.Color), + 2 + EmptyOffset => design.AssociatedMods.Count > 0, + 3 + EmptyOffset => design.Tags.Length > 0, + _ => false, // Should never happen }; } From 226dbdd4a8c982941420a52ea64480aaadcb5d71 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 18 Nov 2023 13:16:51 +0100 Subject: [PATCH 056/786] Improve multi design selection. --- .../Gui/Tabs/DesignTab/DesignColorCombo.cs | 28 +++ .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 24 +-- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 98 ++------- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 186 ++++++++++++++++++ Glamourer/Services/ServiceManager.cs | 1 + 5 files changed, 233 insertions(+), 104 deletions(-) create mode 100644 Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs new file mode 100644 index 0000000..0db26b7 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -0,0 +1,28 @@ +using System.Linq; +using Glamourer.Designs; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Widgets; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public sealed class DesignColorCombo(DesignColors _designColors, bool _skipAutomatic) : + FilterComboCache(_skipAutomatic + ? _designColors.Keys.OrderBy(k => k) + : _designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName), + Glamourer.Log) +{ + protected override bool DrawSelectable(int globalIdx, bool selected) + { + var isAutomatic = !_skipAutomatic && globalIdx == 0; + var key = Items[globalIdx]; + var color = isAutomatic ? 0 : _designColors[key]; + using var c = ImRaii.PushColor(ImGuiCol.Text, color, color != 0); + var ret = base.DrawSelectable(globalIdx, selected); + if (isAutomatic) + ImGuiUtil.HoverTooltip( + "The automatic color uses the colors dependent on the design state, as defined in the regular color definitions."); + return ret; + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 2b0cac6..ca58393 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Linq; using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; @@ -14,27 +13,6 @@ using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class DesignColorCombo : FilterComboCache -{ - private readonly DesignColors _designColors; - - public DesignColorCombo(DesignColors designColors) - : base(designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName), Glamourer.Log) - => _designColors = designColors; - - protected override bool DrawSelectable(int globalIdx, bool selected) - { - var key = Items[globalIdx]; - var color = globalIdx == 0 ? 0 : _designColors[key]; - using var c = ImRaii.PushColor(ImGuiCol.Text, color, color != 0); - var ret = base.DrawSelectable(globalIdx, selected); - if (globalIdx == 0) - ImGuiUtil.HoverTooltip( - "The automatic color uses the colors dependent on the design state, as defined in the regular color definitions."); - return ret; - } -} - public class DesignDetailTab { private readonly SaveService _saveService; @@ -61,7 +39,7 @@ public class DesignDetailTab _manager = manager; _fileSystem = fileSystem; _colors = colors; - _colorCombo = new DesignColorCombo(_colors); + _colorCombo = new DesignColorCombo(_colors, false); } public void Draw() diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 64324d2..c5e6943 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -13,7 +13,6 @@ using Glamourer.Events; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop; -using Glamourer.Services; using Glamourer.State; using Glamourer.Structs; using ImGuiNET; @@ -24,37 +23,11 @@ using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignPanel +public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _customizationDrawer, DesignManager _manager, + ObjectManager _objects, StateManager _state, EquipmentDrawer _equipmentDrawer, ModAssociationsTab _modAssociations, + DesignDetailTab _designDetails, DesignConverter _converter, DatFileService _datFileService, MultiDesignPanel _multiDesignPanel) { - private readonly ObjectManager _objects; - private readonly DesignFileSystemSelector _selector; - private readonly DesignManager _manager; - private readonly CustomizationDrawer _customizationDrawer; - private readonly StateManager _state; - private readonly EquipmentDrawer _equipmentDrawer; - private readonly CustomizationService _customizationService; - private readonly ModAssociationsTab _modAssociations; - private readonly DesignDetailTab _designDetails; - private readonly DesignConverter _converter; - private readonly DatFileService _datFileService; - private readonly FileDialogManager _fileDialog = new(); - - public DesignPanel(DesignFileSystemSelector selector, CustomizationDrawer customizationDrawer, DesignManager manager, ObjectManager objects, - StateManager state, EquipmentDrawer equipmentDrawer, CustomizationService customizationService, ModAssociationsTab modAssociations, - DesignDetailTab designDetails, DesignConverter converter, DatFileService datFileService) - { - _selector = selector; - _customizationDrawer = customizationDrawer; - _manager = manager; - _objects = objects; - _state = state; - _equipmentDrawer = equipmentDrawer; - _customizationService = customizationService; - _modAssociations = modAssociations; - _designDetails = designDetails; - _converter = converter; - _datFileService = datFileService; - } + private readonly FileDialogManager _fileDialog = new(); private HeaderDrawer.Button LockButton() => _selector.Selected == null @@ -88,10 +61,10 @@ public class DesignPanel => new() { Description = "Undo the last change if you accidentally overwrote your design with a different one.", - Icon = FontAwesomeIcon.Undo, - OnClick = UndoOverwrite, - Visible = _selector.Selected != null, - Disabled = !_manager.CanUndo(_selector.Selected), + Icon = FontAwesomeIcon.Undo, + OnClick = UndoOverwrite, + Visible = _selector.Selected != null, + Disabled = !_manager.CanUndo(_selector.Selected), }; private HeaderDrawer.Button ExportToClipboardButton() @@ -215,7 +188,7 @@ public class DesignPanel if (!ImGui.CollapsingHeader("Application Rules")) return; - using (var group1 = ImRaii.Group()) + using (var _ = ImRaii.Group()) { var set = _selector.Selected!.CustomizationSet; var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; @@ -247,13 +220,13 @@ public class DesignPanel } ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); - using (var group2 = ImRaii.Group()) + using (var _ = ImRaii.Group()) { - void ApplyEquip(string label, EquipFlag all, bool stain, IEnumerable slots) + void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable slots) { - var flags = (uint)(all & _selector.Selected!.ApplyEquip); + var flags = (uint)(allFlags & _selector.Selected!.ApplyEquip); - var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)all); + var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); if (stain) foreach (var slot in slots) { @@ -316,7 +289,7 @@ public class DesignPanel using var group = ImRaii.Group(); if (_selector.SelectedPaths.Count > 1) { - DrawMultiSelection(); + _multiDesignPanel.Draw(); } else { @@ -338,44 +311,6 @@ public class DesignPanel _datFileService.CreateSource(); } - private void DrawMultiSelection() - { - if (_selector.SelectedPaths.Count == 0) - return; - - var sizeType = ImGui.GetFrameHeight(); - var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType - 4 * ImGui.GetStyle().CellPadding.X) / 100; - var sizeMods = availableSizePercent * 35; - var sizeFolders = availableSizePercent * 65; - - ImGui.NewLine(); - ImGui.TextUnformatted("Currently Selected Objects"); - ImGui.Separator(); - using var table = ImRaii.Table("mods", 3, ImGuiTableFlags.RowBg); - ImGui.TableSetupColumn("type", ImGuiTableColumnFlags.WidthFixed, sizeType); - ImGui.TableSetupColumn("mod", ImGuiTableColumnFlags.WidthFixed, sizeMods); - ImGui.TableSetupColumn("path", ImGuiTableColumnFlags.WidthFixed, sizeFolders); - - var i = 0; - foreach (var (fullName, path) in _selector.SelectedPaths.Select(p => (p.FullName(), p)) - .OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase)) - { - using var id = ImRaii.PushId(i++); - ImGui.TableNextColumn(); - var icon = (path is DesignFileSystem.Leaf ? FontAwesomeIcon.FileCircleMinus : FontAwesomeIcon.FolderMinus).ToIconString(); - if (ImGuiUtil.DrawDisabledButton(icon, new Vector2(sizeType), "Remove from selection.", false, true)) - _selector.RemovePathFromMultiselection(path); - - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(path is DesignFileSystem.Leaf l ? l.Value.Name : string.Empty); - - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(fullName); - } - } - private void DrawPanel() { using var child = ImRaii.Child("##Panel", -Vector2.One, true); @@ -426,7 +361,8 @@ public class DesignPanel } catch (Exception ex) { - Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {_selector.Selected!.Name}.", NotificationType.Error, false); + Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {_selector.Selected!.Name}.", NotificationType.Error, + false); } } @@ -481,7 +417,7 @@ public class DesignPanel private void DrawSaveToDat() { - var verified = _datFileService.Verify(_selector.Selected!.DesignData.Customize, out var voice); + var verified = _datFileService.Verify(_selector.Selected!.DesignData.Customize, out _); var tt = verified ? "Export the currently configured customizations of this design to a character creation data file." : "The current design contains customizations that can not be applied during character creation."; diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs new file mode 100644 index 0000000..4b1e128 --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Glamourer.Designs; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager _editor, DesignColors _colors) +{ + private readonly DesignColorCombo _colorCombo = new(_colors, true); + + public void Draw() + { + if (_selector.SelectedPaths.Count == 0) + return; + + var width = ImGuiHelpers.ScaledVector2(145, 0); + ImGui.NewLine(); + DrawDesignList(); + var offset = DrawMultiTagger(width); + DrawMultiColor(width, offset); + } + + private void DrawDesignList() + { + using var tree = ImRaii.TreeNode("Currently Selected Objects", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.NoTreePushOnOpen); + ImGui.Separator(); + if (!tree) + return; + + var sizeType = ImGui.GetFrameHeight(); + var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType - 4 * ImGui.GetStyle().CellPadding.X) / 100; + var sizeMods = availableSizePercent * 35; + var sizeFolders = availableSizePercent * 65; + + using (var table = ImRaii.Table("mods", 3, ImGuiTableFlags.RowBg)) + { + if (!table) + return; + + ImGui.TableSetupColumn("type", ImGuiTableColumnFlags.WidthFixed, sizeType); + ImGui.TableSetupColumn("mod", ImGuiTableColumnFlags.WidthFixed, sizeMods); + ImGui.TableSetupColumn("path", ImGuiTableColumnFlags.WidthFixed, sizeFolders); + + var i = 0; + foreach (var (fullName, path) in _selector.SelectedPaths.Select(p => (p.FullName(), p)) + .OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase)) + { + using var id = ImRaii.PushId(i++); + ImGui.TableNextColumn(); + var icon = (path is DesignFileSystem.Leaf ? FontAwesomeIcon.FileCircleMinus : FontAwesomeIcon.FolderMinus).ToIconString(); + if (ImGuiUtil.DrawDisabledButton(icon, new Vector2(sizeType), "Remove from selection.", false, true)) + _selector.RemovePathFromMultiselection(path); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(path is DesignFileSystem.Leaf l ? l.Value.Name : string.Empty); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(fullName); + } + } + + ImGui.Separator(); + } + + private string _tag = string.Empty; + private readonly List _addDesigns = []; + private readonly List<(Design, int)> _removeDesigns = []; + + private float DrawMultiTagger(Vector2 width) + { + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Multi Tagger:"); + ImGui.SameLine(); + var offset = ImGui.GetItemRectSize().X; + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X)); + ImGui.InputTextWithHint("##tag", "Tag Name...", ref _tag, 128); + + UpdateTagCache(); + var label = _addDesigns.Count > 0 + ? $"Add to {_addDesigns.Count} Designs" + : "Add"; + var tooltip = _addDesigns.Count == 0 + ? _tag.Length == 0 + ? "No tag specified." + : $"All designs selected already contain the tag \"{_tag}\"." + : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _addDesigns.Count == 0)) + foreach (var design in _addDesigns) + _editor.AddTag(design, _tag); + + label = _removeDesigns.Count > 0 + ? $"Remove from {_removeDesigns.Count} Designs" + : "Remove"; + tooltip = _removeDesigns.Count == 0 + ? _tag.Length == 0 + ? "No tag specified." + : $"No selected design contains the tag \"{_tag}\" locally." + : $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _removeDesigns.Count == 0)) + foreach (var (design, index) in _removeDesigns) + _editor.RemoveTag(design, index); + ImGui.Separator(); + return offset; + } + + private void DrawMultiColor(Vector2 width, float offset) + { + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Multi Colors:"); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + _colorCombo.Draw("##color", _colorCombo.CurrentSelection ?? string.Empty, "Select a design color.", + ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X), ImGui.GetTextLineHeight()); + + UpdateColorCache(); + var label = _addDesigns.Count > 0 + ? $"Set for {_addDesigns.Count} Designs" + : "Set"; + var tooltip = _addDesigns.Count == 0 + ? _colorCombo.CurrentSelection switch + { + null => "No color specified.", + DesignColors.AutomaticName => "Use the other button to set to automatic.", + _ => $"All designs selected are already set to the color \"{_colorCombo.CurrentSelection}\".", + } + : $"Set the color of {_addDesigns.Count} designs to \"{_colorCombo.CurrentSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _addDesigns.Count == 0)) + foreach (var design in _addDesigns) + _editor.ChangeColor(design, _colorCombo.CurrentSelection!); + + label = _removeDesigns.Count > 0 + ? $"Unset {_removeDesigns.Count} Designs" + : "Unset"; + tooltip = _removeDesigns.Count == 0 + ? "No selected design is set to a non-automatic color." + : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _removeDesigns.Count == 0)) + foreach (var (design, _) in _removeDesigns) + _editor.ChangeColor(design, string.Empty); + + ImGui.Separator(); + } + + private void UpdateTagCache() + { + _addDesigns.Clear(); + _removeDesigns.Clear(); + if (_tag.Length == 0) + return; + + foreach (var leaf in _selector.SelectedPaths.OfType()) + { + var index = leaf.Value.Tags.IndexOf(_tag); + if (index >= 0) + _removeDesigns.Add((leaf.Value, index)); + else + _addDesigns.Add(leaf.Value); + } + } + + private void UpdateColorCache() + { + _addDesigns.Clear(); + _removeDesigns.Clear(); + var selection = _colorCombo.CurrentSelection ?? DesignColors.AutomaticName; + foreach (var leaf in _selector.SelectedPaths.OfType()) + { + if (leaf.Value.Color.Length > 0) + _removeDesigns.Add((leaf.Value, 0)); + if (selection != DesignColors.AutomaticName && leaf.Value.Color != selection) + _addDesigns.Add(leaf.Value); + } + } +} diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 0d38099..19ade32 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -130,6 +130,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() From 9c3c1bdf59eb24c04f28057925b16292a366757b Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 18 Nov 2023 12:18:47 +0000 Subject: [PATCH 057/786] [CI] Updating repo.json for testing_1.0.5.3 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 705d4da..a38f1ef 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.5.0", - "TestingAssemblyVersion": "1.0.5.2", + "TestingAssemblyVersion": "1.0.5.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.0.5.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.5.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.5.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.5.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 321c481c7dc12274bb3ea182c0f4999d8255d0ae Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 21 Nov 2023 17:40:06 +0100 Subject: [PATCH 058/786] Add option to import .chara files as design or onto actors/designs. --- Glamourer/Designs/DesignBase.cs | 10 + Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 22 +- Glamourer/Gui/Tabs/DebugTab.cs | 10 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 17 +- Glamourer/Gui/Tabs/DesignTab/DesignTab.cs | 25 +- Glamourer/Interop/CharaFile/CharaFile.cs | 295 ++++++++++++++++++ .../{DatFileService.cs => ImportService.cs} | 79 +++-- Glamourer/Services/ServiceManager.cs | 2 +- 8 files changed, 412 insertions(+), 48 deletions(-) create mode 100644 Glamourer/Interop/CharaFile/CharaFile.cs rename Glamourer/Interop/{DatFileService.cs => ImportService.cs} (59%) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 4ef567e..183ca99 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -32,6 +32,16 @@ public class DesignBase CustomizationSet = SetCustomizationSet(customize); } + internal DesignBase(CustomizationService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) + { + _designData = designData; + ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; + ApplyEquip = equipFlags & EquipFlagExtensions.All; + _designFlags = 0; + CustomizationSet = SetCustomizationSet(customize); + + } + internal DesignBase(DesignBase clone) { _designData = clone._designData; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 6963053..d049eec 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -36,7 +36,7 @@ public class ActorPanel private readonly DesignConverter _converter; private readonly ObjectManager _objects; private readonly DesignManager _designManager; - private readonly DatFileService _datFileService; + private readonly ImportService _importService; private readonly ICondition _conditions; private ActorIdentifier _identifier; @@ -48,7 +48,7 @@ public class ActorPanel public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer, EquipmentDrawer equipmentDrawer, IdentifierService identification, AutoDesignApplier autoDesignApplier, - Configuration config, DesignConverter converter, ObjectManager objects, DesignManager designManager, DatFileService datFileService, + Configuration config, DesignConverter converter, ObjectManager objects, DesignManager designManager, ImportService importService, ICondition conditions) { _selector = selector; @@ -61,7 +61,7 @@ public class ActorPanel _converter = converter; _objects = objects; _designManager = designManager; - _datFileService = datFileService; + _importService = importService; _conditions = conditions; } @@ -81,9 +81,21 @@ public class ActorPanel if (_state is not { IsLocked: false }) return; - if (_datFileService.CreateImGuiTarget(out var dat)) + if (_importService.CreateDatTarget(out var dat)) + { _stateManager.ChangeCustomize(_state!, dat.Customize, CustomizeApplicationFlags, StateChanged.Source.Manual); - _datFileService.CreateSource(); + Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_state.Identifier}.", + NotificationType.Success, false); + } + else if (_importService.CreateCharaTarget(out var designBase, out var name)) + { + _stateManager.ApplyDesign(designBase, _state!, StateChanged.Source.Manual); + Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_state.Identifier}.", NotificationType.Success, + false); + } + + _importService.CreateDatSource(); + _importService.CreateCharaSource(); } private void DrawHeader() diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index 3b07b33..7d946fb 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -50,7 +50,7 @@ public unsafe class DebugTab : ITab private readonly ObjectManager _objectManager; private readonly GlamourerIpc _ipc; private readonly CodeService _code; - private readonly DatFileService _datFileService; + private readonly ImportService _importService; private readonly ItemManager _items; private readonly ActorService _actors; @@ -81,7 +81,7 @@ public unsafe class DebugTab : ITab DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config, PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface, AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks, - ItemUnlockManager itemUnlocks, DesignConverter designConverter, DatFileService datFileService, InventoryService inventoryService, + ItemUnlockManager itemUnlocks, DesignConverter designConverter, ImportService importService, InventoryService inventoryService, HumanModelList humans, FunModule funModule) { _changeCustomizeService = changeCustomizeService; @@ -107,7 +107,7 @@ public unsafe class DebugTab : ITab _customizeUnlocks = customizeUnlocks; _itemUnlocks = itemUnlocks; _designConverter = designConverter; - _datFileService = datFileService; + _importService = importService; _inventoryService = inventoryService; _humans = humans; _funModule = funModule; @@ -284,10 +284,10 @@ public unsafe class DebugTab : ITab ImGui.InputTextWithHint("##datFilePath", "Dat File Path...", ref _datFilePath, 256); var exists = _datFilePath.Length > 0 && File.Exists(_datFilePath); if (ImGuiUtil.DrawDisabledButton("Load##Dat", Vector2.Zero, string.Empty, !exists)) - _datFile = _datFileService.LoadDesign(_datFilePath, out var tmp) ? tmp : null; + _datFile = _importService.LoadDat(_datFilePath, out var tmp) ? tmp : null; if (ImGuiUtil.DrawDisabledButton("Save##Dat", Vector2.Zero, string.Empty, _datFilePath.Length == 0 || _datFile == null)) - _datFileService.SaveDesign(_datFilePath, _datFile!.Value.Customize, _datFile!.Value.Description); + _importService.SaveDesignAsDat(_datFilePath, _datFile!.Value.Customize, _datFile!.Value.Description); if (_datFile != null) { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index c5e6943..bd6e197 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Xml.Linq; using Dalamud.Interface; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Internal.Notifications; @@ -25,7 +26,7 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _customizationDrawer, DesignManager _manager, ObjectManager _objects, StateManager _state, EquipmentDrawer _equipmentDrawer, ModAssociationsTab _modAssociations, - DesignDetailTab _designDetails, DesignConverter _converter, DatFileService _datFileService, MultiDesignPanel _multiDesignPanel) + DesignDetailTab _designDetails, DesignConverter _converter, ImportService _importService, MultiDesignPanel _multiDesignPanel) { private readonly FileDialogManager _fileDialog = new(); @@ -299,16 +300,22 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer if (_selector.Selected == null || _selector.Selected.WriteProtected()) return; - if (_datFileService.CreateImGuiTarget(out var dat)) + if (_importService.CreateDatTarget(out var dat)) { _manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Clan, dat.Customize[CustomizeIndex.Clan]); _manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]); foreach (var idx in CustomizationExtensions.AllBasic) _manager.ChangeCustomize(_selector.Selected!, idx, dat.Customize[idx]); + Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_selector.Selected.Name}.", NotificationType.Success, false); + } + else if (_importService.CreateCharaTarget(out var designBase, out var name)) + { + _manager.ApplyDesign(_selector.Selected!, designBase); + Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selector.Selected.Name}.", NotificationType.Success, false); } } - _datFileService.CreateSource(); + _importService.CreateDatSource(); } private void DrawPanel() @@ -417,7 +424,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer private void DrawSaveToDat() { - var verified = _datFileService.Verify(_selector.Selected!.DesignData.Customize, out _); + var verified = _importService.Verify(_selector.Selected!.DesignData.Customize, out _); var tt = verified ? "Export the currently configured customizations of this design to a character creation data file." : "The current design contains customizations that can not be applied during character creation."; @@ -428,7 +435,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _fileDialog.SaveFileDialog("Save File...", ".dat", "FFXIV_CHARA_01.dat", ".dat", (v, path) => { if (v && _selector.Selected != null) - _datFileService.SaveDesign(path, _selector.Selected!.DesignData.Customize, _selector.Selected!.Name); + _importService.SaveDesignAsDat(path, _selector.Selected!.DesignData.Customize, _selector.Selected!.Name); }, startPath); _fileDialog.Draw(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index 243fc9c..1c5a0b9 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -1,29 +1,32 @@ using System; +using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Utility; +using Glamourer.Designs; +using Glamourer.Interop; using ImGuiNET; +using OtterGui.Classes; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignTab : ITab +public class DesignTab(DesignFileSystemSelector _selector, DesignPanel _panel, ImportService _importService, DesignManager _manager) + : ITab { - public readonly DesignFileSystemSelector Selector; - private readonly DesignPanel _panel; - - public DesignTab(DesignFileSystemSelector selector, DesignPanel panel) - { - Selector = selector; - _panel = panel; - } - public ReadOnlySpan Label => "Designs"u8; public void DrawContent() { - Selector.Draw(GetDesignSelectorSize()); + _selector.Draw(GetDesignSelectorSize()); + if (_importService.CreateCharaTarget(out var designBase, out var name)) + { + var newDesign = _manager.CreateClone(designBase, name, true); + Glamourer.Messager.NotificationMessage($"Imported Anamnesis .chara file {name} as new design {newDesign.Name}", NotificationType.Success, false); + } + ImGui.SameLine(); _panel.Draw(); + _importService.CreateCharaSource(); } public float GetDesignSelectorSize() diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs new file mode 100644 index 0000000..6f56acc --- /dev/null +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -0,0 +1,295 @@ +using System; +using Glamourer.Customization; +using Glamourer.Designs; +using Glamourer.Services; +using Glamourer.Structs; +using Newtonsoft.Json.Linq; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Race = Penumbra.GameData.Enums.Race; + +namespace Glamourer.Interop.CharaFile; + +public sealed class CharaFile +{ + public string Name = string.Empty; + public DesignData Data = new(); + public CustomizeFlag ApplyCustomize; + public EquipFlag ApplyEquip; + + public static CharaFile? ParseData(ItemManager items, string data, string? name = null) + { + try + { + var jObj = JObject.Parse(data); + SanityCheck(jObj); + var ret = new CharaFile(); + ret.Data.SetDefaultEquipment(items); + ret.Data.ModelId = ParseModelId(jObj); + ret.Name = jObj["Nickname"]?.ToObject() ?? name ?? "New Design"; + ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize); + ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data); + return ret; + } + catch (Exception ex) + { + return null; + } + } + + private static EquipFlag ParseEquipment(ItemManager items, JObject jObj, ref DesignData data) + { + EquipFlag ret = 0; + ParseWeapon(items, jObj, "MainHand", EquipSlot.MainHand, ref data, ref ret); + ParseWeapon(items, jObj, "OffHand", EquipSlot.OffHand, ref data, ref ret); + ParseGear(items, jObj, "HeadGear", EquipSlot.Head, ref data, ref ret); + ParseGear(items, jObj, "Body", EquipSlot.Body, ref data, ref ret); + ParseGear(items, jObj, "Hands", EquipSlot.Hands, ref data, ref ret); + ParseGear(items, jObj, "Legs", EquipSlot.Legs, ref data, ref ret); + ParseGear(items, jObj, "Feet", EquipSlot.Feet, ref data, ref ret); + ParseGear(items, jObj, "Ears", EquipSlot.Ears, ref data, ref ret); + ParseGear(items, jObj, "Neck", EquipSlot.Neck, ref data, ref ret); + ParseGear(items, jObj, "Wrists", EquipSlot.Wrists, ref data, ref ret); + ParseGear(items, jObj, "LeftRing", EquipSlot.LFinger, ref data, ref ret); + ParseGear(items, jObj, "RightRing", EquipSlot.RFinger, ref data, ref ret); + return ret; + } + + private static void ParseWeapon(ItemManager items, JObject jObj, string property, EquipSlot slot, ref DesignData data, ref EquipFlag flags) + { + var jTok = jObj[property]; + if (jTok == null) + return; + + var set = jTok["ModelSet"]?.ToObject() ?? 0; + var type = jTok["ModelBase"]?.ToObject() ?? 0; + var variant = jTok["ModelVariant"]?.ToObject() ?? 0; + var dye = jTok["DyeId"]?.ToObject() ?? 0; + var item = items.Identify(slot, set, type, variant, slot is EquipSlot.OffHand ? data.MainhandType : FullEquipType.Unknown); + if (!item.Valid) + return; + + data.SetItem(slot, item); + data.SetStain(slot, (StainId)dye); + flags |= slot.ToFlag(); + flags |= slot.ToStainFlag(); + } + + private static void ParseGear(ItemManager items, JObject jObj, string property, EquipSlot slot, ref DesignData data, ref EquipFlag flags) + { + var jTok = jObj[property]; + if (jTok == null) + return; + + var set = jTok["ModelBase"]?.ToObject() ?? 0; + var variant = jTok["ModelVariant"]?.ToObject() ?? 0; + var dye = jTok["DyeId"]?.ToObject() ?? 0; + var item = items.Identify(slot, set, variant); + if (!item.Valid) + return; + + data.SetItem(slot, item); + data.SetStain(slot, dye); + flags |= slot.ToFlag(); + flags |= slot.ToStainFlag(); + } + + private static CustomizeFlag ParseCustomize(JObject jObj, ref Customize customize) + { + CustomizeFlag ret = 0; + customize.Race = ParseRace(jObj, ref ret); + customize.Gender = ParseGender(jObj, ref ret); + customize.Clan = ParseTribe(jObj, ref ret); + ParseByte(jObj, "Height", CustomizeIndex.Height, ref customize, ref ret); + ParseByte(jObj, "Head", CustomizeIndex.Face, ref customize, ref ret); + ParseByte(jObj, "Hair", CustomizeIndex.Hairstyle, ref customize, ref ret); + ParseHighlights(jObj, ref customize, ref ret); + ParseByte(jObj, "Skintone", CustomizeIndex.SkinColor, ref customize, ref ret); + ParseByte(jObj, "REyeColor", CustomizeIndex.EyeColorRight, ref customize, ref ret); + ParseByte(jObj, "HairTone", CustomizeIndex.HairColor, ref customize, ref ret); + ParseByte(jObj, "Highlights", CustomizeIndex.HighlightsColor, ref customize, ref ret); + ParseFacial(jObj, ref customize, ref ret); + ParseByte(jObj, "LimbalEyes", CustomizeIndex.TattooColor, ref customize, ref ret); + ParseByte(jObj, "Eyebrows", CustomizeIndex.Eyebrows, ref customize, ref ret); + ParseByte(jObj, "LEyeColor", CustomizeIndex.EyeColorLeft, ref customize, ref ret); + ParseByte(jObj, "Eyes", CustomizeIndex.EyeShape, ref customize, ref ret); + ParseByte(jObj, "Nose", CustomizeIndex.Nose, ref customize, ref ret); + ParseByte(jObj, "Jaw", CustomizeIndex.Jaw, ref customize, ref ret); + ParseByte(jObj, "Mouth", CustomizeIndex.Mouth, ref customize, ref ret); + ParseByte(jObj, "LipsToneFurPattern", CustomizeIndex.LipColor, ref customize, ref ret); + ParseByte(jObj, "EarMuscleTailSize", CustomizeIndex.MuscleMass, ref customize, ref ret); + ParseByte(jObj, "TailEarsType", CustomizeIndex.TailShape, ref customize, ref ret); + ParseByte(jObj, "Bust", CustomizeIndex.BustSize, ref customize, ref ret); + ParseByte(jObj, "FacePaint", CustomizeIndex.FacePaint, ref customize, ref ret); + ParseByte(jObj, "FacePaintColor", CustomizeIndex.FacePaintColor, ref customize, ref ret); + ParseAge(jObj); + + if (ret.HasFlag(CustomizeFlag.EyeShape)) + ret |= CustomizeFlag.SmallIris; + + if (ret.HasFlag(CustomizeFlag.Mouth)) + ret |= CustomizeFlag.Lipstick; + + if (ret.HasFlag(CustomizeFlag.FacePaint)) + ret |= CustomizeFlag.FacePaintReversed; + + return ret; + } + + private static uint ParseModelId(JObject jObj) + { + var jTok = jObj["ModelType"]; + if (jTok == null) + throw new Exception("No Model ID given."); + + var id = jTok.ToObject(); + if (id != 0) + throw new Exception($"Model ID {id} != 0 not supported."); + + return id; + } + + private static void ParseFacial(JObject jObj, ref Customize customize, ref CustomizeFlag application) + { + var jTok = jObj["FacialFeatures"]; + if (jTok == null) + return; + + application |= CustomizeFlag.FacialFeature1 + | CustomizeFlag.FacialFeature2 + | CustomizeFlag.FacialFeature3 + | CustomizeFlag.FacialFeature4 + | CustomizeFlag.FacialFeature5 + | CustomizeFlag.FacialFeature6 + | CustomizeFlag.FacialFeature7 + | CustomizeFlag.LegacyTattoo; + + var value = jTok.ToObject()!; + if (value is "None") + return; + + if (value.Contains("First")) + customize[CustomizeIndex.FacialFeature1] = CustomizeValue.Max; + if (value.Contains("Second")) + customize[CustomizeIndex.FacialFeature2] = CustomizeValue.Max; + if (value.Contains("Third")) + customize[CustomizeIndex.FacialFeature3] = CustomizeValue.Max; + if (value.Contains("Fourth")) + customize[CustomizeIndex.FacialFeature4] = CustomizeValue.Max; + if (value.Contains("Fifth")) + customize[CustomizeIndex.FacialFeature5] = CustomizeValue.Max; + if (value.Contains("Sixth")) + customize[CustomizeIndex.FacialFeature6] = CustomizeValue.Max; + if (value.Contains("Seventh")) + customize[CustomizeIndex.FacialFeature7] = CustomizeValue.Max; + if (value.Contains("LegacyTattoo")) + customize[CustomizeIndex.LegacyTattoo] = CustomizeValue.Max; + } + + private static void ParseHighlights(JObject jObj, ref Customize customize, ref CustomizeFlag application) + { + var jTok = jObj["EnableHighlights"]; + if (jTok == null) + return; + + var value = jTok.ToObject(); + application |= CustomizeFlag.Highlights; + customize[CustomizeIndex.Highlights] = value ? CustomizeValue.Max : CustomizeValue.Zero; + } + + private static Race ParseRace(JObject jObj, ref CustomizeFlag application) + { + var race = jObj["Race"]?.ToObject() switch + { + null => Race.Unknown, + "Hyur" => Race.Hyur, + "Elezen" => Race.Elezen, + "Lalafel" => Race.Lalafell, + "Miqote" => Race.Miqote, + "Roegadyn" => Race.Roegadyn, + "AuRa" => Race.AuRa, + "Hrothgar" => Race.Hrothgar, + "Viera" => Race.Viera, + _ => throw new Exception($"Invalid Race value {jObj["Race"]?.ToObject()}."), + }; + if (race == Race.Unknown) + return Race.Hyur; + + application |= CustomizeFlag.Race; + return race; + } + + private static Gender ParseGender(JObject jObj, ref CustomizeFlag application) + { + var gender = jObj["Gender"]?.ToObject() switch + { + null => Gender.Unknown, + "Masculine" => Gender.Male, + "Feminine" => Gender.Female, + _ => throw new Exception($"Invalid Gender value {jObj["Gender"]?.ToObject()}."), + }; + if (gender == Gender.Unknown) + return Gender.Male; + + application |= CustomizeFlag.Gender; + return gender; + } + + private static void ParseAge(JObject jObj) + { + var age = jObj["Age"]?.ToObject(); + if (age is not null and not "Normal") + throw new Exception($"Age {age} != Normal is not supported."); + } + + private static unsafe void ParseByte(JObject jObj, string property, CustomizeIndex idx, ref Customize customize, + ref CustomizeFlag application) + { + var jTok = jObj[property]; + if (jTok == null) + return; + + customize.Data.Data[idx.ToByteAndMask().ByteIdx] = jTok.ToObject(); + application |= idx.ToFlag(); + } + + private static SubRace ParseTribe(JObject jObj, ref CustomizeFlag application) + { + var tribe = jObj["Tribe"]?.ToObject() switch + { + null => SubRace.Unknown, + "Midlander" => SubRace.Midlander, + "Highlander" => SubRace.Highlander, + "Wildwood" => SubRace.Wildwood, + "Duskwight" => SubRace.Duskwight, + "Plainsfolk" => SubRace.Plainsfolk, + "Dunesfolk" => SubRace.Dunesfolk, + "SeekerOfTheSun" => SubRace.SeekerOfTheSun, + "KeeperOfTheMoon" => SubRace.KeeperOfTheMoon, + "SeaWolf" => SubRace.Seawolf, + "Hellsguard" => SubRace.Hellsguard, + "Raen" => SubRace.Raen, + "Xaela" => SubRace.Xaela, + "Helions" => SubRace.Helion, + "TheLost" => SubRace.Lost, + "Rava" => SubRace.Rava, + "Veena" => SubRace.Veena, + _ => throw new Exception($"Invalid Tribe value {jObj["Tribe"]?.ToObject()}."), + }; + if (tribe == SubRace.Unknown) + return SubRace.Midlander; + + application |= CustomizeFlag.Clan; + return tribe; + } + + private static void SanityCheck(JObject jObj) + { + if (jObj["TypeName"]?.ToObject() is not "Anamnesis Character File") + throw new Exception("Wrong TypeName property set."); + + var type = jObj["ObjectKind"]?.ToObject(); + if (type is not "Player") + throw new Exception($"ObjectKind {type} != Player is not supported."); + } +} diff --git a/Glamourer/Interop/DatFileService.cs b/Glamourer/Interop/ImportService.cs similarity index 59% rename from Glamourer/Interop/DatFileService.cs rename to Glamourer/Interop/ImportService.cs index 0d27bcc..2681abb 100644 --- a/Glamourer/Interop/DatFileService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -1,39 +1,34 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Dalamud.Interface.DragDrop; using Dalamud.Interface.Internal.Notifications; using Glamourer.Customization; +using Glamourer.Designs; using Glamourer.Services; -using Glamourer.Unlocks; using ImGuiNET; using OtterGui.Classes; namespace Glamourer.Interop; -public class DatFileService +public class ImportService(CustomizationService _customizations, IDragDropManager _dragDropManager, ItemManager _items) { - private readonly CustomizationService _customizations; - private readonly CustomizeUnlockManager _unlocks; - private readonly IDragDropManager _dragDropManager; - - public DatFileService(CustomizationService customizations, CustomizeUnlockManager unlocks, IDragDropManager dragDropManager) - { - _customizations = customizations; - _unlocks = unlocks; - _dragDropManager = dragDropManager; - } - - public void CreateSource() - { - _dragDropManager.CreateImGuiSource("DatDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".dat"), m => + public void CreateDatSource() + => _dragDropManager.CreateImGuiSource("DatDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".dat"), m => { ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import customizations for Glamourer..."); return true; }); - } - public bool CreateImGuiTarget(out DatCharacterFile file) + public void CreateCharaSource() + => _dragDropManager.CreateImGuiSource("CharaDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".chara"), m => + { + ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import Anamnesis data for Glamourer..."); + return true; + }); + + public bool CreateDatTarget(out DatCharacterFile file) { if (!_dragDropManager.CreateImGuiTarget("DatDragger", out var files, out _) || files.Count != 1) { @@ -41,10 +36,52 @@ public class DatFileService return false; } - return LoadDesign(files[0], out file); + return LoadDat(files[0], out file); } - public bool LoadDesign(string path, out DatCharacterFile file) + public bool CreateCharaTarget([NotNullWhen(true)] out DesignBase? design, out string name) + { + if (!_dragDropManager.CreateImGuiTarget("CharaDragger", out var files, out _) || files.Count != 1) + { + design = null; + name = string.Empty; + return false; + } + + return LoadChara(files[0], out design, out name); + } + + public bool LoadChara(string path, [NotNullWhen(true)] out DesignBase? design, out string name) + { + if (!File.Exists(path)) + { + design = null; + name = string.Empty; + return false; + } + + try + { + var text = File.ReadAllText(path); + var file = CharaFile.CharaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path)); + if (file == null) + throw new Exception(); + + name = file.Name; + design = new DesignBase(_customizations, file.Data, file.ApplyEquip, file.ApplyCustomize); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not read .chara file {path}.", NotificationType.Error); + design = null; + name = string.Empty; + return false; + } + + return true; + } + + public bool LoadDat(string path, out DatCharacterFile file) { if (!File.Exists(path)) { @@ -70,7 +107,7 @@ public class DatFileService return true; } - public bool SaveDesign(string path, in Customize input, string description) + public bool SaveDesignAsDat(string path, in Customize input, string description) { if (!Verify(input, out var voice)) return false; diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 19ade32..6e9cffa 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -96,7 +96,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); From 2c3f7fb92a5108e6faf42383f0a59247a918ae17 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 22 Nov 2023 14:40:49 +0100 Subject: [PATCH 059/786] Brio compatibility. --- Glamourer/Interop/ObjectManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index 17f997f..52dd397 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -64,7 +64,10 @@ public class ObjectManager : IReadOnlyDictionary for (var i = (int)ScreenActor.CutsceneStart; i < (int)ScreenActor.CutsceneEnd; ++i) { Actor character = _objects.GetObjectAddress(i); - if (!character.Valid) + // Technically the game does not create holes in cutscenes or GPose. + // But for Brio compatibility, we allow holes in GPose. + // Since GPose always has the event actor in the first cutscene slot, we can still optimize in this case. + if (!character.Valid && i == (int)ScreenActor.CutsceneStart) break; HandleIdentifier(character.GetIdentifier(_actors.AwaitedService), character); From a373537adfd14c2475be62694ce485ab0b17df76 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 22 Nov 2023 14:59:57 +0100 Subject: [PATCH 060/786] 1.0.6.0 --- Glamourer/Gui/GlamourerChangelog.cs | 29 ++++++++++++++++++++++++ Glamourer/Interop/CharaFile/CharaFile.cs | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 7e66fcc..3411bf2 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -23,6 +23,7 @@ public class GlamourerChangelog Add1_0_3_0(Changelog); Add1_0_4_0(Changelog); Add1_0_5_0(Changelog); + Add1_0_6_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -43,6 +44,34 @@ public class GlamourerChangelog } } + private static void Add1_0_6_0(Changelog log) + => log.NextVersion("Version 1.0.6.0") + .RegisterHighlight("Added the option to define custom color groups and associate designs with them.") + .RegisterEntry("You can create and name design colors in Settings -> Colors -> Custom Design Colors.", 1) + .RegisterEntry( + "By default, all designs have an automatic coloring corresponding to the current system, that chooses a color dynamically based on application rules.", + 1) + .RegisterEntry( + "Example: You create a custom color named 'Test' and make it bright blue. Now you assign 'Test' to some design in its Design Details, and it will always display bright blue in the design list.", + 1) + .RegisterEntry("Design colors are stored by name. If a color can not be found, the design will display the Missing Color instead.", + 1) + .RegisterEntry("You can filter for designs using specific colors via c:", 1) + .RegisterHighlight( + "You can now filter for the special case 'None' for filters where that makes sense (like Tags or Mod Associations).") + .RegisterHighlight( + "When selecting multiple designs, you can now add or remove tags from them at once, and set their colors at once.") + .RegisterEntry("Improved tri-state checkboxes. The colors of the new symbols can be changed in Color Settings.") + .RegisterEntry("Removed half-baked localization of customization names and fixed some names in application rules.") + .RegisterEntry("Improved Brio compatibility") + .RegisterEntry("Fixed some display issues with text color on locked designs.") + .RegisterEntry("Fixed issues with automatic design color display for customization-only designs.") + .RegisterEntry("Removed borders from the quick design window regardless of custom styling.") + .RegisterEntry("Improved handling of (un)available customization options.") + .RegisterEntry( + "Some configuration like the currently selected tab states are now stored in a separate file that is not backed up and saved less often.") + .RegisterEntry("Added option to open the Glamourer main window at game start independently of Debug Mode."); + private static void Add1_0_5_0(Changelog log) => log.NextVersion("Version 1.0.5.0") .RegisterHighlight("Dyes are can now be favorited the same way equipment pieces can.") diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs index 6f56acc..6a58809 100644 --- a/Glamourer/Interop/CharaFile/CharaFile.cs +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -31,7 +31,7 @@ public sealed class CharaFile ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data); return ret; } - catch (Exception ex) + catch { return null; } From 0a7d8007065901da81567abe2986f847cb1f8a6b Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 22 Nov 2023 14:21:54 +0000 Subject: [PATCH 061/786] [CI] Updating repo.json for 1.0.6.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index a38f1ef..48dcc35 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.5.0", - "TestingAssemblyVersion": "1.0.5.3", + "AssemblyVersion": "1.0.6.0", + "TestingAssemblyVersion": "1.0.6.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.5.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.5.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.5.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From e1fc08fce77bc4ee4822069222e954ac4b28a432 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 2 Dec 2023 17:37:20 +0100 Subject: [PATCH 062/786] Allow parsing strings without backwards compatibility. --- Glamourer/Designs/DesignConverter.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index d8f2b0a..e8b1742 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -102,12 +102,22 @@ public class DesignConverter : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } - case Version: + case 5: { bytes = bytes[DesignBase64Migration.Base64SizeV4..]; version = bytes.DecompressToString(out var decompressed); var jObj2 = JObject.Parse(decompressed); - Debug.Assert(version == Version); + Debug.Assert(version == 5); + ret = jObj2["Identifier"] != null + ? Design.LoadDesign(_customize, _items, jObj2) + : DesignBase.LoadDesignBase(_customize, _items, jObj2); + break; + } + case 6: + { + version = bytes.DecompressToString(out var decompressed); + var jObj2 = JObject.Parse(decompressed); + Debug.Assert(version == 6); ret = jObj2["Identifier"] != null ? Design.LoadDesign(_customize, _items, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); From cf566932f9ae912b96a29327032eaaaa2bdb9c7f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 11:01:04 +0100 Subject: [PATCH 063/786] Fix some issues with NPC Equip. --- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 5 +++++ Glamourer/Gui/Equipment/ItemCombo.cs | 25 +++++++++++++++++++--- Glamourer/Services/ItemManager.cs | 6 +++--- OtterGui | 2 +- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index b8cd9a2..7a0f500 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -345,7 +345,7 @@ public class DesignManager /// Change a non-weapon equipment piece. public void ChangeEquip(Design design, EquipSlot slot, EquipItem item) { - if (!_items.IsItemValid(slot, item.ItemId, out item)) + if (!_items.IsItemValid(slot, item.Id, out item)) return; var old = design.DesignData.Item(slot); diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 3b33f8a..ca78bea 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -263,6 +263,11 @@ public class EquipmentDrawer var change = combo.Draw(armor.Name, armor.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); if (change) armor = combo.CurrentSelection; + else if (combo.CustomVariant.Id > 0) + { + armor = _items.Identify(slot, combo.CustomSetId, combo.CustomVariant); + change = true; + } if (!locked && armor.ModelId.Id != 0) { diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 5062949..4ddbbaa 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Dalamud.Plugin.Services; using Glamourer.Services; @@ -22,6 +23,9 @@ public sealed class ItemCombo : FilterComboCache private ItemId _currentItem; private float _innerWidth; + public SetId CustomSetId { get; private set; } + public Variant CustomVariant { get; private set; } + public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites) : base(() => GetItems(favorites, items, slot), log) { @@ -50,8 +54,9 @@ public sealed class ItemCombo : FilterComboCache public bool Draw(string previewName, ItemId previewIdx, float width, float innerWidth) { - _innerWidth = innerWidth; - _currentItem = previewIdx; + _innerWidth = innerWidth; + _currentItem = previewIdx; + CustomVariant = 0; return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); } @@ -117,4 +122,18 @@ public sealed class ItemCombo : FilterComboCache enumerable = enumerable.Append(ItemManager.SmallClothesItem(slot)); return enumerable.OrderByDescending(favorites.Contains).ThenBy(i => i.Name).Prepend(nothing).ToList(); } + + protected override void OnClosePopup() + { + // If holding control while the popup closes, try to parse the input as a full pair of set id and variant, and set a custom item for that. + if (!ImGui.GetIO().KeyCtrl) + return; + + var split = Filter.Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (split.Length != 2 || !ushort.TryParse(split[0], out var setId) || !byte.TryParse(split[1], out var variant)) + return; + + CustomSetId = setId; + CustomVariant = variant; + } } diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 59e8228..745469e 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -66,7 +66,7 @@ public class ItemManager : IDisposable public static EquipItem SmallClothesItem(EquipSlot slot) => new(SmallClothesNpc, SmallclothesId(slot), 0, SmallClothesNpcModel, 0, 1, slot.ToEquipType(), 0, 0, 0); - public EquipItem Resolve(EquipSlot slot, ItemId itemId) + public EquipItem Resolve(EquipSlot slot, CustomItemId itemId) { slot = slot.ToSlot(); if (itemId == NothingId(slot)) @@ -74,7 +74,7 @@ public class ItemManager : IDisposable if (itemId == SmallclothesId(slot)) return SmallClothesItem(slot); - if (!ItemService.AwaitedService.TryGetValue(itemId, slot, out var item)) + if (!itemId.IsItem || !ItemService.AwaitedService.TryGetValue(itemId.Item, slot, out var item)) return EquipItem.FromId(itemId); if (item.Type.ToSlot() != slot) @@ -151,7 +151,7 @@ public class ItemManager : IDisposable /// Returns whether an item id represents a valid item for a slot and gives the item. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public bool IsItemValid(EquipSlot slot, ItemId itemId, out EquipItem item) + public bool IsItemValid(EquipSlot slot, CustomItemId itemId, out EquipItem item) { item = Resolve(slot, itemId); return item.Valid; diff --git a/OtterGui b/OtterGui index f55733a..8df162f 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit f55733a96fdc9f82c9bbf8272ca6366079aa8e32 +Subproject commit 8df162f7dc7adc8be1af3eeae80bee3c0cfa4c5c From 294cbd46534cbb1c58cebf2ea8a2d2110f0a14f2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 11:04:18 +0100 Subject: [PATCH 064/786] Update OtterGui. --- Glamourer/Gui/UiHelpers.cs | 2 +- OtterGui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index eb1f1c2..7dcfc7d 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -9,8 +9,8 @@ using Glamourer.Unlocks; using ImGuiNET; using Lumina.Misc; using OtterGui; -using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Widgets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/OtterGui b/OtterGui index 8df162f..5c4c53c 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 8df162f7dc7adc8be1af3eeae80bee3c0cfa4c5c +Subproject commit 5c4c53c1f996ebf0dfd0a51ae645a46cad4a803b From cd289247e9479b8c52429194850d9fa2cef8d633 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 11:58:39 +0100 Subject: [PATCH 065/786] Update OtterGui. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 5c4c53c..0c50c8d 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 5c4c53c1f996ebf0dfd0a51ae645a46cad4a803b +Subproject commit 0c50c8d038e4347e6705076f47a89f478666a301 From c11bd629dadf9a94454d90a43c70d401baf3b0c3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 13:45:18 +0100 Subject: [PATCH 066/786] Save off the main thread. --- Glamourer/EphemeralConfig.cs | 3 ++- OtterGui | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/EphemeralConfig.cs index 349a021..4bd57ed 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/EphemeralConfig.cs @@ -66,7 +66,8 @@ public class EphemeralConfig : ISavable public void Save(StreamWriter writer) { - using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }; + using var jWriter = new JsonTextWriter(writer); + jWriter.Formatting = Formatting.Indented; var serializer = new JsonSerializer { Formatting = Formatting.Indented }; serializer.Serialize(jWriter, this); } diff --git a/OtterGui b/OtterGui index 0c50c8d..3a1a3f1 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 0c50c8d038e4347e6705076f47a89f478666a301 +Subproject commit 3a1a3f1a1f2021b063617ac9b294b579a154706e From dd42b7ab7ff50f8c0ee005e83b9dd20df248cfde Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 15:42:55 +0100 Subject: [PATCH 067/786] Add experimental automation condition for gearsets. --- Glamourer/Automation/AutoDesign.cs | 24 ++++++++-- Glamourer/Automation/AutoDesignApplier.cs | 42 +++++++++++++++- Glamourer/Automation/AutoDesignManager.cs | 48 ++++++++++++++----- Glamourer/Events/EquippedGearset.cs | 30 ++++++++++++ Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 50 ++++++++++++++++---- Glamourer/Interop/InventoryService.cs | 22 +++++---- Glamourer/Services/ServiceManager.cs | 1 + 7 files changed, 185 insertions(+), 32 deletions(-) create mode 100644 Glamourer/Events/EquippedGearset.cs diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 7beda18..4cde895 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -27,6 +27,7 @@ public class AutoDesign public Design? Design; public JobGroup Jobs; public Type ApplicationType; + public short GearsetIndex = -1; public string Name(bool incognito) => Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text; @@ -43,10 +44,22 @@ public class AutoDesign Design = Design, ApplicationType = ApplicationType, Jobs = Jobs, + GearsetIndex = GearsetIndex, }; public unsafe bool IsActive(Actor actor) - => actor.IsCharacter && Jobs.Fits(actor.AsCharacter->CharacterData.ClassJob); + { + if (!actor.IsCharacter) + return false; + + var ret = true; + if (GearsetIndex < 0) + ret &= Jobs.Fits(actor.AsCharacter->CharacterData.ClassJob); + else + ret &= AutoDesignApplier.CheckGearset(GearsetIndex); + + return ret; + } public JObject Serialize() => new() @@ -58,9 +71,12 @@ public class AutoDesign private JObject CreateConditionObject() { - var ret = new JObject(); - if (Jobs.Id != 0) - ret["JobGroup"] = Jobs.Id; + var ret = new JObject + { + ["Gearset"] = GearsetIndex, + ["JobGroup"] = Jobs.Id, + }; + return ret; } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 92cd2b0..4b76943 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; @@ -26,6 +27,7 @@ public class AutoDesignApplier : IDisposable private readonly AutoDesignManager _manager; private readonly StateManager _state; private readonly JobService _jobs; + private readonly EquippedGearset _equippedGearset; private readonly ActorService _actors; private readonly CustomizationService _customizations; private readonly CustomizeUnlockManager _customizeUnlocks; @@ -49,7 +51,8 @@ public class AutoDesignApplier : IDisposable public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, CustomizationService customizations, ActorService actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, - AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState) + AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, + EquippedGearset equippedGearset) { _config = config; _manager = manager; @@ -64,15 +67,18 @@ public class AutoDesignApplier : IDisposable _weapons = weapons; _humans = humans; _clientState = clientState; + _equippedGearset = equippedGearset; _jobs.JobChanged += OnJobChange; _event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier); _weapons.Subscribe(OnWeaponLoading, WeaponLoading.Priority.AutoDesignApplier); + _equippedGearset.Subscribe(OnEquippedGearset, EquippedGearset.Priority.AutoDesignApplier); } public void Dispose() { _weapons.Unsubscribe(OnWeaponLoading); _event.Unsubscribe(OnAutomationChange); + _equippedGearset.Unsubscribe(OnEquippedGearset); _jobs.JobChanged -= OnJobChange; } @@ -496,4 +502,38 @@ public class AutoDesignApplier : IDisposable totalMetaFlags |= 0x08; } } + + internal static int NewGearsetId = -1; + + private void OnEquippedGearset(string name, int id, int prior, byte _, byte job) + { + if (!_config.EnableAutoDesigns) + return; + + var (player, data) = _objects.PlayerData; + if (!player.IsValid) + return; + + if (!GetPlayerSet(player, out var set) || !_state.TryGetValue(player, out var state)) + return; + + var respectManual = prior == id; + NewGearsetId = id; + Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob); + NewGearsetId = -1; + foreach (var actor in data.Objects) + _state.ReapplyState(actor); + } + + public static unsafe bool CheckGearset(short check) + { + if (NewGearsetId != -1) + return check == NewGearsetId; + + var module = RaptureGearsetModule.Instance(); + if (module == null) + return false; + + return check == module->CurrentGearsetIndex; + } } diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 8140084..d3fba5c 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -306,6 +306,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos return; var design = set.Designs[which]; + if (design.Jobs.Id == jobs.Id) return; @@ -316,6 +317,22 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, jobs)); } + public void ChangeGearsetCondition(AutoDesignSet set, int which, short index) + { + if (which >= set.Designs.Count || which < 0) + return; + + var design = set.Designs[which]; + if (design.GearsetIndex == index) + return; + + var old = design.GearsetIndex; + design.GearsetIndex = index; + Save(); + Glamourer.Log.Debug($"Changed gearset condition from {old} to {index} for associated design {which + 1} in design set."); + _event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, index)); + } + public void ChangeApplicationType(AutoDesignSet set, int which, AutoDesign.Type type) { if (which >= set.Designs.Count || which < 0) @@ -338,10 +355,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos public void Save(StreamWriter writer) { - using var j = new JsonTextWriter(writer) - { - Formatting = Formatting.Indented, - }; + using var j = new JsonTextWriter(writer); + j.Formatting = Formatting.Indented; Serialize().WriteTo(j); } @@ -456,13 +471,16 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos { if (designIdentifier.Length == 0) { - Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: No design specified.", NotificationType.Warning); + Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: No design specified.", + NotificationType.Warning); return null; } if (!Guid.TryParse(designIdentifier, out var guid)) { - Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: {designIdentifier} is not a valid GUID.", NotificationType.Warning); + Glamourer.Messager.NotificationMessage( + $"Error parsing automatically applied design for set {setName}: {designIdentifier} is not a valid GUID.", + NotificationType.Warning); return null; } @@ -471,7 +489,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos if (design == null) { Glamourer.Messager.NotificationMessage( - $"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.", NotificationType.Warning); + $"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.", + NotificationType.Warning); return null; } } @@ -483,24 +502,31 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos Design = design, ApplicationType = applicationType & AutoDesign.Type.All, }; + return ParseConditions(setName, jObj, ret) ? ret : null; + } + private bool ParseConditions(string setName, JObject jObj, AutoDesign ret) + { var conditions = jObj["Conditions"]; if (conditions == null) - return ret; + return true; var jobs = conditions["JobGroup"]?.ToObject() ?? -1; if (jobs >= 0) { if (!_jobs.JobGroups.TryGetValue((ushort)jobs, out var jobGroup)) { - Glamourer.Messager.NotificationMessage($"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.", NotificationType.Warning); - return null; + Glamourer.Messager.NotificationMessage( + $"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.", + NotificationType.Warning); + return false; } ret.Jobs = jobGroup; } - return ret; + ret.GearsetIndex = conditions["Gearset"]?.ToObject() ?? -1; + return true; } private void Save() diff --git a/Glamourer/Events/EquippedGearset.cs b/Glamourer/Events/EquippedGearset.cs new file mode 100644 index 0000000..a8fafff --- /dev/null +++ b/Glamourer/Events/EquippedGearset.cs @@ -0,0 +1,30 @@ +using System; +using OtterGui.Classes; + +namespace Glamourer.Events; + +/// +/// Triggered when the player equips a gear set. +/// +/// Parameter is the name of the gear set. +/// Parameter is the id of the gear set. +/// Parameter is the id of the prior gear set. +/// Parameter is the id of the associated glamour. +/// Parameter is the job id of the associated job. +/// +/// +public sealed class EquippedGearset : EventWrapper, EquippedGearset.Priority> +{ + public enum Priority + { + /// + AutoDesignApplier = 0, + } + + public EquippedGearset() + : base(nameof(EquippedGearset)) + { } + + public void Invoke(string name, int id, int lastId, byte glamour, byte jobId) + => Invoke(this, name, id, lastId, glamour, jobId); +} diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 3a0f437..918f96a 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Text; @@ -29,17 +30,18 @@ public class SetPanel private readonly CustomizeUnlockManager _customizeUnlocks; private readonly CustomizationService _customizations; - private readonly Configuration _config; - private readonly RevertDesignCombo _designCombo; - private readonly JobGroupCombo _jobGroupCombo; - private readonly IdentifierDrawer _identifierDrawer; + private readonly Configuration _config; + private readonly RevertDesignCombo _designCombo; + private readonly JobGroupCombo _jobGroupCombo; + private readonly IdentifierDrawer _identifierDrawer; private string? _tempName; private int _dragIndex = -1; private Action? _endAction; - public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, RevertDesignCombo designCombo, + public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, + RevertDesignCombo designCombo, CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer, Configuration config) { _selector = selector; @@ -216,11 +218,11 @@ public class SetPanel ImGui.TableNextColumn(); DrawApplicationTypeBoxes(Selection, design, idx, singleRow); ImGui.TableNextColumn(); - _jobGroupCombo.Draw(Selection, design, idx); + DrawConditions(design, idx); } else { - _jobGroupCombo.Draw(Selection, design, idx); + DrawConditions(design, idx); ImGui.TableNextColumn(); DrawApplicationTypeBoxes(Selection, design, idx, singleRow); } @@ -244,6 +246,38 @@ public class SetPanel _endAction = null; } + private int _tmpGearset = int.MaxValue; + + private void DrawConditions(AutoDesign design, int idx) + { + var usingGearset = design.GearsetIndex >= 0; + if (ImGui.Button($"{(usingGearset ? "Gearset:" : "Jobs:")}##usingGearset")) + { + usingGearset = !usingGearset; + _manager.ChangeGearsetCondition(Selection, idx, (short)(usingGearset ? 0 : -1)); + } + + ImGuiUtil.HoverTooltip("Click to switch between Job and Gearset restrictions."); + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + if (usingGearset) + { + var set = 1 + (_tmpGearset == int.MaxValue ? design.GearsetIndex : _tmpGearset); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputInt("##whichGearset", ref set, 0, 0)) + _tmpGearset = Math.Clamp(set, 1, 100); + if (ImGui.IsItemDeactivatedAfterEdit()) + { + _manager.ChangeGearsetCondition(Selection, idx, (short)(_tmpGearset - 1)); + _tmpGearset = int.MaxValue; + } + } + else + { + _jobGroupCombo.Draw(Selection, design, idx); + } + } + private void DrawWarnings(AutoDesign design, int idx) { if (design.Revert) diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index f2832bd..4235da4 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -7,17 +7,20 @@ using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Events; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Penumbra.String; namespace Glamourer.Interop; public unsafe class InventoryService : IDisposable { - private readonly MovedEquipment _event; + private readonly MovedEquipment _movedItemsEvent; + private readonly EquippedGearset _gearsetEvent; private readonly List<(EquipSlot, uint, StainId)> _itemList = new(12); - public InventoryService(MovedEquipment @event, IGameInteropProvider interop) + public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { - _event = @event; + _movedItemsEvent = movedItemsEvent; + _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); _equipGearsetHook = @@ -39,7 +42,10 @@ public unsafe class InventoryService : IDisposable private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) { - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); + var prior = module->CurrentGearsetIndex; + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); + var set = module->GetGearset(gearsetId); + _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), gearsetId, prior, glamourPlateId, set->ClassJob); Glamourer.Log.Excessive($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { @@ -107,7 +113,7 @@ public unsafe class InventoryService : IDisposable Add(EquipSlot.LFinger, ref entry->RingLeft); } - _event.Invoke(_itemList.ToArray()); + _movedItemsEvent.Invoke(_itemList.ToArray()); } return ret; @@ -127,18 +133,18 @@ public unsafe class InventoryService : IDisposable { if (InvokeSource(sourceContainer, sourceSlot, out var source)) if (InvokeTarget(manager, targetContainer, targetSlot, out var target)) - _event.Invoke(new[] + _movedItemsEvent.Invoke(new[] { source, target, }); else - _event.Invoke(new[] + _movedItemsEvent.Invoke(new[] { source, }); else if (InvokeTarget(manager, targetContainer, targetSlot, out var target)) - _event.Invoke(new[] + _movedItemsEvent.Invoke(new[] { target, }); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 6e9cffa..092d8c2 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -73,6 +73,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); From c79997dba8ede3ffa5b3a7d5b09949245fbc8f01 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Nov 2023 18:31:10 +0100 Subject: [PATCH 068/786] Use https submodule. --- .gitmodules | 8 ++++---- OtterGui | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index f74e14d..7203d22 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,16 +1,16 @@ [submodule "OtterGui"] path = OtterGui - url = git@github.com:Ottermandias/OtterGui.git + url = https://github.com/Ottermandias/OtterGui.git branch = main [submodule "Penumbra.GameData"] path = Penumbra.GameData - url = git@github.com:Ottermandias/Penumbra.GameData.git + url = https://github.com/Ottermandias/Penumbra.GameData.git branch = main [submodule "Penumbra.String"] path = Penumbra.String - url = git@github.com:Ottermandias/Penumbra.String.git + url = https://github.com/Ottermandias/Penumbra.String.git branch = main [submodule "Penumbra.Api"] path = Penumbra.Api - url = git@github.com:Ottermandias/Penumbra.Api.git + url = https://github.com/Ottermandias/Penumbra.Api.git branch = main diff --git a/OtterGui b/OtterGui index 3a1a3f1..858b610 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 3a1a3f1a1f2021b063617ac9b294b579a154706e +Subproject commit 858b610a194ee52b4e8e89c0c64d9f2653025524 From 8412c2bc68b76e5f24fe1283d5d25661c70a47fb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Nov 2023 19:52:09 +0100 Subject: [PATCH 069/786] Add NoDocking flags to QDB. --- Glamourer/Gui/DesignQuickBar.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index e6f5eac..4346f01 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -22,8 +22,8 @@ public class DesignQuickBar : Window, IDisposable { private ImGuiWindowFlags GetFlags => _config.Ephemeral.LockDesignQuickBar - ? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove - : ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoFocusOnAppearing; + ? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove + : ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing; private readonly Configuration _config; private readonly DesignCombo _designCombo; @@ -37,7 +37,7 @@ public class DesignQuickBar : Window, IDisposable public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState, ObjectManager objects, AutoDesignApplier autoDesignApplier) - : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration) + : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking) { _config = config; _designCombo = designCombo; From eed11bb67f46221cf1483ccae7311f8bb090c46e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Nov 2023 19:52:30 +0100 Subject: [PATCH 070/786] Fix HQ Item IDs in inventory service. --- Glamourer/Interop/InventoryService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 4235da4..6e72e91 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -70,7 +70,7 @@ public unsafe class InventoryService : IDisposable else if (item.GlamourId != 0) _itemList.Add((slot, item.GlamourId, item.Stain)); else - _itemList.Add((slot, item.ItemID, item.Stain)); + _itemList.Add((slot, FixId(item.ItemID), item.Stain)); } var plate = MirageManager.Instance()->GlamourPlatesSpan[glamourPlateId - 1]; @@ -96,7 +96,7 @@ public unsafe class InventoryService : IDisposable else if (item.GlamourId != 0) _itemList.Add((slot, item.GlamourId, item.Stain)); else - _itemList.Add((slot, item.ItemID, item.Stain)); + _itemList.Add((slot, FixId(item.ItemID), item.Stain)); } Add(EquipSlot.MainHand, ref entry->MainHand); @@ -119,6 +119,9 @@ public unsafe class InventoryService : IDisposable return ret; } + private static uint FixId(uint itemId) + => itemId % 50000; + private delegate int MoveItemDelegate(InventoryManager* manager, InventoryType sourceContainer, ushort sourceSlot, InventoryType targetContainer, ushort targetSlot, byte unk); From 60a53d4bff0a7e60041fe6f85824785e47b86bd7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Nov 2023 21:05:06 +0100 Subject: [PATCH 071/786] Refactor drawing of equipment to be more sane. --- .../CustomizationDrawer.Simple.cs | 6 +- .../Gui/Customization/CustomizationDrawer.cs | 47 +- Glamourer/Gui/Equipment/EquipDrawData.cs | 49 + Glamourer/Gui/Equipment/EquipmentDrawer.cs | 916 +++++++----------- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 100 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 87 +- Glamourer/Gui/ToggleDrawData.cs | 79 ++ Glamourer/Gui/UiHelpers.cs | 13 +- OtterGui | 2 +- 9 files changed, 536 insertions(+), 763 deletions(-) create mode 100644 Glamourer/Gui/Equipment/EquipDrawData.cs create mode 100644 Glamourer/Gui/ToggleDrawData.cs diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 3e15cad..e9ce9e9 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -194,14 +194,14 @@ public partial class CustomizationDrawer { switch (UiHelpers.DrawMetaToggle(_currentIndex.ToDefaultName(), tmp, _currentApply, out var newValue, out var newApply, _locked)) { - case DataChange.Item: + case (true, false): _customize.Set(idx, newValue ? CustomizeValue.Max : CustomizeValue.Zero); Changed |= _currentFlag; break; - case DataChange.ApplyItem: + case (false, true): ChangeApply = newApply ? ChangeApply | _currentFlag : ChangeApply & ~_currentFlag; break; - case DataChange.Item | DataChange.ApplyItem: + case (true, true): ChangeApply = newApply ? ChangeApply | _currentFlag : ChangeApply & ~_currentFlag; _customize.Set(idx, newValue ? CustomizeValue.Max : CustomizeValue.Zero); Changed |= _currentFlag; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 0f76b62..fa725f5 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -14,18 +14,16 @@ using CustomizeData = Penumbra.GameData.Structs.CustomizeData; namespace Glamourer.Gui.Customization; -public partial class CustomizationDrawer : IDisposable +public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizationService _service, CodeService _codes, Configuration _config) + : IDisposable { - private readonly CodeService _codes; - private readonly Configuration _config; - - private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); - private readonly IDalamudTextureWrap? _legacyTattoo; + private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); + private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(pi); private Exception? _terminate; - private Customize _customize; - private CustomizationSet _set = null!; + private Customize _customize = Customize.Default; + private CustomizationSet _set = null!; public Customize Customize => _customize; @@ -46,21 +44,8 @@ public partial class CustomizationDrawer : IDisposable private float _raceSelectorWidth; private bool _withApply; - private readonly CustomizationService _service; - - public CustomizationDrawer(DalamudPluginInterface pi, CustomizationService service, CodeService codes, Configuration config) - { - _service = service; - _codes = codes; - _config = config; - _legacyTattoo = GetLegacyTattooIcon(pi); - _customize = Customize.Default; - } - public void Dispose() - { - _legacyTattoo?.Dispose(); - } + => _legacyTattoo?.Dispose(); public bool Draw(Customize current, bool locked, bool lockedRedraw) { @@ -125,12 +110,6 @@ public partial class CustomizationDrawer : IDisposable Changed |= _currentFlag; } - public bool DrawWetnessState(bool currentValue, out bool newValue, bool locked) - => UiHelpers.DrawCheckbox("Force Wetness", "Force the character to be wet or not.", currentValue, out newValue, locked); - - public DataChange DrawWetnessState(bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) - => UiHelpers.DrawMetaToggle("Force Wetness", currentValue, currentApply, out newValue, out newApply, locked); - private bool DrawInternal() { using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _spacing); @@ -199,13 +178,13 @@ public partial class CustomizationDrawer : IDisposable private void UpdateSizes() { - _spacing = ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemInnerSpacing.X }; - _iconSize = new Vector2(ImGui.GetTextLineHeight() * 2 + _spacing.Y + 2 * ImGui.GetStyle().FramePadding.Y); - _framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding; - _inputIntSize = 2 * _framedIconSize.X + 1 * _spacing.X; + _spacing = ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemInnerSpacing.X }; + _iconSize = new Vector2(ImGui.GetTextLineHeight() * 2 + _spacing.Y + 2 * ImGui.GetStyle().FramePadding.Y); + _framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding; + _inputIntSize = 2 * _framedIconSize.X + 1 * _spacing.X; _inputIntSizeNoButtons = _inputIntSize - 2 * _spacing.X - 2 * ImGui.GetFrameHeight(); - _comboSelectorSize = 4 * _framedIconSize.X + 3 * _spacing.X; - _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; + _comboSelectorSize = 4 * _framedIconSize.X + 3 * _spacing.X; + _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; } private static IDalamudTextureWrap? GetLegacyTattooIcon(DalamudPluginInterface pi) diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs new file mode 100644 index 0000000..ce2ba04 --- /dev/null +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -0,0 +1,49 @@ +using System; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.State; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Equipment; + +public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) +{ + public readonly EquipSlot Slot = slot; + public bool Locked; + public bool DisplayApplication; + + public Action ItemSetter = null!; + public Action StainSetter = null!; + public Action ApplySetter = null!; + public Action ApplyStainSetter = null!; + public EquipItem CurrentItem = designData.Item(slot); + public StainId CurrentStain = designData.Stain(slot); + public bool CurrentApply; + public bool CurrentApplyStain; + + public readonly Gender CurrentGender = designData.Customize.Gender; + public readonly Race CurrentRace = designData.Customize.Race; + + public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot) + => new(slot, design.DesignData) + { + ItemSetter = i => manager.ChangeEquip(design, slot, i), + StainSetter = i => manager.ChangeStain(design, slot, i), + ApplySetter = b => manager.ChangeApplyEquip(design, slot, b), + ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b), + CurrentApply = design.DoApplyEquip(slot), + CurrentApplyStain = design.DoApplyStain(slot), + Locked = design.WriteProtected(), + DisplayApplication = true, + }; + + public static EquipDrawData FromState(StateManager manager, ActorState state, EquipSlot slot) + => new(slot, state.ModelData) + { + ItemSetter = i => manager.ChangeItem(state, slot, i, StateChanged.Source.Manual), + StainSetter = i => manager.ChangeStain(state, slot, i, StateChanged.Source.Manual), + Locked = state.IsLocked, + DisplayApplication = false, + }; +} diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index ca78bea..e2b44de 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -6,10 +6,8 @@ using System.Numerics; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; -using Glamourer.Designs; using Glamourer.Events; using Glamourer.Services; -using Glamourer.Structs; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; @@ -76,211 +74,71 @@ public class EquipmentDrawer _requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale; } - private bool VerifyRestrictedGear(EquipSlot slot, EquipItem gear, Gender gender, Race race) + private bool VerifyRestrictedGear(EquipDrawData data) { - if (slot.IsAccessory()) + if (data.Slot.IsAccessory()) return false; - var (changed, _) = _items.ResolveRestrictedGear(gear.Armor(), slot, race, gender); + var (changed, _) = _items.ResolveRestrictedGear(data.CurrentItem.Armor(), data.Slot, data.CurrentRace, data.CurrentGender); return changed; } - - public DataChange DrawEquip(EquipSlot slot, in DesignData designData, out EquipItem rArmor, out StainId rStain, EquipFlag? cApply, - out bool rApply, out bool rApplyStain, bool locked) - => DrawEquip(slot, designData.Item(slot), out rArmor, designData.Stain(slot), out rStain, cApply, out rApply, out rApplyStain, locked, - designData.Customize.Gender, designData.Customize.Race); - - public DataChange DrawEquip(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, EquipFlag? cApply, - out bool rApply, out bool rApplyStain, bool locked, Gender gender = Gender.Unknown, Race race = Race.Unknown) + public void DrawEquip(EquipDrawData equipDrawData) { if (_config.HideApplyCheckmarks) - cApply = null; + equipDrawData.DisplayApplication = false; - using var id = ImRaii.PushId((int)slot); + using var id = ImRaii.PushId((int)equipDrawData.Slot); var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); if (_config.SmallEquip) - return DrawEquipSmall(slot, cArmor, out rArmor, cStain, out rStain, cApply, out rApply, out rApplyStain, locked, gender, race); - - if (!locked && _codes.EnabledArtisan) - return DrawEquipArtisan(slot, cArmor, out rArmor, cStain, out rStain, cApply, out rApply, out rApplyStain); - - return DrawEquipNormal(slot, cArmor, out rArmor, cStain, out rStain, cApply, out rApply, out rApplyStain, locked, gender, race); + DrawEquipSmall(equipDrawData); + else if (!equipDrawData.Locked && _codes.EnabledArtisan) + DrawEquipArtisan(equipDrawData); + else + DrawEquipNormal(equipDrawData); } - public DataChange DrawWeapons(in DesignData designData, out EquipItem rMainhand, out EquipItem rOffhand, out StainId rMainhandStain, - out StainId rOffhandStain, EquipFlag? cApply, bool allWeapons, out bool rApplyMainhand, out bool rApplyMainhandStain, - out bool rApplyOffhand, out bool rApplyOffhandStain, bool locked) - => DrawWeapons(designData.Item(EquipSlot.MainHand), out rMainhand, designData.Item(EquipSlot.OffHand), out rOffhand, - designData.Stain(EquipSlot.MainHand), out rMainhandStain, designData.Stain(EquipSlot.OffHand), out rOffhandStain, cApply, - allWeapons, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain, locked); - - private DataChange DrawWeapons(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, - StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, - bool allWeapons, out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain, - bool locked) + public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) { - if (cMainhand.ModelId.Id == 0) - { - rOffhand = cOffhand; - rMainhand = cMainhand; - rMainhandStain = cMainhandStain; - rOffhandStain = cOffhandStain; - rApplyMainhand = false; - rApplyMainhandStain = false; - rApplyOffhand = false; - rApplyOffhandStain = false; - return DataChange.None; - } + if (mainhand.CurrentItem.ModelId.Id == 0) + return; if (_config.HideApplyCheckmarks) - cApply = null; + { + mainhand.DisplayApplication = false; + offhand.DisplayApplication = false; + } using var id = ImRaii.PushId("Weapons"); var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); if (_config.SmallEquip) - return DrawWeaponsSmall(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain, - out rOffhandStain, cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain, locked, - allWeapons); - - if (!locked && _codes.EnabledArtisan) - return DrawWeaponsArtisan(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain, - out rOffhandStain, cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain); - - return DrawWeaponsNormal(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain, - out rOffhandStain, cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain, locked, - allWeapons); + DrawWeaponsSmall(mainhand, offhand, allWeapons); + else if (!mainhand.Locked && _codes.EnabledArtisan) + DrawWeaponsArtisan(mainhand, offhand); + else + DrawWeaponsNormal(mainhand, offhand, allWeapons); } - public static bool DrawHatState(bool currentValue, out bool newValue, bool locked) - => UiHelpers.DrawCheckbox("Hat Visible", "Hide or show the characters head gear.", currentValue, out newValue, locked); - - public static DataChange DrawHatState(bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) - => UiHelpers.DrawMetaToggle("Hat Visible", currentValue, currentApply, out newValue, out newApply, locked); - - public static bool DrawVisorState(bool currentValue, out bool newValue, bool locked) - => UiHelpers.DrawCheckbox("Visor Toggled", "Toggle the visor state of the characters head gear.", currentValue, out newValue, locked); - - public static DataChange DrawVisorState(bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) - => UiHelpers.DrawMetaToggle("Visor Toggled", currentValue, currentApply, out newValue, out newApply, locked); - - public static bool DrawWeaponState(bool currentValue, out bool newValue, bool locked) - => UiHelpers.DrawCheckbox("Weapon Visible", "Hide or show the characters weapons when not drawn.", currentValue, out newValue, locked); - - public static DataChange DrawWeaponState(bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) - => UiHelpers.DrawMetaToggle("Weapon Visible", currentValue, currentApply, out newValue, out newApply, locked); - - private bool DrawMainhand(EquipItem current, bool drawAll, out EquipItem weapon, out string label, bool locked, bool small, bool open) + public static void DrawMetaToggle(in ToggleDrawData data) { - weapon = current; - if (!_weaponCombo.TryGetValue(drawAll ? FullEquipType.Unknown : current.Type, out var combo)) + if (data.DisplayApplication) { - label = string.Empty; - return false; + var (valueChanged, applyChanged) = UiHelpers.DrawMetaToggle(data.Label, data.CurrentValue, data.CurrentApply, out var newValue, + out var newApply, data.Locked); + if (valueChanged) + data.SetValue(newValue); + if (applyChanged) + data.SetApply(newApply); } - - label = combo.Label; - - var unknown = !_gPose.InGPose && current.Type is FullEquipType.Unknown; - var ret = false; - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); - using (var disabled = ImRaii.Disabled(locked | unknown)) + else { - if (!locked && open) - UiHelpers.OpenCombo($"##{label}"); - if (combo.Draw(weapon.Name, weapon.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth)) - { - ret = true; - weapon = combo.CurrentSelection; - } + if (UiHelpers.DrawCheckbox(data.Label, data.Tooltip, data.CurrentValue, out var newValue, data.Locked)) + data.SetValue(newValue); } - - if (unknown && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) - ImGui.SetTooltip("The weapon type could not be identified, thus changing it to other weapons of that type is not possible."); - - return ret; - } - - private bool DrawOffhand(EquipItem mainhand, EquipItem current, out EquipItem weapon, out string label, bool locked, bool small, bool clear, - bool open) - { - weapon = current; - if (!_weaponCombo.TryGetValue(current.Type, out var combo)) - { - label = string.Empty; - return false; - } - - label = combo.Label; - locked |= !_gPose.InGPose && (current.Type is FullEquipType.Unknown || mainhand.Type is FullEquipType.Unknown); - using var disabled = ImRaii.Disabled(locked); - if (!locked && open) - UiHelpers.OpenCombo($"##{combo.Label}"); - var change = combo.Draw(weapon.Name, weapon.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); - if (change) - weapon = combo.CurrentSelection; - - if (!locked) - { - var defaultOffhand = _items.GetDefaultOffhand(mainhand); - if (defaultOffhand.Id != weapon.Id) - { - ImGuiUtil.HoverTooltip("Right-click to set to Default."); - if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - change = true; - weapon = defaultOffhand; - } - } - } - - return change; - } - - private bool DrawApply(EquipSlot slot, EquipFlag flags, out bool enabled, bool locked) - => UiHelpers.DrawCheckbox($"##apply{slot}", "Apply this item when applying the Design.", flags.HasFlag(slot.ToFlag()), out enabled, - locked); - - private bool DrawApplyStain(EquipSlot slot, EquipFlag flags, out bool enabled, bool locked) - => UiHelpers.DrawCheckbox($"##applyStain{slot}", "Apply this dye when applying the Design.", flags.HasFlag(slot.ToStainFlag()), - out enabled, locked); - - private bool DrawItem(EquipSlot slot, EquipItem current, out EquipItem armor, out string label, bool locked, bool small, bool clear, - bool open) - { - Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(DrawItem)} on {slot}."); - var combo = _itemCombo[slot.ToIndex()]; - label = combo.Label; - armor = current; - if (!locked && open) - UiHelpers.OpenCombo($"##{combo.Label}"); - - using var disabled = ImRaii.Disabled(locked); - var change = combo.Draw(armor.Name, armor.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); - if (change) - armor = combo.CurrentSelection; - else if (combo.CustomVariant.Id > 0) - { - armor = _items.Identify(slot, combo.CustomSetId, combo.CustomVariant); - change = true; - } - - if (!locked && armor.ModelId.Id != 0) - { - if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - change = true; - armor = ItemManager.NothingItem(slot); - } - - ImGuiUtil.HoverTooltip("Right-click to clear."); - } - - return change; } public bool DrawAllStain(out StainId ret, bool locked) @@ -308,50 +166,97 @@ public class EquipmentDrawer return change; } - private bool DrawStain(EquipSlot slot, StainId current, out StainId ret, bool locked, bool small) + #region Artisan + + private void DrawEquipArtisan(EquipDrawData data) { - var found = _stainData.TryGetValue(current, out var stain); - using var disabled = ImRaii.Disabled(locked); - var change = small - ? _stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) - : _stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength); - ret = current; - if (change) - if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain)) - ret = stain.RowIndex; - else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) - ret = Stain.None.RowIndex; + DrawStainArtisan(data); + ImGui.SameLine(); + DrawArmorArtisan(data); + if (!data.DisplayApplication) + return; - if (!locked && ret != Stain.None.RowIndex) + ImGui.SameLine(); + DrawApply(data); + ImGui.SameLine(); + DrawApplyStain(data); + } + + private void DrawWeaponsArtisan(in EquipDrawData mainhand, in EquipDrawData offhand) + { + using (var _ = ImRaii.PushId(0)) { - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - ret = Stain.None.RowIndex; - change = true; - } - - ImGuiUtil.HoverTooltip("Right-click to clear."); + DrawStainArtisan(mainhand); + ImGui.SameLine(); + DrawWeapon(mainhand); } - return change; + using (var _ = ImRaii.PushId(1)) + { + DrawStainArtisan(offhand); + ImGui.SameLine(); + DrawWeapon(offhand); + } + + return; + + void DrawWeapon(in EquipDrawData current) + { + int setId = current.CurrentItem.ModelId.Id; + int type = current.CurrentItem.WeaponType.Id; + int variant = current.CurrentItem.Variant.Id; + ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); + if (ImGui.InputInt("##setId", ref setId, 0, 0)) + { + var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); + if (newSetId.Id != current.CurrentItem.ModelId.Id) + current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.WeaponType, current.CurrentItem.Variant)); + } + + ImGui.SameLine(); + ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); + if (ImGui.InputInt("##type", ref type, 0, 0)) + { + var newType = (WeaponType)Math.Clamp(type, 0, ushort.MaxValue); + if (newType.Id != current.CurrentItem.WeaponType.Id) + current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.ModelId, newType, current.CurrentItem.Variant)); + } + + ImGui.SameLine(); + ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); + if (ImGui.InputInt("##variant", ref variant, 0, 0)) + { + var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue); + if (newVariant.Id != current.CurrentItem.Variant.Id) + current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.ModelId, current.CurrentItem.WeaponType, newVariant)); + } + } + } + + /// Draw an input for stain that can set arbitrary values instead of choosing valid stains. + private static void DrawStainArtisan(EquipDrawData data) + { + int stainId = data.CurrentStain.Id; + ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); + if (!ImGui.InputInt("##stain", ref stainId, 0, 0)) + return; + + var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue); + if (newStainId != data.CurrentStain.Id) + data.StainSetter(newStainId); } /// Draw an input for armor that can set arbitrary values instead of choosing items. - private bool DrawArmorArtisan(EquipSlot slot, EquipItem current, out EquipItem armor) + private void DrawArmorArtisan(EquipDrawData data) { - int setId = current.ModelId.Id; - int variant = current.Variant.Id; - var ret = false; - armor = current; + int setId = data.CurrentItem.ModelId.Id; + int variant = data.CurrentItem.Variant.Id; ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt("##setId", ref setId, 0, 0)) { var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); - if (newSetId.Id != current.ModelId.Id) - { - armor = _items.Identify(slot, newSetId, current.Variant); - ret = true; - } + if (newSetId.Id != data.CurrentItem.ModelId.Id) + data.ItemSetter(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant)); } ImGui.SameLine(); @@ -359,141 +264,286 @@ public class EquipmentDrawer if (ImGui.InputInt("##variant", ref variant, 0, 0)) { var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue); - if (newVariant != current.Variant) - { - armor = _items.Identify(slot, current.ModelId, newVariant); - ret = true; - } + if (newVariant != data.CurrentItem.Variant) + data.ItemSetter(_items.Identify(data.Slot, data.CurrentItem.ModelId, newVariant)); } - - return ret; } - /// Draw an input for stain that can set arbitrary values instead of choosing valid stains. - private bool DrawStainArtisan(EquipSlot slot, StainId current, out StainId stain) - { - int stainId = current.Id; - ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##stain", ref stainId, 0, 0)) - { - var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue); - if (newStainId != current) - { - stain = newStainId; - return true; - } - } + #endregion - stain = current; - return false; - } + #region Small - private DataChange DrawEquipArtisan(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, - EquipFlag? cApply, out bool rApply, out bool rApplyStain) + private void DrawEquipSmall(in EquipDrawData equipDrawData) { - var changes = DataChange.None; - if (DrawStainArtisan(slot, cStain, out rStain)) - changes |= DataChange.Stain; + DrawStain(equipDrawData, true); ImGui.SameLine(); - if (DrawArmorArtisan(slot, cArmor, out rArmor)) - changes |= DataChange.Item; - if (cApply.HasValue) + DrawItem(equipDrawData, out var label, true, false, false); + if (equipDrawData.DisplayApplication) { ImGui.SameLine(); - if (DrawApply(slot, cApply.Value, out rApply, false)) - changes |= DataChange.ApplyItem; + DrawApply(equipDrawData); ImGui.SameLine(); - if (DrawApplyStain(slot, cApply.Value, out rApplyStain, false)) - changes |= DataChange.ApplyStain; - } - else - { - rApply = false; - rApplyStain = false; + DrawApplyStain(equipDrawData); } - return changes; - } - - private DataChange DrawEquipSmall(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, - EquipFlag? cApply, out bool rApply, out bool rApplyStain, bool locked, Gender gender, Race race) - { - var changes = DataChange.None; - if (DrawStain(slot, cStain, out rStain, locked, true)) - changes |= DataChange.Stain; - ImGui.SameLine(); - if (DrawItem(slot, cArmor, out rArmor, out var label, locked, true, false, false)) - changes |= DataChange.Item; - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApply(slot, cApply.Value, out rApply, false)) - changes |= DataChange.ApplyItem; - ImGui.SameLine(); - if (DrawApplyStain(slot, cApply.Value, out rApplyStain, false)) - changes |= DataChange.ApplyStain; - } - else - { - rApply = false; - rApplyStain = false; - } - - if (VerifyRestrictedGear(slot, rArmor, gender, race)) + if (VerifyRestrictedGear(equipDrawData)) label += " (Restricted)"; ImGui.SameLine(); ImGui.TextUnformatted(label); - - return changes; } - private DataChange DrawEquipNormal(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, - EquipFlag? cApply, out bool rApply, out bool rApplyStain, bool locked, Gender gender, Race race) + private void DrawWeaponsSmall(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) { - var changes = DataChange.None; - cArmor.DrawIcon(_textures, _iconSize, slot); + DrawStain(mainhand, true); + ImGui.SameLine(); + DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, true, false); + if (mainhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApply(mainhand); + ImGui.SameLine(); + DrawApplyStain(mainhand); + } + + if (allWeapons) + mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})"; + WeaponHelpMarker(mainhandLabel); + + if (offhand.CurrentItem.Type is FullEquipType.Unknown) + return; + + DrawStain(offhand, true); + ImGui.SameLine(); + DrawOffhand(mainhand, offhand, out var offhandLabel, true, false, false); + if (offhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApply(offhand); + ImGui.SameLine(); + DrawApplyStain(offhand); + } + + WeaponHelpMarker(offhandLabel); + } + + #endregion + + #region Normal + + private void DrawEquipNormal(in EquipDrawData equipDrawData) + { + equipDrawData.CurrentItem.DrawIcon(_textures, _iconSize, equipDrawData.Slot); var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); ImGui.SameLine(); using var group = ImRaii.Group(); - if (DrawItem(slot, cArmor, out rArmor, out var label, locked, false, right, left)) - changes |= DataChange.Item; - if (cApply.HasValue) + DrawItem(equipDrawData, out var label, false, right, left); + if (equipDrawData.DisplayApplication) { ImGui.SameLine(); - if (DrawApply(slot, cApply.Value, out rApply, locked)) - changes |= DataChange.ApplyItem; - } - else - { - rApply = true; + DrawApply(equipDrawData); } ImGui.SameLine(); ImGui.TextUnformatted(label); - if (DrawStain(slot, cStain, out rStain, locked, false)) - changes |= DataChange.Stain; - if (cApply.HasValue) + DrawStain(equipDrawData, false); + if (equipDrawData.DisplayApplication) { ImGui.SameLine(); - if (DrawApplyStain(slot, cApply.Value, out rApplyStain, locked)) - changes |= DataChange.ApplyStain; - } - else - { - rApplyStain = true; + DrawApplyStain(equipDrawData); } - if (VerifyRestrictedGear(slot, rArmor, gender, race)) + if (VerifyRestrictedGear(equipDrawData)) { ImGui.SameLine(); ImGui.TextUnformatted("(Restricted)"); } - - return changes; } + private void DrawWeaponsNormal(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, + ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }); + + mainhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.MainHand); + var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); + ImGui.SameLine(); + using (var group = ImRaii.Group()) + { + DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, false, left); + if (mainhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApply(mainhand); + } + + WeaponHelpMarker(mainhandLabel, allWeapons ? mainhand.CurrentItem.Type.ToName() : null); + + DrawStain(mainhand, false); + if (mainhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApplyStain(mainhand); + } + } + + if (offhand.CurrentItem.Type is FullEquipType.Unknown) + return; + + offhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.OffHand); + var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); + left = ImGui.IsItemClicked(ImGuiMouseButton.Left); + ImGui.SameLine(); + using (var group = ImRaii.Group()) + { + DrawOffhand(mainhand, offhand, out var offhandLabel, false, right, left); + if (offhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApply(offhand); + } + + WeaponHelpMarker(offhandLabel); + + DrawStain(offhand, false); + if (offhand.DisplayApplication) + { + ImGui.SameLine(); + DrawApplyStain(offhand); + } + } + } + + private void DrawStain(in EquipDrawData data, bool small) + { + var found = _stainData.TryGetValue(data.CurrentStain, out var stain); + using var disabled = ImRaii.Disabled(data.Locked); + var change = small + ? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) + : _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength); + if (change) + if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain)) + data.StainSetter(stain.RowIndex); + else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) + data.StainSetter(Stain.None.RowIndex); + + if (!data.Locked && data.CurrentStain != Stain.None.RowIndex) + { + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + data.StainSetter(Stain.None.RowIndex); + + ImGuiUtil.HoverTooltip("Right-click to clear."); + } + } + + private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open) + { + Debug.Assert(data.Slot.IsEquipment() || data.Slot.IsAccessory(), $"Called {nameof(DrawItem)} on {data.Slot}."); + + var combo = _itemCombo[data.Slot.ToIndex()]; + label = combo.Label; + if (!data.Locked && open) + UiHelpers.OpenCombo($"##{combo.Label}"); + + using var disabled = ImRaii.Disabled(data.Locked); + var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, + _requiredComboWidth); + if (change) + data.ItemSetter(combo.CurrentSelection); + else if (combo.CustomVariant.Id > 0) + data.ItemSetter(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); + + if (!data.Locked && data.CurrentItem.ModelId.Id != 0) + { + if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) + data.ItemSetter(ItemManager.NothingItem(data.Slot)); + + ImGuiUtil.HoverTooltip("Right-click to clear."); + } + } + + private void DrawMainhand(ref EquipDrawData mainhand, ref EquipDrawData offhand, out string label, bool drawAll, bool small, + bool open) + { + if (!_weaponCombo.TryGetValue(drawAll ? FullEquipType.Unknown : mainhand.CurrentItem.Type, out var combo)) + { + label = string.Empty; + return; + } + + label = combo.Label; + var unknown = !_gPose.InGPose && mainhand.CurrentItem.Type is FullEquipType.Unknown; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); + using (var _ = ImRaii.Disabled(mainhand.Locked | unknown)) + { + if (!mainhand.Locked && open) + UiHelpers.OpenCombo($"##{label}"); + if (combo.Draw(mainhand.CurrentItem.Name, mainhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, + _requiredComboWidth)) + { + mainhand.ItemSetter(combo.CurrentSelection); + if (combo.CurrentSelection.Type.ValidOffhand() != mainhand.CurrentItem.Type.ValidOffhand()) + { + offhand.CurrentItem = _items.GetDefaultOffhand(combo.CurrentSelection); + offhand.ItemSetter(offhand.CurrentItem); + } + + mainhand.CurrentItem = combo.CurrentSelection; + } + } + + if (unknown && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) + ImGui.SetTooltip("The weapon type could not be identified, thus changing it to other weapons of that type is not possible."); + } + + private void DrawOffhand(in EquipDrawData mainhand, in EquipDrawData offhand, out string label, bool small, bool clear, bool open) + { + if (!_weaponCombo.TryGetValue(offhand.CurrentItem.Type, out var combo)) + { + label = string.Empty; + return; + } + + label = combo.Label; + var locked = offhand.Locked + || !_gPose.InGPose && (offhand.CurrentItem.Type is FullEquipType.Unknown || mainhand.CurrentItem.Type is FullEquipType.Unknown); + using var disabled = ImRaii.Disabled(locked); + if (!locked && open) + UiHelpers.OpenCombo($"##{combo.Label}"); + if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, + _requiredComboWidth)) + offhand.ItemSetter(combo.CurrentSelection); + + if (locked) + return; + + var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem); + if (defaultOffhand.Id == offhand.CurrentItem.Id) + return; + + ImGuiUtil.HoverTooltip("Right-click to set to Default."); + if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) + offhand.ItemSetter(defaultOffhand); + } + + private static void DrawApply(in EquipDrawData data) + { + if (UiHelpers.DrawCheckbox($"##apply{data.Slot}", "Apply this item when applying the Design.", data.CurrentApply, out var enabled, + data.Locked)) + data.ApplySetter(enabled); + } + + private static void DrawApplyStain(in EquipDrawData data) + { + if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this item when applying the Design.", data.CurrentApplyStain, + out var enabled, + data.Locked)) + data.ApplyStainSetter(enabled); + } + + #endregion + private static void WeaponHelpMarker(string label, string? type = null) { ImGui.SameLine(); @@ -502,261 +552,11 @@ public class EquipmentDrawer + "thus it is only allowed to change weapons to other weapons of the same type."); ImGui.SameLine(); ImGui.TextUnformatted(label); - if (type != null) - { - var pos = ImGui.GetItemRectMin(); - pos.Y += ImGui.GetFrameHeightWithSpacing(); - ImGui.GetWindowDrawList().AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), $"({type})"); - } - } + if (type == null) + return; - private DataChange DrawWeaponsSmall(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, - StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, - out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain, bool locked, - bool allWeapons) - { - var changes = DataChange.None; - if (DrawStain(EquipSlot.MainHand, cMainhandStain, out rMainhandStain, locked, true)) - changes |= DataChange.Stain; - ImGui.SameLine(); - - rOffhand = cOffhand; - if (DrawMainhand(cMainhand, allWeapons, out rMainhand, out var mainhandLabel, locked, true, false)) - { - changes |= DataChange.Item; - if (rMainhand.Type.ValidOffhand() != cMainhand.Type.ValidOffhand()) - { - rOffhand = _items.GetDefaultOffhand(rMainhand); - changes |= DataChange.Item2; - } - } - - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApply(EquipSlot.MainHand, cApply.Value, out rApplyMainhand, locked)) - changes |= DataChange.ApplyItem; - ImGui.SameLine(); - if (DrawApplyStain(EquipSlot.MainHand, cApply.Value, out rApplyMainhandStain, locked)) - changes |= DataChange.ApplyStain; - } - else - { - rApplyMainhand = true; - rApplyMainhandStain = true; - } - - if (allWeapons) - mainhandLabel += $" ({cMainhand.Type.ToName()})"; - WeaponHelpMarker(mainhandLabel); - - if (rOffhand.Type is FullEquipType.Unknown) - { - rOffhandStain = cOffhandStain; - rApplyOffhand = false; - rApplyOffhandStain = false; - return changes; - } - - if (DrawStain(EquipSlot.OffHand, cOffhandStain, out rOffhandStain, locked, true)) - changes |= DataChange.Stain2; - - ImGui.SameLine(); - if (DrawOffhand(rMainhand, rOffhand, out rOffhand, out var offhandLabel, locked, true, false, false)) - changes |= DataChange.Item2; - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApply(EquipSlot.OffHand, cApply.Value, out rApplyOffhand, locked)) - changes |= DataChange.ApplyItem2; - ImGui.SameLine(); - if (DrawApplyStain(EquipSlot.OffHand, cApply.Value, out rApplyOffhandStain, locked)) - changes |= DataChange.ApplyStain2; - } - else - { - rApplyOffhand = true; - rApplyOffhandStain = true; - } - - WeaponHelpMarker(offhandLabel); - - return changes; - } - - private DataChange DrawWeaponsNormal(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, - StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, - out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain, bool locked, - bool allWeapons) - { - var changes = DataChange.None; - - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, - ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }); - - cMainhand.DrawIcon(_textures, _iconSize, EquipSlot.MainHand); - var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); - ImGui.SameLine(); - using (var group = ImRaii.Group()) - { - rOffhand = cOffhand; - if (DrawMainhand(cMainhand, allWeapons, out rMainhand, out var mainhandLabel, locked, false, left)) - { - changes |= DataChange.Item; - if (rMainhand.Type.ValidOffhand() != cMainhand.Type.ValidOffhand()) - { - rOffhand = _items.GetDefaultOffhand(rMainhand); - changes |= DataChange.Item2; - } - } - - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApply(EquipSlot.MainHand, cApply.Value, out rApplyMainhand, locked)) - changes |= DataChange.ApplyItem; - } - else - { - rApplyMainhand = true; - } - - WeaponHelpMarker(mainhandLabel, allWeapons ? cMainhand.Type.ToName() : null); - - if (DrawStain(EquipSlot.MainHand, cMainhandStain, out rMainhandStain, locked, false)) - changes |= DataChange.Stain; - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApplyStain(EquipSlot.MainHand, cApply.Value, out rApplyMainhandStain, locked)) - changes |= DataChange.ApplyStain; - } - else - { - rApplyMainhandStain = true; - } - } - - if (rOffhand.Type is FullEquipType.Unknown) - { - rOffhandStain = cOffhandStain; - rApplyOffhand = false; - rApplyOffhandStain = false; - return changes; - } - - rOffhand.DrawIcon(_textures, _iconSize, EquipSlot.OffHand); - var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); - left = ImGui.IsItemClicked(ImGuiMouseButton.Left); - ImGui.SameLine(); - using (var group = ImRaii.Group()) - { - if (DrawOffhand(rMainhand, rOffhand, out rOffhand, out var offhandLabel, locked, false, right, left)) - changes |= DataChange.Item2; - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApply(EquipSlot.OffHand, cApply.Value, out rApplyOffhand, locked)) - changes |= DataChange.ApplyItem2; - } - else - { - rApplyOffhand = true; - } - - WeaponHelpMarker(offhandLabel); - - if (DrawStain(EquipSlot.OffHand, cOffhandStain, out rOffhandStain, locked, false)) - changes |= DataChange.Stain2; - if (cApply.HasValue) - { - ImGui.SameLine(); - if (DrawApplyStain(EquipSlot.OffHand, cApply.Value, out rApplyOffhandStain, locked)) - changes |= DataChange.ApplyStain2; - } - else - { - rApplyOffhandStain = true; - } - } - - return changes; - } - - private DataChange DrawWeaponsArtisan(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, - StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, - out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain) - { - rApplyMainhand = (cApply ?? 0).HasFlag(EquipFlag.Mainhand); - rApplyMainhandStain = (cApply ?? 0).HasFlag(EquipFlag.MainhandStain); - rApplyOffhand = (cApply ?? 0).HasFlag(EquipFlag.Offhand); - rApplyOffhandStain = (cApply ?? 0).HasFlag(EquipFlag.MainhandStain); - - bool DrawWeapon(EquipItem current, out EquipItem ret) - { - int setId = current.ModelId.Id; - int type = current.WeaponType.Id; - int variant = current.Variant.Id; - ret = current; - var changed = false; - - ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##setId", ref setId, 0, 0)) - { - var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); - if (newSetId.Id != current.ModelId.Id) - { - ret = _items.Identify(EquipSlot.MainHand, newSetId, current.WeaponType, current.Variant); - changed = true; - } - } - - ImGui.SameLine(); - ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##type", ref type, 0, 0)) - { - var newType = (WeaponType)Math.Clamp(type, 0, ushort.MaxValue); - if (newType.Id != current.WeaponType.Id) - { - ret = _items.Identify(EquipSlot.MainHand, current.ModelId, newType, current.Variant); - changed = true; - } - } - - ImGui.SameLine(); - ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##variant", ref variant, 0, 0)) - { - var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue); - if (newVariant.Id != current.Variant.Id) - { - ret = _items.Identify(EquipSlot.MainHand, current.ModelId, current.WeaponType, newVariant); - changed = true; - } - } - - return changed; - } - - var ret = DataChange.None; - using (var id = ImRaii.PushId(0)) - { - if (DrawStainArtisan(EquipSlot.MainHand, cMainhandStain, out rMainhandStain)) - ret |= DataChange.Stain; - ImGui.SameLine(); - if (DrawWeapon(cMainhand, out rMainhand)) - ret |= DataChange.Item; - } - - using (var id = ImRaii.PushId(1)) - { - if (DrawStainArtisan(EquipSlot.OffHand, cOffhandStain, out rOffhandStain)) - ret |= DataChange.Stain; - ImGui.SameLine(); - if (DrawWeapon(cOffhand, out rOffhand)) - ret |= DataChange.Item; - } - - return ret; + var pos = ImGui.GetItemRectMin(); + pos.Y += ImGui.GetFrameHeightWithSpacing(); + ImGui.GetWindowDrawList().AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), $"({type})"); } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index d049eec..5f54965 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -24,21 +24,11 @@ using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorPanel +public class ActorPanel(ActorSelector _selector, StateManager _stateManager, CustomizationDrawer _customizationDrawer, + EquipmentDrawer _equipmentDrawer, IdentifierService _identification, AutoDesignApplier _autoDesignApplier, + Configuration _config, DesignConverter _converter, ObjectManager _objects, DesignManager _designManager, ImportService _importService, + ICondition _conditions) { - private readonly ActorSelector _selector; - private readonly StateManager _stateManager; - private readonly CustomizationDrawer _customizationDrawer; - private readonly EquipmentDrawer _equipmentDrawer; - private readonly IdentifierService _identification; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly Configuration _config; - private readonly DesignConverter _converter; - private readonly ObjectManager _objects; - private readonly DesignManager _designManager; - private readonly ImportService _importService; - private readonly ICondition _conditions; - private ActorIdentifier _identifier; private string _actorName = string.Empty; private Actor _actor = Actor.Null; @@ -46,29 +36,10 @@ public class ActorPanel private ActorState? _state; private bool _lockedRedraw; - public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer, - EquipmentDrawer equipmentDrawer, IdentifierService identification, AutoDesignApplier autoDesignApplier, - Configuration config, DesignConverter converter, ObjectManager objects, DesignManager designManager, ImportService importService, - ICondition conditions) - { - _selector = selector; - _stateManager = stateManager; - _customizationDrawer = customizationDrawer; - _equipmentDrawer = equipmentDrawer; - _identification = identification; - _autoDesignApplier = autoDesignApplier; - _config = config; - _converter = converter; - _objects = objects; - _designManager = designManager; - _importService = importService; - _conditions = conditions; - } - private CustomizeFlag CustomizeApplicationFlags => _lockedRedraw ? CustomizeFlagExtensions.AllRelevant & ~CustomizeFlagExtensions.RedrawRequired : CustomizeFlagExtensions.AllRelevant; - public unsafe void Draw() + public void Draw() { using var group = ImRaii.Group(); (_identifier, _data) = _selector.Selection; @@ -161,8 +132,7 @@ public class ActorPanel if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw)) _stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual); - if (_customizationDrawer.DrawWetnessState(_state!.ModelData.IsWet(), out var newWetness, _state.IsLocked)) - _stateManager.ChangeWetness(_state, newWetness, StateChanged.Source.Manual); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.Wetness, _stateManager, _state)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } @@ -176,62 +146,22 @@ public class ActorPanel var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _state!.IsLocked); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var changes = _equipmentDrawer.DrawEquip(slot, _state!.ModelData, out var newArmor, out var newStain, null, out _, out _, - _state.IsLocked); + var data = EquipDrawData.FromState(_stateManager, _state!, slot); + _equipmentDrawer.DrawEquip(data); if (usedAllStain) - { - changes |= DataChange.Stain; - newStain = newAllStain; - } - - switch (changes) - { - case DataChange.Item: - _stateManager.ChangeItem(_state, slot, newArmor, StateChanged.Source.Manual); - break; - case DataChange.Stain: - _stateManager.ChangeStain(_state, slot, newStain, StateChanged.Source.Manual); - break; - case DataChange.Item | DataChange.Stain: - _stateManager.ChangeEquip(_state, slot, newArmor, newStain, StateChanged.Source.Manual); - break; - } + _stateManager.ChangeStain(_state, slot, newAllStain, StateChanged.Source.Manual); } - var weaponChanges = _equipmentDrawer.DrawWeapons(_state!.ModelData, out var newMainhand, out var newOffhand, out var newMainhandStain, - out var newOffhandStain, null, GameMain.IsInGPose(), out _, out _, out _, out _, _state.IsLocked); - if (usedAllStain) - { - weaponChanges |= DataChange.Stain | DataChange.Stain2; - newMainhandStain = newAllStain; - newOffhandStain = newAllStain; - } - - if (weaponChanges.HasFlag(DataChange.Item)) - if (weaponChanges.HasFlag(DataChange.Stain)) - _stateManager.ChangeEquip(_state, EquipSlot.MainHand, newMainhand, newMainhandStain, StateChanged.Source.Manual); - else - _stateManager.ChangeItem(_state, EquipSlot.MainHand, newMainhand, StateChanged.Source.Manual); - else if (weaponChanges.HasFlag(DataChange.Stain)) - _stateManager.ChangeStain(_state, EquipSlot.MainHand, newMainhandStain, StateChanged.Source.Manual); - - if (weaponChanges.HasFlag(DataChange.Item2)) - if (weaponChanges.HasFlag(DataChange.Stain2)) - _stateManager.ChangeEquip(_state, EquipSlot.OffHand, newOffhand, newOffhandStain, StateChanged.Source.Manual); - else - _stateManager.ChangeItem(_state, EquipSlot.OffHand, newOffhand, StateChanged.Source.Manual); - else if (weaponChanges.HasFlag(DataChange.Stain2)) - _stateManager.ChangeStain(_state, EquipSlot.OffHand, newOffhandStain, StateChanged.Source.Manual); + var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); + var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand); + _equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose()); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); - if (EquipmentDrawer.DrawHatState(_state!.ModelData.IsHatVisible(), out var newHatState, _state!.IsLocked)) - _stateManager.ChangeHatState(_state, newHatState, StateChanged.Source.Manual); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state)); ImGui.SameLine(); - if (EquipmentDrawer.DrawVisorState(_state!.ModelData.IsVisorToggled(), out var newVisorState, _state!.IsLocked)) - _stateManager.ChangeVisorState(_state, newVisorState, StateChanged.Source.Manual); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state)); ImGui.SameLine(); - if (EquipmentDrawer.DrawWeaponState(_state!.ModelData.IsWeaponVisible(), out var newWeaponState, _state!.IsLocked)) - _stateManager.ChangeWeaponState(_state, newWeaponState, StateChanged.Source.Manual); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index bd6e197..c4cf023 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; -using System.Xml.Linq; using Dalamud.Interface; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Internal.Notifications; @@ -95,45 +94,15 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _selector.Selected!.WriteProtected()); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var changes = _equipmentDrawer.DrawEquip(slot, _selector.Selected!.DesignData, out var newArmor, out var newStain, - _selector.Selected.ApplyEquip, out var newApply, out var newApplyStain, _selector.Selected!.WriteProtected()); - if (changes.HasFlag(DataChange.Item)) - _manager.ChangeEquip(_selector.Selected, slot, newArmor); - if (changes.HasFlag(DataChange.Stain)) - _manager.ChangeStain(_selector.Selected, slot, newStain); - else if (usedAllStain) + var data = EquipDrawData.FromDesign(_manager, _selector.Selected!, slot); + _equipmentDrawer.DrawEquip(data); + if (usedAllStain) _manager.ChangeStain(_selector.Selected, slot, newAllStain); - if (changes.HasFlag(DataChange.ApplyItem)) - _manager.ChangeApplyEquip(_selector.Selected, slot, newApply); - if (changes.HasFlag(DataChange.ApplyStain)) - _manager.ChangeApplyStain(_selector.Selected, slot, newApplyStain); } - var weaponChanges = _equipmentDrawer.DrawWeapons(_selector.Selected!.DesignData, out var newMainhand, out var newOffhand, - out var newMainhandStain, out var newOffhandStain, _selector.Selected.ApplyEquip, true, out var applyMain, out var applyMainStain, - out var applyOff, out var applyOffStain, _selector.Selected!.WriteProtected()); - - if (weaponChanges.HasFlag(DataChange.Item)) - _manager.ChangeWeapon(_selector.Selected, EquipSlot.MainHand, newMainhand); - if (weaponChanges.HasFlag(DataChange.Stain)) - _manager.ChangeStain(_selector.Selected, EquipSlot.MainHand, newMainhandStain); - else if (usedAllStain) - _manager.ChangeStain(_selector.Selected, EquipSlot.MainHand, newAllStain); - if (weaponChanges.HasFlag(DataChange.ApplyItem)) - _manager.ChangeApplyEquip(_selector.Selected, EquipSlot.MainHand, applyMain); - if (weaponChanges.HasFlag(DataChange.ApplyStain)) - _manager.ChangeApplyStain(_selector.Selected, EquipSlot.MainHand, applyMainStain); - if (weaponChanges.HasFlag(DataChange.Item2)) - _manager.ChangeWeapon(_selector.Selected, EquipSlot.OffHand, newOffhand); - if (weaponChanges.HasFlag(DataChange.Stain2)) - _manager.ChangeStain(_selector.Selected, EquipSlot.OffHand, newOffhandStain); - else if (usedAllStain) - _manager.ChangeStain(_selector.Selected, EquipSlot.OffHand, newAllStain); - if (weaponChanges.HasFlag(DataChange.ApplyItem2)) - _manager.ChangeApplyEquip(_selector.Selected, EquipSlot.OffHand, applyOff); - if (weaponChanges.HasFlag(DataChange.ApplyStain2)) - _manager.ChangeApplyStain(_selector.Selected, EquipSlot.OffHand, applyOffStain); - + var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand); + var offhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.OffHand); + _equipmentDrawer.DrawWeapons(mainhand, offhand, true); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); DrawEquipmentMetaToggles(); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); @@ -141,22 +110,11 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer private void DrawEquipmentMetaToggles() { - var hatChanges = EquipmentDrawer.DrawHatState(_selector.Selected!.DesignData.IsHatVisible(), - _selector.Selected.DoApplyHatVisible(), - out var newHatState, out var newHatApply, _selector.Selected.WriteProtected()); - ApplyChanges(ActorState.MetaIndex.HatState, hatChanges, newHatState, newHatApply); - + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); ImGui.SameLine(); - var visorChanges = EquipmentDrawer.DrawVisorState(_selector.Selected!.DesignData.IsVisorToggled(), - _selector.Selected.DoApplyVisorToggle(), - out var newVisorState, out var newVisorApply, _selector.Selected.WriteProtected()); - ApplyChanges(ActorState.MetaIndex.VisorState, visorChanges, newVisorState, newVisorApply); - + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); ImGui.SameLine(); - var weaponChanges = EquipmentDrawer.DrawWeaponState(_selector.Selected!.DesignData.IsWeaponVisible(), - _selector.Selected.DoApplyWeaponVisible(), - out var newWeaponState, out var newWeaponApply, _selector.Selected.WriteProtected()); - ApplyChanges(ActorState.MetaIndex.WeaponState, weaponChanges, newWeaponState, newWeaponApply); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); } private void DrawCustomize() @@ -178,9 +136,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _manager.ChangeCustomize(_selector.Selected, idx, _customizationDrawer.Customize[idx]); } - var wetnessChanges = _customizationDrawer.DrawWetnessState(_selector.Selected!.DesignData.IsWet(), - _selector.Selected!.DoApplyWetness(), out var newWetnessState, out var newWetnessApply, _selector.Selected!.WriteProtected()); - ApplyChanges(ActorState.MetaIndex.Wetness, wetnessChanges, newWetnessState, newWetnessApply); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.Wetness, _manager, _selector.Selected!)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } @@ -306,12 +262,14 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _manager.ChangeCustomize(_selector.Selected!, CustomizeIndex.Gender, dat.Customize[CustomizeIndex.Gender]); foreach (var idx in CustomizationExtensions.AllBasic) _manager.ChangeCustomize(_selector.Selected!, idx, dat.Customize[idx]); - Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_selector.Selected.Name}.", NotificationType.Success, false); + Glamourer.Messager.NotificationMessage( + $"Applied games .dat file {dat.Description} customizations to {_selector.Selected.Name}.", NotificationType.Success, false); } else if (_importService.CreateCharaTarget(out var designBase, out var name)) { _manager.ApplyDesign(_selector.Selected!, designBase); - Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selector.Selected.Name}.", NotificationType.Success, false); + Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_selector.Selected.Name}.", + NotificationType.Success, false); } } @@ -441,23 +399,6 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _fileDialog.Draw(); } - private void ApplyChanges(ActorState.MetaIndex index, DataChange change, bool value, bool apply) - { - switch (change) - { - case DataChange.Item: - _manager.ChangeMeta(_selector.Selected!, index, value); - break; - case DataChange.ApplyItem: - _manager.ChangeApplyMeta(_selector.Selected!, index, apply); - break; - case DataChange.Item | DataChange.ApplyItem: - _manager.ChangeApplyMeta(_selector.Selected!, index, apply); - _manager.ChangeMeta(_selector.Selected!, index, value); - break; - } - } - private static unsafe string GetUserPath() => Framework.Instance()->UserPath; } diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs new file mode 100644 index 0000000..f78cf6f --- /dev/null +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -0,0 +1,79 @@ +using System; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.State; + +namespace Glamourer.Gui; + +public ref struct ToggleDrawData +{ + public bool Locked; + public bool DisplayApplication; + + public bool CurrentValue; + public bool CurrentApply; + + public Action SetValue = null!; + public Action SetApply = null!; + + public string Label = string.Empty; + public string Tooltip = string.Empty; + + public ToggleDrawData() + { } + + public static ToggleDrawData FromDesign(ActorState.MetaIndex index, DesignManager manager, Design design) + { + var (label, value, apply, setValue, setApply) = index switch + { + ActorState.MetaIndex.HatState => ("Hat Visible", design.DesignData.IsHatVisible(), design.DoApplyHatVisible(), + (Action)(b => manager.ChangeMeta(design, index, b)), (Action)(b => manager.ChangeApplyMeta(design, index, b))), + ActorState.MetaIndex.VisorState => ("Visor Toggled", design.DesignData.IsVisorToggled(), design.DoApplyVisorToggle(), + b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), + ActorState.MetaIndex.WeaponState => ("Weapon Visible", design.DesignData.IsWeaponVisible(), design.DoApplyWeaponVisible(), + b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), + ActorState.MetaIndex.Wetness => ("Force Wetness", design.DesignData.IsWet(), design.DoApplyWetness(), + b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), + _ => throw new Exception("Unsupported meta index."), + }; + + return new ToggleDrawData + { + Label = label, + Tooltip = string.Empty, + Locked = design.WriteProtected(), + DisplayApplication = true, + CurrentValue = value, + CurrentApply = apply, + SetValue = setValue, + SetApply = setApply, + }; + } + + public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state) + { + var (label, tooltip, value, setValue) = index switch + { + ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear.", state.ModelData.IsHatVisible(), + (Action)(b => manager.ChangeHatState(state, b, StateChanged.Source.Manual))), + ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear.", + state.ModelData.IsVisorToggled(), + b => manager.ChangeVisorState(state, b, StateChanged.Source.Manual)), + ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn.", + state.ModelData.IsWeaponVisible(), + b => manager.ChangeWeaponState(state, b, StateChanged.Source.Manual)), + ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not.", state.ModelData.IsWet(), + b => manager.ChangeWetness(state, b, StateChanged.Source.Manual)), + _ => throw new Exception("Unsupported meta index."), + }; + + return new ToggleDrawData + { + Label = label, + Tooltip = tooltip, + Locked = state.IsLocked, + CurrentValue = value, + SetValue = setValue, + }; + } +} diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 7dcfc7d..22ec758 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -71,14 +71,15 @@ public static class UiHelpers return ret; } - public static DataChange DrawMetaToggle(string label, bool currentValue, bool currentApply, out bool newValue, + public static (bool, bool) DrawMetaToggle(string label, bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) { var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); using (var disabled = ImRaii.Disabled(locked)) { - if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw("##" + label, flags, out flags)) + if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw( + "##" + label, flags, out flags)) { (newValue, newApply) = flags switch { @@ -99,13 +100,7 @@ public static class UiHelpers ImGui.SameLine(); ImGui.TextUnformatted(label); - return (currentApply != newApply, currentValue != newValue) switch - { - (true, true) => DataChange.ApplyItem | DataChange.Item, - (true, false) => DataChange.ApplyItem, - (false, true) => DataChange.Item, - _ => DataChange.None, - }; + return (currentValue != newValue, currentApply != newApply); } public static (EquipFlag, CustomizeFlag) ConvertKeysToFlags() diff --git a/OtterGui b/OtterGui index 858b610..2a79084 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 858b610a194ee52b4e8e89c0c64d9f2653025524 +Subproject commit 2a7908493ab0fd0e576d5fa37a3acbd920be6233 From cdefe64e4e615c179777d6c7bbf0548cfef88320 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Dec 2023 14:38:51 +0100 Subject: [PATCH 072/786] Allow import of .cma files. --- Glamourer/Interop/CharaFile/CmaFile.cs | 111 +++++++++++++++++++++++++ Glamourer/Interop/ImportService.cs | 40 ++++++++- 2 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 Glamourer/Interop/CharaFile/CmaFile.cs diff --git a/Glamourer/Interop/CharaFile/CmaFile.cs b/Glamourer/Interop/CharaFile/CmaFile.cs new file mode 100644 index 0000000..15b8af1 --- /dev/null +++ b/Glamourer/Interop/CharaFile/CmaFile.cs @@ -0,0 +1,111 @@ +using System; +using Glamourer.Designs; +using Glamourer.Services; +using Newtonsoft.Json.Linq; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Interop.CharaFile; + +public sealed class CmaFile +{ + public string Name = string.Empty; + public DesignData Data = new(); + + public static CmaFile? ParseData(ItemManager items, string data, string? name = null) + { + try + { + var jObj = JObject.Parse(data); + var ret = new CmaFile(); + ret.Data.SetDefaultEquipment(items); + ParseMainHand(items, jObj, ref ret.Data); + ParseOffHand(items, jObj, ref ret.Data); + ret.Name = jObj["Description"]?.ToObject() ?? name ?? "New Design"; + ParseEquipment(items, jObj, ref ret.Data); + ParseCustomization(jObj, ref ret.Data); + return ret; + } + catch + { + return null; + } + } + + private static unsafe void ParseCustomization(JObject jObj, ref DesignData data) + { + var bytes = jObj["CharacterBytes"]?.ToObject() ?? string.Empty; + if (bytes.Length is not 26 * 3 - 1) + return; + + bytes = bytes.Replace(" ", string.Empty); + var byteData = Convert.FromHexString(bytes); + fixed (byte* ptr = byteData) + { + data.Customize.Data.Read(ptr); + } + } + + private static unsafe void ParseEquipment(ItemManager items, JObject jObj, ref DesignData data) + { + var bytes = jObj["EquipmentBytes"]?.ToObject() ?? string.Empty; + bytes = bytes.Replace(" ", string.Empty); + var byteData = Convert.FromHexString(bytes); + fixed (byte* ptr = byteData) + { + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var idx = slot.ToIndex(); + if (idx * 4 + 3 >= byteData.Length) + continue; + var armor = ((CharacterArmor*)ptr)[idx]; + var item = items.Identify(slot, armor.Set, armor.Variant); + data.SetItem(slot, item); + data.SetStain(slot, armor.Stain); + } + + data.Customize.Data.Read(ptr); + } + } + + private static void ParseMainHand(ItemManager items, JObject jObj, ref DesignData data) + { + var mainhand = jObj["MainHand"]; + if (mainhand == null) + { + data.SetItem(EquipSlot.MainHand, items.DefaultSword); + data.SetStain(EquipSlot.MainHand, 0); + return; + } + + var set = mainhand["Item1"]?.ToObject() ?? items.DefaultSword.ModelId; + var type = mainhand["Item2"]?.ToObject() ?? items.DefaultSword.WeaponType; + var variant = mainhand["Item3"]?.ToObject() ?? items.DefaultSword.Variant; + var stain = mainhand["Item4"]?.ToObject() ?? 0; + var item = items.Identify(EquipSlot.MainHand, set, type, variant); + + data.SetItem(EquipSlot.MainHand, item.Valid ? item : items.DefaultSword); + data.SetStain(EquipSlot.MainHand, stain); + } + + private static void ParseOffHand(ItemManager items, JObject jObj, ref DesignData data) + { + var offhand = jObj["OffHand"]; + var defaultOffhand = items.GetDefaultOffhand(data.Item(EquipSlot.MainHand)); + if (offhand == null) + { + data.SetItem(EquipSlot.MainHand, defaultOffhand); + data.SetStain(EquipSlot.MainHand, defaultOffhand.ModelId.Id == 0 ? 0 : data.Stain(EquipSlot.MainHand)); + return; + } + + var set = offhand["Item1"]?.ToObject() ?? items.DefaultSword.ModelId; + var type = offhand["Item2"]?.ToObject() ?? items.DefaultSword.WeaponType; + var variant = offhand["Item3"]?.ToObject() ?? items.DefaultSword.Variant; + var stain = offhand["Item4"]?.ToObject() ?? 0; + var item = items.Identify(EquipSlot.OffHand, set, type, variant, data.MainhandType); + + data.SetItem(EquipSlot.OffHand, item.Valid ? item : defaultOffhand); + data.SetStain(EquipSlot.OffHand, defaultOffhand.ModelId.Id == 0 ? 0 : (StainId)stain); + } +} diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 2681abb..217b5fd 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -6,7 +6,9 @@ using Dalamud.Interface.DragDrop; using Dalamud.Interface.Internal.Notifications; using Glamourer.Customization; using Glamourer.Designs; +using Glamourer.Interop.CharaFile; using Glamourer.Services; +using Glamourer.Structs; using ImGuiNET; using OtterGui.Classes; @@ -22,9 +24,9 @@ public class ImportService(CustomizationService _customizations, IDragDropManage }); public void CreateCharaSource() - => _dragDropManager.CreateImGuiSource("CharaDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".chara"), m => + => _dragDropManager.CreateImGuiSource("CharaDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".chara") || m.Extensions.Contains(".cma"), m => { - ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import Anamnesis data for Glamourer..."); + ImGui.TextUnformatted($"Dragging {Path.GetFileName(m.Files[0])} to import Anamnesis/CMTool data for Glamourer..."); return true; }); @@ -47,8 +49,8 @@ public class ImportService(CustomizationService _customizations, IDragDropManage name = string.Empty; return false; } - - return LoadChara(files[0], out design, out name); + + return Path.GetExtension(files[0]) is ".chara" ? LoadChara(files[0], out design, out name) : LoadCma(files[0], out design, out name); } public bool LoadChara(string path, [NotNullWhen(true)] out DesignBase? design, out string name) @@ -81,6 +83,36 @@ public class ImportService(CustomizationService _customizations, IDragDropManage return true; } + public bool LoadCma(string path, [NotNullWhen(true)] out DesignBase? design, out string name) + { + if (!File.Exists(path)) + { + design = null; + name = string.Empty; + return false; + } + + try + { + var text = File.ReadAllText(path); + var file = CmaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path)); + if (file == null) + throw new Exception(); + + name = file.Name; + design = new DesignBase(_customizations, file.Data, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not read .cma file {path}.", NotificationType.Error); + design = null; + name = string.Empty; + return false; + } + + return true; + } + public bool LoadDat(string path, out DatCharacterFile file) { if (!File.Exists(path)) From 87a645b2a3bf9229bfd957e1e0052f68ad4cfa8e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Dec 2023 14:39:10 +0100 Subject: [PATCH 073/786] Try using mainhand item for vfx weapons in some cases. --- Glamourer/Interop/WeaponService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index caea959..e395f8b 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -64,6 +64,9 @@ public unsafe class WeaponService : IDisposable // First call the regular function. if (equipSlot is not EquipSlot.Unknown) _event.Invoke(actor, equipSlot, ref tmpWeapon); + // Sage hack for weapons appearing in animations? + else if (weaponValue == actor.GetMainhand().Value) + _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); if (tmpWeapon.Value != weapon.Value) From 2f1b85a02ab37d79fe26570517912fd3bc08fa37 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 14:02:09 +0100 Subject: [PATCH 074/786] Add Crest flags. --- Glamourer.GameData/Structs/CrestFlag.cs | 50 +++++++++++++++++++++++++ Glamourer/Designs/DesignBase.cs | 40 ++++++++++++++++---- Glamourer/Designs/DesignData.cs | 19 ++++++++++ 3 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 Glamourer.GameData/Structs/CrestFlag.cs diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs new file mode 100644 index 0000000..17275f5 --- /dev/null +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -0,0 +1,50 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent; +using Penumbra.GameData.Enums; + +namespace Glamourer.Structs; + +[Flags] +public enum CrestFlag : ushort +{ + Head = 0x0001, + Body = 0x0002, + Hands = 0x0004, + Legs = 0x0008, + Feet = 0x0010, + Ears = 0x0020, + Neck = 0x0040, + Wrist = 0x0080, + RFinger = 0x0100, + LFinger = 0x0200, + Mainhand = 0x0400, + Offhand = 0x0800, +} + +public static class CrestExtensions +{ + public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); + public const CrestFlag AllRelevant = CrestFlag.Body; + + public static CrestFlag ToCrestFlag(this EquipSlot slot) + => slot switch + { + EquipSlot.MainHand => CrestFlag.Mainhand, + EquipSlot.OffHand => CrestFlag.Offhand, + EquipSlot.Head => CrestFlag.Head, + EquipSlot.Body => CrestFlag.Body, + EquipSlot.Hands => CrestFlag.Hands, + EquipSlot.Legs => CrestFlag.Legs, + EquipSlot.Feet => CrestFlag.Feet, + EquipSlot.Ears => CrestFlag.Ears, + EquipSlot.Neck => CrestFlag.Neck, + EquipSlot.Wrists => CrestFlag.Wrist, + EquipSlot.RFinger => CrestFlag.RFinger, + EquipSlot.LFinger => CrestFlag.LFinger, + _ => 0, + }; + + public static bool Valid(this CrestFlag crest) + => AllRelevant.HasFlag(crest); +} diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 183ca99..1e45052 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -39,7 +39,6 @@ public class DesignBase ApplyEquip = equipFlags & EquipFlagExtensions.All; _designFlags = 0; CustomizationSet = SetCustomizationSet(customize); - } internal DesignBase(DesignBase clone) @@ -83,6 +82,7 @@ public class DesignBase => _applyCustomize; internal EquipFlag ApplyEquip = EquipFlagExtensions.All; + internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; public bool SetCustomize(CustomizationService customizationService, Customize customize) @@ -169,6 +169,9 @@ public class DesignBase public bool DoApplyCustomize(CustomizeIndex idx) => ApplyCustomize.HasFlag(idx.ToFlag()); + public bool DoApplyCrest(EquipSlot slot) + => ApplyCrest.HasFlag(slot.ToFlag()); + internal bool SetApplyEquip(EquipSlot slot, bool value) { var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag(); @@ -199,6 +202,16 @@ public class DesignBase return true; } + internal bool SetApplyCrest(EquipSlot slot, bool value) + { + var newValue = value ? ApplyCrest | slot.ToCrestFlag() : ApplyCrest & ~slot.ToCrestFlag(); + if (newValue == ApplyCrest) + return false; + + ApplyCrest = newValue; + return true; + } + internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags) => new(this, equipFlags, customizeFlags); @@ -246,13 +259,15 @@ public class DesignBase protected JObject SerializeEquipment() { - static JObject Serialize(CustomItemId id, StainId stain, bool apply, bool applyStain) + static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest) => new() { ["ItemId"] = id.Id, ["Stain"] = stain.Id, + ["Crest"] = crest, ["Apply"] = apply, ["ApplyStain"] = applyStain, + ["ApplyCrest"] = applyCrest, }; var ret = new JObject(); @@ -262,7 +277,8 @@ public class DesignBase { var item = _designData.Item(slot); var stain = _designData.Stain(slot); - ret[slot.ToString()] = Serialize(item.Id, stain, DoApplyEquip(slot), DoApplyStain(slot)); + var crest = _designData.Crest(slot); + ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(slot)); } ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply"); @@ -345,13 +361,15 @@ public class DesignBase return; } - static (CustomItemId, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item) + static (CustomItemId, StainId, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item) { var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot).Id; var stain = (StainId)(item?["Stain"]?.ToObject() ?? 0); + var crest = (item?["Crest"]?.ToObject() ?? false); var apply = item?["Apply"]?.ToObject() ?? false; var applyStain = item?["ApplyStain"]?.ToObject() ?? false; - return (id, stain, apply, applyStain); + var applyCrest = item?["ApplyCrest"]?.ToObject() ?? false; + return (id, stain, crest, apply, applyStain, applyCrest); } void PrintWarning(string msg) @@ -362,21 +380,23 @@ public class DesignBase foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]); + var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(slot, equip[slot.ToString()]); PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); design._designData.SetItem(slot, item); design._designData.SetStain(slot, stain); + design._designData.SetCrest(slot, crest); design.SetApplyEquip(slot, apply); design.SetApplyStain(slot, applyStain); + design.SetApplyCrest(slot, applyCrest); } { - var (id, stain, apply, applyStain) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); + var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.MainHand)) id = items.DefaultSword.ItemId; - var (idOff, stainOff, applyOff, applyStainOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); + var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.OffHand)) id = ItemManager.NothingId(FullEquipType.Shield); @@ -387,10 +407,14 @@ public class DesignBase design._designData.SetItem(EquipSlot.OffHand, off); design._designData.SetStain(EquipSlot.MainHand, stain); design._designData.SetStain(EquipSlot.OffHand, stainOff); + design._designData.SetCrest(EquipSlot.MainHand, crest); + design._designData.SetCrest(EquipSlot.OffHand, crestOff); design.SetApplyEquip(EquipSlot.MainHand, apply); design.SetApplyEquip(EquipSlot.OffHand, applyOff); design.SetApplyStain(EquipSlot.MainHand, applyStain); design.SetApplyStain(EquipSlot.OffHand, applyStainOff); + design.SetApplyCrest(EquipSlot.MainHand, applyCrest); + design.SetApplyCrest(EquipSlot.OffHand, applyCrestOff); } var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse); design.SetApplyHatVisible(metaValue.Enabled); diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 4a24f59..fe2e655 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using Glamourer.Customization; using Glamourer.Services; +using Glamourer.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -29,6 +30,7 @@ public unsafe struct DesignData private fixed byte _equipmentBytes[48]; public Customize Customize = Customize.Default; public uint ModelId; + public CrestFlag CrestVisibility; private WeaponType _secondaryMainhand; private WeaponType _secondaryOffhand; private FullEquipType _typeMainhand; @@ -59,6 +61,10 @@ public unsafe struct DesignData return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3]; } + public readonly bool Crest(EquipSlot slot) + => CrestVisibility.HasFlag(slot.ToCrestFlag()); + + public FullEquipType MainhandType => _typeMainhand; @@ -173,6 +179,16 @@ public unsafe struct DesignData _ => false, }; + public bool SetCrest(EquipSlot slot, bool visible) + { + var newValue = visible ? CrestVisibility | slot.ToCrestFlag() : CrestVisibility & ~slot.ToCrestFlag(); + if (newValue == CrestVisibility) + return false; + + CrestVisibility = newValue; + return true; + } + public readonly bool IsWet() => (_states & 0x01) == 0x01; @@ -228,12 +244,15 @@ public unsafe struct DesignData { SetItem(slot, ItemManager.NothingItem(slot)); SetStain(slot, 0); + SetCrest(slot, false); } SetItem(EquipSlot.MainHand, items.DefaultSword); SetStain(EquipSlot.MainHand, 0); + SetCrest(EquipSlot.MainHand, false); SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); SetStain(EquipSlot.OffHand, 0); + SetCrest(EquipSlot.OffHand, false); } From 6f4a7661d75a0748d9a8d4d19a57f7cb0b755c0a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Nov 2023 14:05:29 +0100 Subject: [PATCH 075/786] Add crest changing in designs. --- Glamourer/Designs/DesignBase.cs | 2 +- Glamourer/Designs/DesignManager.cs | 34 ++++++++++++++++++++++++++++++ Glamourer/Events/DesignChanged.cs | 6 ++++++ Glamourer/Events/StateChanged.cs | 3 +++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 1e45052..135c858 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -170,7 +170,7 @@ public class DesignBase => ApplyCustomize.HasFlag(idx.ToFlag()); public bool DoApplyCrest(EquipSlot slot) - => ApplyCrest.HasFlag(slot.ToFlag()); + => ApplyCrest.HasFlag(slot.ToCrestFlag()); internal bool SetApplyEquip(EquipSlot slot, bool value) { diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 7a0f500..7159bac 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -446,6 +446,31 @@ public class DesignManager _event.Invoke(DesignChanged.Type.ApplyStain, design, slot); } + /// Change the crest visibility for any equipment piece. + public void ChangeCrest(Design design, EquipSlot slot, bool crest) + { + var oldCrest = design.DesignData.Crest(slot); + if (!design.GetDesignDataRef().SetCrest(slot, crest)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(design); + Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}."); + _event.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot)); + } + + /// Change whether to apply a specific crest visibility. + public void ChangeApplyCrest(Design design, EquipSlot slot, bool value) + { + if (!design.SetApplyCrest(slot, value)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(design); + Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}."); + _event.Invoke(DesignChanged.Type.ApplyCrest, design, slot); + } + /// Change the bool value of one of the meta flags. public void ChangeMeta(Design design, ActorState.MetaIndex metaIndex, bool value) { @@ -514,6 +539,9 @@ public class DesignManager if (other.DoApplyStain(slot)) ChangeStain(design, slot, other.DesignData.Stain(slot)); + + if (other.DoApplyCrest(slot)) + ChangeCrest(design, slot, other.DesignData.Crest(slot)); } } @@ -528,6 +556,12 @@ public class DesignManager if (other.DoApplyStain(EquipSlot.OffHand)) ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand)); + + if (other.DoApplyCrest(EquipSlot.MainHand)) + ChangeCrest(design, EquipSlot.MainHand, other.DesignData.Crest(EquipSlot.MainHand)); + + if (other.DoApplyCrest(EquipSlot.OffHand)) + ChangeCrest(design, EquipSlot.OffHand, other.DesignData.Crest(EquipSlot.OffHand)); } public void UndoDesignChange(Design design) diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 55956f0..c528fde 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -62,6 +62,9 @@ public sealed class DesignChanged : EventWrapper An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. Stain, + /// An existing design had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. + Crest, + /// An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. ApplyCustomize, @@ -71,6 +74,9 @@ public sealed class DesignChanged : EventWrapper An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. ApplyStain, + /// An existing design changed whether a specific crest visibility is applied. Data is the slot of the equipment [EquipSlot]. + ApplyCrest, + /// An existing design changed its write protection status. Data is the new value [bool]. WriteProtection, diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 2c5c5c8..e02a6d9 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -37,6 +37,9 @@ public sealed class StateChanged : EventWrapper A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. Stain, + /// A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. + Crest, + /// A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] Design, From 512d0a1a5f9650feb296558d841c8660c8e89cd2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Nov 2023 23:54:37 +0100 Subject: [PATCH 076/786] Add interop for Actor and Model. --- Glamourer.GameData/Structs/CrestFlag.cs | 52 +++++++++++++++---- .../DesignTab/DesignFileSystemSelector.cs | 1 + Glamourer/Gui/UiHelpers.cs | 15 ------ Glamourer/Interop/Structs/Actor.cs | 19 +++++++ Glamourer/Interop/Structs/Model.cs | 32 ++++++++++++ Glamourer/State/ActorState.cs | 6 ++- 6 files changed, 98 insertions(+), 27 deletions(-) diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs index 17275f5..d39fd0a 100644 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -1,6 +1,7 @@ using System; -using System.Diagnostics.CodeAnalysis; -using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; using Penumbra.GameData.Enums; namespace Glamourer.Structs; @@ -15,23 +16,37 @@ public enum CrestFlag : ushort Feet = 0x0010, Ears = 0x0020, Neck = 0x0040, - Wrist = 0x0080, + Wrists = 0x0080, RFinger = 0x0100, LFinger = 0x0200, - Mainhand = 0x0400, - Offhand = 0x0800, + MainHand = 0x0400, + OffHand = 0x0800, } public static class CrestExtensions { public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); - public const CrestFlag AllRelevant = CrestFlag.Body; + public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand; + + public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => f.ToRelevantIndex() >= 0).ToArray(); + + public static int ToIndex(this CrestFlag flag) + => BitOperations.TrailingZeroCount((uint)flag); + + public static int ToRelevantIndex(this CrestFlag flag) + => flag switch + { + CrestFlag.Head => 0, + CrestFlag.Body => 1, + CrestFlag.OffHand => 2, + _ => -1, + }; public static CrestFlag ToCrestFlag(this EquipSlot slot) => slot switch { - EquipSlot.MainHand => CrestFlag.Mainhand, - EquipSlot.OffHand => CrestFlag.Offhand, + EquipSlot.MainHand => CrestFlag.MainHand, + EquipSlot.OffHand => CrestFlag.OffHand, EquipSlot.Head => CrestFlag.Head, EquipSlot.Body => CrestFlag.Body, EquipSlot.Hands => CrestFlag.Hands, @@ -39,12 +54,27 @@ public static class CrestExtensions EquipSlot.Feet => CrestFlag.Feet, EquipSlot.Ears => CrestFlag.Ears, EquipSlot.Neck => CrestFlag.Neck, - EquipSlot.Wrists => CrestFlag.Wrist, + EquipSlot.Wrists => CrestFlag.Wrists, EquipSlot.RFinger => CrestFlag.RFinger, EquipSlot.LFinger => CrestFlag.LFinger, _ => 0, }; - public static bool Valid(this CrestFlag crest) - => AllRelevant.HasFlag(crest); + public static EquipSlot ToSlot(this CrestFlag flag) + => flag switch + { + CrestFlag.MainHand => EquipSlot.MainHand, + CrestFlag.OffHand => EquipSlot.OffHand, + CrestFlag.Head => EquipSlot.Head, + CrestFlag.Body => EquipSlot.Body, + CrestFlag.Hands => EquipSlot.Hands, + CrestFlag.Legs => EquipSlot.Legs, + CrestFlag.Feet => EquipSlot.Feet, + CrestFlag.Ears => EquipSlot.Ears, + CrestFlag.Neck => EquipSlot.Neck, + CrestFlag.Wrists => EquipSlot.Wrists, + CrestFlag.RFinger => EquipSlot.RFinger, + CrestFlag.LFinger => EquipSlot.LFinger, + _ => 0, + }; } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index b323b63..b288303 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -118,6 +118,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Open a combo popup with another method than the combo itself. diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 1d1d172..ba7f132 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -106,6 +106,9 @@ public readonly unsafe struct Actor : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()]; + public bool GetCrest(EquipSlot slot) + => (GetFreeCompanyCrestBitfield() & CrestMask(slot)) != 0; + public CharacterWeapon GetMainhand() => new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).ModelId.Value); @@ -115,6 +118,22 @@ public readonly unsafe struct Actor : IEquatable public Customize GetCustomize() => *(Customize*)&AsCharacter->DrawData.CustomizeData; + // TODO remove this when available in ClientStructs + private byte GetFreeCompanyCrestBitfield() + => ((byte*)Address)[0x1BBB]; + + private static byte CrestMask(EquipSlot slot) + => slot switch + { + EquipSlot.OffHand => 0x01, + EquipSlot.Head => 0x02, + EquipSlot.Body => 0x04, + EquipSlot.Hands => 0x08, + EquipSlot.Legs => 0x10, + EquipSlot.Feet => 0x20, + _ => 0x00, + }; + public override string ToString() => $"0x{Address:X}"; } diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index 77bf24e..811ff81 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -91,6 +91,9 @@ public readonly unsafe struct Model : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()]; + public bool GetCrest(EquipSlot slot) + => IsFreeCompanyCrestVisibleOnSlot(slot); + public Customize GetCustomize() => *(Customize*)&AsHuman->Customize; @@ -195,6 +198,35 @@ public readonly unsafe struct Model : IEquatable return discriminator1 == 0 && discriminator2 != 0 ? (second, first) : (first, second); } + // TODO remove these when available in ClientStructs + private bool IsFreeCompanyCrestVisibleOnSlot(EquipSlot slot) + { + if (!IsCharacterBase) + return false; + + var index = (byte)slot.ToIndex(); + if (index >= 12) + return false; + + var characterBase = AsCharacterBase; + var getter = (delegate* unmanaged)((nint*)characterBase->VTable)[95]; + return getter(characterBase, index) != 0; + } + + public void SetFreeCompanyCrestVisibleOnSlot(EquipSlot slot, bool visible) + { + if (!IsCharacterBase) + return; + + var index = (byte)slot.ToIndex(); + if (index >= 12) + return; + + var characterBase = AsCharacterBase; + var setter = (delegate* unmanaged)((nint*)characterBase->VTable)[96]; + setter(characterBase, index, visible ? (byte)1 : (byte)0); + } + public override string ToString() => $"0x{Address:X}"; } diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index 2cb3f2a..f3372e8 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -80,7 +80,8 @@ public class ActorState /// This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. private readonly StateChanged.Source[] _sources = Enumerable - .Repeat(StateChanged.Source.Game, EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5).ToArray(); + .Repeat(StateChanged.Source.Game, + EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + CrestExtensions.AllRelevantSet.Count).ToArray(); internal ActorState(ActorIdentifier identifier) => Identifier = identifier.CreatePermanent(); @@ -88,6 +89,9 @@ public class ActorState public ref StateChanged.Source this[EquipSlot slot, bool stain] => ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)]; + public ref StateChanged.Source this[CrestFlag slot] + => ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToRelevantIndex()]; + public ref StateChanged.Source this[CustomizeIndex type] => ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type]; From cd0196ddb4b42c58cf6340425a7592ec5a54f3eb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 26 Nov 2023 00:29:15 +0100 Subject: [PATCH 077/786] UI for crests. --- Glamourer.GameData/Structs/CrestFlag.cs | 18 ++++ Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 30 +++++-- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 94 ++++++++++++++------- Glamourer/Gui/ToggleDrawData.cs | 25 ++++++ 4 files changed, 132 insertions(+), 35 deletions(-) diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs index d39fd0a..3ef9f1e 100644 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -77,4 +77,22 @@ public static class CrestExtensions CrestFlag.LFinger => EquipSlot.LFinger, _ => 0, }; + + public static string ToLabel(this CrestFlag flag) + => flag switch + { + CrestFlag.Head => "Head", + CrestFlag.Body => "Chest", + CrestFlag.Hands => "Gauntlets", + CrestFlag.Legs => "Pants", + CrestFlag.Feet => "Boot", + CrestFlag.Ears => "Earrings", + CrestFlag.Neck => "Necklace", + CrestFlag.Wrists => "Bracelet", + CrestFlag.RFinger => "Right Ring", + CrestFlag.LFinger => "Left Ring", + CrestFlag.MainHand => "Weapon", + CrestFlag.OffHand => "Shield", + _ => string.Empty, + }; } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 5f54965..e4006ad 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -5,6 +5,7 @@ using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using Glamourer.Automation; using Glamourer.Customization; using Glamourer.Designs; @@ -157,14 +158,33 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus _equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose()); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state)); - ImGui.SameLine(); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state)); - ImGui.SameLine(); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state)); + DrawEquipmentMetaToggles(); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } + private void DrawEquipmentMetaToggles() + { + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.Head, _stateManager, _state!)); + } + + ImGui.SameLine(); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.Body, _stateManager, _state!)); + } + + ImGui.SameLine(); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.OffHand, _stateManager, _state!)); + } + } + private void DrawMonsterPanel() { var names = _identification.AwaitedService.ModelCharaNames(_state!.ModelData.ModelId); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index c4cf023..129b5c7 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -110,11 +110,25 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer private void DrawEquipmentMetaToggles() { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.Head, _manager, _selector.Selected!)); + } + ImGui.SameLine(); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.Body, _manager, _selector.Selected!)); + } + ImGui.SameLine(); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.OffHand, _manager, _selector.Selected!)); + } } private void DrawCustomize() @@ -140,6 +154,50 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } + private void DrawCustomizeApplication() + { + var set = _selector.Selected!.CustomizationSet; + var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; + var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; + if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) + { + var newFlags = flags == 3; + _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, newFlags); + _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, newFlags); + foreach (var index in CustomizationExtensions.AllBasic) + _manager.ChangeApplyCustomize(_selector.Selected!, index, newFlags); + } + + var applyClan = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Clan); + if (ImGui.Checkbox($"Apply {CustomizeIndex.Clan.ToDefaultName()}", ref applyClan)) + _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, applyClan); + + var applyGender = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Gender); + if (ImGui.Checkbox($"Apply {CustomizeIndex.Gender.ToDefaultName()}", ref applyGender)) + _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, applyGender); + + + foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable)) + { + var apply = _selector.Selected!.DoApplyCustomize(index); + if (ImGui.Checkbox($"Apply {set.Option(index)}", ref apply)) + _manager.ChangeApplyCustomize(_selector.Selected!, index, apply); + } + } + + private void DrawCrestApplication() + { + var flags = (uint)_selector.Selected!.ApplyCrest; + var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); + foreach (var flag in CrestExtensions.AllRelevantSet) + { + var slot = flag.ToSlot(); + var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(slot); + if (ImGui.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) + _manager.ChangeApplyCrest(_selector.Selected!, slot, apply); + } + } + private void DrawApplicationRules() { if (!ImGui.CollapsingHeader("Application Rules")) @@ -147,33 +205,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer using (var _ = ImRaii.Group()) { - var set = _selector.Selected!.CustomizationSet; - var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; - var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; - if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) - { - var newFlags = flags == 3; - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, newFlags); - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, newFlags); - foreach (var index in CustomizationExtensions.AllBasic) - _manager.ChangeApplyCustomize(_selector.Selected!, index, newFlags); - } - - var applyClan = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Clan); - if (ImGui.Checkbox($"Apply {CustomizeIndex.Clan.ToDefaultName()}", ref applyClan)) - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, applyClan); - - var applyGender = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Gender); - if (ImGui.Checkbox($"Apply {CustomizeIndex.Gender.ToDefaultName()}", ref applyGender)) - _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, applyGender); - - - foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable)) - { - var apply = _selector.Selected!.DoApplyCustomize(index); - if (ImGui.Checkbox($"Apply {set.Option(index)}", ref apply)) - _manager.ChangeApplyCustomize(_selector.Selected!, index, apply); - } + DrawCustomizeApplication(); + ImGui.NewLine(); + DrawCrestApplication(); } ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index f78cf6f..edb06ae 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -2,6 +2,8 @@ using Glamourer.Designs; using Glamourer.Events; using Glamourer.State; +using Glamourer.Structs; +using Penumbra.GameData.Enums; namespace Glamourer.Gui; @@ -50,6 +52,29 @@ public ref struct ToggleDrawData }; } + public static ToggleDrawData CrestFromDesign(EquipSlot slot, DesignManager manager, Design design) + => new() + { + Label = $"{slot.ToCrestFlag().ToLabel()} Crest", + Tooltip = string.Empty, + Locked = design.WriteProtected(), + DisplayApplication = true, + CurrentValue = design.DesignData.Crest(slot), + CurrentApply = design.DoApplyCrest(slot), + SetValue = v => manager.ChangeCrest(design, slot, v), + SetApply = v => manager.ChangeApplyCrest(design, slot, v), + }; + + public static ToggleDrawData CrestFromState(EquipSlot slot, StateManager manager, ActorState state) + => new() + { + Label = $"{slot.ToCrestFlag().ToLabel()} Crest", + Tooltip = "Hide or show your free company crest on this piece of gear.", + Locked = state.IsLocked, + CurrentValue = state.ModelData.Crest(slot), // TODO + SetValue = v => { }, //manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual), + }; + public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state) { var (label, tooltip, value, setValue) = index switch From 3177e6ca290cb69705de161ea09ae08cc3b2594e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 26 Nov 2023 11:43:26 +0100 Subject: [PATCH 078/786] Update OtterGui. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 2a79084..3e2d4ae 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 2a7908493ab0fd0e576d5fa37a3acbd920be6233 +Subproject commit 3e2d4ae934694918d312280d62127cf1a55b03e4 From 668d4c033facefd5e1b53538e5097420f25856f7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Dec 2023 16:03:48 +0100 Subject: [PATCH 079/786] Add CrestService. --- Glamourer/Interop/ChangeCustomizeService.cs | 9 +- Glamourer/Interop/CrestService.cs | 92 +++++++++++++++++++++ Glamourer/Interop/UpdateSlotService.cs | 4 +- Glamourer/Services/ServiceManager.cs | 1 + Glamourer/State/StateListener.cs | 2 +- OtterGui | 2 +- 6 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 Glamourer/Interop/CrestService.cs diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index a5a46e6..9e9a043 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -23,7 +23,7 @@ public unsafe class ChangeCustomizeService : EventWrapper _original; /// Check whether we in a manual customize update, in which case we need to not toggle certain flags. - public static readonly ThreadLocal InUpdate = new(() => false); + public static readonly InMethodChecker InUpdate = new(); public enum Priority { @@ -70,9 +70,8 @@ public unsafe class ChangeCustomizeService : EventWrapper(new Customize(*(CustomizeData*)data)); Invoke(this, (Model)human, customize); diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs new file mode 100644 index 0000000..edc61a5 --- /dev/null +++ b/Glamourer/Interop/CrestService.cs @@ -0,0 +1,92 @@ +using System; +using Dalamud.Hooking; +using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; +using Glamourer.Interop.Structs; +using OtterGui.Classes; +using Penumbra.GameData.Enums; + +namespace Glamourer.Interop; + +/// +/// Triggered when the crest visibility is updated on a model. +/// +/// Parameter is the model with an update. +/// Parameter is the equipment slot changed. +/// Parameter is the whether the crest will be shown. +/// +/// +public sealed unsafe class CrestService : EventWrapper>, CrestService.Priority>, IDisposable +{ + public enum Priority + { + /// + StateListener = 0, + } + + public CrestService(IGameInteropProvider interop) + : base(nameof(CrestService)) + { + _humanSetFreeCompanyCrestVisibleOnSlot = + interop.HookFromAddress(_humanVTable[96], HumanSetFreeCompanyCrestVisibleOnSlotDetour); + _weaponSetFreeCompanyCrestVisibleOnSlot = + interop.HookFromAddress(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour); + _humanSetFreeCompanyCrestVisibleOnSlot.Enable(); + _weaponSetFreeCompanyCrestVisibleOnSlot.Enable(); + } + + protected override void Dispose(bool _) + { + _humanSetFreeCompanyCrestVisibleOnSlot.Dispose(); + _weaponSetFreeCompanyCrestVisibleOnSlot.Dispose(); + } + + public void Invoke(Model model, EquipSlot slot, ref bool visible) + { + var ret = new Ref(visible); + Invoke(this, model, slot, ret); + visible = ret; + } + + public void UpdateCrest(Model drawObject, EquipSlot slot, bool crest) + { + using var _ = _inUpdate.EnterMethod(); + drawObject.SetFreeCompanyCrestVisibleOnSlot(slot, crest); + } + + private readonly InMethodChecker _inUpdate = new(); + + private delegate void SetCrestDelegateIntern(nint drawObject, byte slot, byte visible); + + [Signature(global::Penumbra.GameData.Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] + private readonly nint* _humanVTable = null!; + + [Signature(global::Penumbra.GameData.Sigs.WeaponVTable, ScanType = ScanType.StaticAddress)] + private readonly nint* _weaponVTable = null!; + + private readonly Hook _humanSetFreeCompanyCrestVisibleOnSlot; + private readonly Hook _weaponSetFreeCompanyCrestVisibleOnSlot; + + private void HumanSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) + { + var slot = ((uint)slotIdx).ToEquipSlot(); + var rVisible = visible != 0; + var inUpdate = _inUpdate.InMethod; + if (!inUpdate) + Invoke(drawObject, slot, ref rVisible); + Glamourer.Log.Excessive( + $"[Human.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} for slot {slot} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); + _humanSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); + } + + private void WeaponSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) + { + var rVisible = visible != 0; + var inUpdate = _inUpdate.InMethod; + if (!inUpdate) + Invoke(drawObject, EquipSlot.BothHand, ref rVisible); + Glamourer.Log.Excessive( + $"[Weapon.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); + _weaponSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); + } +} diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index f336f5a..f5a0ec0 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -21,9 +21,7 @@ public unsafe class UpdateSlotService : IDisposable } public void Dispose() - { - _flagSlotForUpdateHook.Dispose(); - } + => _flagSlotForUpdateHook.Dispose(); public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data) { diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 092d8c2..93a9854 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -98,6 +98,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 3fdf4f7..3cef72a 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -490,7 +490,7 @@ public class StateListener : IDisposable private void OnVisorChange(Model model, Ref value) { // Skip updates when in customize update. - if (ChangeCustomizeService.InUpdate.IsValueCreated && ChangeCustomizeService.InUpdate.Value) + if (ChangeCustomizeService.InUpdate.InMethod) return; // Find appropriate actor and state. diff --git a/OtterGui b/OtterGui index 3e2d4ae..f624aca 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 3e2d4ae934694918d312280d62127cf1a55b03e4 +Subproject commit f624aca526bd1f36364d63443ed1c6e83499b8b7 From 358e33346f6c6f2f03ce076ca86a6e7a526796fe Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Dec 2023 21:44:23 +0100 Subject: [PATCH 080/786] Rework some stuff, add debug tab. --- Glamourer.GameData/Structs/CrestFlag.cs | 34 ++++++++++--- Glamourer/Gui/Tabs/DebugTab.cs | 29 +++++++++-- Glamourer/Interop/CrestService.cs | 68 +++++++++++++++++++++++-- Glamourer/Interop/Structs/Actor.cs | 17 ++++--- Glamourer/Interop/Structs/Model.cs | 32 ------------ Glamourer/State/ActorState.cs | 2 +- 6 files changed, 129 insertions(+), 53 deletions(-) diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs index 3ef9f1e..11a7260 100644 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; using Penumbra.GameData.Enums; namespace Glamourer.Structs; @@ -23,17 +22,22 @@ public enum CrestFlag : ushort OffHand = 0x0800, } +public enum CrestType : byte +{ + None, + Human, + Mainhand, + Offhand, +}; + public static class CrestExtensions { public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand; - public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => f.ToRelevantIndex() >= 0).ToArray(); + public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => AllRelevant.HasFlag(f)).ToArray(); - public static int ToIndex(this CrestFlag flag) - => BitOperations.TrailingZeroCount((uint)flag); - - public static int ToRelevantIndex(this CrestFlag flag) + public static int ToInternalIndex(this CrestFlag flag) => flag switch { CrestFlag.Head => 0, @@ -42,6 +46,24 @@ public static class CrestExtensions _ => -1, }; + public static (CrestType Type, byte Index) ToIndex(this CrestFlag flag) + => flag switch + { + CrestFlag.Head => (CrestType.Human, 0), + CrestFlag.Body => (CrestType.Human, 1), + CrestFlag.Hands => (CrestType.Human, 2), + CrestFlag.Legs => (CrestType.Human, 3), + CrestFlag.Feet => (CrestType.Human, 4), + CrestFlag.Ears => (CrestType.None, 0), + CrestFlag.Neck => (CrestType.None, 0), + CrestFlag.Wrists => (CrestType.None, 0), + CrestFlag.RFinger => (CrestType.None, 0), + CrestFlag.LFinger => (CrestType.None, 0), + CrestFlag.MainHand => (CrestType.None, 0), + CrestFlag.OffHand => (CrestType.Offhand, 0), + _ => (CrestType.None, 0), + }; + public static CrestFlag ToCrestFlag(this EquipSlot slot) => slot switch { diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index 7d946fb..52385b8 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -21,6 +21,7 @@ using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.State; +using Glamourer.Structs; using Glamourer.Unlocks; using Glamourer.Utility; using ImGuiNET; @@ -43,6 +44,7 @@ public unsafe class DebugTab : ITab private readonly VisorService _visorService; private readonly ChangeCustomizeService _changeCustomizeService; private readonly UpdateSlotService _updateSlotService; + private readonly CrestService _crestService; private readonly WeaponService _weaponService; private readonly MetaService _metaService; private readonly InventoryService _inventoryService; @@ -50,7 +52,7 @@ public unsafe class DebugTab : ITab private readonly ObjectManager _objectManager; private readonly GlamourerIpc _ipc; private readonly CodeService _code; - private readonly ImportService _importService; + private readonly ImportService _importService; private readonly ItemManager _items; private readonly ActorService _actors; @@ -82,7 +84,7 @@ public unsafe class DebugTab : ITab PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface, AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks, ItemUnlockManager itemUnlocks, DesignConverter designConverter, ImportService importService, InventoryService inventoryService, - HumanModelList humans, FunModule funModule) + HumanModelList humans, FunModule funModule, CrestService crestService) { _changeCustomizeService = changeCustomizeService; _visorService = visorService; @@ -107,10 +109,11 @@ public unsafe class DebugTab : ITab _customizeUnlocks = customizeUnlocks; _itemUnlocks = itemUnlocks; _designConverter = designConverter; - _importService = importService; + _importService = importService; _inventoryService = inventoryService; _humans = humans; _funModule = funModule; + _crestService = crestService; } public ReadOnlySpan Label @@ -200,6 +203,7 @@ public unsafe class DebugTab : ITab DrawWetness(actor, model); DrawEquip(actor, model); DrawCustomize(actor, model); + DrawCrests(actor, model); } private string _objectFilter = string.Empty; @@ -477,6 +481,25 @@ public unsafe class DebugTab : ITab } } + private void DrawCrests(Actor actor, Model model) + { + using var id = ImRaii.PushId("Crests"); + foreach (var crestFlag in CrestExtensions.AllRelevantSet) + { + id.Push((int)crestFlag); + var modelCrest = CrestService.GetModelCrest(actor, crestFlag); + ImGuiUtil.DrawTableColumn($"{crestFlag.ToLabel()} Crest"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetCrest(crestFlag).ToString() : "No Character"); + ImGuiUtil.DrawTableColumn(modelCrest.ToString()); + + ImGui.TableNextColumn(); + if (model.IsHuman && ImGui.SmallButton("Toggle")) + _crestService.UpdateCrest(actor, crestFlag, !modelCrest); + + id.Pop(); + } + } + #endregion #region Penumbra diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index edc61a5..253a4ea 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -2,7 +2,10 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Interop.Structs; +using Glamourer.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; @@ -27,6 +30,7 @@ public sealed unsafe class CrestService : EventWrapper(_humanVTable[96], HumanSetFreeCompanyCrestVisibleOnSlotDetour); _weaponSetFreeCompanyCrestVisibleOnSlot = @@ -48,10 +52,68 @@ public sealed unsafe class CrestService : EventWrapper)((nint*)model.AsCharacterBase->VTable)[95]; + return getter(model.AsHuman, index) != 0; + } + case CrestType.Offhand: + { + var model = (Model)gameObject.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject; + if (!model.IsWeapon) + return false; + + var getter = (delegate* unmanaged)((nint*)model.AsCharacterBase->VTable)[95]; + return getter(model.AsWeapon, index) != 0; + } + } + + return false; + } + + public void UpdateCrest(Actor gameObject, CrestFlag slot, bool crest) + { + if (!gameObject.IsCharacter) + return; + + var (type, index) = slot.ToIndex(); + switch (type) + { + case CrestType.Human: + { + var model = gameObject.Model; + if (!model.IsHuman) + return; + + using var _ = _inUpdate.EnterMethod(); + var setter = (delegate* unmanaged)((nint*)model.AsCharacterBase->VTable)[96]; + setter(model.AsHuman, index, crest ? (byte)1 : (byte)0); + break; + } + case CrestType.Offhand: + { + var model = (Model)gameObject.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject; + if (!model.IsWeapon) + return; + + using var _ = _inUpdate.EnterMethod(); + var setter = (delegate* unmanaged)((nint*)model.AsCharacterBase->VTable)[96]; + setter(model.AsWeapon, index, crest ? (byte)1 : (byte)0); + break; + } + } } private readonly InMethodChecker _inUpdate = new(); diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index ba7f132..0a6196b 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -3,6 +3,7 @@ using System; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Customization; +using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; @@ -106,7 +107,7 @@ public readonly unsafe struct Actor : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()]; - public bool GetCrest(EquipSlot slot) + public bool GetCrest(CrestFlag slot) => (GetFreeCompanyCrestBitfield() & CrestMask(slot)) != 0; public CharacterWeapon GetMainhand() @@ -122,15 +123,15 @@ public readonly unsafe struct Actor : IEquatable private byte GetFreeCompanyCrestBitfield() => ((byte*)Address)[0x1BBB]; - private static byte CrestMask(EquipSlot slot) + private static byte CrestMask(CrestFlag slot) => slot switch { - EquipSlot.OffHand => 0x01, - EquipSlot.Head => 0x02, - EquipSlot.Body => 0x04, - EquipSlot.Hands => 0x08, - EquipSlot.Legs => 0x10, - EquipSlot.Feet => 0x20, + CrestFlag.OffHand => 0x01, + CrestFlag.Head => 0x02, + CrestFlag.Body => 0x04, + CrestFlag.Hands => 0x08, + CrestFlag.Legs => 0x10, + CrestFlag.Feet => 0x20, _ => 0x00, }; diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index 811ff81..77bf24e 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -91,9 +91,6 @@ public readonly unsafe struct Model : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()]; - public bool GetCrest(EquipSlot slot) - => IsFreeCompanyCrestVisibleOnSlot(slot); - public Customize GetCustomize() => *(Customize*)&AsHuman->Customize; @@ -198,35 +195,6 @@ public readonly unsafe struct Model : IEquatable return discriminator1 == 0 && discriminator2 != 0 ? (second, first) : (first, second); } - // TODO remove these when available in ClientStructs - private bool IsFreeCompanyCrestVisibleOnSlot(EquipSlot slot) - { - if (!IsCharacterBase) - return false; - - var index = (byte)slot.ToIndex(); - if (index >= 12) - return false; - - var characterBase = AsCharacterBase; - var getter = (delegate* unmanaged)((nint*)characterBase->VTable)[95]; - return getter(characterBase, index) != 0; - } - - public void SetFreeCompanyCrestVisibleOnSlot(EquipSlot slot, bool visible) - { - if (!IsCharacterBase) - return; - - var index = (byte)slot.ToIndex(); - if (index >= 12) - return; - - var characterBase = AsCharacterBase; - var setter = (delegate* unmanaged)((nint*)characterBase->VTable)[96]; - setter(characterBase, index, visible ? (byte)1 : (byte)0); - } - public override string ToString() => $"0x{Address:X}"; } diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index f3372e8..3cd7cba 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -90,7 +90,7 @@ public class ActorState => ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)]; public ref StateChanged.Source this[CrestFlag slot] - => ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToRelevantIndex()]; + => ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()]; public ref StateChanged.Source this[CustomizeIndex type] => ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type]; From cc09cced612aded2e7e48c61f35cbb41db9bc6cf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 2 Dec 2023 16:33:01 +0100 Subject: [PATCH 081/786] Revamp, temp state. --- Glamourer.GameData/Structs/CrestFlag.cs | 44 +++------ Glamourer/Automation/AutoDesign.cs | 21 +++-- Glamourer/Automation/AutoDesignApplier.cs | 22 ++++- Glamourer/Designs/Design.cs | 9 +- Glamourer/Designs/DesignBase.cs | 51 +++++----- Glamourer/Designs/DesignConverter.cs | 66 ++++++------- Glamourer/Designs/DesignData.cs | 14 +-- Glamourer/Designs/DesignManager.cs | 14 ++- Glamourer/Gui/DesignQuickBar.cs | 4 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 24 ++--- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 4 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 19 ++-- Glamourer/Gui/ToggleDrawData.cs | 12 +-- Glamourer/Gui/UiHelpers.cs | 10 +- Glamourer/Interop/CrestService.cs | 97 ++++++++++---------- Glamourer/Interop/Structs/Actor.cs | 18 +--- Glamourer/Interop/WeaponService.cs | 8 +- Glamourer/Services/CommandService.cs | 6 +- Glamourer/State/StateApplier.cs | 44 ++++----- Glamourer/State/StateEditor.cs | 13 +++ Glamourer/State/StateListener.cs | 68 +++++++++++++- Glamourer/State/StateManager.cs | 95 +++++++++++-------- 22 files changed, 365 insertions(+), 298 deletions(-) diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs index 11a7260..61ccc7e 100644 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -8,18 +8,18 @@ namespace Glamourer.Structs; [Flags] public enum CrestFlag : ushort { - Head = 0x0001, - Body = 0x0002, - Hands = 0x0004, - Legs = 0x0008, - Feet = 0x0010, - Ears = 0x0020, - Neck = 0x0040, - Wrists = 0x0080, - RFinger = 0x0100, - LFinger = 0x0200, - MainHand = 0x0400, - OffHand = 0x0800, + OffHand = 0x0001, + Head = 0x0002, + Body = 0x0004, + Hands = 0x0008, + Legs = 0x0010, + Feet = 0x0020, + Ears = 0x0040, + Neck = 0x0080, + Wrists = 0x0100, + RFinger = 0x0200, + LFinger = 0x0400, + MainHand = 0x0800, } public enum CrestType : byte @@ -32,7 +32,7 @@ public enum CrestType : byte public static class CrestExtensions { - public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); + public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Mainhand << 1) - 1); public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand; public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => AllRelevant.HasFlag(f)).ToArray(); @@ -82,24 +82,6 @@ public static class CrestExtensions _ => 0, }; - public static EquipSlot ToSlot(this CrestFlag flag) - => flag switch - { - CrestFlag.MainHand => EquipSlot.MainHand, - CrestFlag.OffHand => EquipSlot.OffHand, - CrestFlag.Head => EquipSlot.Head, - CrestFlag.Body => EquipSlot.Body, - CrestFlag.Hands => EquipSlot.Hands, - CrestFlag.Legs => EquipSlot.Legs, - CrestFlag.Feet => EquipSlot.Feet, - CrestFlag.Ears => EquipSlot.Ears, - CrestFlag.Neck => EquipSlot.Neck, - CrestFlag.Wrists => EquipSlot.Wrists, - CrestFlag.RFinger => EquipSlot.RFinger, - CrestFlag.LFinger => EquipSlot.LFinger, - _ => 0, - }; - public static string ToLabel(this CrestFlag flag) => flag switch { diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 4cde895..0a26759 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -15,13 +15,13 @@ public class AutoDesign [Flags] public enum Type : byte { - Armor = 0x01, - Customizations = 0x02, - Weapons = 0x04, - Stains = 0x08, - Accessories = 0x10, + Armor = 0x01, + Customizations = 0x02, + Weapons = 0x04, + GearCustomization = 0x08, + Accessories = 0x10, - All = Armor | Accessories | Customizations | Weapons | Stains, + All = Armor | Accessories | Customizations | Weapons | GearCustomization, } public Design? Design; @@ -80,19 +80,20 @@ public class AutoDesign return ret; } - public (EquipFlag Equip, CustomizeFlag Customize, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat() + public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat() { var equipFlags = (ApplicationType.HasFlag(Type.Weapons) ? WeaponFlags : 0) | (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0) | (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0) - | (ApplicationType.HasFlag(Type.Stains) ? StainFlags : 0); + | (ApplicationType.HasFlag(Type.GearCustomization) ? StainFlags : 0); var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0; + var crestFlag = ApplicationType.HasFlag(Type.GearCustomization) ? CrestExtensions.AllRelevant : 0; if (Revert) - return (equipFlags, customizeFlags, ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Armor), + return (equipFlags, customizeFlags, crestFlag, ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations)); - return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, + return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, crestFlag & Design.ApplyCrest, ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(), ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(), ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(), diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 4b76943..ddc8636 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -268,6 +268,7 @@ public class AutoDesignApplier : IDisposable { EquipFlag totalEquipFlags = 0; CustomizeFlag totalCustomizeFlags = 0; + CrestFlag totalCrestFlags = 0; byte totalMetaFlags = 0; if (set.BaseState == AutoDesignSet.Base.Game) _state.ResetStateFixed(state); @@ -291,10 +292,11 @@ public class AutoDesignApplier : IDisposable if (!data.IsHuman) continue; - var (equipFlags, customizeFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat(); + var (equipFlags, customizeFlags, crestFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat(); ReduceMeta(state, data, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual, source); ReduceCustomize(state, data, customizeFlags, ref totalCustomizeFlags, respectManual, source); ReduceEquip(state, data, equipFlags, ref totalEquipFlags, respectManual, source, fromJobChange); + ReduceCrests(state, data, crestFlags, ref totalCrestFlags, respectManual, source); } if (totalCustomizeFlags != 0) @@ -324,6 +326,24 @@ public class AutoDesignApplier : IDisposable } } + private void ReduceCrests(ActorState state, in DesignData design, CrestFlag crestFlags, ref CrestFlag totalCrestFlags, bool respectManual, + StateChanged.Source source) + { + crestFlags &= ~totalCrestFlags; + if (crestFlags == 0) + return; + + foreach (var slot in CrestExtensions.AllRelevantSet) + { + if (!crestFlags.HasFlag(slot)) + continue; + + if (!respectManual || state[slot] is not StateChanged.Source.Manual) + _state.ChangeCrest(state, slot, design.Crest(slot), source); + totalCrestFlags |= slot; + } + } + private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual, StateChanged.Source source, bool fromJobChange) { diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 5c106e3..d10fe29 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Dalamud.Interface.Internal.Notifications; -using Glamourer.Customization; -using Glamourer.Gui; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; @@ -28,8 +26,8 @@ public sealed class Design : DesignBase, ISavable internal Design(Design other) : base(other) { - Tags = Tags.ToArray(); - Description = Description; + Tags = other.Tags.ToArray(); + Description = other.Description; AssociatedMods = new SortedList(other.AssociatedMods); } @@ -69,8 +67,7 @@ public sealed class Design : DesignBase, ISavable ["Equipment"] = SerializeEquipment(), ["Customize"] = SerializeCustomize(), ["Mods"] = SerializeMods(), - } - ; + }; return ret; } diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 135c858..d859e8e 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Internal.Notifications; using Glamourer.Customization; using Glamourer.Services; using Glamourer.Structs; @@ -9,6 +7,8 @@ using OtterGui.Classes; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using System; +using System.Linq; namespace Glamourer.Designs; @@ -169,8 +169,8 @@ public class DesignBase public bool DoApplyCustomize(CustomizeIndex idx) => ApplyCustomize.HasFlag(idx.ToFlag()); - public bool DoApplyCrest(EquipSlot slot) - => ApplyCrest.HasFlag(slot.ToCrestFlag()); + public bool DoApplyCrest(CrestFlag slot) + => ApplyCrest.HasFlag(slot); internal bool SetApplyEquip(EquipSlot slot, bool value) { @@ -202,9 +202,9 @@ public class DesignBase return true; } - internal bool SetApplyCrest(EquipSlot slot, bool value) + internal bool SetApplyCrest(CrestFlag slot, bool value) { - var newValue = value ? ApplyCrest | slot.ToCrestFlag() : ApplyCrest & ~slot.ToCrestFlag(); + var newValue = value ? ApplyCrest | slot : ApplyCrest & ~slot; if (newValue == ApplyCrest) return false; @@ -212,28 +212,32 @@ public class DesignBase return true; } - internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags) - => new(this, equipFlags, customizeFlags); + internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) + => new(this, equipFlags, customizeFlags, crestFlags); internal readonly struct FlagRestrictionResetter : IDisposable { private readonly DesignBase _design; private readonly EquipFlag _oldEquipFlags; private readonly CustomizeFlag _oldCustomizeFlags; + private readonly CrestFlag _oldCrestFlags; - public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags) + public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { _design = d; _oldEquipFlags = d.ApplyEquip; _oldCustomizeFlags = d.ApplyCustomizeRaw; + _oldCrestFlags = d.ApplyCrest; d.ApplyEquip &= equipFlags; d.ApplyCustomize &= customizeFlags; + d.ApplyCrest &= crestFlags; } public void Dispose() { _design.ApplyEquip = _oldEquipFlags; _design.ApplyCustomize = _oldCustomizeFlags; + _design.ApplyCrest = _oldCrestFlags; } } @@ -275,10 +279,11 @@ public class DesignBase { foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) { - var item = _designData.Item(slot); - var stain = _designData.Stain(slot); - var crest = _designData.Crest(slot); - ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(slot)); + var item = _designData.Item(slot); + var stain = _designData.Stain(slot); + var crestSlot = slot.ToCrestFlag(); + var crest = _designData.Crest(crestSlot); + ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot)); } ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply"); @@ -365,7 +370,7 @@ public class DesignBase { var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot).Id; var stain = (StainId)(item?["Stain"]?.ToObject() ?? 0); - var crest = (item?["Crest"]?.ToObject() ?? false); + var crest = item?["Crest"]?.ToObject() ?? false; var apply = item?["Apply"]?.ToObject() ?? false; var applyStain = item?["ApplyStain"]?.ToObject() ?? false; var applyCrest = item?["ApplyCrest"]?.ToObject() ?? false; @@ -384,19 +389,21 @@ public class DesignBase PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); + var crestSlot = slot.ToCrestFlag(); design._designData.SetItem(slot, item); design._designData.SetStain(slot, stain); - design._designData.SetCrest(slot, crest); + design._designData.SetCrest(crestSlot, crest); design.SetApplyEquip(slot, apply); design.SetApplyStain(slot, applyStain); - design.SetApplyCrest(slot, applyCrest); + design.SetApplyCrest(crestSlot, applyCrest); } { var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.MainHand)) id = items.DefaultSword.ItemId; - var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); + var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) = + ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.OffHand)) id = ItemManager.NothingId(FullEquipType.Shield); @@ -407,14 +414,14 @@ public class DesignBase design._designData.SetItem(EquipSlot.OffHand, off); design._designData.SetStain(EquipSlot.MainHand, stain); design._designData.SetStain(EquipSlot.OffHand, stainOff); - design._designData.SetCrest(EquipSlot.MainHand, crest); - design._designData.SetCrest(EquipSlot.OffHand, crestOff); + design._designData.SetCrest(CrestFlag.MainHand, crest); + design._designData.SetCrest(CrestFlag.OffHand, crestOff); design.SetApplyEquip(EquipSlot.MainHand, apply); design.SetApplyEquip(EquipSlot.OffHand, applyOff); design.SetApplyStain(EquipSlot.MainHand, applyStain); design.SetApplyStain(EquipSlot.OffHand, applyStainOff); - design.SetApplyCrest(EquipSlot.MainHand, applyCrest); - design.SetApplyCrest(EquipSlot.OffHand, applyCrestOff); + design.SetApplyCrest(CrestFlag.MainHand, applyCrest); + design.SetApplyCrest(CrestFlag.OffHand, applyCrestOff); } var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse); design.SetApplyHatVisible(metaValue.Enabled); diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index e8b1742..cd6db7e 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -13,22 +13,9 @@ using Penumbra.GameData.Enums; namespace Glamourer.Designs; -public class DesignConverter +public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizationService _customize, HumanModelList _humans) { - public const byte Version = 5; - - private readonly ItemManager _items; - private readonly DesignManager _designs; - private readonly CustomizationService _customize; - private readonly HumanModelList _humans; - - public DesignConverter(ItemManager items, DesignManager designs, CustomizationService customize, HumanModelList humans) - { - _items = items; - _designs = designs; - _customize = customize; - _humans = humans; - } + public const byte Version = 6; public JObject ShareJObject(DesignBase design) => design.JsonSerialize(); @@ -36,32 +23,33 @@ public class DesignConverter public JObject ShareJObject(Design design) => design.JsonSerialize(); - public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags) + public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { - var design = Convert(state, equipFlags, customizeFlags); + var design = Convert(state, equipFlags, customizeFlags, crestFlags); return ShareJObject(design); } public string ShareBase64(Design design) - => ShareBackwardCompatible(ShareJObject(design), design); + => ShareBase64(ShareJObject(design)); public string ShareBase64(DesignBase design) - => ShareBackwardCompatible(ShareJObject(design), design); + => ShareBase64(ShareJObject(design)); public string ShareBase64(ActorState state) - => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All); + => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All); - public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags) + public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { - var design = Convert(state, equipFlags, customizeFlags); - return ShareBackwardCompatible(ShareJObject(design), design); + var design = Convert(state, equipFlags, customizeFlags, crestFlags); + return ShareBase64(ShareJObject(design)); } - public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags) + public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { var design = _designs.CreateTemporary(); design.ApplyEquip = equipFlags & EquipFlagExtensions.All; - design.ApplyCustomize = customizeFlags; + design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; + design.ApplyCrest = crestFlags & CrestExtensions.All; design.SetApplyHatVisible(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand)); @@ -123,6 +111,16 @@ public class DesignConverter : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } + case Version: + { + version = bytes.DecompressToString(out var decompressed); + var jObj2 = JObject.Parse(decompressed); + Debug.Assert(version == Version); + ret = jObj2["Identifier"] != null + ? Design.LoadDesign(_customize, _items, jObj2) + : DesignBase.LoadDesignBase(_customize, _items, jObj2); + break; + } default: throw new Exception($"Unknown Version {bytes[0]}."); } } @@ -138,6 +136,7 @@ public class DesignConverter if (!equip) { ret.ApplyEquip = 0; + ret.ApplyCrest = 0; ret.SetApplyHatVisible(false); ret.SetApplyWeaponVisible(false); ret.SetApplyVisorToggle(false); @@ -146,23 +145,10 @@ public class DesignConverter return ret; } - private static string ShareBase64(JObject jObj) + private static string ShareBase64(JObject jObject) { - var json = jObj.ToString(Formatting.None); + var json = jObject.ToString(Formatting.None); var compressed = json.Compress(Version); return System.Convert.ToBase64String(compressed); } - - private static string ShareBackwardCompatible(JObject jObject, DesignBase design) - { - var oldBase64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, - design.DoApplyHatVisible(), design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected(), 1f); - var oldBytes = System.Convert.FromBase64String(oldBase64); - var json = jObject.ToString(Formatting.None); - var compressed = json.Compress(Version); - var bytes = new byte[oldBytes.Length + compressed.Length]; - oldBytes.CopyTo(bytes, 0); - compressed.CopyTo(bytes, oldBytes.Length); - return System.Convert.ToBase64String(bytes); - } } diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index fe2e655..4b0d53b 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -61,8 +61,8 @@ public unsafe struct DesignData return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3]; } - public readonly bool Crest(EquipSlot slot) - => CrestVisibility.HasFlag(slot.ToCrestFlag()); + public readonly bool Crest(CrestFlag slot) + => CrestVisibility.HasFlag(slot); public FullEquipType MainhandType @@ -179,9 +179,9 @@ public unsafe struct DesignData _ => false, }; - public bool SetCrest(EquipSlot slot, bool visible) + public bool SetCrest(CrestFlag slot, bool visible) { - var newValue = visible ? CrestVisibility | slot.ToCrestFlag() : CrestVisibility & ~slot.ToCrestFlag(); + var newValue = visible ? CrestVisibility | slot : CrestVisibility & ~slot; if (newValue == CrestVisibility) return false; @@ -244,15 +244,15 @@ public unsafe struct DesignData { SetItem(slot, ItemManager.NothingItem(slot)); SetStain(slot, 0); - SetCrest(slot, false); + SetCrest(slot.ToCrestFlag(), false); } SetItem(EquipSlot.MainHand, items.DefaultSword); SetStain(EquipSlot.MainHand, 0); - SetCrest(EquipSlot.MainHand, false); + SetCrest(CrestFlag.MainHand, false); SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); SetStain(EquipSlot.OffHand, 0); - SetCrest(EquipSlot.OffHand, false); + SetCrest(CrestFlag.OffHand, false); } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 7159bac..392301f 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -8,6 +8,7 @@ using Glamourer.Events; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; +using Glamourer.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; @@ -447,7 +448,7 @@ public class DesignManager } /// Change the crest visibility for any equipment piece. - public void ChangeCrest(Design design, EquipSlot slot, bool crest) + public void ChangeCrest(Design design, CrestFlag slot, bool crest) { var oldCrest = design.DesignData.Crest(slot); if (!design.GetDesignDataRef().SetCrest(slot, crest)) @@ -460,7 +461,7 @@ public class DesignManager } /// Change whether to apply a specific crest visibility. - public void ChangeApplyCrest(Design design, EquipSlot slot, bool value) + public void ChangeApplyCrest(Design design, CrestFlag slot, bool value) { if (!design.SetApplyCrest(slot, value)) return; @@ -539,7 +540,10 @@ public class DesignManager if (other.DoApplyStain(slot)) ChangeStain(design, slot, other.DesignData.Stain(slot)); + } + foreach (var slot in Enum.GetValues()) + { if (other.DoApplyCrest(slot)) ChangeCrest(design, slot, other.DesignData.Crest(slot)); } @@ -556,12 +560,6 @@ public class DesignManager if (other.DoApplyStain(EquipSlot.OffHand)) ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand)); - - if (other.DoApplyCrest(EquipSlot.MainHand)) - ChangeCrest(design, EquipSlot.MainHand, other.DesignData.Crest(EquipSlot.MainHand)); - - if (other.DoApplyCrest(EquipSlot.OffHand)) - ChangeCrest(design, EquipSlot.OffHand, other.DesignData.Crest(EquipSlot.OffHand)); } public void UndoDesignChange(Design design) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 4346f01..68abd0a 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -159,8 +159,8 @@ public class DesignQuickBar : Window, IDisposable return; } - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); - using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index e4006ad..a294b08 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -5,7 +5,6 @@ using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; -using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using Glamourer.Automation; using Glamourer.Customization; using Glamourer.Designs; @@ -16,6 +15,7 @@ using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.State; +using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -167,21 +167,21 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.Head, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.Body, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.OffHand, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!)); } } @@ -296,8 +296,8 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus { ImGui.OpenPopup("Save as Design"); _newName = _state!.Identifier.ToName(); - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); - _newDesign = _converter.Convert(_state, applyGear, applyCustomize); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest); } private void SaveDesignDrawPopup() @@ -332,8 +332,8 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus { try { - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); - var text = _converter.ShareBase64(_state!, applyGear, applyCustomize); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + var text = _converter.ShareBase64(_state!, applyGear, applyCustomize, applyCrest); ImGui.SetClipboardText(text); } catch (Exception ex) @@ -372,9 +372,9 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) return; - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize), state, + _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state, StateChanged.Source.Manual); } @@ -390,9 +390,9 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) return; - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize), state, + _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state, StateChanged.Source.Manual); } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 918f96a..6b0e301 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -286,7 +286,7 @@ public class SetPanel var size = new Vector2(ImGui.GetFrameHeight()); size.X += ImGuiHelpers.GlobalScale; - var (equipFlags, customizeFlags, _, _, _, _) = design.ApplyWhat(); + var (equipFlags, customizeFlags, _, _, _, _, _) = design.ApplyWhat(); var sb = new StringBuilder(); foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)) { @@ -457,7 +457,7 @@ public class SetPanel "Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."), (AutoDesign.Type.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."), (AutoDesign.Type.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."), - (AutoDesign.Type.Stains, "Apply all dye changes that are enabled in this design."), + (AutoDesign.Type.GearCustomization, "Apply all dye and crest changes that are enabled in this design."), (AutoDesign.Type.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), }; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 129b5c7..b6e5fa3 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -113,21 +113,21 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.Head, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selector.Selected!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.Body, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selector.Selected!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.OffHand, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!)); } } @@ -191,10 +191,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); foreach (var flag in CrestExtensions.AllRelevantSet) { - var slot = flag.ToSlot(); - var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(slot); + var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(flag); if (ImGui.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) - _manager.ChangeApplyCrest(_selector.Selected!, slot, apply); + _manager.ChangeApplyCrest(_selector.Selected!, flag, apply); } } @@ -389,8 +388,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); - using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); } } @@ -408,8 +407,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); - using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize); + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); } } diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index edb06ae..dda4584 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -52,10 +52,10 @@ public ref struct ToggleDrawData }; } - public static ToggleDrawData CrestFromDesign(EquipSlot slot, DesignManager manager, Design design) + public static ToggleDrawData CrestFromDesign(CrestFlag slot, DesignManager manager, Design design) => new() { - Label = $"{slot.ToCrestFlag().ToLabel()} Crest", + Label = $"{slot.ToLabel()} Crest", Tooltip = string.Empty, Locked = design.WriteProtected(), DisplayApplication = true, @@ -65,14 +65,14 @@ public ref struct ToggleDrawData SetApply = v => manager.ChangeApplyCrest(design, slot, v), }; - public static ToggleDrawData CrestFromState(EquipSlot slot, StateManager manager, ActorState state) + public static ToggleDrawData CrestFromState(CrestFlag slot, StateManager manager, ActorState state) => new() { - Label = $"{slot.ToCrestFlag().ToLabel()} Crest", + Label = $"{slot.ToLabel()} Crest", Tooltip = "Hide or show your free company crest on this piece of gear.", Locked = state.IsLocked, - CurrentValue = state.ModelData.Crest(slot), // TODO - SetValue = v => { }, //manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual), + CurrentValue = state.ModelData.Crest(slot), + SetValue = v => manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual), }; public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state) diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 81aaf3c..6e83838 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -88,13 +88,13 @@ public static class UiHelpers return (currentValue != newValue, currentApply != newApply); } - public static (EquipFlag, CustomizeFlag) ConvertKeysToFlags() + public static (EquipFlag, CustomizeFlag, CrestFlag) ConvertKeysToFlags() => (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch { - (false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant), - (true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant), - (true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0), - (false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant), + (false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All), + (true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All), + (true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0, CrestExtensions.All), + (false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant, 0), }; public static (bool, bool) ConvertKeysToBool() diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 253a4ea..9285ec6 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; @@ -19,11 +20,11 @@ namespace Glamourer.Interop; /// Parameter is the whether the crest will be shown. /// /// -public sealed unsafe class CrestService : EventWrapper>, CrestService.Priority>, IDisposable +public sealed unsafe class CrestService : EventWrapper>, CrestService.Priority> { public enum Priority { - /// + /// StateListener = 0, } @@ -37,19 +38,51 @@ public sealed unsafe class CrestService : EventWrapper(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour); _humanSetFreeCompanyCrestVisibleOnSlot.Enable(); _weaponSetFreeCompanyCrestVisibleOnSlot.Enable(); + _crestChangeHook.Enable(); } + public void UpdateCrests(Actor gameObject, CrestFlag flags) + { + if (!gameObject.IsCharacter) + return; + + flags &= CrestExtensions.AllRelevant; + var currentCrests = gameObject.CrestBitfield; + using var update = _inUpdate.EnterMethod(); + _crestChangeHook.Original(gameObject.AsCharacter, (byte) flags); + gameObject.CrestBitfield = currentCrests; + } + + public delegate void DrawObjectCrestUpdateDelegate(Model drawObject, CrestFlag slot, ref bool value); + + public event DrawObjectCrestUpdateDelegate? ModelCrestSetup; + protected override void Dispose(bool _) { _humanSetFreeCompanyCrestVisibleOnSlot.Dispose(); _weaponSetFreeCompanyCrestVisibleOnSlot.Dispose(); + _crestChangeHook.Dispose(); } - public void Invoke(Model model, EquipSlot slot, ref bool visible) + private delegate void CrestChangeDelegate(Character* character, byte crestFlags); + + [Signature("E8 ?? ?? ?? ?? 48 8B 55 ?? 49 8B CE E8", DetourName = nameof(CrestChangeDetour))] + private readonly Hook _crestChangeHook = null!; + + private void CrestChangeDetour(Character* character, byte crestFlags) { - var ret = new Ref(visible); - Invoke(this, model, slot, ret); - visible = ret; + var actor = (Actor)character; + foreach (var slot in CrestExtensions.AllRelevantSet) + { + var newValue = new Ref(((CrestFlag)crestFlags).HasFlag(slot)); + Invoke(this, actor, slot, newValue); + crestFlags = (byte)(newValue.Value ? crestFlags | (byte)slot : crestFlags & (byte)~slot); + } + + Glamourer.Log.Information( + $"Called CrestChange on {(ulong)character:X} with {crestFlags:X} and prior flags {((Actor)character).CrestBitfield}."); + using var _ = _inUpdate.EnterMethod(); + _crestChangeHook.Original(character, crestFlags); } public static bool GetModelCrest(Actor gameObject, CrestFlag slot) @@ -83,42 +116,9 @@ public sealed unsafe class CrestService : EventWrapper)((nint*)model.AsCharacterBase->VTable)[96]; - setter(model.AsHuman, index, crest ? (byte)1 : (byte)0); - break; - } - case CrestType.Offhand: - { - var model = (Model)gameObject.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject; - if (!model.IsWeapon) - return; - - using var _ = _inUpdate.EnterMethod(); - var setter = (delegate* unmanaged)((nint*)model.AsCharacterBase->VTable)[96]; - setter(model.AsWeapon, index, crest ? (byte)1 : (byte)0); - break; - } - } - } - private readonly InMethodChecker _inUpdate = new(); - private delegate void SetCrestDelegateIntern(nint drawObject, byte slot, byte visible); + private delegate void SetCrestDelegateIntern(DrawObject* drawObject, byte slot, byte visible); [Signature(global::Penumbra.GameData.Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] private readonly nint* _humanVTable = null!; @@ -129,26 +129,27 @@ public sealed unsafe class CrestService : EventWrapper _humanSetFreeCompanyCrestVisibleOnSlot; private readonly Hook _weaponSetFreeCompanyCrestVisibleOnSlot; - private void HumanSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) + private void HumanSetFreeCompanyCrestVisibleOnSlotDetour(DrawObject* drawObject, byte slotIdx, byte visible) { - var slot = ((uint)slotIdx).ToEquipSlot(); var rVisible = visible != 0; var inUpdate = _inUpdate.InMethod; + var slot = (CrestFlag)((ushort)CrestFlag.Head << slotIdx); if (!inUpdate) - Invoke(drawObject, slot, ref rVisible); + ModelCrestSetup?.Invoke(drawObject, slot, ref rVisible); + Glamourer.Log.Excessive( - $"[Human.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} for slot {slot} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); + $"[Human.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{(ulong)drawObject:X} for slot {slot} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); _humanSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); } - private void WeaponSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) + private void WeaponSetFreeCompanyCrestVisibleOnSlotDetour(DrawObject* drawObject, byte slotIdx, byte visible) { var rVisible = visible != 0; var inUpdate = _inUpdate.InMethod; - if (!inUpdate) - Invoke(drawObject, EquipSlot.BothHand, ref rVisible); + if (!inUpdate && slotIdx == 0) + ModelCrestSetup?.Invoke(drawObject, CrestFlag.OffHand, ref rVisible); Glamourer.Log.Excessive( - $"[Weapon.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); + $"[Weapon.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{(ulong)drawObject:X} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); _weaponSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); } } diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 0a6196b..750527b 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -108,7 +108,7 @@ public readonly unsafe struct Actor : IEquatable => ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()]; public bool GetCrest(CrestFlag slot) - => (GetFreeCompanyCrestBitfield() & CrestMask(slot)) != 0; + => CrestBitfield.HasFlag(slot); public CharacterWeapon GetMainhand() => new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).ModelId.Value); @@ -120,20 +120,8 @@ public readonly unsafe struct Actor : IEquatable => *(Customize*)&AsCharacter->DrawData.CustomizeData; // TODO remove this when available in ClientStructs - private byte GetFreeCompanyCrestBitfield() - => ((byte*)Address)[0x1BBB]; - - private static byte CrestMask(CrestFlag slot) - => slot switch - { - CrestFlag.OffHand => 0x01, - CrestFlag.Head => 0x02, - CrestFlag.Body => 0x04, - CrestFlag.Hands => 0x08, - CrestFlag.Legs => 0x10, - CrestFlag.Feet => 0x20, - _ => 0x00, - }; + internal ref CrestFlag CrestBitfield + => ref *(CrestFlag*)((byte*)Address + 0x1BBB); public override string ToString() => $"0x{Address:X}"; diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index e395f8b..7d25d82 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -5,6 +5,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; using Glamourer.Interop.Structs; +using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -13,6 +14,7 @@ namespace Glamourer.Interop; public unsafe class WeaponService : IDisposable { private readonly WeaponLoading _event; + private readonly CrestService _crestService; private readonly ThreadLocal _inUpdate = new(() => false); @@ -20,9 +22,10 @@ public unsafe class WeaponService : IDisposable _original; - public WeaponService(WeaponLoading @event, IGameInteropProvider interop) + public WeaponService(WeaponLoading @event, IGameInteropProvider interop, CrestService crestService) { - _event = @event; + _event = @event; + _crestService = crestService; _loadWeaponHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour); _original = @@ -69,6 +72,7 @@ public unsafe class WeaponService : IDisposable _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); + if (tmpWeapon.Value != weapon.Value) { if (tmpWeapon.Set.Id == 0) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 6435568..253442f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -165,7 +165,7 @@ public class CommandService : IDisposable .AddInitialPurple("Customizations, ") .AddInitialPurple("Equipment, ") .AddInitialPurple("Accessories, ") - .AddInitialPurple("Dyes and ") + .AddInitialPurple("Dyes & Crests and ") .AddInitialPurple("Weapons, where ").AddPurple("CEADW") .AddText(" means everything should be toggled on, and no value means nothing should be toggled on.") .BuiltString); @@ -268,7 +268,7 @@ public class CommandService : IDisposable applicationFlags |= AutoDesign.Type.Accessories; break; case 'd': - applicationFlags |= AutoDesign.Type.Stains; + applicationFlags |= AutoDesign.Type.GearCustomization; break; case 'w': applicationFlags |= AutoDesign.Type.Weapons; @@ -472,7 +472,7 @@ public class CommandService : IDisposable && _stateManager.GetOrCreate(identifier, data.Objects[0], out state))) continue; - var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant); + var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All); _designManager.CreateClone(design, split[0], true); return true; } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index a32449e..5eaf672 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,13 +1,12 @@ using System.Linq; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; +using Glamourer.Structs; using Penumbra.Api.Enums; -using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -17,30 +16,9 @@ namespace Glamourer.State; /// This class applies changes made to state to actual objects in the game. /// It handles applying those changes as well as redrawing the actor if necessary. /// -public class StateApplier +public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, WeaponService _weapon, ChangeCustomizeService _changeCustomize, + ItemManager _items, PenumbraService _penumbra, MetaService _metaService, ObjectManager _objects, CrestService _crests) { - private readonly PenumbraService _penumbra; - private readonly UpdateSlotService _updateSlot; - private readonly VisorService _visor; - private readonly WeaponService _weapon; - private readonly MetaService _metaService; - private readonly ChangeCustomizeService _changeCustomize; - private readonly ItemManager _items; - private readonly ObjectManager _objects; - - public StateApplier(UpdateSlotService updateSlot, VisorService visor, WeaponService weapon, ChangeCustomizeService changeCustomize, - ItemManager items, PenumbraService penumbra, MetaService metaService, ObjectManager objects) - { - _updateSlot = updateSlot; - _visor = visor; - _weapon = weapon; - _changeCustomize = changeCustomize; - _items = items; - _penumbra = penumbra; - _metaService = metaService; - _objects = objects; - } - /// Simply force a redraw regardless of conditions. public void ForceRedraw(ActorData data) { @@ -279,6 +257,22 @@ public class StateApplier return data; } + /// Change the crest state on actors. + public void ChangeCrests(ActorData data, CrestFlag flags) + { + foreach (var actor in data.Objects.Where(a => a.IsCharacter)) + _crests.UpdateCrests(actor, flags); + } + + /// + public ActorData ChangeCrests(ActorState state, bool apply) + { + var data = GetData(state); + if (apply) + ChangeCrests(data, state.ModelData.CrestVisibility); + return data; + } + private ActorData GetData(ActorState state) { _objects.Update(); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 4bd39b4..3b9d0cb 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -4,6 +4,7 @@ using Dalamud.Plugin.Services; using Glamourer.Customization; using Glamourer.Events; using Glamourer.Services; +using Glamourer.Structs; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -197,6 +198,18 @@ public class StateEditor return true; } + /// Change the crest of an equipment piece. + public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, out bool oldCrest, uint key = 0) + { + oldCrest = state.ModelData.Crest(slot); + if (!state.CanUnlock(key)) + return false; + + state.ModelData.SetCrest(slot, crest); + state[slot] = source; + return true; + } + public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue, uint key = 0) { diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 3cef72a..ae6de2f 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -13,6 +13,7 @@ using Penumbra.GameData.Structs; using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using Glamourer.Structs; namespace Glamourer.State; @@ -42,6 +43,7 @@ public class StateListener : IDisposable private readonly MovedEquipment _movedEquipment; private readonly GPoseService _gPose; private readonly ChangeCustomizeService _changeCustomizeService; + private readonly CrestService _crestService; private readonly ICondition _condition; private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid; @@ -52,7 +54,7 @@ public class StateListener : IDisposable SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, - ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition) + ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition, CrestService crestService) { _manager = manager; _items = items; @@ -74,6 +76,7 @@ public class StateListener : IDisposable _changeCustomizeService = changeCustomizeService; _customizations = customizations; _condition = condition; + _crestService = crestService; Subscribe(); } @@ -405,6 +408,58 @@ public class StateListener : IDisposable } } + private void OnCrestChange(Actor actor, CrestFlag slot, Ref value) + { + if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + return; + + if (!actor.Identifier(_actors.AwaitedService, out var identifier) + || !_manager.TryGetValue(identifier, out var state)) + return; + + switch (UpdateBaseCrest(actor, state, slot, value.Value)) + { + case UpdateState.Change: + if (state[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game); + else + value.Value = state.ModelData.Crest(slot); + break; + case UpdateState.NoChange: + case UpdateState.HatHack: + value.Value = state.ModelData.Crest(slot); + break; + case UpdateState.Transformed: break; + } + } + + private void OnModelCrestSetup(Model model, CrestFlag slot, ref bool value) + { + var actor = _penumbra.GameObjectFromDrawObject(model); + if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + return; + + if (!actor.Identifier(_actors.AwaitedService, out var identifier) + || !_manager.TryGetValue(identifier, out var state)) + return; + + value = state.ModelData.Crest(slot); + } + + private static UpdateState UpdateBaseCrest(Actor actor, ActorState state, CrestFlag slot, bool visible) + { + if (actor.IsTransformed) + return UpdateState.Transformed; + + if (state.BaseData.Crest(slot) != visible) + { + state.BaseData.SetCrest(slot, visible); + return UpdateState.Change; + } + + return UpdateState.NoChange; + } + /// Update base data for a single changed weapon slot. private unsafe UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterWeapon weapon) { @@ -616,6 +671,8 @@ public class StateListener : IDisposable _headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener); _weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener); _changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener); + _crestService.Subscribe(OnCrestChange, CrestService.Priority.StateListener); + _crestService.ModelCrestSetup += OnModelCrestSetup; } private void Unsubscribe() @@ -629,6 +686,8 @@ public class StateListener : IDisposable _headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange); _weaponVisibility.Unsubscribe(OnWeaponVisibilityChange); _changeCustomizeService.Unsubscribe(OnCustomizeChange); + _crestService.Unsubscribe(OnCrestChange); + _crestService.ModelCrestSetup -= OnModelCrestSetup; } private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject) @@ -639,8 +698,9 @@ public class StateListener : IDisposable if (_creatingState == null) return; - _applier.ChangeHatState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsHatVisible()); - _applier.ChangeWeaponState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsWeaponVisible()); - _applier.ChangeWetness(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsWet()); + var data = new ActorData(gameObject, _creatingIdentifier.ToName()); + _applier.ChangeHatState(data, _creatingState.ModelData.IsHatVisible()); + _applier.ChangeWeaponState(data, _creatingState.ModelData.IsWeaponVisible()); + _applier.ChangeWetness(data, _creatingState.ModelData.IsWet()); } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 5157c60..68c9cfc 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -10,6 +10,7 @@ using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; +using Glamourer.Structs; using Penumbra.GameData.Actors; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; @@ -17,32 +18,12 @@ using Penumbra.GameData.Structs; namespace Glamourer.State; -public class StateManager : IReadOnlyDictionary +public class StateManager(ActorService _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor, + HumanModelList _humans, ICondition _condition, IClientState _clientState) + : IReadOnlyDictionary { - private readonly ActorService _actors; - private readonly ItemManager _items; - private readonly HumanModelList _humans; - private readonly StateChanged _event; - private readonly StateApplier _applier; - private readonly StateEditor _editor; - private readonly ICondition _condition; - private readonly IClientState _clientState; - private readonly Dictionary _states = new(); - public StateManager(ActorService actors, ItemManager items, StateChanged @event, StateApplier applier, StateEditor editor, - HumanModelList humans, ICondition condition, IClientState clientState) - { - _actors = actors; - _items = items; - _event = @event; - _applier = applier; - _editor = editor; - _humans = humans; - _condition = condition; - _clientState = clientState; - } - public IEnumerator> GetEnumerator() => _states.GetEnumerator(); @@ -83,7 +64,7 @@ public class StateManager : IReadOnlyDictionary // and the draw objects data for the model data (where possible). state = new ActorState(identifier) { - ModelData = FromActor(actor, true, false), + ModelData = FromActor(actor, true, false), BaseData = FromActor(actor, false, false), LastJob = (byte)(actor.IsCharacter ? actor.AsCharacter->CharacterData.ClassJob : 0), LastTerritory = _clientState.TerritoryType, @@ -162,6 +143,9 @@ public class StateManager : IReadOnlyDictionary // Visor state is a flag on the game object, but we can see the actual state on the draw object. ret.SetVisor(VisorService.GetVisorState(model)); + + foreach (var slot in CrestExtensions.AllRelevantSet) + ret.SetCrest(slot, CrestService.GetModelCrest(actor, slot)); } else { @@ -180,6 +164,9 @@ public class StateManager : IReadOnlyDictionary off = actor.GetOffhand(); FistWeaponHack(ref ret, ref main, ref off); ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled); + + foreach (var slot in CrestExtensions.AllRelevantSet) + ret.SetCrest(slot, actor.GetCrest(slot)); } // Set the weapons regardless of source. @@ -206,7 +193,7 @@ public class StateManager : IReadOnlyDictionary if (mainhand.Set.Id is < 1601 or >= 1651) return; - var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, (Variant) offhand.Type.Id); + var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, (Variant)offhand.Type.Id); offhand.Set = (SetId)(mainhand.Set.Id + 50); offhand.Variant = mainhand.Variant; offhand.Type = mainhand.Type; @@ -304,6 +291,18 @@ public class StateManager : IReadOnlyDictionary _event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot)); } + /// Change the crest of an equipment piece. + public void ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, uint key = 0) + { + if (!_editor.ChangeCrest(state, slot, crest, source, out var old, key)) + return; + + var actors = _applier.ChangeCrests(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); + Glamourer.Log.Verbose( + $"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]"); + _event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot)); + } + /// Change hat visibility. public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0) { @@ -356,19 +355,8 @@ public class StateManager : IReadOnlyDictionary public void ApplyDesign(DesignBase design, ActorState state, StateChanged.Source source, uint key = 0) { - void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain) - { - var unused = (applyPiece, applyStain) switch - { - (false, false) => false, - (true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key), - (false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key), - (true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _, - out _, key), - }; - } - - if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(), source, + if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(), + source, out var oldModelId, key)) return; @@ -393,12 +381,28 @@ public class StateManager : IReadOnlyDictionary foreach (var slot in EquipSlotExtensions.FullSlots) HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot)); + + foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest)) + _editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key); } var actors = ApplyAll(state, redraw, false); Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(StateChanged.Type.Design, state[ActorState.MetaIndex.Wetness], state, actors, design); + return; + + void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain) + { + var unused = (applyPiece, applyStain) switch + { + (false, false) => false, + (true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key), + (false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key), + (true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _, + out _, key), + }; + } } private ActorData ApplyAll(ActorState state, bool redraw, bool withLock) @@ -430,6 +434,7 @@ public class StateManager : IReadOnlyDictionary _applier.ChangeHatState(actors, state.ModelData.IsHatVisible()); _applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible()); _applier.ChangeVisor(actors, state.ModelData.IsVisorToggled()); + _applier.ChangeCrests(actors, state.ModelData.CrestVisibility); } return actors; @@ -453,10 +458,13 @@ public class StateManager : IReadOnlyDictionary state[slot, true] = StateChanged.Source.Game; state[slot, false] = StateChanged.Source.Game; } - + foreach (var type in Enum.GetValues()) state[type] = StateChanged.Source.Game; + foreach (var slot in CrestExtensions.AllRelevantSet) + state[slot] = StateChanged.Source.Game; + var actors = ActorData.Invalid; if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) actors = ApplyAll(state, redraw, true); @@ -491,6 +499,15 @@ public class StateManager : IReadOnlyDictionary } } + foreach (var slot in CrestExtensions.AllRelevantSet) + { + if (state[slot] is StateChanged.Source.Fixed) + { + state[slot] = StateChanged.Source.Game; + state.ModelData.SetCrest(slot, state.BaseData.Crest(slot)); + } + } + if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed) { state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game; From f414eedf7b0991fb9b4b5c76f98b3bb4145fcd56 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 2 Dec 2023 16:56:17 +0000 Subject: [PATCH 082/786] [CI] Updating repo.json for 1.0.6.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 48dcc35..2cfbb9b 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.6.0", - "TestingAssemblyVersion": "1.0.6.0", + "AssemblyVersion": "1.0.6.1", + "TestingAssemblyVersion": "1.0.6.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 11ab85545f2dfdbbba2cafc834fde39873e1024a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 2 Dec 2023 17:34:28 +0100 Subject: [PATCH 083/786] Some Stuff --- Glamourer/Designs/DesignConverter.cs | 11 +---------- Glamourer/Gui/Tabs/DebugTab.cs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index cd6db7e..6ab6901 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -111,16 +111,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } - case Version: - { - version = bytes.DecompressToString(out var decompressed); - var jObj2 = JObject.Parse(decompressed); - Debug.Assert(version == Version); - ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, jObj2) - : DesignBase.LoadDesignBase(_customize, _items, jObj2); - break; - } + default: throw new Exception($"Unknown Version {bytes[0]}."); } } diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index 52385b8..ad1d46f 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using System.Text; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface; @@ -483,21 +484,28 @@ public unsafe class DebugTab : ITab private void DrawCrests(Actor actor, Model model) { - using var id = ImRaii.PushId("Crests"); + using var id = ImRaii.PushId("Crests"); + CrestFlag whichToggle = 0; + CrestFlag totalModelFlags = 0; foreach (var crestFlag in CrestExtensions.AllRelevantSet) { id.Push((int)crestFlag); var modelCrest = CrestService.GetModelCrest(actor, crestFlag); + if (modelCrest) + totalModelFlags |= crestFlag; ImGuiUtil.DrawTableColumn($"{crestFlag.ToLabel()} Crest"); ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetCrest(crestFlag).ToString() : "No Character"); ImGuiUtil.DrawTableColumn(modelCrest.ToString()); ImGui.TableNextColumn(); if (model.IsHuman && ImGui.SmallButton("Toggle")) - _crestService.UpdateCrest(actor, crestFlag, !modelCrest); + whichToggle = crestFlag; id.Pop(); } + + if (whichToggle != 0) + _crestService.UpdateCrests(actor, totalModelFlags ^ whichToggle); } #endregion From 2cafa4e32b922eb1e109eed22c55c81ea0de50e9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 2 Dec 2023 18:02:00 +0100 Subject: [PATCH 084/786] Update workflows and submodules. --- .github/workflows/release.yml | 2 +- .github/workflows/test_release.yml | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4b62441..50ed81a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: actions/checkout@v2 with: - submodules: true + submodules: recursive - name: Setup .NET uses: actions/setup-dotnet@v1 with: diff --git a/.github/workflows/test_release.yml b/.github/workflows/test_release.yml index b3123e0..ccdc6e3 100644 --- a/.github/workflows/test_release.yml +++ b/.github/workflows/test_release.yml @@ -11,7 +11,7 @@ jobs: steps: - uses: actions/checkout@v2 with: - submodules: true + submodules: recursive - name: Setup .NET uses: actions/setup-dotnet@v1 with: diff --git a/Penumbra.Api b/Penumbra.Api index 80f9793..cfc5171 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 80f9793ef2ddaa50246b7112fde4d9b2098d8823 +Subproject commit cfc51714f74cae93608bc507775a9580cd1801de diff --git a/Penumbra.GameData b/Penumbra.GameData index 545aab1..ffdb966 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 545aab1f007158a5d53bc6a7d73b6b2992deb9b3 +Subproject commit ffdb966fec5a657893289e655c641ceb3af1d59f From dd289e6bd7f46fcf92abac2f1fc1c2e02e7728d9 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 2 Dec 2023 17:04:01 +0000 Subject: [PATCH 085/786] [CI] Updating repo.json for testing_1.0.6.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 2cfbb9b..7622352 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.6.1", - "TestingAssemblyVersion": "1.0.6.1", + "TestingAssemblyVersion": "1.0.6.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.0.6.1/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.6.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From e317b3683f2370f5f38e4603f14cbb179596ea2b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 2 Dec 2023 21:47:14 +0100 Subject: [PATCH 086/786] Fix issues with meta toggles. --- Glamourer/Interop/MetaService.cs | 4 ++-- Glamourer/Interop/WeaponService.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs index 7ed0948..30378a1 100644 --- a/Glamourer/Interop/MetaService.cs +++ b/Glamourer/Interop/MetaService.cs @@ -49,9 +49,9 @@ public unsafe class MetaService : IDisposable if (!actor.IsCharacter) return; - // The function seems to not do anything if the head is 0, sometimes? + // The function seems to not do anything if the head is 0, but also breaks for carbuncles turned human, sometimes? var old = actor.AsCharacter->DrawData.Head.Id; - if (old == 0) + if (old == 0 && actor.AsCharacter->CharacterData.ModelCharaId == 0) actor.AsCharacter->DrawData.Head.Id = 1; _hideHatGearHook.Original(&actor.AsCharacter->DrawData, 0, (byte)(value ? 0 : 1)); actor.AsCharacter->DrawData.Head.Id = old; diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 7d25d82..389a05f 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -68,7 +68,8 @@ public unsafe class WeaponService : IDisposable if (equipSlot is not EquipSlot.Unknown) _event.Invoke(actor, equipSlot, ref tmpWeapon); // Sage hack for weapons appearing in animations? - else if (weaponValue == actor.GetMainhand().Value) + // Check for weapon value 0 for certain cases (e.g. carbuncles transforming to humans) because that breaks some stuff (weapon hiding?) otherwise. + else if (weaponValue == actor.GetMainhand().Value && weaponValue != 0) _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); From 6ee1501c0942bd365c7e12130682e6859b1ddd47 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 2 Dec 2023 20:53:01 +0000 Subject: [PATCH 087/786] [CI] Updating repo.json for testing_1.0.6.3 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 7622352..cbeeea3 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.6.1", - "TestingAssemblyVersion": "1.0.6.2", + "TestingAssemblyVersion": "1.0.6.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.0.6.1/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.6.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.6.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a7b1d45b75dfd749ff2d0276db8c6f6cb700afb3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 4 Dec 2023 16:25:49 +0100 Subject: [PATCH 088/786] Make filter combo tooltips always enabled. --- Glamourer/Gui/Equipment/GlamourerColorCombo.cs | 13 ++++--------- OtterGui | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index 5cdb6d4..0bd55dd 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -13,17 +13,12 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public sealed class GlamourerColorCombo : FilterComboColors +public sealed class GlamourerColorCombo(float _comboWidth, StainData _stains, FavoriteManager _favorites) + : FilterComboColors(_comboWidth, CreateFunc(_stains, _favorites), Glamourer.Log) { - private readonly FavoriteManager _favorites; - - public GlamourerColorCombo(float comboWidth, StainData stains, FavoriteManager favorites) - : base(comboWidth, CreateFunc(stains, favorites), Glamourer.Log) - => _favorites = favorites; - protected override bool DrawSelectable(int globalIdx, bool selected) { - using (var space = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(4, 0))) + using (var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(4, 0))) { if (globalIdx == 0) { @@ -32,7 +27,7 @@ public sealed class GlamourerColorCombo : FilterComboColors } else { - UiHelpers.DrawFavoriteStar(_favorites, (StainId)Items[globalIdx].Key); + UiHelpers.DrawFavoriteStar(_favorites, Items[globalIdx].Key); } ImGui.SameLine(); diff --git a/OtterGui b/OtterGui index f624aca..6a73878 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit f624aca526bd1f36364d63443ed1c6e83499b8b7 +Subproject commit 6a73878abbb477dc441ade70ad40352e2da9cc4c From a04b7cd1dbc3f09b85a04a0c44a6dc9ef11d8e01 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 13 Dec 2023 16:45:33 +0100 Subject: [PATCH 089/786] Add function to obtain items and stains from appearance data. --- Glamourer/Designs/DesignConverter.cs | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 6ab6901..f1ee47c 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Text; using Glamourer.Customization; @@ -10,6 +11,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Designs; @@ -142,4 +144,45 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi var compressed = json.Compress(Version); return System.Convert.ToBase64String(compressed); } + + public IEnumerable<(EquipSlot Slot, EquipItem Item, StainId Stain)> FromDrawData(IReadOnlyList armors, + CharacterWeapon mainhand, CharacterWeapon offhand) + { + if (armors.Count != 10) + throw new ArgumentException("Invalid length of armor array."); + + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var index = (int)slot.ToIndex(); + var armor = armors[index]; + var item = _items.Identify(slot, armor.Set, armor.Variant); + if (!item.Valid) + { + Glamourer.Log.Warning($"Appearance data {armor} for slot {slot} invalid, item could not be identified."); + item = ItemManager.NothingItem(slot); + } + + yield return (slot, item, armor.Stain); + } + + var mh = _items.Identify(EquipSlot.MainHand, mainhand.Set, mainhand.Type, mainhand.Variant, FullEquipType.Unknown); + if (!mh.Valid) + { + Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); + mh = _items.DefaultSword; + } + + yield return (EquipSlot.MainHand, mh, mainhand.Stain); + + var oh = _items.Identify(EquipSlot.OffHand, offhand.Set, offhand.Type, offhand.Variant, mh.Type); + if (!oh.Valid) + { + Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified."); + oh = _items.GetDefaultOffhand(mh); + if (!oh.Valid) + oh = ItemManager.NothingItem(FullEquipType.Shield); + } + + yield return (EquipSlot.OffHand, oh, offhand.Stain); + } } From 4b92eae7232f2893d3fa7e6f6554c85493de0946 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 13 Dec 2023 16:46:14 +0100 Subject: [PATCH 090/786] Rework debug tab. --- .../Customization/CustomizationNpcOptions.cs | 224 ++- Glamourer/Glamourer.cs | 7 +- Glamourer/Gui/Tabs/DebugTab.cs | 1783 ----------------- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 113 ++ .../Gui/Tabs/DebugTab/ActorServicePanel.cs | 73 + .../Gui/Tabs/DebugTab/AutoDesignPanel.cs | 48 + .../DebugTab/CustomizationServicePanel.cs | 67 + .../Tabs/DebugTab/CustomizationUnlockPanel.cs | 45 + Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs | 42 + Glamourer/Gui/Tabs/DebugTab/DebugTab.cs | 91 + Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 93 + .../Gui/Tabs/DebugTab/DesignConverterPanel.cs | 97 + .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 122 ++ .../Gui/Tabs/DebugTab/DesignTesterPanel.cs | 202 ++ Glamourer/Gui/Tabs/DebugTab/FunPanel.cs | 35 + .../Gui/Tabs/DebugTab/IdentifierPanel.cs | 61 + Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs | 52 + Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 108 + .../Gui/Tabs/DebugTab/ItemManagerPanel.cs | 39 + .../Gui/Tabs/DebugTab/ItemUnlockPanel.cs | 63 + Glamourer/Gui/Tabs/DebugTab/JobPanel.cs | 76 + .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 286 +++ .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 92 + .../Gui/Tabs/DebugTab/ObjectManagerPanel.cs | 85 + Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 93 + .../Gui/Tabs/DebugTab/RestrictedGearPanel.cs | 42 + .../Gui/Tabs/DebugTab/RetainedStatePanel.cs | 26 + Glamourer/Gui/Tabs/DebugTab/StainPanel.cs | 53 + .../Gui/Tabs/DebugTab/UnlockableItemsPanel.cs | 65 + Glamourer/Gui/Tabs/NpcCombo.cs | 36 + Glamourer/Services/ServiceManager.cs | 21 +- 31 files changed, 2450 insertions(+), 1790 deletions(-) delete mode 100644 Glamourer/Gui/Tabs/DebugTab.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/DebugTab.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/FunPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/JobPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/StainPanel.cs create mode 100644 Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs create mode 100644 Glamourer/Gui/Tabs/NpcCombo.cs diff --git a/Glamourer.GameData/Customization/CustomizationNpcOptions.cs b/Glamourer.GameData/Customization/CustomizationNpcOptions.cs index 3cd988b..254af2e 100644 --- a/Glamourer.GameData/Customization/CustomizationNpcOptions.cs +++ b/Glamourer.GameData/Customization/CustomizationNpcOptions.cs @@ -1,14 +1,234 @@ -using System.Collections.Generic; -using System.Linq; +using System; +using Dalamud.Plugin.Services; using Lumina.Excel; using Lumina.Excel.GeneratedSheets; using OtterGui; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Penumbra.GameData; namespace Glamourer.Customization; public static class CustomizationNpcOptions { + public unsafe struct NpcData + { + public string Name; + public Customize Customize; + private fixed byte _equip[40]; + public CharacterWeapon Mainhand; + public CharacterWeapon Offhand; + public uint Id; + public bool VisorToggled; + public ObjectKind Kind; + + public ReadOnlySpan Equip + { + get + { + fixed (byte* ptr = _equip) + { + return new ReadOnlySpan((CharacterArmor*)ptr, 10); + } + } + } + + public string WriteGear() + { + var sb = new StringBuilder(128); + var span = Equip; + for (var i = 0; i < 10; ++i) + { + sb.Append(span[i].Set.Id.ToString("D4")); + sb.Append('-'); + sb.Append(span[i].Variant.Id.ToString("D3")); + sb.Append('-'); + sb.Append(span[i].Stain.Id.ToString("D3")); + sb.Append(", "); + } + + sb.Append(Mainhand.Set.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Mainhand.Type.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Mainhand.Variant.Id.ToString("D3")); + sb.Append('-'); + sb.Append(Mainhand.Stain.Id.ToString("D4")); + sb.Append(", "); + sb.Append(Offhand.Set.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Offhand.Type.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Offhand.Variant.Id.ToString("D3")); + sb.Append('-'); + sb.Append(Offhand.Stain.Id.ToString("D3")); + return sb.ToString(); + } + + internal void Set(int idx, uint value) + { + fixed (byte* ptr = _equip) + { + ((uint*)ptr)[idx] = value; + } + } + + public bool DataEquals(in NpcData other) + { + if (VisorToggled != other.VisorToggled) + return false; + + if (!Customize.Equals(other.Customize)) + return false; + + if (!Mainhand.Equals(other.Mainhand)) + return false; + + if (!Offhand.Equals(other.Offhand)) + return false; + + fixed (byte* ptr1 = _equip, ptr2 = other._equip) + { + return new ReadOnlySpan(ptr1, 40).SequenceEqual(new ReadOnlySpan(ptr2, 40)); + } + } + } + + private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) + { + data.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); + data.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); + data.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); + data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); + data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); + data.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); + data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); + data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); + data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); + data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); + data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); + data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); + data.VisorToggled = row.Visor; + } + + public static unsafe IReadOnlyList CreateNpcData(IReadOnlyDictionary eNpcs, + IReadOnlyDictionary bnpcNames, IObjectIdentifier identifier, IDataManager data) + { + var enpcSheet = data.GetExcelSheet()!; + var bnpcSheet = data.GetExcelSheet()!; + var list = new List(eNpcs.Count + (int)bnpcSheet.RowCount); + foreach (var (id, name) in eNpcs) + { + var row = enpcSheet.GetRow(id); + if (row == null || name.IsNullOrWhitespace()) + continue; + + var (valid, customize) = FromEnpcBase(row); + if (!valid) + continue; + + var ret = new NpcData + { + Name = name, + Customize = customize, + Id = id, + Kind = ObjectKind.EventNpc, + }; + + if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) + { + ApplyNpcEquip(ref ret, equip); + } + else + { + ret.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); + ret.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); + ret.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); + ret.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); + ret.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); + ret.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); + ret.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); + ret.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); + ret.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); + ret.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); + ret.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); + ret.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); + ret.VisorToggled = row.Visor; + } + + list.Add(ret); + } + + foreach (var baseRow in bnpcSheet) + { + if (baseRow.ModelChara.Value!.Type != 1) + continue; + + var bnpcNameIds = identifier.GetBnpcNames(baseRow.RowId); + if (bnpcNameIds.Count == 0) + continue; + + var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!); + if (!valid) + continue; + + var equip = baseRow.NpcEquip.Value!; + var ret = new NpcData + { + Customize = customize, + Id = baseRow.RowId, + Kind = ObjectKind.BattleNpc, + }; + ApplyNpcEquip(ref ret, equip); + foreach (var bnpcNameId in bnpcNameIds) + { + if (bnpcNames.TryGetValue(bnpcNameId.Id, out var name) && !name.IsNullOrWhitespace()) + list.Add(ret with { Name = name }); + } + } + + var groups = list.GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList()); + list.Clear(); + foreach (var (name, duplicates) in groups.OrderBy(kvp => kvp.Key)) + { + for (var i = 0; i < duplicates.Count; ++i) + { + var current = duplicates[i]; + var add = true; + for (var j = 0; j < i; ++j) + { + if (current.DataEquals(duplicates[j])) + { + duplicates.RemoveAt(i--); + break; + } + } + } + + if (duplicates.Count == 1) + list.Add(duplicates[0]); + else + list.AddRange(duplicates + .Select(duplicate => duplicate with { Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})" })); + } + + var lastWeird = list.FindIndex(d => char.IsAsciiLetterOrDigit(d.Name[0])); + if (lastWeird != -1) + { + list.AddRange(list.Take(lastWeird)); + list.RemoveRange(0, lastWeird); + } + + return list; + } + + public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, ExcelSheet bNpc, ExcelSheet eNpc) { diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 2ac26ce..a31a696 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -1,5 +1,6 @@ using System.Reflection; using Dalamud.Plugin; +using Glamourer.Api; using Glamourer.Gui; using Glamourer.Interop; using Glamourer.Services; @@ -32,11 +33,13 @@ public class Glamourer : IDalamudPlugin { _services = ServiceManager.CreateProvider(pluginInterface, Log); Messager = _services.GetRequiredService(); + _services.GetRequiredService(); + _services.GetRequiredService(); + _services.GetRequiredService(); _services.GetRequiredService(); // Initialize State Listener. _services.GetRequiredService(); // initialize ui. _services.GetRequiredService(); // initialize commands. - _services.GetRequiredService(); - _services.GetRequiredService(); + _services.GetRequiredService(); // initialize IPC. Log.Information($"Glamourer v{Version} loaded successfully."); } catch diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs deleted file mode 100644 index ad1d46f..0000000 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ /dev/null @@ -1,1783 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Text; -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Interface; -using Dalamud.Interface.Utility; -using Dalamud.Plugin; -using FFXIVClientStructs.FFXIV.Client.Game; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Glamourer.Api; -using Glamourer.Automation; -using Glamourer.Customization; -using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.Interop; -using Glamourer.Interop.Penumbra; -using Glamourer.Interop.Structs; -using Glamourer.Services; -using Glamourer.State; -using Glamourer.Structs; -using Glamourer.Unlocks; -using Glamourer.Utility; -using ImGuiNET; -using Newtonsoft.Json.Linq; -using OtterGui; -using OtterGui.Raii; -using OtterGui.Widgets; -using Penumbra.Api.Enums; -using Penumbra.GameData.Data; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using ImGuiClip = OtterGui.ImGuiClip; - -namespace Glamourer.Gui.Tabs; - -public unsafe class DebugTab : ITab -{ - private readonly DalamudPluginInterface _pluginInterface; - private readonly Configuration _config; - private readonly VisorService _visorService; - private readonly ChangeCustomizeService _changeCustomizeService; - private readonly UpdateSlotService _updateSlotService; - private readonly CrestService _crestService; - private readonly WeaponService _weaponService; - private readonly MetaService _metaService; - private readonly InventoryService _inventoryService; - private readonly PenumbraService _penumbra; - private readonly ObjectManager _objectManager; - private readonly GlamourerIpc _ipc; - private readonly CodeService _code; - private readonly ImportService _importService; - - private readonly ItemManager _items; - private readonly ActorService _actors; - private readonly CustomizationService _customization; - private readonly JobService _jobs; - private readonly CustomizeUnlockManager _customizeUnlocks; - private readonly ItemUnlockManager _itemUnlocks; - - private readonly DesignManager _designManager; - private readonly DesignFileSystem _designFileSystem; - private readonly AutoDesignManager _autoDesignManager; - private readonly DesignConverter _designConverter; - private readonly HumanModelList _humans; - - private readonly PenumbraChangedItemTooltip _penumbraTooltip; - - private readonly StateManager _state; - private readonly FunModule _funModule; - - private int _gameObjectIndex; - - public bool IsVisible - => _config.DebugMode; - - public DebugTab(ChangeCustomizeService changeCustomizeService, VisorService visorService, UpdateSlotService updateSlotService, - WeaponService weaponService, PenumbraService penumbra, - ActorService actors, ItemManager items, CustomizationService customization, ObjectManager objectManager, - DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config, - PenumbraChangedItemTooltip penumbraTooltip, MetaService metaService, GlamourerIpc ipc, DalamudPluginInterface pluginInterface, - AutoDesignManager autoDesignManager, JobService jobs, CodeService code, CustomizeUnlockManager customizeUnlocks, - ItemUnlockManager itemUnlocks, DesignConverter designConverter, ImportService importService, InventoryService inventoryService, - HumanModelList humans, FunModule funModule, CrestService crestService) - { - _changeCustomizeService = changeCustomizeService; - _visorService = visorService; - _updateSlotService = updateSlotService; - _weaponService = weaponService; - _penumbra = penumbra; - _actors = actors; - _items = items; - _customization = customization; - _objectManager = objectManager; - _designFileSystem = designFileSystem; - _designManager = designManager; - _state = state; - _config = config; - _penumbraTooltip = penumbraTooltip; - _metaService = metaService; - _ipc = ipc; - _pluginInterface = pluginInterface; - _autoDesignManager = autoDesignManager; - _jobs = jobs; - _code = code; - _customizeUnlocks = customizeUnlocks; - _itemUnlocks = itemUnlocks; - _designConverter = designConverter; - _importService = importService; - _inventoryService = inventoryService; - _humans = humans; - _funModule = funModule; - _crestService = crestService; - } - - public ReadOnlySpan Label - => "Debug"u8; - - public void DrawContent() - { - using var child = ImRaii.Child("MainWindowChild"); - if (!child) - return; - - DrawInteropHeader(); - DrawGameDataHeader(); - DrawPenumbraHeader(); - DrawDesigns(); - DrawState(); - DrawAutoDesigns(); - DrawInventory(); - DrawUnlocks(); - DrawFun(); - DrawIpc(); - } - - #region Interop - - private void DrawInteropHeader() - { - if (!ImGui.CollapsingHeader("Interop")) - return; - - DrawModelEvaluation(); - DrawObjectManager(); - DrawDatFiles(); - } - - private void DrawModelEvaluation() - { - using var tree = ImRaii.TreeNode("Model Evaluation"); - if (!tree) - return; - - ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); - var actor = (Actor)_objectManager.Objects.GetObjectAddress(_gameObjectIndex); - var model = actor.Model; - using var table = ImRaii.Table("##evaluationTable", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableHeader("Actor"); - ImGui.TableNextColumn(); - ImGui.TableHeader("Model"); - ImGui.TableNextColumn(); - - ImGuiUtil.DrawTableColumn("Address"); - ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(actor.ToString()); - ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(model.ToString()); - ImGui.TableNextColumn(); - if (actor.IsCharacter) - { - ImGui.TextUnformatted(actor.AsCharacter->CharacterData.ModelCharaId.ToString()); - if (actor.AsCharacter->CharacterData.TransformationId != 0) - ImGui.TextUnformatted($"Transformation Id: {actor.AsCharacter->CharacterData.TransformationId}"); - if (actor.AsCharacter->CharacterData.ModelCharaId_2 != -1) - ImGui.TextUnformatted($"ModelChara2 {actor.AsCharacter->CharacterData.ModelCharaId_2}"); - if (actor.AsCharacter->CharacterData.StatusEffectVFXId != 0) - ImGui.TextUnformatted($"Status Id: {actor.AsCharacter->CharacterData.StatusEffectVFXId}"); - } - - ImGuiUtil.DrawTableColumn("Mainhand"); - ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetMainhand().ToString() : "No Character"); - - var (mainhand, offhand, mainModel, offModel) = model.GetWeapons(actor); - ImGuiUtil.DrawTableColumn(mainModel.ToString()); - ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(mainhand.ToString()); - - ImGuiUtil.DrawTableColumn("Offhand"); - ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetOffhand().ToString() : "No Character"); - ImGuiUtil.DrawTableColumn(offModel.ToString()); - ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(offhand.ToString()); - - DrawVisor(actor, model); - DrawHatState(actor, model); - DrawWeaponState(actor, model); - DrawWetness(actor, model); - DrawEquip(actor, model); - DrawCustomize(actor, model); - DrawCrests(actor, model); - } - - private string _objectFilter = string.Empty; - - private void DrawObjectManager() - { - using var tree = ImRaii.TreeNode("Object Manager"); - if (!tree) - return; - - _objectManager.Update(); - - using (var table = ImRaii.Table("##data", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit)) - { - if (!table) - return; - - ImGuiUtil.DrawTableColumn("Last Update"); - ImGuiUtil.DrawTableColumn(_objectManager.LastUpdate.ToString(CultureInfo.InvariantCulture)); - ImGui.TableNextColumn(); - - ImGuiUtil.DrawTableColumn("World"); - ImGuiUtil.DrawTableColumn(_actors.Valid ? _actors.AwaitedService.Data.ToWorldName(_objectManager.World) : "Service Missing"); - ImGuiUtil.DrawTableColumn(_objectManager.World.ToString()); - - ImGuiUtil.DrawTableColumn("Player Character"); - ImGuiUtil.DrawTableColumn($"{_objectManager.Player.Utf8Name} ({_objectManager.Player.Index})"); - ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(_objectManager.Player.ToString()); - - ImGuiUtil.DrawTableColumn("In GPose"); - ImGuiUtil.DrawTableColumn(_objectManager.IsInGPose.ToString()); - ImGui.TableNextColumn(); - - if (_objectManager.IsInGPose) - { - ImGuiUtil.DrawTableColumn("GPose Player"); - ImGuiUtil.DrawTableColumn($"{_objectManager.GPosePlayer.Utf8Name} ({_objectManager.GPosePlayer.Index})"); - ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(_objectManager.GPosePlayer.ToString()); - } - - ImGuiUtil.DrawTableColumn("Number of Players"); - ImGuiUtil.DrawTableColumn(_objectManager.Count.ToString()); - ImGui.TableNextColumn(); - } - - var filterChanged = ImGui.InputTextWithHint("##Filter", "Filter...", ref _objectFilter, 64); - using var table2 = ImRaii.Table("##data2", 3, - ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, - new Vector2(-1, 20 * ImGui.GetTextLineHeightWithSpacing())); - if (!table2) - return; - - if (filterChanged) - ImGui.SetScrollY(0); - - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); - ImGui.TableNextRow(); - - var remainder = ImGuiClip.FilteredClippedDraw(_objectManager, skips, - p => p.Value.Label.Contains(_objectFilter, StringComparison.OrdinalIgnoreCase), p - => - { - ImGuiUtil.DrawTableColumn(p.Key.ToString()); - ImGuiUtil.DrawTableColumn(p.Value.Label); - ImGuiUtil.DrawTableColumn(string.Join(", ", p.Value.Objects.OrderBy(a => a.Index).Select(a => a.Index.ToString()))); - }); - ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeightWithSpacing()); - } - - private string _datFilePath = string.Empty; - private DatCharacterFile? _datFile = null; - - private void DrawDatFiles() - { - using var tree = ImRaii.TreeNode("Character Dat File"); - if (!tree) - return; - - ImGui.InputTextWithHint("##datFilePath", "Dat File Path...", ref _datFilePath, 256); - var exists = _datFilePath.Length > 0 && File.Exists(_datFilePath); - if (ImGuiUtil.DrawDisabledButton("Load##Dat", Vector2.Zero, string.Empty, !exists)) - _datFile = _importService.LoadDat(_datFilePath, out var tmp) ? tmp : null; - - if (ImGuiUtil.DrawDisabledButton("Save##Dat", Vector2.Zero, string.Empty, _datFilePath.Length == 0 || _datFile == null)) - _importService.SaveDesignAsDat(_datFilePath, _datFile!.Value.Customize, _datFile!.Value.Description); - - if (_datFile != null) - { - ImGui.TextUnformatted(_datFile.Value.Magic.ToString()); - ImGui.TextUnformatted(_datFile.Value.Version.ToString()); - ImGui.TextUnformatted(_datFile.Value.Time.LocalDateTime.ToString("g")); - ImGui.TextUnformatted(_datFile.Value.Voice.ToString()); - ImGui.TextUnformatted(_datFile.Value.Customize.Data.ToString()); - ImGui.TextUnformatted(_datFile.Value.Description); - } - } - - private void DrawVisor(Actor actor, Model model) - { - using var id = ImRaii.PushId("Visor"); - ImGuiUtil.DrawTableColumn("Visor State"); - ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->DrawData.IsVisorToggled.ToString() : "No Character"); - ImGuiUtil.DrawTableColumn(model.IsHuman ? VisorService.GetVisorState(model).ToString() : "No Human"); - ImGui.TableNextColumn(); - if (!model.IsHuman) - return; - - if (ImGui.SmallButton("Set True")) - _visorService.SetVisorState(model, true); - ImGui.SameLine(); - if (ImGui.SmallButton("Set False")) - _visorService.SetVisorState(model, false); - ImGui.SameLine(); - if (ImGui.SmallButton("Toggle")) - _visorService.SetVisorState(model, !VisorService.GetVisorState(model)); - } - - private void DrawHatState(Actor actor, Model model) - { - using var id = ImRaii.PushId("HatState"); - ImGuiUtil.DrawTableColumn("Hat State"); - ImGuiUtil.DrawTableColumn(actor.IsCharacter - ? actor.AsCharacter->DrawData.IsHatHidden ? "Hidden" : actor.GetArmor(EquipSlot.Head).ToString() - : "No Character"); - ImGuiUtil.DrawTableColumn(model.IsHuman - ? model.AsHuman->Head.Value == 0 ? "No Hat" : model.GetArmor(EquipSlot.Head).ToString() - : "No Human"); - ImGui.TableNextColumn(); - if (!model.IsHuman) - return; - - if (ImGui.SmallButton("Hide")) - _updateSlotService.UpdateSlot(model, EquipSlot.Head, CharacterArmor.Empty); - ImGui.SameLine(); - if (ImGui.SmallButton("Show")) - _updateSlotService.UpdateSlot(model, EquipSlot.Head, actor.GetArmor(EquipSlot.Head)); - ImGui.SameLine(); - if (ImGui.SmallButton("Toggle")) - _updateSlotService.UpdateSlot(model, EquipSlot.Head, - model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty); - } - - private void DrawWeaponState(Actor actor, Model model) - { - using var id = ImRaii.PushId("WeaponState"); - ImGuiUtil.DrawTableColumn("Weapon State"); - ImGuiUtil.DrawTableColumn(actor.IsCharacter - ? actor.AsCharacter->DrawData.IsWeaponHidden ? "Hidden" : "Visible" - : "No Character"); - var text = string.Empty; - - if (!model.IsHuman) - { - text = "No Model"; - } - else if (model.AsDrawObject->Object.ChildObject == null) - { - text = "No Weapon"; - } - else - { - var weapon = (DrawObject*)model.AsDrawObject->Object.ChildObject; - if ((weapon->Flags & 0x09) == 0x09) - text = "Visible"; - else - text = "Hidden"; - } - - ImGuiUtil.DrawTableColumn(text); - ImGui.TableNextColumn(); - if (!model.IsHuman) - return; - } - - private void DrawWetness(Actor actor, Model model) - { - using var id = ImRaii.PushId("Wetness"); - ImGuiUtil.DrawTableColumn("Wetness"); - ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->IsGPoseWet ? "GPose" : "None" : "No Character"); - var modelString = model.IsCharacterBase - ? $"{model.AsCharacterBase->SwimmingWetness:F4} Swimming\n" - + $"{model.AsCharacterBase->WeatherWetness:F4} Weather\n" - + $"{model.AsCharacterBase->ForcedWetness:F4} Forced\n" - + $"{model.AsCharacterBase->WetnessDepth:F4} Depth\n" - : "No CharacterBase"; - ImGuiUtil.DrawTableColumn(modelString); - ImGui.TableNextColumn(); - if (!actor.IsCharacter) - return; - - if (ImGui.SmallButton("GPose On")) - actor.AsCharacter->IsGPoseWet = true; - ImGui.SameLine(); - if (ImGui.SmallButton("GPose Off")) - actor.AsCharacter->IsGPoseWet = false; - ImGui.SameLine(); - if (ImGui.SmallButton("GPose Toggle")) - actor.AsCharacter->IsGPoseWet = !actor.AsCharacter->IsGPoseWet; - } - - private void DrawEquip(Actor actor, Model model) - { - using var id = ImRaii.PushId("Equipment"); - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - using var id2 = ImRaii.PushId((int)slot); - ImGuiUtil.DrawTableColumn(slot.ToName()); - ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetArmor(slot).ToString() : "No Character"); - ImGuiUtil.DrawTableColumn(model.IsHuman ? model.GetArmor(slot).ToString() : "No Human"); - ImGui.TableNextColumn(); - if (!model.IsHuman) - continue; - - if (ImGui.SmallButton("Change Piece")) - _updateSlotService.UpdateArmor(model, slot, - new CharacterArmor((SetId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0)); - ImGui.SameLine(); - if (ImGui.SmallButton("Change Stain")) - _updateSlotService.UpdateStain(model, slot, 5); - ImGui.SameLine(); - if (ImGui.SmallButton("Reset")) - _updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot)); - } - } - - private void DrawCustomize(Actor actor, Model model) - { - using var id = ImRaii.PushId("Customize"); - var actorCustomize = new Customize(actor.IsCharacter - ? *(Penumbra.GameData.Structs.CustomizeData*)&actor.AsCharacter->DrawData.CustomizeData - : new Penumbra.GameData.Structs.CustomizeData()); - var modelCustomize = new Customize(model.IsHuman - ? *(Penumbra.GameData.Structs.CustomizeData*)model.AsHuman->Customize.Data - : new Penumbra.GameData.Structs.CustomizeData()); - foreach (var type in Enum.GetValues()) - { - using var id2 = ImRaii.PushId((int)type); - ImGuiUtil.DrawTableColumn(type.ToDefaultName()); - ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actorCustomize[type].Value.ToString("X2") : "No Character"); - ImGuiUtil.DrawTableColumn(model.IsHuman ? modelCustomize[type].Value.ToString("X2") : "No Human"); - ImGui.TableNextColumn(); - if (!model.IsHuman || type.ToFlag().RequiresRedraw()) - continue; - - if (ImGui.SmallButton("++")) - { - var value = modelCustomize[type].Value; - var (_, mask) = type.ToByteAndMask(); - var shift = BitOperations.TrailingZeroCount(mask); - var newValue = value + (1 << shift); - modelCustomize.Set(type, (CustomizeValue)newValue); - _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); - } - - ImGui.SameLine(); - if (ImGui.SmallButton("--")) - { - var value = modelCustomize[type].Value; - var (_, mask) = type.ToByteAndMask(); - var shift = BitOperations.TrailingZeroCount(mask); - var newValue = value - (1 << shift); - modelCustomize.Set(type, (CustomizeValue)newValue); - _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); - } - - ImGui.SameLine(); - if (ImGui.SmallButton("Reset")) - { - modelCustomize.Set(type, actorCustomize[type]); - _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); - } - } - } - - private void DrawCrests(Actor actor, Model model) - { - using var id = ImRaii.PushId("Crests"); - CrestFlag whichToggle = 0; - CrestFlag totalModelFlags = 0; - foreach (var crestFlag in CrestExtensions.AllRelevantSet) - { - id.Push((int)crestFlag); - var modelCrest = CrestService.GetModelCrest(actor, crestFlag); - if (modelCrest) - totalModelFlags |= crestFlag; - ImGuiUtil.DrawTableColumn($"{crestFlag.ToLabel()} Crest"); - ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetCrest(crestFlag).ToString() : "No Character"); - ImGuiUtil.DrawTableColumn(modelCrest.ToString()); - - ImGui.TableNextColumn(); - if (model.IsHuman && ImGui.SmallButton("Toggle")) - whichToggle = crestFlag; - - id.Pop(); - } - - if (whichToggle != 0) - _crestService.UpdateCrests(actor, totalModelFlags ^ whichToggle); - } - - #endregion - - #region Penumbra - - private Model _drawObject = Model.Null; - - private void DrawPenumbraHeader() - { - if (!ImGui.CollapsingHeader("Penumbra")) - return; - - using var table = ImRaii.Table("##PenumbraTable", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - return; - - ImGuiUtil.DrawTableColumn("Available"); - ImGuiUtil.DrawTableColumn(_penumbra.Available.ToString()); - ImGui.TableNextColumn(); - if (ImGui.SmallButton("Unattach")) - _penumbra.Unattach(); - ImGui.SameLine(); - if (ImGui.SmallButton("Reattach")) - _penumbra.Reattach(); - - ImGuiUtil.DrawTableColumn("Draw Object"); - ImGui.TableNextColumn(); - var address = _drawObject.Address; - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - if (ImGui.InputScalar("##drawObjectPtr", ImGuiDataType.U64, (nint)(&address), IntPtr.Zero, IntPtr.Zero, "%llx", - ImGuiInputTextFlags.CharsHexadecimal)) - _drawObject = address; - ImGuiUtil.DrawTableColumn(_penumbra.Available - ? $"0x{_penumbra.GameObjectFromDrawObject(_drawObject).Address:X}" - : "Penumbra Unavailable"); - - ImGuiUtil.DrawTableColumn("Cutscene Object"); - ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - ImGui.InputInt("##CutsceneIndex", ref _gameObjectIndex, 0, 0); - ImGuiUtil.DrawTableColumn(_penumbra.Available - ? _penumbra.CutsceneParent(_gameObjectIndex).ToString() - : "Penumbra Unavailable"); - - ImGuiUtil.DrawTableColumn("Redraw Object"); - ImGui.TableNextColumn(); - ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - ImGui.InputInt("##redrawObject", ref _gameObjectIndex, 0, 0); - ImGui.TableNextColumn(); - using (var disabled = ImRaii.Disabled(!_penumbra.Available)) - { - if (ImGui.SmallButton("Redraw")) - _penumbra.RedrawObject((ObjectIndex)_gameObjectIndex, RedrawType.Redraw); - } - - ImGuiUtil.DrawTableColumn("Last Tooltip Date"); - ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastTooltip > DateTime.MinValue ? _penumbraTooltip.LastTooltip.ToLongTimeString() : "Never"); - ImGui.TableNextColumn(); - - ImGuiUtil.DrawTableColumn("Last Click Date"); - ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastClick > DateTime.MinValue ? _penumbraTooltip.LastClick.ToLongTimeString() : "Never"); - ImGui.TableNextColumn(); - - ImGui.Separator(); - ImGui.Separator(); - foreach (var (slot, item) in _penumbraTooltip.LastItems) - { - ImGuiUtil.DrawTableColumn($"{slot.ToName()} Revert-Item"); - ImGuiUtil.DrawTableColumn(item.Valid ? item.Name : "None"); - ImGui.TableNextColumn(); - } - } - - #endregion - - #region GameData - - private void DrawGameDataHeader() - { - if (!ImGui.CollapsingHeader("Game Data")) - return; - - DrawIdentifierService(); - DrawRestrictedGear(); - DrawActorService(); - DrawItemService(); - DrawStainService(); - DrawCustomizationService(); - DrawJobService(); - } - - private void DrawJobService() - { - using var tree = ImRaii.TreeNode("Job Service"); - if (!tree) - return; - - using (var t = ImRaii.TreeNode("Jobs")) - { - if (t) - { - using var table = ImRaii.Table("##jobs", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (table) - foreach (var (id, job) in _jobs.Jobs) - { - ImGuiUtil.DrawTableColumn(id.ToString("D3")); - ImGuiUtil.DrawTableColumn(job.Name); - ImGuiUtil.DrawTableColumn(job.Abbreviation); - } - } - } - - using (var t = ImRaii.TreeNode("All Job Groups")) - { - if (t) - { - using var table = ImRaii.Table("##groups", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (table) - foreach (var (group, idx) in _jobs.AllJobGroups.WithIndex()) - { - ImGuiUtil.DrawTableColumn(idx.ToString("D3")); - ImGuiUtil.DrawTableColumn(group.Name); - ImGuiUtil.DrawTableColumn(group.Count.ToString()); - } - } - } - - using (var t = ImRaii.TreeNode("Valid Job Groups")) - { - if (t) - { - using var table = ImRaii.Table("##groups", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (table) - foreach (var (id, group) in _jobs.JobGroups) - { - ImGuiUtil.DrawTableColumn(id.ToString("D3")); - ImGuiUtil.DrawTableColumn(group.Name); - ImGuiUtil.DrawTableColumn(group.Count.ToString()); - } - } - } - } - - private string _gamePath = string.Empty; - private int _setId; - private int _secondaryId; - private int _variant; - - private void DrawIdentifierService() - { - using var disabled = ImRaii.Disabled(!_items.IdentifierService.Valid); - using var tree = ImRaii.TreeNode("Identifier Service"); - if (!tree || !_items.IdentifierService.Valid) - return; - - disabled.Dispose(); - - - static void Text(string text) - { - if (text.Length > 0) - ImGui.TextUnformatted(text); - } - - ImGui.TextUnformatted("Parse Game Path"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); - ImGui.InputTextWithHint("##gamePath", "Enter game path...", ref _gamePath, 256); - var fileInfo = _items.IdentifierService.AwaitedService.GamePathParser.GetFileInfo(_gamePath); - ImGui.TextUnformatted( - $"{fileInfo.ObjectType} {fileInfo.EquipSlot} {fileInfo.PrimaryId} {fileInfo.SecondaryId} {fileInfo.Variant} {fileInfo.BodySlot} {fileInfo.CustomizationType}"); - Text(string.Join("\n", _items.IdentifierService.AwaitedService.Identify(_gamePath).Keys)); - - ImGui.Separator(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Identify Model"); - ImGui.SameLine(); - DrawInputModelSet(true); - - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - var identified = _items.Identify(slot, (SetId)_setId, (Variant)_variant); - Text(identified.Name); - ImGuiUtil.HoverTooltip(string.Join("\n", - _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (Variant)_variant, slot) - .Select(i => $"{i.Name} {i.Id} {i.ItemId} {i.IconId}"))); - } - - var weapon = _items.Identify(EquipSlot.MainHand, (SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant); - Text(weapon.Name); - ImGuiUtil.HoverTooltip(string.Join("\n", - _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant, EquipSlot.MainHand))); - } - - private void DrawRestrictedGear() - { - using var tree = ImRaii.TreeNode("Restricted Gear Service"); - if (!tree) - return; - - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Resolve Model"); - DrawInputModelSet(false); - foreach (var race in Enum.GetValues().Skip(1)) - { - foreach (var gender in new[] - { - Gender.Male, - Gender.Female, - }) - { - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - var (replaced, model) = - _items.RestrictedGear.ResolveRestricted(new CharacterArmor((SetId)_setId, (Variant)_variant, 0), slot, race, gender); - if (replaced) - ImGui.TextUnformatted($"{race.ToName()} - {gender} - {slot.ToName()} resolves to {model}."); - } - } - } - } - - private void DrawInputModelSet(bool withWeapon) - { - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - ImGui.InputInt("##SetId", ref _setId, 0, 0); - if (withWeapon) - { - ImGui.SameLine(); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - ImGui.InputInt("##TypeId", ref _secondaryId, 0, 0); - } - - ImGui.SameLine(); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - ImGui.InputInt("##Variant", ref _variant, 0, 0); - } - - private string _bnpcFilter = string.Empty; - private string _enpcFilter = string.Empty; - private string _companionFilter = string.Empty; - private string _mountFilter = string.Empty; - private string _ornamentFilter = string.Empty; - private string _worldFilter = string.Empty; - - private void DrawActorService() - { - using var disabled = ImRaii.Disabled(!_actors.Valid); - using var tree = ImRaii.TreeNode("Actor Service"); - if (!tree || !_actors.Valid) - return; - - disabled.Dispose(); - - DrawBnpcTable(); - DrawNameTable("ENPCs", ref _enpcFilter, _actors.AwaitedService.Data.ENpcs.Select(kvp => (kvp.Key, kvp.Value))); - DrawNameTable("Companions", ref _companionFilter, _actors.AwaitedService.Data.Companions.Select(kvp => (kvp.Key, kvp.Value))); - DrawNameTable("Mounts", ref _mountFilter, _actors.AwaitedService.Data.Mounts.Select(kvp => (kvp.Key, kvp.Value))); - DrawNameTable("Ornaments", ref _ornamentFilter, _actors.AwaitedService.Data.Ornaments.Select(kvp => (kvp.Key, kvp.Value))); - DrawNameTable("Worlds", ref _worldFilter, _actors.AwaitedService.Data.Worlds.Select(kvp => ((uint)kvp.Key, kvp.Value))); - } - - private void DrawBnpcTable() - { - using var _ = ImRaii.PushId(1); - using var tree = ImRaii.TreeNode("BNPCs"); - if (!tree) - return; - - var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref _bnpcFilter, 256); - var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; - using var table = ImRaii.Table("##table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, - new Vector2(-1, 10 * height)); - if (!table) - return; - - if (resetScroll) - ImGui.SetScrollY(0); - ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableSetupColumn("3", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(height); - ImGui.TableNextRow(); - var data = _actors.AwaitedService.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Key.ToString("D5"), kvp.Value)); - var remainder = ImGuiClip.FilteredClippedDraw(data, skips, - p => p.Item2.Contains(_bnpcFilter) || p.Item3.Contains(_bnpcFilter, StringComparison.OrdinalIgnoreCase), - p => - { - ImGuiUtil.DrawTableColumn(p.Item2); - ImGuiUtil.DrawTableColumn(p.Item3); - var bnpcs = _items.IdentifierService.AwaitedService.GetBnpcsFromName(p.Item1); - ImGuiUtil.DrawTableColumn(string.Join(", ", bnpcs.Select(b => b.Id.ToString()))); - }); - ImGuiClip.DrawEndDummy(remainder, height); - } - - private static void DrawNameTable(string label, ref string filter, IEnumerable<(uint, string)> names) - { - using var _ = ImRaii.PushId(label); - using var tree = ImRaii.TreeNode(label); - if (!tree) - return; - - var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref filter, 256); - var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; - using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, - new Vector2(-1, 10 * height)); - if (!table) - return; - - if (resetScroll) - ImGui.SetScrollY(0); - ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(height); - ImGui.TableNextColumn(); - var f = filter; - var remainder = ImGuiClip.FilteredClippedDraw(names.Select(p => (p.Item1.ToString("D5"), p.Item2)), skips, - p => p.Item1.Contains(f) || p.Item2.Contains(f, StringComparison.OrdinalIgnoreCase), - p => - { - ImGuiUtil.DrawTableColumn(p.Item1); - ImGuiUtil.DrawTableColumn(p.Item2); - }); - ImGuiClip.DrawEndDummy(remainder, height); - } - - private string _itemFilter = string.Empty; - - private void DrawItemService() - { - using var disabled = ImRaii.Disabled(!_items.ItemService.Valid); - using var tree = ImRaii.TreeNode("Item Manager"); - if (!tree || !_items.ItemService.Valid) - return; - - disabled.Dispose(); - ImRaii.TreeNode($"Default Sword: {_items.DefaultSword.Name} ({_items.DefaultSword.ItemId}) ({_items.DefaultSword.Weapon()})", - ImGuiTreeNodeFlags.Leaf).Dispose(); - DrawNameTable("All Items (Main)", ref _itemFilter, - _items.ItemService.AwaitedService.AllItems(true).Select(p => (p.Item1.Id, - $"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) - .OrderBy(p => p.Item1)); - DrawNameTable("All Items (Off)", ref _itemFilter, - _items.ItemService.AwaitedService.AllItems(false).Select(p => (p.Item1.Id, - $"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) - .OrderBy(p => p.Item1)); - foreach (var type in Enum.GetValues().Skip(1)) - { - DrawNameTable(type.ToName(), ref _itemFilter, - _items.ItemService.AwaitedService[type] - .Select(p => (Id: p.ItemId.Id, $"{p.Name} ({(p.WeaponType == 0 ? p.Armor().ToString() : p.Weapon().ToString())})"))); - } - } - - private string _stainFilter = string.Empty; - - private void DrawStainService() - { - using var tree = ImRaii.TreeNode("Stain Service"); - if (!tree) - return; - - var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref _stainFilter, 256); - var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; - using var table = ImRaii.Table("##table", 4, - ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.SizingFixedFit, - new Vector2(-1, 10 * height)); - if (!table) - return; - - if (resetScroll) - ImGui.SetScrollY(0); - - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(height); - ImGui.TableNextRow(); - var remainder = ImGuiClip.FilteredClippedDraw(_items.Stains, skips, - p => p.Key.Id.ToString().Contains(_stainFilter) || p.Value.Name.Contains(_stainFilter, StringComparison.OrdinalIgnoreCase), - p => - { - ImGuiUtil.DrawTableColumn(p.Key.Id.ToString("D3")); - ImGui.TableNextColumn(); - ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(), - ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight()), - p.Value.RgbaColor, 5 * ImGuiHelpers.GlobalScale); - ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight())); - ImGuiUtil.DrawTableColumn(p.Value.Name); - ImGuiUtil.DrawTableColumn($"#{p.Value.R:X2}{p.Value.G:X2}{p.Value.B:X2}{(p.Value.Gloss ? ", Glossy" : string.Empty)}"); - }); - ImGuiClip.DrawEndDummy(remainder, height); - } - - private void DrawCustomizationService() - { - using var disabled = ImRaii.Disabled(!_customization.Valid); - using var tree = ImRaii.TreeNode("Customization Service"); - if (!tree || !_customization.Valid) - return; - - disabled.Dispose(); - - foreach (var clan in _customization.AwaitedService.Clans) - { - foreach (var gender in _customization.AwaitedService.Genders) - { - var set = _customization.AwaitedService.GetList(clan, gender); - DrawCustomizationInfo(set); - DrawNpcCustomizationInfo(set); - } - } - } - - private void DrawCustomizationInfo(CustomizationSet set) - { - using var tree = ImRaii.TreeNode($"{_customization.ClanName(set.Clan, set.Gender)} {set.Gender}"); - if (!tree) - return; - - using var table = ImRaii.Table("data", 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - return; - - foreach (var index in Enum.GetValues()) - { - ImGuiUtil.DrawTableColumn(index.ToString()); - ImGuiUtil.DrawTableColumn(set.Option(index)); - ImGuiUtil.DrawTableColumn(set.IsAvailable(index) ? "Available" : "Unavailable"); - ImGuiUtil.DrawTableColumn(set.Type(index).ToString()); - ImGuiUtil.DrawTableColumn(set.Count(index).ToString()); - } - } - - private void DrawNpcCustomizationInfo(CustomizationSet set) - { - using var tree = ImRaii.TreeNode($"{_customization.ClanName(set.Clan, set.Gender)} {set.Gender} (NPC Options)"); - if (!tree) - return; - - using var table = ImRaii.Table("npc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - return; - - foreach (var (index, value) in set.NpcOptions) - { - ImGuiUtil.DrawTableColumn(index.ToString()); - ImGuiUtil.DrawTableColumn(value.Value.ToString()); - } - } - - #endregion - - #region Designs - - private string _base64 = string.Empty; - private string _restore = string.Empty; - private byte[] _base64Bytes = Array.Empty(); - private byte[] _restoreBytes = Array.Empty(); - private DesignData _parse64 = new(); - private Exception? _parse64Failure; - - private void DrawDesigns() - { - if (!ImGui.CollapsingHeader("Designs")) - return; - - DrawDesignManager(); - DrawDesignTester(); - DrawDesignConverter(); - } - - private void DrawDesignManager() - { - using var tree = ImRaii.TreeNode($"Design Manager ({_designManager.Designs.Count} Designs)###Design Manager"); - if (!tree) - return; - - foreach (var (design, idx) in _designManager.Designs.WithIndex()) - { - using var t = ImRaii.TreeNode($"{design.Name}##{idx}"); - if (!t) - continue; - - DrawDesign(design); - var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, - design.DoApplyHatVisible(), - design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected()); - using var font = ImRaii.PushFont(UiBuilder.MonoFont); - ImGuiUtil.TextWrapped(base64); - if (ImGui.IsItemClicked()) - ImGui.SetClipboardText(base64); - } - } - - private void DrawDesignTester() - { - using var tree = ImRaii.TreeNode("Base64 Design Tester"); - if (!tree) - return; - - ImGui.SetNextItemWidth(-1); - ImGui.InputTextWithHint("##base64", "Base 64 input...", ref _base64, 2047); - if (ImGui.IsItemDeactivatedAfterEdit()) - { - try - { - _base64Bytes = Convert.FromBase64String(_base64); - _parse64Failure = null; - } - catch (Exception ex) - { - _base64Bytes = Array.Empty(); - _parse64Failure = ex; - } - - if (_parse64Failure == null) - try - { - _parse64 = DesignBase64Migration.MigrateBase64(_items, _humans, _base64, out var ef, out var cf, out var wp, out var ah, - out var av, - out var aw); - _restore = DesignBase64Migration.CreateOldBase64(in _parse64, ef, cf, ah, av, aw, wp); - _restoreBytes = Convert.FromBase64String(_restore); - } - catch (Exception ex) - { - _parse64Failure = ex; - _restore = string.Empty; - } - } - - if (_parse64Failure != null) - { - ImGuiUtil.TextWrapped(_parse64Failure.ToString()); - } - else if (_restore.Length > 0) - { - DrawDesignData(_parse64); - using var font = ImRaii.PushFont(UiBuilder.MonoFont); - ImGui.TextUnformatted(_base64); - using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 })) - { - foreach (var (c1, c2) in _restore.Zip(_base64)) - { - using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF4040D0, c1 != c2); - ImGui.TextUnformatted(c1.ToString()); - ImGui.SameLine(); - } - } - - ImGui.NewLine(); - - foreach (var ((b1, b2), idx) in _base64Bytes.Zip(_restoreBytes).WithIndex()) - { - using (var group = ImRaii.Group()) - { - ImGui.TextUnformatted(idx.ToString("D2")); - ImGui.TextUnformatted(b1.ToString("X2")); - using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF4040D0, b1 != b2); - ImGui.TextUnformatted(b2.ToString("X2")); - } - - ImGui.SameLine(); - } - - ImGui.NewLine(); - } - - if (_parse64Failure != null && _base64Bytes.Length > 0) - { - using var font = ImRaii.PushFont(UiBuilder.MonoFont); - foreach (var (b, idx) in _base64Bytes.WithIndex()) - { - using (var group = ImRaii.Group()) - { - ImGui.TextUnformatted(idx.ToString("D2")); - ImGui.TextUnformatted(b.ToString("X2")); - } - - ImGui.SameLine(); - } - - ImGui.NewLine(); - } - } - - private string _clipboardText = string.Empty; - private byte[] _clipboardData = Array.Empty(); - private byte[] _dataUncompressed = Array.Empty(); - private byte _version = 0; - private string _textUncompressed = string.Empty; - private JObject? _json = null; - private DesignBase? _tmpDesign = null; - private Exception? _clipboardProblem = null; - - private void DrawDesignConverter() - { - using var tree = ImRaii.TreeNode("Design Converter"); - if (!tree) - return; - - if (ImGui.Button("Import Clipboard")) - { - _clipboardText = string.Empty; - _clipboardData = Array.Empty(); - _dataUncompressed = Array.Empty(); - _textUncompressed = string.Empty; - _json = null; - _tmpDesign = null; - _clipboardProblem = null; - - try - { - _clipboardText = ImGui.GetClipboardText(); - _clipboardData = Convert.FromBase64String(_clipboardText); - _version = _clipboardData[0]; - if (_version == 5) - _clipboardData = _clipboardData[DesignBase64Migration.Base64SizeV4..]; - _version = _clipboardData.Decompress(out _dataUncompressed); - _textUncompressed = Encoding.UTF8.GetString(_dataUncompressed); - _json = JObject.Parse(_textUncompressed); - _tmpDesign = _designConverter.FromBase64(_clipboardText, true, true, out _); - } - catch (Exception ex) - { - _clipboardProblem = ex; - } - } - - if (_clipboardText.Length > 0) - { - using var f = ImRaii.PushFont(UiBuilder.MonoFont); - ImGuiUtil.TextWrapped(_clipboardText); - } - - if (_clipboardData.Length > 0) - { - using var f = ImRaii.PushFont(UiBuilder.MonoFont); - ImGuiUtil.TextWrapped(string.Join(" ", _clipboardData.Select(b => b.ToString("X2")))); - } - - if (_dataUncompressed.Length > 0) - { - using var f = ImRaii.PushFont(UiBuilder.MonoFont); - ImGuiUtil.TextWrapped(string.Join(" ", _dataUncompressed.Select(b => b.ToString("X2")))); - } - - if (_textUncompressed.Length > 0) - { - using var f = ImRaii.PushFont(UiBuilder.MonoFont); - ImGuiUtil.TextWrapped(_textUncompressed); - } - - if (_json != null) - ImGui.TextUnformatted("JSON Parsing Successful!"); - - if (_tmpDesign != null) - DrawDesign(_tmpDesign); - - if (_clipboardProblem != null) - { - using var f = ImRaii.PushFont(UiBuilder.MonoFont); - ImGuiUtil.TextWrapped(_clipboardProblem.ToString()); - } - } - - public void DrawState(ActorData data, ActorState state) - { - using var table = ImRaii.Table("##state", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); - if (!table) - return; - - ImGuiUtil.DrawTableColumn("Name"); - ImGuiUtil.DrawTableColumn(state.Identifier.ToString()); - ImGui.TableNextColumn(); - if (ImGui.Button("Reset")) - _state.ResetState(state, StateChanged.Source.Manual); - - ImGui.TableNextRow(); - - static void PrintRow(string label, T actor, T model, StateChanged.Source source) where T : notnull - { - ImGuiUtil.DrawTableColumn(label); - ImGuiUtil.DrawTableColumn(actor.ToString()!); - ImGuiUtil.DrawTableColumn(model.ToString()!); - ImGuiUtil.DrawTableColumn(source.ToString()); - } - - static string ItemString(in DesignData data, EquipSlot slot) - { - var item = data.Item(slot); - return $"{item.Name} ({item.ModelId.Id}{(item.WeaponType != 0 ? $"-{item.WeaponType.Id}" : string.Empty)}-{item.Variant})"; - } - - PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]); - ImGui.TableNextRow(); - PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state[ActorState.MetaIndex.Wetness]); - ImGui.TableNextRow(); - - if (state.BaseData.IsHuman && state.ModelData.IsHuman) - { - PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state[ActorState.MetaIndex.HatState]); - ImGui.TableNextRow(); - PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(), - state[ActorState.MetaIndex.VisorState]); - ImGui.TableNextRow(); - PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(), - state[ActorState.MetaIndex.WeaponState]); - ImGui.TableNextRow(); - foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) - { - PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state[slot, false]); - ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString()); - ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString()); - ImGuiUtil.DrawTableColumn(state[slot, true].ToString()); - } - - foreach (var type in Enum.GetValues()) - { - PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state[type]); - ImGui.TableNextRow(); - } - } - else - { - ImGuiUtil.DrawTableColumn(string.Join(" ", state.BaseData.GetCustomizeBytes().Select(b => b.ToString("X2")))); - ImGuiUtil.DrawTableColumn(string.Join(" ", state.ModelData.GetCustomizeBytes().Select(b => b.ToString("X2")))); - ImGui.TableNextRow(); - ImGuiUtil.DrawTableColumn(string.Join(" ", state.BaseData.GetEquipmentBytes().Select(b => b.ToString("X2")))); - ImGuiUtil.DrawTableColumn(string.Join(" ", state.ModelData.GetEquipmentBytes().Select(b => b.ToString("X2")))); - } - } - - public static void DrawDesignData(in DesignData data) - { - if (data.IsHuman) - { - using var table = ImRaii.Table("##equip", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); - if (!table) - return; - - foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) - { - var item = data.Item(slot); - var stain = data.Stain(slot); - ImGuiUtil.DrawTableColumn(slot.ToName()); - ImGuiUtil.DrawTableColumn(item.Name); - ImGuiUtil.DrawTableColumn(item.ItemId.ToString()); - ImGuiUtil.DrawTableColumn(stain.ToString()); - } - - ImGuiUtil.DrawTableColumn("Hat Visible"); - ImGuiUtil.DrawTableColumn(data.IsHatVisible().ToString()); - ImGui.TableNextRow(); - ImGuiUtil.DrawTableColumn("Visor Toggled"); - ImGuiUtil.DrawTableColumn(data.IsVisorToggled().ToString()); - ImGui.TableNextRow(); - ImGuiUtil.DrawTableColumn("Weapon Visible"); - ImGuiUtil.DrawTableColumn(data.IsWeaponVisible().ToString()); - ImGui.TableNextRow(); - - ImGuiUtil.DrawTableColumn("Model ID"); - ImGuiUtil.DrawTableColumn(data.ModelId.ToString()); - ImGui.TableNextRow(); - - foreach (var index in Enum.GetValues()) - { - var value = data.Customize[index]; - ImGuiUtil.DrawTableColumn(index.ToDefaultName()); - ImGuiUtil.DrawTableColumn(value.Value.ToString()); - ImGui.TableNextRow(); - } - - ImGuiUtil.DrawTableColumn("Is Wet"); - ImGuiUtil.DrawTableColumn(data.IsWet().ToString()); - ImGui.TableNextRow(); - } - else - { - ImGui.TextUnformatted($"Model ID {data.ModelId}"); - ImGui.Separator(); - using var font = ImRaii.PushFont(UiBuilder.MonoFont); - ImGui.TextUnformatted("Customize Array"); - ImGui.Separator(); - ImGuiUtil.TextWrapped(string.Join(" ", data.GetCustomizeBytes().Select(b => b.ToString("X2")))); - - ImGui.TextUnformatted("Equipment Array"); - ImGui.Separator(); - ImGuiUtil.TextWrapped(string.Join(" ", data.GetEquipmentBytes().Select(b => b.ToString("X2")))); - } - } - - private void DrawDesign(DesignBase design) - { - using var table = ImRaii.Table("##equip", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); - if (design is Design d) - { - ImGuiUtil.DrawTableColumn("Name"); - ImGuiUtil.DrawTableColumn(d.Name); - ImGuiUtil.DrawTableColumn($"({d.Index})"); - ImGui.TableNextColumn(); - ImGui.TextUnformatted("Description (Hover)"); - ImGuiUtil.HoverTooltip(d.Description); - ImGui.TableNextRow(); - - ImGuiUtil.DrawTableColumn("Identifier"); - ImGuiUtil.DrawTableColumn(d.Identifier.ToString()); - ImGui.TableNextRow(); - ImGuiUtil.DrawTableColumn("Design File System Path"); - ImGuiUtil.DrawTableColumn(_designFileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : "No Path Known"); - ImGui.TableNextRow(); - - ImGuiUtil.DrawTableColumn("Creation"); - ImGuiUtil.DrawTableColumn(d.CreationDate.ToString()); - ImGui.TableNextRow(); - ImGuiUtil.DrawTableColumn("Update"); - ImGuiUtil.DrawTableColumn(d.LastEdit.ToString()); - ImGui.TableNextRow(); - ImGuiUtil.DrawTableColumn("Tags"); - ImGuiUtil.DrawTableColumn(string.Join(", ", d.Tags)); - ImGui.TableNextRow(); - } - - foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) - { - var item = design.DesignData.Item(slot); - var apply = design.DoApplyEquip(slot); - var stain = design.DesignData.Stain(slot); - var applyStain = design.DoApplyStain(slot); - ImGuiUtil.DrawTableColumn(slot.ToName()); - ImGuiUtil.DrawTableColumn(item.Name); - ImGuiUtil.DrawTableColumn(item.ItemId.ToString()); - ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep"); - ImGuiUtil.DrawTableColumn(stain.ToString()); - ImGuiUtil.DrawTableColumn(applyStain ? "Apply" : "Keep"); - } - - ImGuiUtil.DrawTableColumn("Hat Visible"); - ImGuiUtil.DrawTableColumn(design.DesignData.IsHatVisible().ToString()); - ImGuiUtil.DrawTableColumn(design.DoApplyHatVisible() ? "Apply" : "Keep"); - ImGui.TableNextRow(); - ImGuiUtil.DrawTableColumn("Visor Toggled"); - ImGuiUtil.DrawTableColumn(design.DesignData.IsVisorToggled().ToString()); - ImGuiUtil.DrawTableColumn(design.DoApplyVisorToggle() ? "Apply" : "Keep"); - ImGui.TableNextRow(); - ImGuiUtil.DrawTableColumn("Weapon Visible"); - ImGuiUtil.DrawTableColumn(design.DesignData.IsWeaponVisible().ToString()); - ImGuiUtil.DrawTableColumn(design.DoApplyWeaponVisible() ? "Apply" : "Keep"); - ImGui.TableNextRow(); - - ImGuiUtil.DrawTableColumn("Model ID"); - ImGuiUtil.DrawTableColumn(design.DesignData.ModelId.ToString()); - ImGui.TableNextRow(); - - foreach (var index in Enum.GetValues()) - { - var value = design.DesignData.Customize[index]; - var apply = design.DoApplyCustomize(index); - ImGuiUtil.DrawTableColumn(index.ToDefaultName()); - ImGuiUtil.DrawTableColumn(value.Value.ToString()); - ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep"); - ImGui.TableNextRow(); - } - - ImGuiUtil.DrawTableColumn("Is Wet"); - ImGuiUtil.DrawTableColumn(design.DesignData.IsWet().ToString()); - ImGui.TableNextRow(); - } - - #endregion - - #region State - - private void DrawState() - { - if (!ImGui.CollapsingHeader($"State ({_state.Count})###State")) - return; - - DrawActorTrees(); - DrawRetainedStates(); - } - - private void DrawActorTrees() - { - using var tree = ImRaii.TreeNode("Active Actors"); - if (!tree) - return; - - _objectManager.Update(); - foreach (var (identifier, actors) in _objectManager) - { - if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Trash.ToIconString()}##{actors.Label}", new Vector2(ImGui.GetFrameHeight()), - string.Empty, !_state.ContainsKey(identifier), true)) - _state.DeleteState(identifier); - - ImGui.SameLine(); - using var t = ImRaii.TreeNode(actors.Label); - if (!t) - continue; - - if (_state.GetOrCreate(identifier, actors.Objects[0], out var state)) - DrawState(actors, state); - else - ImGui.TextUnformatted("Invalid actor."); - } - } - - private void DrawRetainedStates() - { - using var tree = ImRaii.TreeNode("Retained States (Inactive Actors)"); - if (!tree) - return; - - foreach (var (identifier, state) in _state.Where(kvp => !_objectManager.ContainsKey(kvp.Key))) - { - using var t = ImRaii.TreeNode(identifier.ToString()); - if (t) - DrawState(ActorData.Invalid, state); - } - } - - #endregion - - #region Auto Designs - - private void DrawAutoDesigns() - { - if (!ImGui.CollapsingHeader("Auto Designs")) - return; - - foreach (var (set, idx) in _autoDesignManager.WithIndex()) - { - using var id = ImRaii.PushId(idx); - using var tree = ImRaii.TreeNode(set.Name); - if (!tree) - continue; - - using var table = ImRaii.Table("##autoDesign", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - continue; - - ImGuiUtil.DrawTableColumn("Name"); - ImGuiUtil.DrawTableColumn(set.Name); - - ImGuiUtil.DrawTableColumn("Index"); - ImGuiUtil.DrawTableColumn(idx.ToString()); - - ImGuiUtil.DrawTableColumn("Enabled"); - ImGuiUtil.DrawTableColumn(set.Enabled.ToString()); - - ImGuiUtil.DrawTableColumn("Actor"); - ImGuiUtil.DrawTableColumn(set.Identifiers[0].ToString()); - - foreach (var (design, designIdx) in set.Designs.WithIndex()) - { - ImGuiUtil.DrawTableColumn($"{design.Name(false)} ({designIdx})"); - ImGuiUtil.DrawTableColumn($"{design.ApplicationType} {design.Jobs.Name}"); - } - } - } - - #endregion - - #region Unlocks - - private void DrawUnlocks() - { - if (!ImGui.CollapsingHeader("Unlocks")) - return; - - DrawCustomizationUnlocks(); - DrawItemUnlocks(); - DrawUnlockableItems(); - } - - private void DrawCustomizationUnlocks() - { - using var tree = ImRaii.TreeNode("Customization"); - if (!tree) - return; - - - using var table = ImRaii.Table("customizationUnlocks", 6, - ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, - new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeight())); - if (!table) - return; - - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); - ImGui.TableNextRow(); - var remainder = ImGuiClip.ClippedDraw(_customizeUnlocks.Unlockable, skips, t => - { - ImGuiUtil.DrawTableColumn(t.Key.Index.ToDefaultName()); - ImGuiUtil.DrawTableColumn(t.Key.CustomizeId.ToString()); - ImGuiUtil.DrawTableColumn(t.Key.Value.Value.ToString()); - ImGuiUtil.DrawTableColumn(t.Value.Data.ToString()); - ImGuiUtil.DrawTableColumn(t.Value.Name); - ImGuiUtil.DrawTableColumn(_customizeUnlocks.IsUnlocked(t.Key, out var time) - ? time == DateTimeOffset.MinValue - ? "Always" - : time.LocalDateTime.ToString("g") - : "Never"); - }, _customizeUnlocks.Unlockable.Count); - ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); - } - - private void DrawItemUnlocks() - { - using var tree = ImRaii.TreeNode("Unlocked Items"); - if (!tree) - return; - - using var table = ImRaii.Table("itemUnlocks", 5, - ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, - new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeight())); - if (!table) - return; - - ImGui.TableSetupColumn("ItemId", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, 400 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Slot", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Model", ImGuiTableColumnFlags.WidthFixed, 80 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Unlock", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); - - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); - ImGui.TableNextRow(); - var remainder = ImGuiClip.ClippedDraw(_itemUnlocks, skips, t => - { - ImGuiUtil.DrawTableColumn(t.Key.ToString()); - if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) - { - ImGuiUtil.DrawTableColumn(equip.Name); - ImGuiUtil.DrawTableColumn(equip.Type.ToName()); - ImGuiUtil.DrawTableColumn(equip.Weapon().ToString()); - } - else - { - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - } - - ImGuiUtil.DrawTableColumn(_itemUnlocks.IsUnlocked(t.Key, out var time) - ? time == DateTimeOffset.MinValue - ? "Always" - : time.LocalDateTime.ToString("g") - : "Never"); - }, _itemUnlocks.Count); - ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); - } - - private void DrawUnlockableItems() - { - using var tree = ImRaii.TreeNode("Unlockable Items"); - if (!tree) - return; - - using var table = ImRaii.Table("unlockableItem", 6, - ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, - new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeight())); - if (!table) - return; - - ImGui.TableSetupColumn("ItemId", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, 400 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Slot", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Model", ImGuiTableColumnFlags.WidthFixed, 80 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Unlock", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("Criteria", ImGuiTableColumnFlags.WidthStretch); - - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); - ImGui.TableNextRow(); - var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlockable, skips, t => - { - ImGuiUtil.DrawTableColumn(t.Key.ToString()); - if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) - { - ImGuiUtil.DrawTableColumn(equip.Name); - ImGuiUtil.DrawTableColumn(equip.Type.ToName()); - ImGuiUtil.DrawTableColumn(equip.Weapon().ToString()); - } - else - { - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - } - - ImGuiUtil.DrawTableColumn(_itemUnlocks.IsUnlocked(t.Key, out var time) - ? time == DateTimeOffset.MinValue - ? "Always" - : time.LocalDateTime.ToString("g") - : "Never"); - ImGuiUtil.DrawTableColumn(t.Value.ToString()); - }, _itemUnlocks.Unlockable.Count); - ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); - } - - #endregion - - #region Inventory - - private void DrawInventory() - { - if (!ImGui.CollapsingHeader("Inventory")) - return; - - var inventory = InventoryManager.Instance(); - if (inventory == null) - return; - - ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)inventory:X}"); - - var equip = inventory->GetInventoryContainer(InventoryType.EquippedItems); - if (equip == null || equip->Loaded == 0) - return; - - ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)equip:X}"); - - using var table = ImRaii.Table("items", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); - if (!table) - return; - - for (var i = 0; i < equip->Size; ++i) - { - ImGuiUtil.DrawTableColumn(i.ToString()); - var item = equip->GetInventorySlot(i); - if (item == null) - { - ImGuiUtil.DrawTableColumn("NULL"); - ImGui.TableNextRow(); - } - else - { - ImGuiUtil.DrawTableColumn(item->ItemID.ToString()); - ImGuiUtil.DrawTableColumn(item->GlamourID.ToString()); - ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)item:X}"); - } - } - } - - #endregion - - #region Fun - - private void DrawFun() - { - if (!ImGui.CollapsingHeader("Fun Module")) - return; - - ImGui.TextUnformatted($"Current Festival: {_funModule.CurrentFestival}"); - ImGui.TextUnformatted($"Festivals Enabled: {_config.DisableFestivals switch { 1 => "Undecided", 0 => "Enabled", _ => "Disabled" }}"); - ImGui.TextUnformatted($"Popup Open: {ImGui.IsPopupOpen("FestivalPopup", ImGuiPopupFlags.AnyPopup)}"); - if (ImGui.Button("Force Christmas")) - _funModule.ForceFestival(FunModule.FestivalType.Christmas); - if (ImGui.Button("Force Halloween")) - _funModule.ForceFestival(FunModule.FestivalType.Halloween); - if (ImGui.Button("Force April First")) - _funModule.ForceFestival(FunModule.FestivalType.AprilFirst); - if (ImGui.Button("Force None")) - _funModule.ForceFestival(FunModule.FestivalType.None); - if (ImGui.Button("Revert")) - _funModule.ResetFestival(); - if (ImGui.Button("Reset Popup")) - { - _config.DisableFestivals = 1; - _config.Save(); - } - } - - #endregion - - #region IPC - - private string _gameObjectName = string.Empty; - private string _base64Apply = string.Empty; - - private void DrawIpc() - { - if (!ImGui.CollapsingHeader("IPC Tester")) - return; - - 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); - 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.Objects[_gameObjectIndex] as Character); - if (base64 != null) - ImGuiUtil.CopyOnClickSelectable(base64); - 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.Objects[_gameObjectIndex] as Character); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAll); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##AllName")) - GlamourerIpc.ApplyAllSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllToCharacter); - ImGui.TableNextColumn(); - if (ImGui.Button("Apply##AllCharacter")) - GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character); - - 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.Objects[_gameObjectIndex] as Character); - - 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.Objects[_gameObjectIndex] as Character); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); - ImGui.TableNextColumn(); - if (ImGui.Button("Unlock##CustomizeCharacter")) - GlamourerIpc.UnlockSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); - - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertToAutomation); - ImGui.TableNextColumn(); - if (ImGui.Button("Revert##CustomizeCharacter")) - GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); - } - - #endregion -} diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs new file mode 100644 index 0000000..71df7b9 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -0,0 +1,113 @@ +using System; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Glamourer.Customization; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.Interop; +using Glamourer.Interop.Structs; +using Glamourer.State; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using Penumbra.GameData.Enums; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IDebugTabTree +{ + public string Label + => $"Active Actors ({_stateManager.Count})###Active Actors"; + + public bool Disabled + => false; + + public void Draw() + { + _objectManager.Update(); + foreach (var (identifier, actors) in _objectManager) + { + if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Trash.ToIconString()}##{actors.Label}", new Vector2(ImGui.GetFrameHeight()), + string.Empty, !_stateManager.ContainsKey(identifier), true)) + _stateManager.DeleteState(identifier); + + ImGui.SameLine(); + using var t = ImRaii.TreeNode(actors.Label); + if (!t) + continue; + + if (_stateManager.GetOrCreate(identifier, actors.Objects[0], out var state)) + DrawState(_stateManager, actors, state); + else + ImGui.TextUnformatted("Invalid actor."); + } + } + + public static void DrawState(StateManager stateManager, ActorData data, ActorState state) + { + using var table = ImRaii.Table("##state", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); + if (!table) + return; + + ImGuiUtil.DrawTableColumn("Name"); + ImGuiUtil.DrawTableColumn(state.Identifier.ToString()); + ImGui.TableNextColumn(); + if (ImGui.Button("Reset")) + stateManager.ResetState(state, StateChanged.Source.Manual); + + ImGui.TableNextRow(); + + static void PrintRow(string label, T actor, T model, StateChanged.Source source) where T : notnull + { + ImGuiUtil.DrawTableColumn(label); + ImGuiUtil.DrawTableColumn(actor.ToString()!); + ImGuiUtil.DrawTableColumn(model.ToString()!); + ImGuiUtil.DrawTableColumn(source.ToString()); + } + + static string ItemString(in DesignData data, EquipSlot slot) + { + var item = data.Item(slot); + return $"{item.Name} ({item.ModelId.Id}{(item.WeaponType != 0 ? $"-{item.WeaponType.Id}" : string.Empty)}-{item.Variant})"; + } + + PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]); + ImGui.TableNextRow(); + PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state[ActorState.MetaIndex.Wetness]); + ImGui.TableNextRow(); + + if (state.BaseData.IsHuman && state.ModelData.IsHuman) + { + PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state[ActorState.MetaIndex.HatState]); + ImGui.TableNextRow(); + PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(), + state[ActorState.MetaIndex.VisorState]); + ImGui.TableNextRow(); + PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(), + state[ActorState.MetaIndex.WeaponState]); + ImGui.TableNextRow(); + foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) + { + PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state[slot, false]); + ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString()); + ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString()); + ImGuiUtil.DrawTableColumn(state[slot, true].ToString()); + } + + foreach (var type in Enum.GetValues()) + { + PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state[type]); + ImGui.TableNextRow(); + } + } + else + { + ImGuiUtil.DrawTableColumn(string.Join(" ", state.BaseData.GetCustomizeBytes().Select(b => b.ToString("X2")))); + ImGuiUtil.DrawTableColumn(string.Join(" ", state.ModelData.GetCustomizeBytes().Select(b => b.ToString("X2")))); + ImGui.TableNextRow(); + ImGuiUtil.DrawTableColumn(string.Join(" ", state.BaseData.GetEquipmentBytes().Select(b => b.ToString("X2")))); + ImGuiUtil.DrawTableColumn(string.Join(" ", state.ModelData.GetEquipmentBytes().Select(b => b.ToString("X2")))); + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs new file mode 100644 index 0000000..42fc52b --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Interface.Utility; +using Glamourer.Services; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using ImGuiClip = OtterGui.ImGuiClip; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class ActorServicePanel(ActorService _actors, ItemManager _items) : IDebugTabTree +{ + public string Label + => "Actor Service"; + + public bool Disabled + => !_actors.Valid; + + private string _bnpcFilter = string.Empty; + private string _enpcFilter = string.Empty; + private string _companionFilter = string.Empty; + private string _mountFilter = string.Empty; + private string _ornamentFilter = string.Empty; + private string _worldFilter = string.Empty; + + public void Draw() + { + DrawBnpcTable(); + DebugTab.DrawNameTable("ENPCs", ref _enpcFilter, _actors.AwaitedService.Data.ENpcs.Select(kvp => (kvp.Key, kvp.Value))); + DebugTab.DrawNameTable("Companions", ref _companionFilter, _actors.AwaitedService.Data.Companions.Select(kvp => (kvp.Key, kvp.Value))); + DebugTab.DrawNameTable("Mounts", ref _mountFilter, _actors.AwaitedService.Data.Mounts.Select(kvp => (kvp.Key, kvp.Value))); + DebugTab.DrawNameTable("Ornaments", ref _ornamentFilter, _actors.AwaitedService.Data.Ornaments.Select(kvp => (kvp.Key, kvp.Value))); + DebugTab.DrawNameTable("Worlds", ref _worldFilter, _actors.AwaitedService.Data.Worlds.Select(kvp => ((uint)kvp.Key, kvp.Value))); + } + + private void DrawBnpcTable() + { + using var _ = ImRaii.PushId(1); + using var tree = ImRaii.TreeNode("BNPCs"); + if (!tree) + return; + + var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref _bnpcFilter, 256); + var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; + using var table = ImRaii.Table("##table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, + new Vector2(-1, 10 * height)); + if (!table) + return; + + if (resetScroll) + ImGui.SetScrollY(0); + ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("3", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableNextColumn(); + var skips = ImGuiClip.GetNecessarySkips(height); + ImGui.TableNextRow(); + var data = _actors.AwaitedService.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Key.ToString("D5"), kvp.Value)); + var remainder = ImGuiClip.FilteredClippedDraw(data, skips, + p => p.Item2.Contains(_bnpcFilter) || p.Item3.Contains(_bnpcFilter, StringComparison.OrdinalIgnoreCase), + p => + { + ImGuiUtil.DrawTableColumn(p.Item2); + ImGuiUtil.DrawTableColumn(p.Item3); + var bnpcs = _items.IdentifierService.AwaitedService.GetBnpcsFromName(p.Item1); + ImGuiUtil.DrawTableColumn(string.Join(", ", bnpcs.Select(b => b.Id.ToString()))); + }); + ImGuiClip.DrawEndDummy(remainder, height); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs new file mode 100644 index 0000000..8e29b8b --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs @@ -0,0 +1,48 @@ +using Glamourer.Automation; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IDebugTabTree +{ + public string Label + => "Auto Designs"; + + public bool Disabled + => false; + + public void Draw() + { + foreach (var (set, idx) in _autoDesignManager.WithIndex()) + { + using var id = ImRaii.PushId(idx); + using var tree = ImRaii.TreeNode(set.Name); + if (!tree) + continue; + + using var table = ImRaii.Table("##autoDesign", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + if (!table) + continue; + + ImGuiUtil.DrawTableColumn("Name"); + ImGuiUtil.DrawTableColumn(set.Name); + + ImGuiUtil.DrawTableColumn("Index"); + ImGuiUtil.DrawTableColumn(idx.ToString()); + + ImGuiUtil.DrawTableColumn("Enabled"); + ImGuiUtil.DrawTableColumn(set.Enabled.ToString()); + + ImGuiUtil.DrawTableColumn("Actor"); + ImGuiUtil.DrawTableColumn(set.Identifiers[0].ToString()); + + foreach (var (design, designIdx) in set.Designs.WithIndex()) + { + ImGuiUtil.DrawTableColumn($"{design.Name(false)} ({designIdx})"); + ImGuiUtil.DrawTableColumn($"{design.ApplicationType} {design.Jobs.Name}"); + } + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs new file mode 100644 index 0000000..6088651 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -0,0 +1,67 @@ +using System; +using Glamourer.Customization; +using Glamourer.Services; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class CustomizationServicePanel(CustomizationService _customization) : IDebugTabTree +{ + public string Label + => "Customization Service"; + + public bool Disabled + => !_customization.Valid; + + public void Draw() + { + foreach (var clan in _customization.AwaitedService.Clans) + { + foreach (var gender in _customization.AwaitedService.Genders) + { + var set = _customization.AwaitedService.GetList(clan, gender); + DrawCustomizationInfo(set); + DrawNpcCustomizationInfo(set); + } + } + } + + private void DrawCustomizationInfo(CustomizationSet set) + { + using var tree = ImRaii.TreeNode($"{_customization.ClanName(set.Clan, set.Gender)} {set.Gender}"); + if (!tree) + return; + + using var table = ImRaii.Table("data", 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + if (!table) + return; + + foreach (var index in Enum.GetValues()) + { + ImGuiUtil.DrawTableColumn(index.ToString()); + ImGuiUtil.DrawTableColumn(set.Option(index)); + ImGuiUtil.DrawTableColumn(set.IsAvailable(index) ? "Available" : "Unavailable"); + ImGuiUtil.DrawTableColumn(set.Type(index).ToString()); + ImGuiUtil.DrawTableColumn(set.Count(index).ToString()); + } + } + + private void DrawNpcCustomizationInfo(CustomizationSet set) + { + using var tree = ImRaii.TreeNode($"{_customization.ClanName(set.Clan, set.Gender)} {set.Gender} (NPC Options)"); + if (!tree) + return; + + using var table = ImRaii.Table("npc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + if (!table) + return; + + foreach (var (index, value) in set.NpcOptions) + { + ImGuiUtil.DrawTableColumn(index.ToString()); + ImGuiUtil.DrawTableColumn(value.Value.ToString()); + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs new file mode 100644 index 0000000..f253923 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs @@ -0,0 +1,45 @@ +using System; +using System.Numerics; +using Glamourer.Customization; +using Glamourer.Unlocks; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class CustomizationUnlockPanel(CustomizeUnlockManager _customizeUnlocks) : IDebugTabTree +{ + public string Label + => "Customizations"; + + public bool Disabled + => false; + + public void Draw() + { + using var table = ImRaii.Table("customizationUnlocks", 6, + ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, + new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeight())); + if (!table) + return; + + ImGui.TableNextColumn(); + var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); + ImGui.TableNextRow(); + var remainder = ImGuiClip.ClippedDraw(_customizeUnlocks.Unlockable, skips, t => + { + ImGuiUtil.DrawTableColumn(t.Key.Index.ToDefaultName()); + ImGuiUtil.DrawTableColumn(t.Key.CustomizeId.ToString()); + ImGuiUtil.DrawTableColumn(t.Key.Value.Value.ToString()); + ImGuiUtil.DrawTableColumn(t.Value.Data.ToString()); + ImGuiUtil.DrawTableColumn(t.Value.Name); + ImGuiUtil.DrawTableColumn(_customizeUnlocks.IsUnlocked(t.Key, out var time) + ? time == DateTimeOffset.MinValue + ? "Always" + : time.LocalDateTime.ToString("g") + : "Never"); + }, _customizeUnlocks.Unlockable.Count); + ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs new file mode 100644 index 0000000..85ba96c --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs @@ -0,0 +1,42 @@ +using System.IO; +using System.Numerics; +using Glamourer.Customization; +using Glamourer.Interop; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class DatFilePanel(ImportService _importService) : IDebugTabTree +{ + public string Label + => "Character Dat File"; + + public bool Disabled + => false; + + private string _datFilePath = string.Empty; + private DatCharacterFile? _datFile = null; + + public void Draw() + { + ImGui.InputTextWithHint("##datFilePath", "Dat File Path...", ref _datFilePath, 256); + var exists = _datFilePath.Length > 0 && File.Exists(_datFilePath); + if (ImGuiUtil.DrawDisabledButton("Load##Dat", Vector2.Zero, string.Empty, !exists)) + _datFile = _importService.LoadDat(_datFilePath, out var tmp) ? tmp : null; + + if (ImGuiUtil.DrawDisabledButton("Save##Dat", Vector2.Zero, string.Empty, _datFilePath.Length == 0 || _datFile == null)) + _importService.SaveDesignAsDat(_datFilePath, _datFile!.Value.Customize, _datFile!.Value.Description); + + if (_datFile != null) + { + ImGui.TextUnformatted(_datFile.Value.Magic.ToString()); + ImGui.TextUnformatted(_datFile.Value.Version.ToString()); + ImGui.TextUnformatted(_datFile.Value.Time.LocalDateTime.ToString("g")); + ImGui.TextUnformatted(_datFile.Value.Voice.ToString()); + ImGui.TextUnformatted(_datFile.Value.Customize.Data.ToString()); + ImGui.TextUnformatted(_datFile.Value.Description); + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs new file mode 100644 index 0000000..52c7b53 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Interface.Utility; +using ImGuiNET; +using Microsoft.Extensions.DependencyInjection; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Widgets; +using ImGuiClip = Dalamud.Interface.Utility.ImGuiClip; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public unsafe class DebugTab(IServiceProvider _provider) : ITab +{ + private readonly Configuration _config = _provider.GetRequiredService(); + + public bool IsVisible + => _config.DebugMode; + + public ReadOnlySpan Label + => "Debug"u8; + + private readonly DebugTabHeader[] _headers = + [ + DebugTabHeader.CreateInterop(_provider), + DebugTabHeader.CreateGameData(_provider), + DebugTabHeader.CreateDesigns(_provider), + DebugTabHeader.CreateState(_provider), + DebugTabHeader.CreateUnlocks(_provider), + ]; + + public void DrawContent() + { + using var child = ImRaii.Child("MainWindowChild"); + if (!child) + return; + + foreach (var header in _headers) + header.Draw(); + } + + public static void DrawInputModelSet(bool withWeapon, ref int setId, ref int secondaryId, ref int variant) + { + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + ImGui.InputInt("##SetId", ref setId, 0, 0); + if (withWeapon) + { + ImGui.SameLine(); + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + ImGui.InputInt("##TypeId", ref secondaryId, 0, 0); + } + + ImGui.SameLine(); + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + ImGui.InputInt("##Variant", ref variant, 0, 0); + } + + public static void DrawNameTable(string label, ref string filter, IEnumerable<(uint, string)> names) + { + using var _ = ImRaii.PushId(label); + using var tree = ImRaii.TreeNode(label); + if (!tree) + return; + + var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref filter, 256); + var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; + using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, + new Vector2(-1, 10 * height)); + if (!table) + return; + + if (resetScroll) + ImGui.SetScrollY(0); + ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableNextColumn(); + var skips = ImGuiClip.GetNecessarySkips(height); + ImGui.TableNextColumn(); + var f = filter; + var remainder = ImGuiClip.FilteredClippedDraw(names.Select(p => (p.Item1.ToString("D5"), p.Item2)), skips, + p => p.Item1.Contains(f) || p.Item2.Contains(f, StringComparison.OrdinalIgnoreCase), + p => + { + ImGuiUtil.DrawTableColumn(p.Item1); + ImGuiUtil.DrawTableColumn(p.Item2); + }); + ImGuiClip.DrawEndDummy(remainder, height); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs new file mode 100644 index 0000000..e010b2a --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using ImGuiNET; +using Microsoft.Extensions.DependencyInjection; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public interface IDebugTabTree +{ + public string Label { get; } + public void Draw(); + + public bool Disabled { get; } +} + +public class DebugTabHeader(string label, params IDebugTabTree[] subTrees) +{ + public string Label { get; } = label; + public IReadOnlyList SubTrees { get; } = subTrees; + + public void Draw() + { + if (!ImGui.CollapsingHeader(Label)) + return; + + foreach (var subTree in SubTrees) + { + using (var disabled = ImRaii.Disabled(subTree.Disabled)) + { + using var tree = ImRaii.TreeNode(subTree.Label); + if (!tree) + continue; + } + + subTree.Draw(); + } + } + + public static DebugTabHeader CreateInterop(IServiceProvider provider) + => new + ( + "Interop", + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService() + ); + + public static DebugTabHeader CreateGameData(IServiceProvider provider) + => new + ( + "Game Data", + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService() + ); + + public static DebugTabHeader CreateDesigns(IServiceProvider provider) + => new + ( + "Designs", + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService() + ); + + public static DebugTabHeader CreateState(IServiceProvider provider) + => new + ( + "State", + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService() + ); + + public static DebugTabHeader CreateUnlocks(IServiceProvider provider) + => new + ( + "Unlocks", + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService() + ); +} diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs new file mode 100644 index 0000000..9553f72 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs @@ -0,0 +1,97 @@ +using System; +using System.Linq; +using System.Text; +using Dalamud.Interface; +using Glamourer.Designs; +using Glamourer.Utility; +using ImGuiNET; +using Newtonsoft.Json.Linq; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class DesignConverterPanel(DesignConverter _designConverter) : IDebugTabTree +{ + public string Label + => "Design Converter"; + + public bool Disabled + => false; + + private string _clipboardText = string.Empty; + private byte[] _clipboardData = []; + private byte[] _dataUncompressed = []; + private byte _version = 0; + private string _textUncompressed = string.Empty; + private JObject? _json = null; + private DesignBase? _tmpDesign = null; + private Exception? _clipboardProblem = null; + + public void Draw() + { + if (ImGui.Button("Import Clipboard")) + { + _clipboardText = string.Empty; + _clipboardData = []; + _dataUncompressed = []; + _textUncompressed = string.Empty; + _json = null; + _tmpDesign = null; + _clipboardProblem = null; + + try + { + _clipboardText = ImGui.GetClipboardText(); + _clipboardData = Convert.FromBase64String(_clipboardText); + _version = _clipboardData[0]; + if (_version == 5) + _clipboardData = _clipboardData[DesignBase64Migration.Base64SizeV4..]; + _version = _clipboardData.Decompress(out _dataUncompressed); + _textUncompressed = Encoding.UTF8.GetString(_dataUncompressed); + _json = JObject.Parse(_textUncompressed); + _tmpDesign = _designConverter.FromBase64(_clipboardText, true, true, out _); + } + catch (Exception ex) + { + _clipboardProblem = ex; + } + } + + if (_clipboardText.Length > 0) + { + using var f = ImRaii.PushFont(UiBuilder.MonoFont); + ImGuiUtil.TextWrapped(_clipboardText); + } + + if (_clipboardData.Length > 0) + { + using var f = ImRaii.PushFont(UiBuilder.MonoFont); + ImGuiUtil.TextWrapped(string.Join(" ", _clipboardData.Select(b => b.ToString("X2")))); + } + + if (_dataUncompressed.Length > 0) + { + using var f = ImRaii.PushFont(UiBuilder.MonoFont); + ImGuiUtil.TextWrapped(string.Join(" ", _dataUncompressed.Select(b => b.ToString("X2")))); + } + + if (_textUncompressed.Length > 0) + { + using var f = ImRaii.PushFont(UiBuilder.MonoFont); + ImGuiUtil.TextWrapped(_textUncompressed); + } + + if (_json != null) + ImGui.TextUnformatted("JSON Parsing Successful!"); + + if (_tmpDesign != null) + DesignManagerPanel.DrawDesign(_tmpDesign, null); + + if (_clipboardProblem != null) + { + using var f = ImRaii.PushFont(UiBuilder.MonoFont); + ImGuiUtil.TextWrapped(_clipboardProblem.ToString()); + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs new file mode 100644 index 0000000..af4814a --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -0,0 +1,122 @@ +using System; +using System.Linq; +using Dalamud.Interface; +using Glamourer.Customization; +using Glamourer.Designs; +using Glamourer.Structs; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using Penumbra.GameData.Enums; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _designFileSystem) : IDebugTabTree +{ + public string Label + => $"Design Manager ({_designManager.Designs.Count} Designs)###Design Manager"; + + public bool Disabled + => false; + + public void Draw() + { + foreach (var (design, idx) in _designManager.Designs.WithIndex()) + { + using var t = ImRaii.TreeNode($"{design.Name}##{idx}"); + if (!t) + continue; + + DrawDesign(design, _designFileSystem); + var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, + design.DoApplyHatVisible(), + design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected()); + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + ImGuiUtil.TextWrapped(base64); + if (ImGui.IsItemClicked()) + ImGui.SetClipboardText(base64); + } + } + + public static void DrawDesign(DesignBase design, DesignFileSystem? fileSystem) + { + using var table = ImRaii.Table("##equip", 8, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); + if (design is Design d) + { + ImGuiUtil.DrawTableColumn("Name"); + ImGuiUtil.DrawTableColumn(d.Name); + ImGuiUtil.DrawTableColumn($"({d.Index})"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted("Description (Hover)"); + ImGuiUtil.HoverTooltip(d.Description); + ImGui.TableNextRow(); + + ImGuiUtil.DrawTableColumn("Identifier"); + ImGuiUtil.DrawTableColumn(d.Identifier.ToString()); + ImGui.TableNextRow(); + ImGuiUtil.DrawTableColumn("Design File System Path"); + if (fileSystem != null) + ImGuiUtil.DrawTableColumn(fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : "No Path Known"); + ImGui.TableNextRow(); + + ImGuiUtil.DrawTableColumn("Creation"); + ImGuiUtil.DrawTableColumn(d.CreationDate.ToString()); + ImGui.TableNextRow(); + ImGuiUtil.DrawTableColumn("Update"); + ImGuiUtil.DrawTableColumn(d.LastEdit.ToString()); + ImGui.TableNextRow(); + ImGuiUtil.DrawTableColumn("Tags"); + ImGuiUtil.DrawTableColumn(string.Join(", ", d.Tags)); + ImGui.TableNextRow(); + } + + foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) + { + var item = design.DesignData.Item(slot); + var apply = design.DoApplyEquip(slot); + var stain = design.DesignData.Stain(slot); + var applyStain = design.DoApplyStain(slot); + var crest = design.DesignData.Crest(slot.ToCrestFlag()); + var applyCrest = design.DoApplyCrest(slot.ToCrestFlag()); + ImGuiUtil.DrawTableColumn(slot.ToName()); + ImGuiUtil.DrawTableColumn(item.Name); + ImGuiUtil.DrawTableColumn(item.ItemId.ToString()); + ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep"); + ImGuiUtil.DrawTableColumn(stain.ToString()); + ImGuiUtil.DrawTableColumn(applyStain ? "Apply" : "Keep"); + ImGuiUtil.DrawTableColumn(crest.ToString()); + ImGuiUtil.DrawTableColumn(applyCrest ? "Apply" : "Keep"); + } + + ImGuiUtil.DrawTableColumn("Hat Visible"); + ImGuiUtil.DrawTableColumn(design.DesignData.IsHatVisible().ToString()); + ImGuiUtil.DrawTableColumn(design.DoApplyHatVisible() ? "Apply" : "Keep"); + ImGui.TableNextRow(); + ImGuiUtil.DrawTableColumn("Visor Toggled"); + ImGuiUtil.DrawTableColumn(design.DesignData.IsVisorToggled().ToString()); + ImGuiUtil.DrawTableColumn(design.DoApplyVisorToggle() ? "Apply" : "Keep"); + ImGui.TableNextRow(); + ImGuiUtil.DrawTableColumn("Weapon Visible"); + ImGuiUtil.DrawTableColumn(design.DesignData.IsWeaponVisible().ToString()); + ImGuiUtil.DrawTableColumn(design.DoApplyWeaponVisible() ? "Apply" : "Keep"); + ImGui.TableNextRow(); + + ImGuiUtil.DrawTableColumn("Model ID"); + ImGuiUtil.DrawTableColumn(design.DesignData.ModelId.ToString()); + ImGui.TableNextRow(); + + foreach (var index in Enum.GetValues()) + { + var value = design.DesignData.Customize[index]; + var apply = design.DoApplyCustomize(index); + ImGuiUtil.DrawTableColumn(index.ToDefaultName()); + ImGuiUtil.DrawTableColumn(value.Value.ToString()); + ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep"); + ImGui.TableNextRow(); + } + + ImGuiUtil.DrawTableColumn("Is Wet"); + ImGuiUtil.DrawTableColumn(design.DesignData.IsWet().ToString()); + ImGui.TableNextRow(); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs new file mode 100644 index 0000000..481006e --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -0,0 +1,202 @@ +using System; +using System.Linq; +using Dalamud.Interface; +using Glamourer.Customization; +using Glamourer.Designs; +using Glamourer.Services; +using Glamourer.Structs; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using Penumbra.GameData.Data; +using Penumbra.GameData.Enums; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDebugTabTree +{ + public string Label + => "Base64 Design Tester"; + + public bool Disabled + => false; + + private string _base64 = string.Empty; + private string _restore = string.Empty; + private byte[] _base64Bytes = []; + private byte[] _restoreBytes = []; + private DesignData _parse64 = new(); + private Exception? _parse64Failure; + + public void Draw() + { + DrawBase64Input(); + DrawDesignData(); + DrawBytes(); + } + + private void DrawBase64Input() + { + ImGui.SetNextItemWidth(-1); + ImGui.InputTextWithHint("##base64", "Base 64 input...", ref _base64, 2047); + if (!ImGui.IsItemDeactivatedAfterEdit()) + return; + + try + { + _base64Bytes = Convert.FromBase64String(_base64); + _parse64Failure = null; + } + catch (Exception ex) + { + _base64Bytes = Array.Empty(); + _parse64Failure = ex; + } + + if (_parse64Failure != null) + return; + + try + { + _parse64 = DesignBase64Migration.MigrateBase64(_items, _humans, _base64, out var ef, out var cf, out var wp, out var ah, + out var av, + out var aw); + _restore = DesignBase64Migration.CreateOldBase64(in _parse64, ef, cf, ah, av, aw, wp); + _restoreBytes = Convert.FromBase64String(_restore); + } + catch (Exception ex) + { + _parse64Failure = ex; + _restore = string.Empty; + } + } + + private void DrawDesignData() + { + if (_parse64Failure != null) + { + ImGuiUtil.TextWrapped(_parse64Failure.ToString()); + return; + } + + if (_restore.Length <= 0) + return; + + DrawDesignData(_parse64); + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted(_base64); + using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 })) + { + foreach (var (c1, c2) in _restore.Zip(_base64)) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF4040D0, c1 != c2); + ImGui.TextUnformatted(c1.ToString()); + ImGui.SameLine(); + } + } + + ImGui.NewLine(); + + foreach (var ((b1, b2), idx) in _base64Bytes.Zip(_restoreBytes).WithIndex()) + { + using (var group = ImRaii.Group()) + { + ImGui.TextUnformatted(idx.ToString("D2")); + ImGui.TextUnformatted(b1.ToString("X2")); + using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF4040D0, b1 != b2); + ImGui.TextUnformatted(b2.ToString("X2")); + } + + ImGui.SameLine(); + } + + ImGui.NewLine(); + } + + private void DrawBytes() + { + if (_parse64Failure == null || _base64Bytes.Length <= 0) + return; + + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + foreach (var (b, idx) in _base64Bytes.WithIndex()) + { + using (var group = ImRaii.Group()) + { + ImGui.TextUnformatted(idx.ToString("D2")); + ImGui.TextUnformatted(b.ToString("X2")); + } + + ImGui.SameLine(); + } + + ImGui.NewLine(); + } + + public static void DrawDesignData(in DesignData data) + { + if (data.IsHuman) + DrawHumanData(data); + else + DrawMonsterData(data); + } + + private static void DrawHumanData(in DesignData data) + { + using var table = ImRaii.Table("##equip", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); + if (!table) + return; + + foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) + { + var item = data.Item(slot); + var stain = data.Stain(slot); + var crest = data.Crest(slot.ToCrestFlag()); + ImGuiUtil.DrawTableColumn(slot.ToName()); + ImGuiUtil.DrawTableColumn(item.Name); + ImGuiUtil.DrawTableColumn(item.ItemId.ToString()); + ImGuiUtil.DrawTableColumn(stain.ToString()); + ImGuiUtil.DrawTableColumn(crest.ToString()); + } + + ImGuiUtil.DrawTableColumn("Hat Visible"); + ImGuiUtil.DrawTableColumn(data.IsHatVisible().ToString()); + ImGui.TableNextRow(); + ImGuiUtil.DrawTableColumn("Visor Toggled"); + ImGuiUtil.DrawTableColumn(data.IsVisorToggled().ToString()); + ImGui.TableNextRow(); + ImGuiUtil.DrawTableColumn("Weapon Visible"); + ImGuiUtil.DrawTableColumn(data.IsWeaponVisible().ToString()); + ImGui.TableNextRow(); + + ImGuiUtil.DrawTableColumn("Model ID"); + ImGuiUtil.DrawTableColumn(data.ModelId.ToString()); + ImGui.TableNextRow(); + + foreach (var index in Enum.GetValues()) + { + var value = data.Customize[index]; + ImGuiUtil.DrawTableColumn(index.ToDefaultName()); + ImGuiUtil.DrawTableColumn(value.Value.ToString()); + ImGui.TableNextRow(); + } + + ImGuiUtil.DrawTableColumn("Is Wet"); + ImGuiUtil.DrawTableColumn(data.IsWet().ToString()); + ImGui.TableNextRow(); + } + + private static void DrawMonsterData(in DesignData data) + { + ImGui.TextUnformatted($"Model ID {data.ModelId}"); + ImGui.Separator(); + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted("Customize Array"); + ImGui.Separator(); + ImGuiUtil.TextWrapped(string.Join(" ", data.GetCustomizeBytes().Select(b => b.ToString("X2")))); + + ImGui.TextUnformatted("Equipment Array"); + ImGui.Separator(); + ImGuiUtil.TextWrapped(string.Join(" ", data.GetEquipmentBytes().Select(b => b.ToString("X2")))); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs new file mode 100644 index 0000000..9afc125 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs @@ -0,0 +1,35 @@ +using Glamourer.State; +using ImGuiNET; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class FunPanel(FunModule _funModule, Configuration _config) : IDebugTabTree +{ + public string Label + => "Fun Module"; + + public bool Disabled + => false; + + public void Draw() + { + ImGui.TextUnformatted($"Current Festival: {_funModule.CurrentFestival}"); + ImGui.TextUnformatted($"Festivals Enabled: {_config.DisableFestivals switch { 1 => "Undecided", 0 => "Enabled", _ => "Disabled" }}"); + ImGui.TextUnformatted($"Popup Open: {ImGui.IsPopupOpen("FestivalPopup", ImGuiPopupFlags.AnyPopup)}"); + if (ImGui.Button("Force Christmas")) + _funModule.ForceFestival(FunModule.FestivalType.Christmas); + if (ImGui.Button("Force Halloween")) + _funModule.ForceFestival(FunModule.FestivalType.Halloween); + if (ImGui.Button("Force April First")) + _funModule.ForceFestival(FunModule.FestivalType.AprilFirst); + if (ImGui.Button("Force None")) + _funModule.ForceFestival(FunModule.FestivalType.None); + if (ImGui.Button("Revert")) + _funModule.ResetFestival(); + if (ImGui.Button("Reset Popup")) + { + _config.DisableFestivals = 1; + _config.Save(); + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs new file mode 100644 index 0000000..b17f35a --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs @@ -0,0 +1,61 @@ +using System.Linq; +using Dalamud.Interface.Utility; +using Glamourer.Services; +using ImGuiNET; +using OtterGui; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class IdentifierPanel(ItemManager _items) : IDebugTabTree +{ + public string Label + => "Identifier Service"; + + public bool Disabled + => !_items.IdentifierService.Valid; + + private string _gamePath = string.Empty; + private int _setId; + private int _secondaryId; + private int _variant; + + public void Draw() + { + static void Text(string text) + { + if (text.Length > 0) + ImGui.TextUnformatted(text); + } + + ImGui.TextUnformatted("Parse Game Path"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); + ImGui.InputTextWithHint("##gamePath", "Enter game path...", ref _gamePath, 256); + var fileInfo = _items.IdentifierService.AwaitedService.GamePathParser.GetFileInfo(_gamePath); + ImGui.TextUnformatted( + $"{fileInfo.ObjectType} {fileInfo.EquipSlot} {fileInfo.PrimaryId} {fileInfo.SecondaryId} {fileInfo.Variant} {fileInfo.BodySlot} {fileInfo.CustomizationType}"); + Text(string.Join("\n", _items.IdentifierService.AwaitedService.Identify(_gamePath).Keys)); + + ImGui.Separator(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Identify Model"); + ImGui.SameLine(); + DebugTab.DrawInputModelSet(true, ref _setId, ref _secondaryId, ref _variant); + + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var identified = _items.Identify(slot, (SetId)_setId, (Variant)_variant); + Text(identified.Name); + ImGuiUtil.HoverTooltip(string.Join("\n", + _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (Variant)_variant, slot) + .Select(i => $"{i.Name} {i.Id} {i.ItemId} {i.IconId}"))); + } + + var weapon = _items.Identify(EquipSlot.MainHand, (SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant); + Text(weapon.Name); + ImGuiUtil.HoverTooltip(string.Join("\n", + _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant, EquipSlot.MainHand))); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs new file mode 100644 index 0000000..1334f2b --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs @@ -0,0 +1,52 @@ +using FFXIVClientStructs.FFXIV.Client.Game; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public unsafe class InventoryPanel : IDebugTabTree +{ + public string Label + => "Inventory"; + + public bool Disabled + => false; + + public void Draw() + { + var inventory = InventoryManager.Instance(); + if (inventory == null) + return; + + ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)inventory:X}"); + + var equip = inventory->GetInventoryContainer(InventoryType.EquippedItems); + if (equip == null || equip->Loaded == 0) + return; + + ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)equip:X}"); + + using var table = ImRaii.Table("items", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); + if (!table) + return; + + for (var i = 0; i < equip->Size; ++i) + { + ImGuiUtil.DrawTableColumn(i.ToString()); + var item = equip->GetInventorySlot(i); + if (item == null) + { + ImGuiUtil.DrawTableColumn("NULL"); + ImGui.TableNextRow(); + } + else + { + ImGuiUtil.DrawTableColumn(item->ItemID.ToString()); + ImGuiUtil.DrawTableColumn(item->GlamourID.ToString()); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)item:X}"); + } + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs new file mode 100644 index 0000000..e42d252 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -0,0 +1,108 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Plugin; +using Glamourer.Api; +using Glamourer.Interop; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManager _objectManager) : IDebugTabTree +{ + public string Label + => "IPC Tester"; + + public bool Disabled + => false; + + private int _gameObjectIndex; + private string _gameObjectName = string.Empty; + private string _base64Apply = string.Empty; + + 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); + 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.Objects[_gameObjectIndex] as Character); + if (base64 != null) + ImGuiUtil.CopyOnClickSelectable(base64); + 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.Objects[_gameObjectIndex] as Character); + + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAll); + ImGui.TableNextColumn(); + if (ImGui.Button("Apply##AllName")) + GlamourerIpc.ApplyAllSubscriber(_pluginInterface).Invoke(_base64Apply, _gameObjectName); + + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllToCharacter); + ImGui.TableNextColumn(); + if (ImGui.Button("Apply##AllCharacter")) + GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface) + .Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character); + + 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.Objects[_gameObjectIndex] as Character); + + 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.Objects[_gameObjectIndex] as Character); + + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); + ImGui.TableNextColumn(); + if (ImGui.Button("Unlock##CustomizeCharacter")) + GlamourerIpc.UnlockSubscriber(_pluginInterface) + .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); + + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertToAutomation); + ImGui.TableNextColumn(); + if (ImGui.Button("Revert##CustomizeCharacter")) + GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) + .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs new file mode 100644 index 0000000..7ef34b6 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using Glamourer.Services; +using ImGuiNET; +using OtterGui.Raii; +using Penumbra.GameData.Enums; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class ItemManagerPanel(ItemManager _items) : IDebugTabTree +{ + public string Label + => "Item Manager"; + + public bool Disabled + => !_items.ItemService.Valid; + + private string _itemFilter = string.Empty; + + public void Draw() + { + ImRaii.TreeNode($"Default Sword: {_items.DefaultSword.Name} ({_items.DefaultSword.ItemId}) ({_items.DefaultSword.Weapon()})", + ImGuiTreeNodeFlags.Leaf).Dispose(); + DebugTab.DrawNameTable("All Items (Main)", ref _itemFilter, + _items.ItemService.AwaitedService.AllItems(true).Select(p => (p.Item1.Id, + $"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) + .OrderBy(p => p.Item1)); + DebugTab.DrawNameTable("All Items (Off)", ref _itemFilter, + _items.ItemService.AwaitedService.AllItems(false).Select(p => (p.Item1.Id, + $"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) + .OrderBy(p => p.Item1)); + foreach (var type in Enum.GetValues().Skip(1)) + { + DebugTab.DrawNameTable(type.ToName(), ref _itemFilter, + _items.ItemService.AwaitedService[type] + .Select(p => (p.ItemId.Id, $"{p.Name} ({(p.WeaponType == 0 ? p.Armor().ToString() : p.Weapon().ToString())})"))); + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs new file mode 100644 index 0000000..a218d96 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs @@ -0,0 +1,63 @@ +using System; +using System.Numerics; +using Dalamud.Interface.Utility; +using Glamourer.Services; +using Glamourer.Unlocks; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using Penumbra.GameData.Enums; +using ImGuiClip = OtterGui.ImGuiClip; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IDebugTabTree +{ + public string Label + => "Unlocked Items"; + + public bool Disabled + => false; + + public void Draw() + { + using var table = ImRaii.Table("itemUnlocks", 5, + ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, + new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeight())); + if (!table) + return; + + ImGui.TableSetupColumn("ItemId", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, 400 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Slot", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Model", ImGuiTableColumnFlags.WidthFixed, 80 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Unlock", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); + + ImGui.TableNextColumn(); + var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); + ImGui.TableNextRow(); + var remainder = ImGuiClip.ClippedDraw(_itemUnlocks, skips, t => + { + ImGuiUtil.DrawTableColumn(t.Key.ToString()); + if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) + { + ImGuiUtil.DrawTableColumn(equip.Name); + ImGuiUtil.DrawTableColumn(equip.Type.ToName()); + ImGuiUtil.DrawTableColumn(equip.Weapon().ToString()); + } + else + { + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + } + + ImGuiUtil.DrawTableColumn(_itemUnlocks.IsUnlocked(t.Key, out var time) + ? time == DateTimeOffset.MinValue + ? "Always" + : time.LocalDateTime.ToString("g") + : "Never"); + }, _itemUnlocks.Count); + ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs b/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs new file mode 100644 index 0000000..0fa8765 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs @@ -0,0 +1,76 @@ +using Glamourer.Interop; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class JobPanel(JobService _jobs) : IDebugTabTree +{ + public string Label + => "Job Service"; + + public bool Disabled + => false; + + public void Draw() + { + DrawJobs(); + DrawJobGroups(); + DrawValidJobGroups(); + } + + private void DrawJobs() + { + using var t = ImRaii.TreeNode("Jobs"); + if (!t) + return; + + using var table = ImRaii.Table("##jobs", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + if (!table) + return; + + foreach (var (id, job) in _jobs.Jobs) + { + ImGuiUtil.DrawTableColumn(id.ToString("D3")); + ImGuiUtil.DrawTableColumn(job.Name); + ImGuiUtil.DrawTableColumn(job.Abbreviation); + } + } + + private void DrawJobGroups() + { + using var t = ImRaii.TreeNode("All Job Groups"); + if (!t) + return; + + using var table = ImRaii.Table("##groups", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + if (!table) + return; + + foreach (var (group, idx) in _jobs.AllJobGroups.WithIndex()) + { + ImGuiUtil.DrawTableColumn(idx.ToString("D3")); + ImGuiUtil.DrawTableColumn(group.Name); + ImGuiUtil.DrawTableColumn(group.Count.ToString()); + } + } + + private void DrawValidJobGroups() + { + using var t = ImRaii.TreeNode("Valid Job Groups"); + if (!t) + return; + + using var table = ImRaii.Table("##groups", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + if (!table) + return; + + foreach (var (id, group) in _jobs.JobGroups) + { + ImGuiUtil.DrawTableColumn(id.ToString("D3")); + ImGuiUtil.DrawTableColumn(group.Name); + ImGuiUtil.DrawTableColumn(group.Count.ToString()); + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs new file mode 100644 index 0000000..0b0526b --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -0,0 +1,286 @@ +using System; +using System.Numerics; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Glamourer.Customization; +using Glamourer.Interop; +using Glamourer.Interop.Structs; +using Glamourer.Structs; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public unsafe class ModelEvaluationPanel( + ObjectManager _objectManager, + VisorService _visorService, + UpdateSlotService _updateSlotService, + ChangeCustomizeService _changeCustomizeService, + CrestService _crestService) : IDebugTabTree +{ + public string Label + => "Model Evaluation"; + + public bool Disabled + => false; + + private int _gameObjectIndex; + + public void Draw() + { + ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); + var actor = (Actor)_objectManager.Objects.GetObjectAddress(_gameObjectIndex); + var model = actor.Model; + using var table = ImRaii.Table("##evaluationTable", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.TableHeader("Actor"); + ImGui.TableNextColumn(); + ImGui.TableHeader("Model"); + ImGui.TableNextColumn(); + + ImGuiUtil.DrawTableColumn("Address"); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable(actor.ToString()); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable(model.ToString()); + ImGui.TableNextColumn(); + if (actor.IsCharacter) + { + ImGui.TextUnformatted(actor.AsCharacter->CharacterData.ModelCharaId.ToString()); + if (actor.AsCharacter->CharacterData.TransformationId != 0) + ImGui.TextUnformatted($"Transformation Id: {actor.AsCharacter->CharacterData.TransformationId}"); + if (actor.AsCharacter->CharacterData.ModelCharaId_2 != -1) + ImGui.TextUnformatted($"ModelChara2 {actor.AsCharacter->CharacterData.ModelCharaId_2}"); + if (actor.AsCharacter->CharacterData.StatusEffectVFXId != 0) + ImGui.TextUnformatted($"Status Id: {actor.AsCharacter->CharacterData.StatusEffectVFXId}"); + } + + ImGuiUtil.DrawTableColumn("Mainhand"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetMainhand().ToString() : "No Character"); + + var (mainhand, offhand, mainModel, offModel) = model.GetWeapons(actor); + ImGuiUtil.DrawTableColumn(mainModel.ToString()); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable(mainhand.ToString()); + + ImGuiUtil.DrawTableColumn("Offhand"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetOffhand().ToString() : "No Character"); + ImGuiUtil.DrawTableColumn(offModel.ToString()); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable(offhand.ToString()); + + DrawVisor(actor, model); + DrawHatState(actor, model); + DrawWeaponState(actor, model); + DrawWetness(actor, model); + DrawEquip(actor, model); + DrawCustomize(actor, model); + DrawCrests(actor, model); + } + + private void DrawVisor(Actor actor, Model model) + { + using var id = ImRaii.PushId("Visor"); + ImGuiUtil.DrawTableColumn("Visor State"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->DrawData.IsVisorToggled.ToString() : "No Character"); + ImGuiUtil.DrawTableColumn(model.IsHuman ? VisorService.GetVisorState(model).ToString() : "No Human"); + ImGui.TableNextColumn(); + if (!model.IsHuman) + return; + + if (ImGui.SmallButton("Set True")) + _visorService.SetVisorState(model, true); + ImGui.SameLine(); + if (ImGui.SmallButton("Set False")) + _visorService.SetVisorState(model, false); + ImGui.SameLine(); + if (ImGui.SmallButton("Toggle")) + _visorService.SetVisorState(model, !VisorService.GetVisorState(model)); + } + + private void DrawHatState(Actor actor, Model model) + { + using var id = ImRaii.PushId("HatState"); + ImGuiUtil.DrawTableColumn("Hat State"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter + ? actor.AsCharacter->DrawData.IsHatHidden ? "Hidden" : actor.GetArmor(EquipSlot.Head).ToString() + : "No Character"); + ImGuiUtil.DrawTableColumn(model.IsHuman + ? model.AsHuman->Head.Value == 0 ? "No Hat" : model.GetArmor(EquipSlot.Head).ToString() + : "No Human"); + ImGui.TableNextColumn(); + if (!model.IsHuman) + return; + + if (ImGui.SmallButton("Hide")) + _updateSlotService.UpdateSlot(model, EquipSlot.Head, CharacterArmor.Empty); + ImGui.SameLine(); + if (ImGui.SmallButton("Show")) + _updateSlotService.UpdateSlot(model, EquipSlot.Head, actor.GetArmor(EquipSlot.Head)); + ImGui.SameLine(); + if (ImGui.SmallButton("Toggle")) + _updateSlotService.UpdateSlot(model, EquipSlot.Head, + model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty); + } + + private void DrawWeaponState(Actor actor, Model model) + { + using var id = ImRaii.PushId("WeaponState"); + ImGuiUtil.DrawTableColumn("Weapon State"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter + ? actor.AsCharacter->DrawData.IsWeaponHidden ? "Hidden" : "Visible" + : "No Character"); + var text = string.Empty; + + if (!model.IsHuman) + { + text = "No Model"; + } + else if (model.AsDrawObject->Object.ChildObject == null) + { + text = "No Weapon"; + } + else + { + var weapon = (DrawObject*)model.AsDrawObject->Object.ChildObject; + if ((weapon->Flags & 0x09) == 0x09) + text = "Visible"; + else + text = "Hidden"; + } + + ImGuiUtil.DrawTableColumn(text); + ImGui.TableNextColumn(); + if (!model.IsHuman) + return; + } + + private void DrawWetness(Actor actor, Model model) + { + using var id = ImRaii.PushId("Wetness"); + ImGuiUtil.DrawTableColumn("Wetness"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->IsGPoseWet ? "GPose" : "None" : "No Character"); + var modelString = model.IsCharacterBase + ? $"{model.AsCharacterBase->SwimmingWetness:F4} Swimming\n" + + $"{model.AsCharacterBase->WeatherWetness:F4} Weather\n" + + $"{model.AsCharacterBase->ForcedWetness:F4} Forced\n" + + $"{model.AsCharacterBase->WetnessDepth:F4} Depth\n" + : "No CharacterBase"; + ImGuiUtil.DrawTableColumn(modelString); + ImGui.TableNextColumn(); + if (!actor.IsCharacter) + return; + + if (ImGui.SmallButton("GPose On")) + actor.AsCharacter->IsGPoseWet = true; + ImGui.SameLine(); + if (ImGui.SmallButton("GPose Off")) + actor.AsCharacter->IsGPoseWet = false; + ImGui.SameLine(); + if (ImGui.SmallButton("GPose Toggle")) + actor.AsCharacter->IsGPoseWet = !actor.AsCharacter->IsGPoseWet; + } + + private void DrawEquip(Actor actor, Model model) + { + using var id = ImRaii.PushId("Equipment"); + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + using var id2 = ImRaii.PushId((int)slot); + ImGuiUtil.DrawTableColumn(slot.ToName()); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetArmor(slot).ToString() : "No Character"); + ImGuiUtil.DrawTableColumn(model.IsHuman ? model.GetArmor(slot).ToString() : "No Human"); + ImGui.TableNextColumn(); + if (!model.IsHuman) + continue; + + if (ImGui.SmallButton("Change Piece")) + _updateSlotService.UpdateArmor(model, slot, + new CharacterArmor((SetId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0)); + ImGui.SameLine(); + if (ImGui.SmallButton("Change Stain")) + _updateSlotService.UpdateStain(model, slot, 5); + ImGui.SameLine(); + if (ImGui.SmallButton("Reset")) + _updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot)); + } + } + + private void DrawCustomize(Actor actor, Model model) + { + using var id = ImRaii.PushId("Customize"); + var actorCustomize = new Customize(actor.IsCharacter + ? *(Penumbra.GameData.Structs.CustomizeData*)&actor.AsCharacter->DrawData.CustomizeData + : new Penumbra.GameData.Structs.CustomizeData()); + var modelCustomize = new Customize(model.IsHuman + ? *(Penumbra.GameData.Structs.CustomizeData*)model.AsHuman->Customize.Data + : new Penumbra.GameData.Structs.CustomizeData()); + foreach (var type in Enum.GetValues()) + { + using var id2 = ImRaii.PushId((int)type); + ImGuiUtil.DrawTableColumn(type.ToDefaultName()); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actorCustomize[type].Value.ToString("X2") : "No Character"); + ImGuiUtil.DrawTableColumn(model.IsHuman ? modelCustomize[type].Value.ToString("X2") : "No Human"); + ImGui.TableNextColumn(); + if (!model.IsHuman || type.ToFlag().RequiresRedraw()) + continue; + + if (ImGui.SmallButton("++")) + { + var value = modelCustomize[type].Value; + var (_, mask) = type.ToByteAndMask(); + var shift = BitOperations.TrailingZeroCount(mask); + var newValue = value + (1 << shift); + modelCustomize.Set(type, (CustomizeValue)newValue); + _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); + } + + ImGui.SameLine(); + if (ImGui.SmallButton("--")) + { + var value = modelCustomize[type].Value; + var (_, mask) = type.ToByteAndMask(); + var shift = BitOperations.TrailingZeroCount(mask); + var newValue = value - (1 << shift); + modelCustomize.Set(type, (CustomizeValue)newValue); + _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); + } + + ImGui.SameLine(); + if (ImGui.SmallButton("Reset")) + { + modelCustomize.Set(type, actorCustomize[type]); + _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); + } + } + } + + private void DrawCrests(Actor actor, Model model) + { + using var id = ImRaii.PushId("Crests"); + CrestFlag whichToggle = 0; + CrestFlag totalModelFlags = 0; + foreach (var crestFlag in CrestExtensions.AllRelevantSet) + { + id.Push((int)crestFlag); + var modelCrest = CrestService.GetModelCrest(actor, crestFlag); + if (modelCrest) + totalModelFlags |= crestFlag; + ImGuiUtil.DrawTableColumn($"{crestFlag.ToLabel()} Crest"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.GetCrest(crestFlag).ToString() : "No Character"); + ImGuiUtil.DrawTableColumn(modelCrest.ToString()); + + ImGui.TableNextColumn(); + if (model.IsHuman && ImGui.SmallButton("Toggle")) + whichToggle = crestFlag; + + id.Pop(); + } + + if (whichToggle != 0) + _crestService.UpdateCrests(actor, totalModelFlags ^ whichToggle); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs new file mode 100644 index 0000000..53af228 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -0,0 +1,92 @@ +using System; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.Customization; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.Interop; +using Glamourer.State; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using ImGuiClip = OtterGui.ImGuiClip; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) : IDebugTabTree +{ + public string Label + => "NPC Appearance"; + + public bool Disabled + => false; + + private string _npcFilter = string.Empty; + private bool _customizeOrGear = false; + + public void Draw() + { + ImGui.Checkbox("Compare Customize (or Gear)", ref _customizeOrGear); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64); + + using var table = ImRaii.Table("npcs", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, + new Vector2(-1, 400 * ImGuiHelpers.GlobalScale)); + if (!table) + return; + + ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300); + ImGui.TableSetupColumn("Kind", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Visor", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Compare", ImGuiTableColumnFlags.WidthStretch); + + ImGui.TableNextColumn(); + var skips = ImGuiClip.GetNecessarySkips(ImGui.GetFrameHeightWithSpacing()); + ImGui.TableNextRow(); + var idx = 0; + var remainder = ImGuiClip.FilteredClippedDraw(_npcCombo.Items, skips, + d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), Draw); + ImGui.TableNextColumn(); + ImGuiClip.DrawEndDummy(remainder, ImGui.GetFrameHeightWithSpacing()); + + + return; + + void Draw(CustomizationNpcOptions.NpcData data) + { + using var id = ImRaii.PushId(idx++); + var disabled = !_state.GetOrCreate(_objectManager.Player, out var state); + ImGui.TableNextColumn(); + if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled, false)) + { + foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand)) + _state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual); + _state.ChangeVisorState(state!, data.VisorToggled, StateChanged.Source.Manual); + _state.ChangeCustomize(state!, data.Customize, CustomizeFlagExtensions.All, StateChanged.Source.Manual); + } + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(data.Name); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(data.Kind is ObjectKind.BattleNpc ? "B" : "E"); + + using (var icon = ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(data.VisorToggled ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); + } + + using var mono = ImRaii.PushFont(UiBuilder.MonoFont); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(_customizeOrGear ? data.Customize.Data.ToString() : data.WriteGear()); + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs new file mode 100644 index 0000000..cf9e443 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -0,0 +1,85 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Numerics; +using Glamourer.Interop; +using Glamourer.Services; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class ObjectManagerPanel(ObjectManager _objectManager, ActorService _actors) : IDebugTabTree +{ + public string Label + => "Object Manager"; + + public bool Disabled + => false; + + private string _objectFilter = string.Empty; + + public void Draw() + { + _objectManager.Update(); + using (var table = ImRaii.Table("##data", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit)) + { + if (!table) + return; + + ImGuiUtil.DrawTableColumn("Last Update"); + ImGuiUtil.DrawTableColumn(_objectManager.LastUpdate.ToString(CultureInfo.InvariantCulture)); + ImGui.TableNextColumn(); + + ImGuiUtil.DrawTableColumn("World"); + ImGuiUtil.DrawTableColumn(_actors.Valid ? _actors.AwaitedService.Data.ToWorldName(_objectManager.World) : "Service Missing"); + ImGuiUtil.DrawTableColumn(_objectManager.World.ToString()); + + ImGuiUtil.DrawTableColumn("Player Character"); + ImGuiUtil.DrawTableColumn($"{_objectManager.Player.Utf8Name} ({_objectManager.Player.Index})"); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable(_objectManager.Player.ToString()); + + ImGuiUtil.DrawTableColumn("In GPose"); + ImGuiUtil.DrawTableColumn(_objectManager.IsInGPose.ToString()); + ImGui.TableNextColumn(); + + if (_objectManager.IsInGPose) + { + ImGuiUtil.DrawTableColumn("GPose Player"); + ImGuiUtil.DrawTableColumn($"{_objectManager.GPosePlayer.Utf8Name} ({_objectManager.GPosePlayer.Index})"); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable(_objectManager.GPosePlayer.ToString()); + } + + ImGuiUtil.DrawTableColumn("Number of Players"); + ImGuiUtil.DrawTableColumn(_objectManager.Count.ToString()); + ImGui.TableNextColumn(); + } + + var filterChanged = ImGui.InputTextWithHint("##Filter", "Filter...", ref _objectFilter, 64); + using var table2 = ImRaii.Table("##data2", 3, + ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, + new Vector2(-1, 20 * ImGui.GetTextLineHeightWithSpacing())); + if (!table2) + return; + + if (filterChanged) + ImGui.SetScrollY(0); + + ImGui.TableNextColumn(); + var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); + ImGui.TableNextRow(); + + var remainder = ImGuiClip.FilteredClippedDraw(_objectManager, skips, + p => p.Value.Label.Contains(_objectFilter, StringComparison.OrdinalIgnoreCase), p + => + { + ImGuiUtil.DrawTableColumn(p.Key.ToString()); + ImGuiUtil.DrawTableColumn(p.Value.Label); + ImGuiUtil.DrawTableColumn(string.Join(", ", p.Value.Objects.OrderBy(a => a.Index).Select(a => a.Index.ToString()))); + }); + ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeightWithSpacing()); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs new file mode 100644 index 0000000..6352412 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -0,0 +1,93 @@ +using System; +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Glamourer.Gui.Tabs.DebugTab; +using Glamourer.Gui; +using Glamourer.Interop.Penumbra; +using Glamourer.Interop.Structs; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using Penumbra.Api.Enums; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItemTooltip _penumbraTooltip) : IDebugTabTree +{ + public string Label + => "Penumbra Interop"; + + public bool Disabled + => false; + + private int _gameObjectIndex; + private Model _drawObject = Model.Null; + + public void Draw() + { + if (!ImGui.CollapsingHeader("Penumbra")) + return; + + using var table = ImRaii.Table("##PenumbraTable", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + if (!table) + return; + + ImGuiUtil.DrawTableColumn("Available"); + ImGuiUtil.DrawTableColumn(_penumbra.Available.ToString()); + ImGui.TableNextColumn(); + if (ImGui.SmallButton("Unattach")) + _penumbra.Unattach(); + ImGui.SameLine(); + if (ImGui.SmallButton("Reattach")) + _penumbra.Reattach(); + + ImGuiUtil.DrawTableColumn("Draw Object"); + ImGui.TableNextColumn(); + var address = _drawObject.Address; + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + if (ImGui.InputScalar("##drawObjectPtr", ImGuiDataType.U64, (nint)(&address), nint.Zero, nint.Zero, "%llx", + ImGuiInputTextFlags.CharsHexadecimal)) + _drawObject = address; + ImGuiUtil.DrawTableColumn(_penumbra.Available + ? $"0x{_penumbra.GameObjectFromDrawObject(_drawObject).Address:X}" + : "Penumbra Unavailable"); + + ImGuiUtil.DrawTableColumn("Cutscene Object"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + ImGui.InputInt("##CutsceneIndex", ref _gameObjectIndex, 0, 0); + ImGuiUtil.DrawTableColumn(_penumbra.Available + ? _penumbra.CutsceneParent(_gameObjectIndex).ToString() + : "Penumbra Unavailable"); + + ImGuiUtil.DrawTableColumn("Redraw Object"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + ImGui.InputInt("##redrawObject", ref _gameObjectIndex, 0, 0); + ImGui.TableNextColumn(); + using (var disabled = ImRaii.Disabled(!_penumbra.Available)) + { + if (ImGui.SmallButton("Redraw")) + _penumbra.RedrawObject((ObjectIndex)_gameObjectIndex, RedrawType.Redraw); + } + + ImGuiUtil.DrawTableColumn("Last Tooltip Date"); + ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastTooltip > DateTime.MinValue ? _penumbraTooltip.LastTooltip.ToLongTimeString() : "Never"); + ImGui.TableNextColumn(); + + ImGuiUtil.DrawTableColumn("Last Click Date"); + ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastClick > DateTime.MinValue ? _penumbraTooltip.LastClick.ToLongTimeString() : "Never"); + ImGui.TableNextColumn(); + + ImGui.Separator(); + ImGui.Separator(); + foreach (var (slot, item) in _penumbraTooltip.LastItems) + { + ImGuiUtil.DrawTableColumn($"{slot.ToName()} Revert-Item"); + ImGuiUtil.DrawTableColumn(item.Valid ? item.Name : "None"); + ImGui.TableNextColumn(); + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs b/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs new file mode 100644 index 0000000..7e1e600 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs @@ -0,0 +1,42 @@ +using System; +using System.Linq; +using Glamourer.Services; +using ImGuiNET; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class RestrictedGearPanel(ItemManager _items) : IDebugTabTree +{ + public string Label + => "Restricted Gear Service"; + + public bool Disabled + => false; + + private int _setId; + private int _secondaryId; + private int _variant; + + public void Draw() + { + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Resolve Model"); + DebugTab.DrawInputModelSet(false, ref _setId, ref _secondaryId, ref _variant); + foreach (var race in Enum.GetValues().Skip(1)) + { + ReadOnlySpan genders = [Gender.Male, Gender.Female]; + foreach (var gender in genders) + { + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var (replaced, model) = + _items.RestrictedGear.ResolveRestricted(new CharacterArmor((SetId)_setId, (Variant)_variant, 0), slot, race, gender); + if (replaced) + ImGui.TextUnformatted($"{race.ToName()} - {gender} - {slot.ToName()} resolves to {model}."); + } + } + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs new file mode 100644 index 0000000..68841bc --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs @@ -0,0 +1,26 @@ +using System.Linq; +using Glamourer.Interop; +using Glamourer.Interop.Structs; +using Glamourer.State; +using OtterGui.Raii; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class RetainedStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IDebugTabTree +{ + public string Label + => "Retained States (Inactive Actors)"; + + public bool Disabled + => false; + + public void Draw() + { + foreach (var (identifier, state) in _stateManager.Where(kvp => !_objectManager.ContainsKey(kvp.Key))) + { + using var t = ImRaii.TreeNode(identifier.ToString()); + if (t) + ActiveStatePanel.DrawState(_stateManager, ActorData.Invalid, state); + } + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/StainPanel.cs b/Glamourer/Gui/Tabs/DebugTab/StainPanel.cs new file mode 100644 index 0000000..643b6dc --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/StainPanel.cs @@ -0,0 +1,53 @@ +using System; +using System.Numerics; +using Dalamud.Interface.Utility; +using Glamourer.Services; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using ImGuiClip = OtterGui.ImGuiClip; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class StainPanel(ItemManager _items) : IDebugTabTree +{ + public string Label + => "Stain Service"; + + public bool Disabled + => false; + + private string _stainFilter = string.Empty; + + public void Draw() + { + var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref _stainFilter, 256); + var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; + using var table = ImRaii.Table("##table", 4, + ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.SizingFixedFit, + new Vector2(-1, 10 * height)); + if (!table) + return; + + if (resetScroll) + ImGui.SetScrollY(0); + + ImGui.TableNextColumn(); + var skips = ImGuiClip.GetNecessarySkips(height); + ImGui.TableNextRow(); + var remainder = ImGuiClip.FilteredClippedDraw(_items.Stains, skips, + p => p.Key.Id.ToString().Contains(_stainFilter) || p.Value.Name.Contains(_stainFilter, StringComparison.OrdinalIgnoreCase), + p => + { + ImGuiUtil.DrawTableColumn(p.Key.Id.ToString("D3")); + ImGui.TableNextColumn(); + ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(), + ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight()), + p.Value.RgbaColor, 5 * ImGuiHelpers.GlobalScale); + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight())); + ImGuiUtil.DrawTableColumn(p.Value.Name); + ImGuiUtil.DrawTableColumn($"#{p.Value.R:X2}{p.Value.G:X2}{p.Value.B:X2}{(p.Value.Gloss ? ", Glossy" : string.Empty)}"); + }); + ImGuiClip.DrawEndDummy(remainder, height); + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs new file mode 100644 index 0000000..cfd00fa --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs @@ -0,0 +1,65 @@ +using System; +using System.Numerics; +using Dalamud.Interface.Utility; +using Glamourer.Services; +using Glamourer.Unlocks; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using Penumbra.GameData.Enums; +using ImGuiClip = OtterGui.ImGuiClip; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IDebugTabTree +{ + public string Label + => "Unlockable Items"; + + public bool Disabled + => false; + + public void Draw() + { + using var table = ImRaii.Table("unlockableItem", 6, + ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, + new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeight())); + if (!table) + return; + + ImGui.TableSetupColumn("ItemId", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, 400 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Slot", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Model", ImGuiTableColumnFlags.WidthFixed, 80 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Unlock", ImGuiTableColumnFlags.WidthFixed, 120 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Criteria", ImGuiTableColumnFlags.WidthStretch); + + ImGui.TableNextColumn(); + var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); + ImGui.TableNextRow(); + var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlockable, skips, t => + { + ImGuiUtil.DrawTableColumn(t.Key.ToString()); + if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) + { + ImGuiUtil.DrawTableColumn(equip.Name); + ImGuiUtil.DrawTableColumn(equip.Type.ToName()); + ImGuiUtil.DrawTableColumn(equip.Weapon().ToString()); + } + else + { + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + } + + ImGuiUtil.DrawTableColumn(_itemUnlocks.IsUnlocked(t.Key, out var time) + ? time == DateTimeOffset.MinValue + ? "Always" + : time.LocalDateTime.ToString("g") + : "Never"); + ImGuiUtil.DrawTableColumn(t.Value.ToString()); + }, _itemUnlocks.Unlockable.Count); + ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); + } +} diff --git a/Glamourer/Gui/Tabs/NpcCombo.cs b/Glamourer/Gui/Tabs/NpcCombo.cs new file mode 100644 index 0000000..ab8d08d --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcCombo.cs @@ -0,0 +1,36 @@ +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using Dalamud.Plugin.Services; +using Glamourer.Customization; +using Glamourer.Services; +using OtterGui.Widgets; +using Penumbra.GameData; + +namespace Glamourer.Gui.Tabs; + +public class NpcCombo(ActorService actorManager, IdentifierService identifier, IDataManager data) + : FilterComboBase(new LazyList(actorManager, identifier, data), false, Glamourer.Log) +{ + private class LazyList(ActorService actorManager, IdentifierService identifier, IDataManager data) + : IReadOnlyList + { + private readonly Task> _task + = Task.Run(() => CustomizationNpcOptions.CreateNpcData(actorManager.AwaitedService.Data.ENpcs, actorManager.AwaitedService.Data.BNpcs, identifier.AwaitedService, data)); + + public IEnumerator GetEnumerator() + => _task.Result.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _task.Result.Count; + + public CustomizationNpcOptions.NpcData this[int index] + => _task.Result[index]; + } + + protected override string ToString(CustomizationNpcOptions.NpcData obj) + => obj.Name; +} diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 93a9854..8dc4c8d 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -1,4 +1,7 @@ -using Dalamud.Plugin; +using System; +using System.Linq; +using System.Reflection; +using Dalamud.Plugin; using Glamourer.Api; using Glamourer.Automation; using Glamourer.Designs; @@ -9,6 +12,7 @@ using Glamourer.Gui.Equipment; using Glamourer.Gui.Tabs; using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Gui.Tabs.AutomationTab; +using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Interop; @@ -37,7 +41,8 @@ public static class ServiceManager .AddDesigns() .AddState() .AddUi() - .AddApi(); + .AddApi() + .AddDebug(); return services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true }); } @@ -149,7 +154,17 @@ public static class ServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); + + private static IServiceCollection AddDebug(this IServiceCollection services) + { + services.AddSingleton(p => new DebugTab(p)); + var iType = typeof(IDebugTabTree); + foreach (var type in Assembly.GetAssembly(iType)!.GetTypes().Where(t => !t.IsInterface && iType.IsAssignableFrom(t))) + services.AddSingleton(type); + return services; + } private static IServiceCollection AddApi(this IServiceCollection services) => services.AddSingleton() From 768354be3179349224a6768ee6310cce4ca29427 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 19 Dec 2023 16:42:21 +0100 Subject: [PATCH 091/786] Update one set. --- Glamourer/State/FunEquipSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/State/FunEquipSet.cs b/Glamourer/State/FunEquipSet.cs index 720eb9e..4df0d7d 100644 --- a/Glamourer/State/FunEquipSet.cs +++ b/Glamourer/State/FunEquipSet.cs @@ -64,7 +64,7 @@ internal class FunEquipSet new Group(6005, 1, 0058, 1, 6005, 1, 0000, 0, 6005, 1), // Reindeer new Group(0231, 1, 0231, 1, 0279, 1, 0231, 1, 0231, 1), // Starlight new Group(0231, 1, 6030, 1, 0279, 1, 0231, 1, 0231, 1), // Starlight - new Group(0053, 1, 0053, 1, 0279, 1, 0279, 1, 0053, 1), // Sweet Dream + new Group(0053, 1, 0053, 1, 0279, 1, 0049, 6, 0053, 1), // Sweet Dream new Group(0136, 1, 0136, 1, 0136, 1, 0000, 0, 0000, 0) // Snowman ); From e37f16eb1568ef4bbb3c3993459015e472aa82db Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 20 Dec 2023 16:46:20 +0100 Subject: [PATCH 092/786] Remove unused state. --- Glamourer/State/WeaponState.cs | 65 ---------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 Glamourer/State/WeaponState.cs diff --git a/Glamourer/State/WeaponState.cs b/Glamourer/State/WeaponState.cs deleted file mode 100644 index a06ff79..0000000 --- a/Glamourer/State/WeaponState.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Glamourer.Events; -using Glamourer.Services; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer.State; - -/// Currently unused. -public unsafe struct WeaponState -{ - private fixed ulong _weapons[FullEquipTypeExtensions.NumWeaponTypes]; - private fixed byte _sources[FullEquipTypeExtensions.NumWeaponTypes]; - - public CustomItemId? this[FullEquipType type] - { - get - { - if (!ToIndex(type, out var idx)) - return null; - - var weapon = _weapons[idx]; - if (weapon == 0) - return null; - - return new CustomItemId(weapon); - } - } - - public EquipItem Get(ItemManager items, EquipItem value) - { - var id = this[value.Type]; - if (id == null) - return value; - - var item = items.Resolve(value.Type, id.Value); - return item.Type != value.Type ? value : item; - } - - public void Set(FullEquipType type, EquipItem value, StateChanged.Source source) - { - if (!ToIndex(type, out var idx)) - return; - - _weapons[idx] = value.Id.Id; - _sources[idx] = (byte)source; - } - - public void RemoveFixedDesignSources() - { - for (var i = 0; i < FullEquipTypeExtensions.NumWeaponTypes; ++i) - { - if (_sources[i] is (byte) StateChanged.Source.Fixed) - _sources[i] = (byte) StateChanged.Source.Manual; - } - } - - private static bool ToIndex(FullEquipType type, out int index) - { - index = ToIndex(type); - return index is >= 0 and < FullEquipTypeExtensions.NumWeaponTypes; - } - - private static int ToIndex(FullEquipType type) - => (int)type - FullEquipTypeExtensions.WeaponTypesOffset; -} From 5b648ea2a077ac4c115d2fb22ecce2784a1884a6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 20 Dec 2023 16:46:33 +0100 Subject: [PATCH 093/786] Do not crash when failing to load textures. --- Glamourer/Services/TextureService.cs | 43 ++++++++++++++++++---------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index 8f65bfa..e740677 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -37,9 +37,9 @@ public sealed class TextureService : TextureCache, IDisposable } } - private static IDalamudTextureWrap[] CreateSlotIcons(UiBuilder uiBuilder) + private static IDalamudTextureWrap?[] CreateSlotIcons(UiBuilder uiBuilder) { - var ret = new IDalamudTextureWrap[12]; + var ret = new IDalamudTextureWrap?[12]; using var uldWrapper = uiBuilder.LoadUld("ui/uld/ArmouryBoard.uld"); @@ -49,20 +49,33 @@ public sealed class TextureService : TextureCache, IDisposable return ret; } - ret[0] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 1)!; - ret[1] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 2)!; - ret[2] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 3)!; - ret[3] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 5)!; - ret[4] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 6)!; - ret[5] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 8)!; - ret[6] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 9)!; - ret[7] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 10)!; - ret[8] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 11)!; - ret[9] = ret[8]; - ret[10] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 0)!; - ret[11] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", 7)!; + SetIcon(EquipSlot.Head, 1); + SetIcon(EquipSlot.Body, 2); + SetIcon(EquipSlot.Hands, 3); + SetIcon(EquipSlot.Legs, 5); + SetIcon(EquipSlot.Feet, 6); + SetIcon(EquipSlot.Ears, 8); + SetIcon(EquipSlot.Neck, 9); + SetIcon(EquipSlot.Wrists, 10); + SetIcon(EquipSlot.RFinger, 11); + SetIcon(EquipSlot.MainHand, 0); + SetIcon(EquipSlot.OffHand, 7); + ret[EquipSlot.LFinger.ToIndex()] = ret[EquipSlot.RFinger.ToIndex()]; - uldWrapper.Dispose(); return ret; + + void SetIcon(EquipSlot slot, int index) + { + try + { + ret[slot.ToIndex()] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", index)!; + } + catch (Exception ex) + { + Glamourer.Log.Error($"Could not get empty slot texture for {slot.ToName()}, icon will be left empty. " + + $"This may be because of incompatible mods affecting your character screen interface:\n{ex}"); + ret[slot.ToIndex()] = null; + } + } } } From 36d95c37bc3f7c43ba03c023e7a794c38f8aca17 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 20 Dec 2023 16:47:37 +0100 Subject: [PATCH 094/786] Make compile job not depend on git. --- Glamourer/Automation/AutoDesignApplier.cs | 4 ++-- Glamourer/Glamourer.csproj | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index ddc8636..deb9e43 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -39,8 +39,8 @@ public class AutoDesignApplier : IDisposable private readonly IClientState _clientState; private ActorState? _jobChangeState; - private readonly Dictionary _jobChangeMainhand = new(); - private readonly Dictionary _jobChangeOffhand = new(); + private readonly Dictionary _jobChangeMainhand = []; + private readonly Dictionary _jobChangeOffhand = []; private void ResetJobChange() { diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 7ecb3b6..12ec3a7 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -108,8 +108,9 @@ - - + + + @@ -122,6 +123,7 @@ PreserveNewest + From a982c0a1c1e456f34c4e28e925a622c03a2b13d7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 21 Dec 2023 15:06:56 +0100 Subject: [PATCH 095/786] Update gamedata and services. --- .../Customization/CustomizationManager.cs | 4 +- .../Customization/CustomizationNpcOptions.cs | 316 +----------------- .../Customization/CustomizationOptions.cs | 8 +- Glamourer.GameData/Customization/Customize.cs | 5 +- .../Customization/CustomizeIndex.cs | 4 +- .../Customization/DatCharacterFile.cs | 9 +- .../Customization/NpcCustomizeSet.cs | 282 ++++++++++++++++ Glamourer.GameData/Customization/NpcData.cs | 89 +++++ Glamourer/Api/GlamourerIpc.cs | 8 +- Glamourer/Automation/AutoDesignApplier.cs | 18 +- Glamourer/Automation/AutoDesignManager.cs | 20 +- Glamourer/Automation/FixedDesignMigrator.cs | 8 +- Glamourer/Designs/DesignBase.cs | 8 +- Glamourer/Designs/DesignBase64Migration.cs | 16 +- Glamourer/Designs/DesignConverter.cs | 6 +- Glamourer/Designs/DesignData.cs | 39 ++- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Glamourer.cs | 21 +- .../Customization/CustomizationDrawer.Icon.cs | 9 +- .../Gui/Customization/CustomizationDrawer.cs | 6 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 39 +-- .../Gui/Equipment/GlamourerColorCombo.cs | 11 +- Glamourer/Gui/Equipment/ItemCombo.cs | 8 +- Glamourer/Gui/Equipment/WeaponCombo.cs | 8 +- Glamourer/Gui/MainWindow.cs | 7 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 6 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 27 +- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 6 +- .../Gui/Tabs/AutomationTab/HumanNpcCombo.cs | 32 +- .../Tabs/AutomationTab/IdentifierDrawer.cs | 23 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- .../Gui/Tabs/AutomationTab/SetSelector.cs | 12 +- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 2 +- .../Gui/Tabs/DebugTab/ActorServicePanel.cs | 28 +- .../DebugTab/CustomizationServicePanel.cs | 8 +- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 5 +- .../Gui/Tabs/DebugTab/DesignTesterPanel.cs | 2 +- .../Gui/Tabs/DebugTab/IdentifierPanel.cs | 17 +- .../Gui/Tabs/DebugTab/ItemManagerPanel.cs | 14 +- .../Gui/Tabs/DebugTab/ItemUnlockPanel.cs | 2 +- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 10 +- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 2 +- .../Gui/Tabs/DebugTab/ObjectManagerPanel.cs | 6 +- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 2 +- .../Gui/Tabs/DebugTab/RestrictedGearPanel.cs | 2 +- .../Gui/Tabs/DebugTab/UnlockableItemsPanel.cs | 2 +- Glamourer/Gui/Tabs/NpcCombo.cs | 33 +- Glamourer/Gui/Tabs/SettingsTab.cs | 2 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 63 ++-- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 15 +- Glamourer/Gui/UiHelpers.cs | 2 +- Glamourer/Interop/ChangeCustomizeService.cs | 9 +- Glamourer/Interop/CharaFile/CmaFile.cs | 12 +- Glamourer/Interop/ContextMenuService.cs | 16 +- Glamourer/Interop/ImportService.cs | 2 +- Glamourer/Interop/ObjectManager.cs | 22 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 4 +- Glamourer/Interop/WeaponService.cs | 4 +- Glamourer/Services/CommandService.cs | 13 +- Glamourer/Services/CustomizationService.cs | 109 +++--- Glamourer/Services/DalamudServices.cs | 61 +--- Glamourer/Services/IGamePathParser.cs | 6 + Glamourer/Services/ItemManager.cs | 53 ++- Glamourer/Services/ServiceManager.cs | 67 ++-- Glamourer/Services/ServiceWrapper.cs | 93 ------ Glamourer/State/FunModule.cs | 21 +- Glamourer/State/StateApplier.cs | 2 +- Glamourer/State/StateEditor.cs | 4 +- Glamourer/State/StateListener.cs | 44 +-- Glamourer/State/StateManager.cs | 20 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 6 +- Glamourer/Unlocks/ItemUnlockManager.cs | 19 +- OtterGui | 2 +- Penumbra.GameData | 2 +- 74 files changed, 907 insertions(+), 960 deletions(-) create mode 100644 Glamourer.GameData/Customization/NpcCustomizeSet.cs create mode 100644 Glamourer.GameData/Customization/NpcData.cs create mode 100644 Glamourer/Services/IGamePathParser.cs delete mode 100644 Glamourer/Services/ServiceWrapper.cs diff --git a/Glamourer.GameData/Customization/CustomizationManager.cs b/Glamourer.GameData/Customization/CustomizationManager.cs index 9221838..b02498c 100644 --- a/Glamourer.GameData/Customization/CustomizationManager.cs +++ b/Glamourer.GameData/Customization/CustomizationManager.cs @@ -12,9 +12,9 @@ public class CustomizationManager : ICustomizationManager private CustomizationManager() { } - public static ICustomizationManager Create(ITextureProvider textures, IDataManager gameData, IPluginLog log) + public static ICustomizationManager Create(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) { - _options ??= new CustomizationOptions(textures, gameData, log); + _options ??= new CustomizationOptions(textures, gameData, log, npcCustomizeSet); return new CustomizationManager(); } diff --git a/Glamourer.GameData/Customization/CustomizationNpcOptions.cs b/Glamourer.GameData/Customization/CustomizationNpcOptions.cs index 254af2e..043ccf8 100644 --- a/Glamourer.GameData/Customization/CustomizationNpcOptions.cs +++ b/Glamourer.GameData/Customization/CustomizationNpcOptions.cs @@ -1,240 +1,13 @@ -using System; -using Dalamud.Plugin.Services; -using Lumina.Excel; -using Lumina.Excel.GeneratedSheets; -using OtterGui; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; +using Penumbra.GameData.Enums; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using Dalamud.Utility; -using FFXIVClientStructs.FFXIV.Client.Game.Object; -using Penumbra.GameData; namespace Glamourer.Customization; public static class CustomizationNpcOptions { - public unsafe struct NpcData + public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, NpcCustomizeSet npcCustomizeSet) { - public string Name; - public Customize Customize; - private fixed byte _equip[40]; - public CharacterWeapon Mainhand; - public CharacterWeapon Offhand; - public uint Id; - public bool VisorToggled; - public ObjectKind Kind; - - public ReadOnlySpan Equip - { - get - { - fixed (byte* ptr = _equip) - { - return new ReadOnlySpan((CharacterArmor*)ptr, 10); - } - } - } - - public string WriteGear() - { - var sb = new StringBuilder(128); - var span = Equip; - for (var i = 0; i < 10; ++i) - { - sb.Append(span[i].Set.Id.ToString("D4")); - sb.Append('-'); - sb.Append(span[i].Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(span[i].Stain.Id.ToString("D3")); - sb.Append(", "); - } - - sb.Append(Mainhand.Set.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Mainhand.Type.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Mainhand.Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(Mainhand.Stain.Id.ToString("D4")); - sb.Append(", "); - sb.Append(Offhand.Set.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Offhand.Type.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Offhand.Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(Offhand.Stain.Id.ToString("D3")); - return sb.ToString(); - } - - internal void Set(int idx, uint value) - { - fixed (byte* ptr = _equip) - { - ((uint*)ptr)[idx] = value; - } - } - - public bool DataEquals(in NpcData other) - { - if (VisorToggled != other.VisorToggled) - return false; - - if (!Customize.Equals(other.Customize)) - return false; - - if (!Mainhand.Equals(other.Mainhand)) - return false; - - if (!Offhand.Equals(other.Offhand)) - return false; - - fixed (byte* ptr1 = _equip, ptr2 = other._equip) - { - return new ReadOnlySpan(ptr1, 40).SequenceEqual(new ReadOnlySpan(ptr2, 40)); - } - } - } - - private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) - { - data.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); - data.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); - data.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); - data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); - data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); - data.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); - data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); - data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); - data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); - data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); - data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); - data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); - data.VisorToggled = row.Visor; - } - - public static unsafe IReadOnlyList CreateNpcData(IReadOnlyDictionary eNpcs, - IReadOnlyDictionary bnpcNames, IObjectIdentifier identifier, IDataManager data) - { - var enpcSheet = data.GetExcelSheet()!; - var bnpcSheet = data.GetExcelSheet()!; - var list = new List(eNpcs.Count + (int)bnpcSheet.RowCount); - foreach (var (id, name) in eNpcs) - { - var row = enpcSheet.GetRow(id); - if (row == null || name.IsNullOrWhitespace()) - continue; - - var (valid, customize) = FromEnpcBase(row); - if (!valid) - continue; - - var ret = new NpcData - { - Name = name, - Customize = customize, - Id = id, - Kind = ObjectKind.EventNpc, - }; - - if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) - { - ApplyNpcEquip(ref ret, equip); - } - else - { - ret.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); - ret.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); - ret.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); - ret.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); - ret.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); - ret.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); - ret.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); - ret.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); - ret.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); - ret.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); - ret.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); - ret.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); - ret.VisorToggled = row.Visor; - } - - list.Add(ret); - } - - foreach (var baseRow in bnpcSheet) - { - if (baseRow.ModelChara.Value!.Type != 1) - continue; - - var bnpcNameIds = identifier.GetBnpcNames(baseRow.RowId); - if (bnpcNameIds.Count == 0) - continue; - - var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!); - if (!valid) - continue; - - var equip = baseRow.NpcEquip.Value!; - var ret = new NpcData - { - Customize = customize, - Id = baseRow.RowId, - Kind = ObjectKind.BattleNpc, - }; - ApplyNpcEquip(ref ret, equip); - foreach (var bnpcNameId in bnpcNameIds) - { - if (bnpcNames.TryGetValue(bnpcNameId.Id, out var name) && !name.IsNullOrWhitespace()) - list.Add(ret with { Name = name }); - } - } - - var groups = list.GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList()); - list.Clear(); - foreach (var (name, duplicates) in groups.OrderBy(kvp => kvp.Key)) - { - for (var i = 0; i < duplicates.Count; ++i) - { - var current = duplicates[i]; - var add = true; - for (var j = 0; j < i; ++j) - { - if (current.DataEquals(duplicates[j])) - { - duplicates.RemoveAt(i--); - break; - } - } - } - - if (duplicates.Count == 1) - list.Add(duplicates[0]); - else - list.AddRange(duplicates - .Select(duplicate => duplicate with { Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})" })); - } - - var lastWeird = list.FindIndex(d => char.IsAsciiLetterOrDigit(d.Name[0])); - if (lastWeird != -1) - { - list.AddRange(list.Take(lastWeird)); - list.RemoveRange(0, lastWeird); - } - - return list; - } - - - public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, - ExcelSheet bNpc, ExcelSheet eNpc) - { - var customizes = bNpc.SelectWhere(FromBnpcCustomize) - .Concat(eNpc.SelectWhere(FromEnpcBase)).ToList(); - var dict = new Dictionary<(SubRace, Gender), HashSet<(CustomizeIndex, CustomizeValue)>>(); var customizeIndices = new[] { @@ -251,7 +24,7 @@ public static class CustomizationNpcOptions CustomizeIndex.EyeColorRight, }; - foreach (var customize in customizes) + foreach (var customize in npcCustomizeSet.Select(s => s.Customize)) { var set = sets[CustomizationOptions.ToIndex(customize.Clan, customize.Gender)]; foreach (var customizeIndex in customizeIndices) @@ -265,7 +38,7 @@ public static class CustomizationNpcOptions if (!dict.TryGetValue((set.Clan, set.Gender), out var npcSet)) { - npcSet = new HashSet<(CustomizeIndex, CustomizeValue)> { (customizeIndex, value) }; + npcSet = [(customizeIndex, value)]; dict.Add((set.Clan, set.Gender), npcSet); } else @@ -278,85 +51,4 @@ public static class CustomizationNpcOptions return dict.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<(CustomizeIndex, CustomizeValue)>)kvp.Value.OrderBy(p => p.Item1).ThenBy(p => p.Item2.Value).ToArray()); } - - private static (bool, Customize) FromBnpcCustomize(BNpcCustomize bnpcCustomize) - { - var customize = new Customize(); - customize.Data.Set(0, (byte)bnpcCustomize.Race.Row); - customize.Data.Set(1, bnpcCustomize.Gender); - customize.Data.Set(2, bnpcCustomize.BodyType); - customize.Data.Set(3, bnpcCustomize.Height); - customize.Data.Set(4, (byte)bnpcCustomize.Tribe.Row); - customize.Data.Set(5, bnpcCustomize.Face); - customize.Data.Set(6, bnpcCustomize.HairStyle); - customize.Data.Set(7, bnpcCustomize.HairHighlight); - customize.Data.Set(8, bnpcCustomize.SkinColor); - customize.Data.Set(9, bnpcCustomize.EyeHeterochromia); - customize.Data.Set(10, bnpcCustomize.HairColor); - customize.Data.Set(11, bnpcCustomize.HairHighlightColor); - customize.Data.Set(12, bnpcCustomize.FacialFeature); - customize.Data.Set(13, bnpcCustomize.FacialFeatureColor); - customize.Data.Set(14, bnpcCustomize.Eyebrows); - customize.Data.Set(15, bnpcCustomize.EyeColor); - customize.Data.Set(16, bnpcCustomize.EyeShape); - customize.Data.Set(17, bnpcCustomize.Nose); - customize.Data.Set(18, bnpcCustomize.Jaw); - customize.Data.Set(19, bnpcCustomize.Mouth); - customize.Data.Set(20, bnpcCustomize.LipColor); - customize.Data.Set(21, bnpcCustomize.BustOrTone1); - customize.Data.Set(22, bnpcCustomize.ExtraFeature1); - customize.Data.Set(23, bnpcCustomize.ExtraFeature2OrBust); - customize.Data.Set(24, bnpcCustomize.FacePaint); - customize.Data.Set(25, bnpcCustomize.FacePaintColor); - - if (customize.BodyType.Value != 1 - || !CustomizationOptions.Races.Contains(customize.Race) - || !CustomizationOptions.Clans.Contains(customize.Clan) - || !CustomizationOptions.Genders.Contains(customize.Gender)) - return (false, Customize.Default); - - return (true, customize); - } - - private static (bool, Customize) FromEnpcBase(ENpcBase enpcBase) - { - if (enpcBase.ModelChara.Value?.Type != 1) - return (false, Customize.Default); - - var customize = new Customize(); - customize.Data.Set(0, (byte)enpcBase.Race.Row); - customize.Data.Set(1, enpcBase.Gender); - customize.Data.Set(2, enpcBase.BodyType); - customize.Data.Set(3, enpcBase.Height); - customize.Data.Set(4, (byte)enpcBase.Tribe.Row); - customize.Data.Set(5, enpcBase.Face); - customize.Data.Set(6, enpcBase.HairStyle); - customize.Data.Set(7, enpcBase.HairHighlight); - customize.Data.Set(8, enpcBase.SkinColor); - customize.Data.Set(9, enpcBase.EyeHeterochromia); - customize.Data.Set(10, enpcBase.HairColor); - customize.Data.Set(11, enpcBase.HairHighlightColor); - customize.Data.Set(12, enpcBase.FacialFeature); - customize.Data.Set(13, enpcBase.FacialFeatureColor); - customize.Data.Set(14, enpcBase.Eyebrows); - customize.Data.Set(15, enpcBase.EyeColor); - customize.Data.Set(16, enpcBase.EyeShape); - customize.Data.Set(17, enpcBase.Nose); - customize.Data.Set(18, enpcBase.Jaw); - customize.Data.Set(19, enpcBase.Mouth); - customize.Data.Set(20, enpcBase.LipColor); - customize.Data.Set(21, enpcBase.BustOrTone1); - customize.Data.Set(22, enpcBase.ExtraFeature1); - customize.Data.Set(23, enpcBase.ExtraFeature2OrBust); - customize.Data.Set(24, enpcBase.FacePaint); - customize.Data.Set(25, enpcBase.FacePaintColor); - - if (customize.BodyType.Value != 1 - || !CustomizationOptions.Races.Contains(customize.Race) - || !CustomizationOptions.Clans.Contains(customize.Clan) - || !CustomizationOptions.Genders.Contains(customize.Gender)) - return (false, Customize.Default); - - return (true, customize); - } } diff --git a/Glamourer.GameData/Customization/CustomizationOptions.cs b/Glamourer.GameData/Customization/CustomizationOptions.cs index fdbac65..3580ef8 100644 --- a/Glamourer.GameData/Customization/CustomizationOptions.cs +++ b/Glamourer.GameData/Customization/CustomizationOptions.cs @@ -62,7 +62,7 @@ public partial class CustomizationOptions public string GetName(CustomName name) => _names[(int)name]; - internal CustomizationOptions(ITextureProvider textures, IDataManager gameData, IPluginLog log) + internal CustomizationOptions(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) { var tmp = new TemporaryData(gameData, this, log); _icons = new IconStorage(textures, gameData); @@ -73,7 +73,7 @@ public partial class CustomizationOptions _customizationSets[ToIndex(race, gender)] = tmp.GetSet(race, gender); } - tmp.SetNpcData(_customizationSets); + tmp.SetNpcData(_customizationSets, npcCustomizeSet); } // Obtain localized names of customization options and race names from the game data. @@ -163,9 +163,9 @@ public partial class CustomizationOptions return set; } - public void SetNpcData(CustomizationSet[] sets) + public void SetNpcData(CustomizationSet[] sets, NpcCustomizeSet npcCustomizeSet) { - var data = CustomizationNpcOptions.CreateNpcData(sets, _bnpcCustomize, _enpcBase); + var data = CustomizationNpcOptions.CreateNpcData(sets, npcCustomizeSet); foreach (var set in sets) { if (data.TryGetValue((set.Clan, set.Gender), out var npcData)) diff --git a/Glamourer.GameData/Customization/Customize.cs b/Glamourer.GameData/Customization/Customize.cs index 90dac63..b19ef22 100644 --- a/Glamourer.GameData/Customization/Customize.cs +++ b/Glamourer.GameData/Customization/Customize.cs @@ -1,13 +1,14 @@ using System; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Customization; public unsafe struct Customize { - public Penumbra.GameData.Structs.CustomizeData Data; + public CustomizeArray Data; - public Customize(in Penumbra.GameData.Structs.CustomizeData data) + public Customize(in CustomizeArray data) { Data = data.Clone(); } diff --git a/Glamourer.GameData/Customization/CustomizeIndex.cs b/Glamourer.GameData/Customization/CustomizeIndex.cs index 38aca21..cbd22ed 100644 --- a/Glamourer.GameData/Customization/CustomizeIndex.cs +++ b/Glamourer.GameData/Customization/CustomizeIndex.cs @@ -144,14 +144,14 @@ public static class CustomizationExtensions }; [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static unsafe CustomizeValue Get(this in Penumbra.GameData.Structs.CustomizeData data, CustomizeIndex index) + public static unsafe CustomizeValue Get(this in Penumbra.GameData.Structs.CustomizeArray data, CustomizeIndex index) { var (offset, mask) = index.ToByteAndMask(); return (CustomizeValue)(data.Data[offset] & mask); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static unsafe bool Set(this ref Penumbra.GameData.Structs.CustomizeData data, CustomizeIndex index, CustomizeValue value) + public static unsafe bool Set(this ref Penumbra.GameData.Structs.CustomizeArray data, CustomizeIndex index, CustomizeValue value) { var (offset, mask) = index.ToByteAndMask(); return mask != 0xFF diff --git a/Glamourer.GameData/Customization/DatCharacterFile.cs b/Glamourer.GameData/Customization/DatCharacterFile.cs index 304334a..f33af69 100644 --- a/Glamourer.GameData/Customization/DatCharacterFile.cs +++ b/Glamourer.GameData/Customization/DatCharacterFile.cs @@ -3,13 +3,14 @@ using System.IO; using System.Runtime.InteropServices; using System.Text; using Dalamud.Memory; +using Penumbra.GameData.Structs; namespace Glamourer.Customization; [StructLayout(LayoutKind.Explicit, Size = Size)] public unsafe struct DatCharacterFile { - public const int Size = 4 + 4 + 4 + 4 + Penumbra.GameData.Structs.CustomizeData.Size + 2 + 4 + 41 * 4; // 212 + public const int Size = 4 + 4 + 4 + 4 + CustomizeArray.Size + 2 + 4 + 41 * 4; // 212 [FieldOffset(0)] private fixed byte _data[Size]; @@ -27,12 +28,12 @@ public unsafe struct DatCharacterFile private readonly uint _padding = 0; [FieldOffset(16)] - private Penumbra.GameData.Structs.CustomizeData _customize; + private CustomizeArray _customize; - [FieldOffset(16 + Penumbra.GameData.Structs.CustomizeData.Size)] + [FieldOffset(16 + CustomizeArray.Size)] private ushort _voice; - [FieldOffset(16 + Penumbra.GameData.Structs.CustomizeData.Size + 2)] + [FieldOffset(16 + CustomizeArray.Size + 2)] private uint _timeStamp; [FieldOffset(Size - 41 * 4)] diff --git a/Glamourer.GameData/Customization/NpcCustomizeSet.cs b/Glamourer.GameData/Customization/NpcCustomizeSet.cs new file mode 100644 index 0000000..3ba64a0 --- /dev/null +++ b/Glamourer.GameData/Customization/NpcCustomizeSet.cs @@ -0,0 +1,282 @@ +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Dalamud.Plugin.Services; +using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Lumina.Excel.GeneratedSheets; +using OtterGui.Services; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Structs; + +namespace Glamourer.Customization; + +public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList +{ + public string Name + => nameof(NpcCustomizeSet); + + private readonly List _data = []; + + public long Time { get; private set; } + public long Memory { get; private set; } + public int TotalCount + => _data.Count; + + public Task Awaiter { get; } + + public NpcCustomizeSet(IDataManager data, DictENpc eNpcs, DictBNpc bNpcs, DictBNpcNames bNpcNames) + { + var waitTask = Task.WhenAll(eNpcs.Awaiter, bNpcs.Awaiter, bNpcNames.Awaiter); + Awaiter = waitTask.ContinueWith(_ => + { + var watch = Stopwatch.StartNew(); + var eNpcTask = Task.Run(() => CreateEnpcData(data, eNpcs)); + var bNpcTask = Task.Run(() => CreateBnpcData(data, bNpcs, bNpcNames)); + FilterAndOrderNpcData(eNpcTask.Result, bNpcTask.Result); + Time = watch.ElapsedMilliseconds; + }); + } + + private static List CreateEnpcData(IDataManager data, DictENpc eNpcs) + { + var enpcSheet = data.GetExcelSheet()!; + var list = new List(eNpcs.Count); + + foreach (var (id, name) in eNpcs) + { + var row = enpcSheet.GetRow(id.Id); + if (row == null || name.IsNullOrWhitespace()) + continue; + + var (valid, customize) = FromEnpcBase(row); + if (!valid) + continue; + + var ret = new NpcData + { + Name = name, + Customize = customize, + Id = id, + Kind = ObjectKind.EventNpc, + }; + + if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) + { + ApplyNpcEquip(ref ret, equip); + } + else + { + ret.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); + ret.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); + ret.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); + ret.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); + ret.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); + ret.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); + ret.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); + ret.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); + ret.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); + ret.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); + ret.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); + ret.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); + ret.VisorToggled = row.Visor; + } + + list.Add(ret); + } + + return list; + } + + private static List CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames) + { + var bnpcSheet = data.GetExcelSheet()!; + var list = new List((int)bnpcSheet.RowCount); + foreach (var baseRow in bnpcSheet) + { + if (baseRow.ModelChara.Value!.Type != 1) + continue; + + var bnpcNameIds = bNpcNames[baseRow.RowId]; + if (bnpcNameIds.Count == 0) + continue; + + var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!); + if (!valid) + continue; + + var equip = baseRow.NpcEquip.Value!; + var ret = new NpcData + { + Customize = customize, + Id = baseRow.RowId, + Kind = ObjectKind.BattleNpc, + }; + ApplyNpcEquip(ref ret, equip); + foreach (var bnpcNameId in bnpcNameIds) + { + if (bNpcs.TryGetValue(bnpcNameId.Id, out var name) && !name.IsNullOrWhitespace()) + list.Add(ret with { Name = name }); + } + } + + return list; + } + + private void FilterAndOrderNpcData(List eNpcEquip, List bNpcEquip) + { + _data.Clear(); + _data.EnsureCapacity(eNpcEquip.Count + bNpcEquip.Count); + var groups = eNpcEquip.Concat(bNpcEquip).GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList()); + foreach (var (name, duplicates) in groups.OrderBy(kvp => kvp.Key)) + { + for (var i = 0; i < duplicates.Count; ++i) + { + var current = duplicates[i]; + for (var j = 0; j < i; ++j) + { + if (current.DataEquals(duplicates[j])) + { + duplicates.RemoveAt(i--); + break; + } + } + } + + if (duplicates.Count == 1) + { + _data.Add(duplicates[0]); + Memory += 96; + } + else + { + _data.AddRange(duplicates + .Select(duplicate => duplicate with + { + Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})" + })); + Memory += 96 * duplicates.Count + duplicates.Sum(d => d.Name.Length * 2); + } + } + + var lastWeird = _data.FindIndex(d => char.IsAsciiLetterOrDigit(d.Name[0])); + if (lastWeird != -1) + { + _data.AddRange(_data.Take(lastWeird)); + _data.RemoveRange(0, lastWeird); + } + _data.TrimExcess(); + } + + private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) + { + data.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); + data.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); + data.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); + data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); + data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); + data.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); + data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); + data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); + data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); + data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); + data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); + data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); + data.VisorToggled = row.Visor; + } + + private static (bool, Customize) FromBnpcCustomize(BNpcCustomize bnpcCustomize) + { + var customize = new Customize(); + customize.Data.Set(0, (byte)bnpcCustomize.Race.Row); + customize.Data.Set(1, bnpcCustomize.Gender); + customize.Data.Set(2, bnpcCustomize.BodyType); + customize.Data.Set(3, bnpcCustomize.Height); + customize.Data.Set(4, (byte)bnpcCustomize.Tribe.Row); + customize.Data.Set(5, bnpcCustomize.Face); + customize.Data.Set(6, bnpcCustomize.HairStyle); + customize.Data.Set(7, bnpcCustomize.HairHighlight); + customize.Data.Set(8, bnpcCustomize.SkinColor); + customize.Data.Set(9, bnpcCustomize.EyeHeterochromia); + customize.Data.Set(10, bnpcCustomize.HairColor); + customize.Data.Set(11, bnpcCustomize.HairHighlightColor); + customize.Data.Set(12, bnpcCustomize.FacialFeature); + customize.Data.Set(13, bnpcCustomize.FacialFeatureColor); + customize.Data.Set(14, bnpcCustomize.Eyebrows); + customize.Data.Set(15, bnpcCustomize.EyeColor); + customize.Data.Set(16, bnpcCustomize.EyeShape); + customize.Data.Set(17, bnpcCustomize.Nose); + customize.Data.Set(18, bnpcCustomize.Jaw); + customize.Data.Set(19, bnpcCustomize.Mouth); + customize.Data.Set(20, bnpcCustomize.LipColor); + customize.Data.Set(21, bnpcCustomize.BustOrTone1); + customize.Data.Set(22, bnpcCustomize.ExtraFeature1); + customize.Data.Set(23, bnpcCustomize.ExtraFeature2OrBust); + customize.Data.Set(24, bnpcCustomize.FacePaint); + customize.Data.Set(25, bnpcCustomize.FacePaintColor); + + if (customize.BodyType.Value != 1 + || !CustomizationOptions.Races.Contains(customize.Race) + || !CustomizationOptions.Clans.Contains(customize.Clan) + || !CustomizationOptions.Genders.Contains(customize.Gender)) + return (false, Customize.Default); + + return (true, customize); + } + + private static (bool, Customize) FromEnpcBase(ENpcBase enpcBase) + { + if (enpcBase.ModelChara.Value?.Type != 1) + return (false, Customize.Default); + + var customize = new Customize(); + customize.Data.Set(0, (byte)enpcBase.Race.Row); + customize.Data.Set(1, enpcBase.Gender); + customize.Data.Set(2, enpcBase.BodyType); + customize.Data.Set(3, enpcBase.Height); + customize.Data.Set(4, (byte)enpcBase.Tribe.Row); + customize.Data.Set(5, enpcBase.Face); + customize.Data.Set(6, enpcBase.HairStyle); + customize.Data.Set(7, enpcBase.HairHighlight); + customize.Data.Set(8, enpcBase.SkinColor); + customize.Data.Set(9, enpcBase.EyeHeterochromia); + customize.Data.Set(10, enpcBase.HairColor); + customize.Data.Set(11, enpcBase.HairHighlightColor); + customize.Data.Set(12, enpcBase.FacialFeature); + customize.Data.Set(13, enpcBase.FacialFeatureColor); + customize.Data.Set(14, enpcBase.Eyebrows); + customize.Data.Set(15, enpcBase.EyeColor); + customize.Data.Set(16, enpcBase.EyeShape); + customize.Data.Set(17, enpcBase.Nose); + customize.Data.Set(18, enpcBase.Jaw); + customize.Data.Set(19, enpcBase.Mouth); + customize.Data.Set(20, enpcBase.LipColor); + customize.Data.Set(21, enpcBase.BustOrTone1); + customize.Data.Set(22, enpcBase.ExtraFeature1); + customize.Data.Set(23, enpcBase.ExtraFeature2OrBust); + customize.Data.Set(24, enpcBase.FacePaint); + customize.Data.Set(25, enpcBase.FacePaintColor); + + if (customize.BodyType.Value != 1 + || !CustomizationOptions.Races.Contains(customize.Race) + || !CustomizationOptions.Clans.Contains(customize.Clan) + || !CustomizationOptions.Genders.Contains(customize.Gender)) + return (false, Customize.Default); + + return (true, customize); + } + + public IEnumerator GetEnumerator() + => _data.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _data.Count; + + public NpcData this[int index] + => _data[index]; +} diff --git a/Glamourer.GameData/Customization/NpcData.cs b/Glamourer.GameData/Customization/NpcData.cs new file mode 100644 index 0000000..6942147 --- /dev/null +++ b/Glamourer.GameData/Customization/NpcData.cs @@ -0,0 +1,89 @@ +using System; +using System.Text; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Penumbra.GameData.Structs; + +namespace Glamourer.Customization; + +public unsafe struct NpcData +{ + public string Name; + public Customize Customize; + private fixed byte _equip[40]; + public CharacterWeapon Mainhand; + public CharacterWeapon Offhand; + public NpcId Id; + public bool VisorToggled; + public ObjectKind Kind; + + public ReadOnlySpan Equip + { + get + { + fixed (byte* ptr = _equip) + { + return new ReadOnlySpan((CharacterArmor*)ptr, 10); + } + } + } + + public string WriteGear() + { + var sb = new StringBuilder(128); + var span = Equip; + for (var i = 0; i < 10; ++i) + { + sb.Append(span[i].Set.Id.ToString("D4")); + sb.Append('-'); + sb.Append(span[i].Variant.Id.ToString("D3")); + sb.Append('-'); + sb.Append(span[i].Stain.Id.ToString("D3")); + sb.Append(", "); + } + + sb.Append(Mainhand.Skeleton.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Mainhand.Weapon.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Mainhand.Variant.Id.ToString("D3")); + sb.Append('-'); + sb.Append(Mainhand.Stain.Id.ToString("D4")); + sb.Append(", "); + sb.Append(Offhand.Skeleton.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Offhand.Weapon.Id.ToString("D4")); + sb.Append('-'); + sb.Append(Offhand.Variant.Id.ToString("D3")); + sb.Append('-'); + sb.Append(Offhand.Stain.Id.ToString("D3")); + return sb.ToString(); + } + + internal void Set(int idx, uint value) + { + fixed (byte* ptr = _equip) + { + ((uint*)ptr)[idx] = value; + } + } + + public bool DataEquals(in NpcData other) + { + if (VisorToggled != other.VisorToggled) + return false; + + if (!Customize.Equals(other.Customize)) + return false; + + if (!Mainhand.Equals(other.Mainhand)) + return false; + + if (!Offhand.Equals(other.Offhand)) + return false; + + fixed (byte* ptr1 = _equip, ptr2 = other._equip) + { + return new ReadOnlySpan(ptr1, 40).SequenceEqual(new ReadOnlySpan(ptr2, 40)); + } + } +} diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 398cdf2..53cfb18 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -7,10 +7,10 @@ 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; @@ -22,11 +22,11 @@ public partial class GlamourerIpc : IDisposable private readonly StateManager _stateManager; private readonly ObjectManager _objects; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly DesignConverter _designConverter; private readonly AutoDesignApplier _autoDesignApplier; - public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorService actors, + public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorManager actors, DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier) { _stateManager = stateManager; @@ -142,7 +142,7 @@ public partial class GlamourerIpc : IDisposable private IEnumerable FindActors(Character? character) { - var id = _actors.AwaitedService.FromObject(character, true, true, false); + var id = _actors.FromObject(character, true, true, false); if (!id.IsValid) yield break; diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index deb9e43..023b2c0 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -15,7 +15,7 @@ using Glamourer.Structs; using Glamourer.Unlocks; using OtterGui.Classes; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -28,7 +28,7 @@ public class AutoDesignApplier : IDisposable private readonly StateManager _state; private readonly JobService _jobs; private readonly EquippedGearset _equippedGearset; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly CustomizationService _customizations; private readonly CustomizeUnlockManager _customizeUnlocks; private readonly ItemUnlockManager _itemUnlocks; @@ -50,7 +50,7 @@ public class AutoDesignApplier : IDisposable } public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, - CustomizationService customizations, ActorService actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, + CustomizationService customizations, ActorManager actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, EquippedGearset equippedGearset) { @@ -87,7 +87,7 @@ public class AutoDesignApplier : IDisposable if (_jobChangeState == null || !_config.EnableAutoDesigns) return; - var id = actor.GetIdentifier(_actors.AwaitedService); + var id = actor.GetIdentifier(_actors); if (id == _jobChangeState.Identifier) { var current = _jobChangeState.BaseData.Item(slot); @@ -161,7 +161,7 @@ public class AutoDesignApplier : IDisposable { foreach (var actor in data.Objects) { - var specificId = actor.GetIdentifier(_actors.AwaitedService); + var specificId = actor.GetIdentifier(_actors); if (_state.GetOrCreate(specificId, actor, out var state)) { Reduce(actor, state, newSet, false, false); @@ -203,7 +203,7 @@ public class AutoDesignApplier : IDisposable private void OnJobChange(Actor actor, Job oldJob, Job newJob) { - if (!_config.EnableAutoDesigns || !actor.Identifier(_actors.AwaitedService, out var id)) + if (!_config.EnableAutoDesigns || !actor.Identifier(_actors, out var id)) return; if (!GetPlayerSet(id, out var set)) @@ -312,13 +312,13 @@ public class AutoDesignApplier : IDisposable if (_manager.EnabledSets.TryGetValue(identifier, out set)) return true; - identifier = _actors.AwaitedService.CreatePlayer(identifier.PlayerName, ushort.MaxValue); + identifier = _actors.CreatePlayer(identifier.PlayerName, ushort.MaxValue); return _manager.EnabledSets.TryGetValue(identifier, out set); case IdentifierType.Retainer: case IdentifierType.Npc: return _manager.EnabledSets.TryGetValue(identifier, out set); case IdentifierType.Owned: - identifier = _actors.AwaitedService.CreateNpc(identifier.Kind, identifier.DataId); + identifier = _actors.CreateNpc(identifier.Kind, identifier.DataId); return _manager.EnabledSets.TryGetValue(identifier, out set); default: set = null; @@ -470,7 +470,7 @@ public class AutoDesignApplier : IDisposable totalCustomizeFlags |= CustomizeFlag.Face; } - var set = _customizations.AwaitedService.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); + var set = _customizations.Service.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); var face = state.ModelData.Customize.Face; foreach (var index in Enum.GetValues()) { diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index d3fba5c..e997a78 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -17,6 +17,8 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Filesystem; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Automation; @@ -28,17 +30,17 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos private readonly JobService _jobs; private readonly DesignManager _designs; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly AutomationChanged _event; private readonly DesignChanged _designEvent; - private readonly List _data = new(); - private readonly Dictionary _enabled = new(); + private readonly List _data = []; + private readonly Dictionary _enabled = []; public IReadOnlyDictionary EnabledSets => _enabled; - public AutoDesignManager(JobService jobs, ActorService actors, SaveService saveService, DesignManager designs, AutomationChanged @event, + public AutoDesignManager(JobService jobs, ActorManager actors, SaveService saveService, DesignManager designs, AutomationChanged @event, FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent) { _jobs = jobs; @@ -419,7 +421,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos continue; } - var id = _actors.AwaitedService.FromJson(obj["Identifier"] as JObject); + var id = _actors.FromJson(obj["Identifier"] as JObject); if (!IdentifierValid(id, out var group)) { Glamourer.Messager.NotificationMessage("Skipped loading Automation Set: Invalid Identifier.", NotificationType.Warning); @@ -562,9 +564,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos var name = manager.Data.ToName(identifier.Kind, identifier.DataId); var table = identifier.Kind switch { - ObjectKind.BattleNpc => manager.Data.BNpcs, + ObjectKind.BattleNpc => (IReadOnlyDictionary)manager.Data.BNpcs, ObjectKind.EventNpc => manager.Data.ENpcs, - _ => new Dictionary(), + _ => new Dictionary(), }; return table.Where(kvp => kvp.Value == name) .Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id, @@ -580,12 +582,12 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos }, IdentifierType.Retainer => new[] { - _actors.AwaitedService.CreateRetainer(identifier.PlayerName, + _actors.CreateRetainer(identifier.PlayerName, identifier.Retainer == ActorIdentifier.RetainerType.Mannequin ? ActorIdentifier.RetainerType.Mannequin : ActorIdentifier.RetainerType.Bell).CreatePermanent(), }, - IdentifierType.Npc => CreateNpcs(_actors.AwaitedService, identifier), + IdentifierType.Npc => CreateNpcs(_actors, identifier), _ => Array.Empty(), }; } diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index 0dc20b4..d30271e 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -20,7 +20,7 @@ public class FixedDesignMigrator public FixedDesignMigrator(JobService jobs) => _jobs = jobs; - public void ConsumeMigratedData(ActorService actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager) + public void ConsumeMigratedData(ActorManager actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager) { if (_migratedData == null) return; @@ -35,15 +35,15 @@ public class FixedDesignMigrator var id = ActorIdentifier.Invalid; if (ByteString.FromString(data.Name, out var byteString, false)) { - id = actors.AwaitedService.CreatePlayer(byteString, ushort.MaxValue); + id = actors.CreatePlayer(byteString, ushort.MaxValue); if (!id.IsValid) - id = actors.AwaitedService.CreateRetainer(byteString, ActorIdentifier.RetainerType.Both); + id = actors.CreateRetainer(byteString, ActorIdentifier.RetainerType.Both); } if (!id.IsValid) { byteString = ByteString.FromSpanUnsafe("Mig Ration"u8, true, false, true); - id = actors.AwaitedService.CreatePlayer(byteString, actors.AwaitedService.Data.Worlds.First().Key); + id = actors.CreatePlayer(byteString, actors.Data.Worlds.First().Key); if (!id.IsValid) { Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {data.Name}.", NotificationType.Error); diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index d859e8e..c9662ef 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -4,11 +4,11 @@ using Glamourer.Services; using Glamourer.Structs; using Newtonsoft.Json.Linq; using OtterGui.Classes; -using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using System; using System.Linq; +using Penumbra.GameData.DataContainers; namespace Glamourer.Designs; @@ -91,7 +91,7 @@ public class DesignBase return false; _designData.Customize.Load(customize); - CustomizationSet = customizationService.AwaitedService.GetList(customize.Clan, customize.Gender); + CustomizationSet = customizationService.Service.GetList(customize.Clan, customize.Gender); return true; } @@ -243,8 +243,8 @@ public class DesignBase private CustomizationSet SetCustomizationSet(CustomizationService customize) => !_designData.IsHuman - ? customize.AwaitedService.GetList(SubRace.Midlander, Gender.Male) - : customize.AwaitedService.GetList(_designData.Customize.Clan, _designData.Customize.Gender); + ? customize.Service.GetList(SubRace.Midlander, Gender.Male) + : customize.Service.GetList(_designData.Customize.Clan, _designData.Customize.Gender); #endregion diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index 18cef7a..c756395 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -3,7 +3,7 @@ using Glamourer.Customization; using Glamourer.Services; using Glamourer.Structs; using OtterGui; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -121,9 +121,9 @@ public class DesignBase64Migration data.SetStain(slot, mdl.Stain); } - var main = cur[0].Set.Id == 0 + var main = cur[0].X.Id == 0 ? items.DefaultSword - : items.Identify(EquipSlot.MainHand, cur[0].Set, cur[0].Type, cur[0].Variant); + : items.Identify(EquipSlot.MainHand, cur[0].X, cur[0].Y, cur[0].Variant); if (!main.Valid) { Glamourer.Log.Warning("Base64 string invalid, weapon could not be identified."); @@ -135,10 +135,10 @@ public class DesignBase64Migration EquipItem off; // Fist weapon hack - if (main.ModelId.Id is > 1600 and < 1651 && cur[1].Variant == 0) + if (main.PrimaryId.Id is > 1600 and < 1651 && cur[1].Variant == 0) { - off = items.Identify(EquipSlot.OffHand, (SetId)(main.ModelId.Id + 50), main.WeaponType, main.Variant, main.Type); - var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Set, (Variant)cur[1].Type.Id); + off = items.Identify(EquipSlot.OffHand, (PrimaryId)(main.PrimaryId.Id + 50), main.SecondaryId, main.Variant, main.Type); + var gauntlet = items.Identify(EquipSlot.Hands, cur[1].X, (Variant)cur[1].Y.Id); if (gauntlet.Valid) { data.SetItem(EquipSlot.Hands, gauntlet); @@ -147,9 +147,9 @@ public class DesignBase64Migration } else { - off = cur[0].Set.Id == 0 + off = cur[0].X.Id == 0 ? ItemManager.NothingItem(FullEquipType.Shield) - : items.Identify(EquipSlot.OffHand, cur[1].Set, cur[1].Type, cur[1].Variant, main.Type); + : items.Identify(EquipSlot.OffHand, cur[1].X, cur[1].Y, cur[1].Variant, main.Type); } if (main.Type.ValidOffhand() != FullEquipType.Unknown && !off.Valid) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index f1ee47c..038509d 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -9,7 +9,7 @@ using Glamourer.Structs; using Glamourer.Utility; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -165,7 +165,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (slot, item, armor.Stain); } - var mh = _items.Identify(EquipSlot.MainHand, mainhand.Set, mainhand.Type, mainhand.Variant, FullEquipType.Unknown); + var mh = _items.Identify(EquipSlot.MainHand, mainhand.X, mainhand.Y, mainhand.Variant, FullEquipType.Unknown); if (!mh.Valid) { Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); @@ -174,7 +174,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (EquipSlot.MainHand, mh, mainhand.Stain); - var oh = _items.Identify(EquipSlot.OffHand, offhand.Set, offhand.Type, offhand.Variant, mh.Type); + var oh = _items.Identify(EquipSlot.OffHand, offhand.X, offhand.Y, offhand.Variant, mh.Type); if (!oh.Valid) { Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified."); diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 4b0d53b..68fa154 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -7,7 +7,6 @@ using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String.Functions; -using CustomizeData = Penumbra.GameData.Structs.CustomizeData; namespace Glamourer.Designs; @@ -31,8 +30,8 @@ public unsafe struct DesignData public Customize Customize = Customize.Default; public uint ModelId; public CrestFlag CrestVisibility; - private WeaponType _secondaryMainhand; - private WeaponType _secondaryOffhand; + private SecondaryId _secondaryMainhand; + private SecondaryId _secondaryOffhand; private FullEquipType _typeMainhand; private FullEquipType _typeOffhand; private byte _states; @@ -75,18 +74,18 @@ public unsafe struct DesignData => slot.ToIndex() switch { // @formatter:off - 0 => EquipItem.FromIds(_itemIds[ 0], _iconIds[ 0], (SetId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (WeaponType)0, _equipmentBytes[ 2], FullEquipType.Head, name: _nameHead ), - 1 => EquipItem.FromIds(_itemIds[ 1], _iconIds[ 1], (SetId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (WeaponType)0, _equipmentBytes[ 6], FullEquipType.Body, name: _nameBody ), - 2 => EquipItem.FromIds(_itemIds[ 2], _iconIds[ 2], (SetId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (WeaponType)0, _equipmentBytes[10], FullEquipType.Hands, name: _nameHands ), - 3 => EquipItem.FromIds(_itemIds[ 3], _iconIds[ 3], (SetId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (WeaponType)0, _equipmentBytes[14], FullEquipType.Legs, name: _nameLegs ), - 4 => EquipItem.FromIds(_itemIds[ 4], _iconIds[ 4], (SetId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (WeaponType)0, _equipmentBytes[18], FullEquipType.Feet, name: _nameFeet ), - 5 => EquipItem.FromIds(_itemIds[ 5], _iconIds[ 5], (SetId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (WeaponType)0, _equipmentBytes[22], FullEquipType.Ears, name: _nameEars ), - 6 => EquipItem.FromIds(_itemIds[ 6], _iconIds[ 6], (SetId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (WeaponType)0, _equipmentBytes[26], FullEquipType.Neck, name: _nameNeck ), - 7 => EquipItem.FromIds(_itemIds[ 7], _iconIds[ 7], (SetId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (WeaponType)0, _equipmentBytes[30], FullEquipType.Wrists, name: _nameWrists ), - 8 => EquipItem.FromIds(_itemIds[ 8], _iconIds[ 8], (SetId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (WeaponType)0, _equipmentBytes[34], FullEquipType.Finger, name: _nameRFinger ), - 9 => EquipItem.FromIds(_itemIds[ 9], _iconIds[ 9], (SetId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (WeaponType)0, _equipmentBytes[38], FullEquipType.Finger, name: _nameLFinger ), - 10 => EquipItem.FromIds(_itemIds[10], _iconIds[10], (SetId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, _equipmentBytes[42], _typeMainhand, name: _nameMainhand), - 11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], (SetId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, _equipmentBytes[46], _typeOffhand, name: _nameOffhand ), + 0 => EquipItem.FromIds((ItemId)_itemIds[ 0], (IconId)_iconIds[ 0], (PrimaryId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 2], FullEquipType.Head, name: _nameHead ), + 1 => EquipItem.FromIds((ItemId)_itemIds[ 1], (IconId)_iconIds[ 1], (PrimaryId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 6], FullEquipType.Body, name: _nameBody ), + 2 => EquipItem.FromIds((ItemId)_itemIds[ 2], (IconId)_iconIds[ 2], (PrimaryId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[10], FullEquipType.Hands, name: _nameHands ), + 3 => EquipItem.FromIds((ItemId)_itemIds[ 3], (IconId)_iconIds[ 3], (PrimaryId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[14], FullEquipType.Legs, name: _nameLegs ), + 4 => EquipItem.FromIds((ItemId)_itemIds[ 4], (IconId)_iconIds[ 4], (PrimaryId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[18], FullEquipType.Feet, name: _nameFeet ), + 5 => EquipItem.FromIds((ItemId)_itemIds[ 5], (IconId)_iconIds[ 5], (PrimaryId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[22], FullEquipType.Ears, name: _nameEars ), + 6 => EquipItem.FromIds((ItemId)_itemIds[ 6], (IconId)_iconIds[ 6], (PrimaryId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[26], FullEquipType.Neck, name: _nameNeck ), + 7 => EquipItem.FromIds((ItemId)_itemIds[ 7], (IconId)_iconIds[ 7], (PrimaryId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[30], FullEquipType.Wrists, name: _nameWrists ), + 8 => EquipItem.FromIds((ItemId)_itemIds[ 8], (IconId)_iconIds[ 8], (PrimaryId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[34], FullEquipType.Finger, name: _nameRFinger ), + 9 => EquipItem.FromIds((ItemId)_itemIds[ 9], (IconId)_iconIds[ 9], (PrimaryId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[38], FullEquipType.Finger, name: _nameLFinger ), + 10 => EquipItem.FromIds((ItemId)_itemIds[10], (IconId)_iconIds[10], (PrimaryId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, (Variant)_equipmentBytes[42], _typeMainhand, name: _nameMainhand), + 11 => EquipItem.FromIds((ItemId)_itemIds[11], (IconId)_iconIds[11], (PrimaryId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, (Variant)_equipmentBytes[46], _typeOffhand, name: _nameOffhand ), _ => new EquipItem(), // @formatter:on }; @@ -129,8 +128,8 @@ public unsafe struct DesignData _itemIds[index] = item.ItemId.Id; _iconIds[index] = item.IconId.Id; - _equipmentBytes[4 * index + 0] = (byte)item.ModelId.Id; - _equipmentBytes[4 * index + 1] = (byte)(item.ModelId.Id >> 8); + _equipmentBytes[4 * index + 0] = (byte)item.PrimaryId.Id; + _equipmentBytes[4 * index + 1] = (byte)(item.PrimaryId.Id >> 8); _equipmentBytes[4 * index + 2] = item.Variant.Id; switch (index) { @@ -148,12 +147,12 @@ public unsafe struct DesignData // @formatter:on case 10: _nameMainhand = item.Name; - _secondaryMainhand = item.WeaponType; + _secondaryMainhand = item.SecondaryId; _typeMainhand = item.Type; return true; case 11: _nameOffhand = item.Name; - _secondaryOffhand = item.WeaponType; + _secondaryOffhand = item.SecondaryId; _typeOffhand = item.Type; return true; } @@ -294,7 +293,7 @@ public unsafe struct DesignData public readonly byte[] GetCustomizeBytes() { - var ret = new byte[CustomizeData.Size]; + var ret = new byte[CustomizeArray.Size]; fixed (byte* retPtr = ret, inPtr = Customize.Data.Data) { MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length); diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 392301f..6b8578e 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -12,7 +12,7 @@ using Glamourer.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index a31a696..00b94bb 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -8,6 +8,7 @@ using Glamourer.State; using Microsoft.Extensions.DependencyInjection; using OtterGui.Classes; using OtterGui.Log; +using OtterGui.Services; namespace Glamourer; @@ -25,21 +26,21 @@ public class Glamourer : IDalamudPlugin public static readonly Logger Log = new(); public static MessageService Messager { get; private set; } = null!; - private readonly ServiceProvider _services; + private readonly ServiceManager _services; public Glamourer(DalamudPluginInterface pluginInterface) { try { - _services = ServiceManager.CreateProvider(pluginInterface, Log); - Messager = _services.GetRequiredService(); - _services.GetRequiredService(); - _services.GetRequiredService(); - _services.GetRequiredService(); - _services.GetRequiredService(); // Initialize State Listener. - _services.GetRequiredService(); // initialize ui. - _services.GetRequiredService(); // initialize commands. - _services.GetRequiredService(); // initialize IPC. + _services = ServiceManagerA.CreateProvider(pluginInterface, Log); + Messager = _services.GetService(); + _services.GetService(); + _services.GetService(); + _services.GetService(); + _services.GetService(); // Initialize State Listener. + _services.GetService(); // initialize ui. + _services.GetService(); // initialize commands. + _services.GetService(); // initialize IPC. Log.Information($"Glamourer v{Version} loaded successfully."); } catch diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index e4c15d4..f435ba8 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,6 +1,5 @@ using System; using System.Numerics; -using Dalamud.Interface.Utility; using Glamourer.Customization; using ImGuiNET; using OtterGui; @@ -28,7 +27,7 @@ public partial class CustomizationDrawer npc = true; } - var icon = _service.AwaitedService.GetIcon(custom!.Value.IconId); + var icon = _service.Service.GetIcon(custom!.Value.IconId); using (var disabled = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) { if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) @@ -68,7 +67,7 @@ public partial class CustomizationDrawer for (var i = 0; i < _currentCount; ++i) { var custom = _set.Data(_currentIndex, i, _customize.Face); - var icon = _service.AwaitedService.GetIcon(custom.IconId); + var icon = _service.Service.GetIcon(custom.IconId); using (var _ = ImRaii.Group()) { using var frameColor = ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed, current == i); @@ -179,8 +178,8 @@ public partial class CustomizationDrawer var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero; var feature = _set.Data(featureIdx, 0, face); var icon = featureIdx == CustomizeIndex.LegacyTattoo - ? _legacyTattoo ?? _service.AwaitedService.GetIcon(feature.IconId) - : _service.AwaitedService.GetIcon(feature.IconId); + ? _legacyTattoo ?? _service.Service.GetIcon(feature.IconId) + : _service.Service.GetIcon(feature.IconId); if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, Vector4.Zero, enabled ? Vector4.One : _redTint)) { diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index fa725f5..a288065 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -10,7 +10,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; -using CustomizeData = Penumbra.GameData.Structs.CustomizeData; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; @@ -120,7 +120,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio return DrawArtisan(); DrawRaceGenderSelector(); - _set = _service.AwaitedService.GetList(_customize.Clan, _customize.Gender); + _set = _service.Service.GetList(_customize.Clan, _customize.Gender); foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage]) PercentageSelector(id); @@ -153,7 +153,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio private unsafe bool DrawArtisan() { - for (var i = 0; i < CustomizeData.Size; ++i) + for (var i = 0; i < CustomizeArray.Size; ++i) { using var id = ImRaii.PushId(i); int value = _customize.Data.Data[i]; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index e2b44de..cc94ed0 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -12,7 +12,7 @@ using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -24,7 +24,7 @@ public class EquipmentDrawer private readonly ItemManager _items; private readonly GlamourerColorCombo _stainCombo; - private readonly StainData _stainData; + private readonly DictStains _stainData; private readonly ItemCombo[] _itemCombo; private readonly Dictionary _weaponCombo; private readonly CodeService _codes; @@ -66,8 +66,8 @@ public class EquipmentDrawer _iconSize = new Vector2(2 * ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y); _comboLength = DefaultWidth * ImGuiHelpers.GlobalScale; if (_requiredComboWidthUnscaled == 0) - _requiredComboWidthUnscaled = _items.ItemService.AwaitedService.AllItems(true) - .Concat(_items.ItemService.AwaitedService.AllItems(false)) + _requiredComboWidthUnscaled = _items.ItemData.AllItems(true) + .Concat(_items.ItemData.AllItems(false)) .Max(i => ImGui.CalcTextSize($"{i.Item2.Name} ({i.Item2.ModelString})").X) / ImGuiHelpers.GlobalScale; @@ -102,7 +102,7 @@ public class EquipmentDrawer public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) { - if (mainhand.CurrentItem.ModelId.Id == 0) + if (mainhand.CurrentItem.PrimaryId.Id == 0) return; if (_config.HideApplyCheckmarks) @@ -202,24 +202,24 @@ public class EquipmentDrawer void DrawWeapon(in EquipDrawData current) { - int setId = current.CurrentItem.ModelId.Id; - int type = current.CurrentItem.WeaponType.Id; + int setId = current.CurrentItem.PrimaryId.Id; + int type = current.CurrentItem.SecondaryId.Id; int variant = current.CurrentItem.Variant.Id; ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt("##setId", ref setId, 0, 0)) { - var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); - if (newSetId.Id != current.CurrentItem.ModelId.Id) - current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.WeaponType, current.CurrentItem.Variant)); + var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue); + if (newSetId.Id != current.CurrentItem.PrimaryId.Id) + current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant)); } ImGui.SameLine(); ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt("##type", ref type, 0, 0)) { - var newType = (WeaponType)Math.Clamp(type, 0, ushort.MaxValue); - if (newType.Id != current.CurrentItem.WeaponType.Id) - current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.ModelId, newType, current.CurrentItem.Variant)); + var newType = (SecondaryId)Math.Clamp(type, 0, ushort.MaxValue); + if (newType.Id != current.CurrentItem.SecondaryId.Id) + current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant)); } ImGui.SameLine(); @@ -228,7 +228,8 @@ public class EquipmentDrawer { var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue); if (newVariant.Id != current.CurrentItem.Variant.Id) - current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.ModelId, current.CurrentItem.WeaponType, newVariant)); + current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, current.CurrentItem.SecondaryId, + newVariant)); } } } @@ -249,13 +250,13 @@ public class EquipmentDrawer /// Draw an input for armor that can set arbitrary values instead of choosing items. private void DrawArmorArtisan(EquipDrawData data) { - int setId = data.CurrentItem.ModelId.Id; + int setId = data.CurrentItem.PrimaryId.Id; int variant = data.CurrentItem.Variant.Id; ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt("##setId", ref setId, 0, 0)) { - var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); - if (newSetId.Id != data.CurrentItem.ModelId.Id) + var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue); + if (newSetId.Id != data.CurrentItem.PrimaryId.Id) data.ItemSetter(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant)); } @@ -265,7 +266,7 @@ public class EquipmentDrawer { var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue); if (newVariant != data.CurrentItem.Variant) - data.ItemSetter(_items.Identify(data.Slot, data.CurrentItem.ModelId, newVariant)); + data.ItemSetter(_items.Identify(data.Slot, data.CurrentItem.PrimaryId, newVariant)); } } @@ -454,7 +455,7 @@ public class EquipmentDrawer else if (combo.CustomVariant.Id > 0) data.ItemSetter(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); - if (!data.Locked && data.CurrentItem.ModelId.Id != 0) + if (!data.Locked && data.CurrentItem.PrimaryId.Id != 0) { if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) data.ItemSetter(ItemManager.NothingItem(data.Slot)); diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index 0bd55dd..1073ac6 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -8,12 +8,12 @@ using Dalamud.Interface.Utility.Raii; using Glamourer.Unlocks; using ImGuiNET; using OtterGui.Widgets; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public sealed class GlamourerColorCombo(float _comboWidth, StainData _stains, FavoriteManager _favorites) +public sealed class GlamourerColorCombo(float _comboWidth, DictStains _stains, FavoriteManager _favorites) : FilterComboColors(_comboWidth, CreateFunc(_stains, _favorites), Glamourer.Log) { protected override bool DrawSelectable(int globalIdx, bool selected) @@ -40,8 +40,9 @@ public sealed class GlamourerColorCombo(float _comboWidth, StainData _stains, Fa return base.DrawSelectable(globalIdx, selected); } - private static Func>> CreateFunc(StainData stains, + private static Func>> CreateFunc(DictStains stains, FavoriteManager favorites) - => () => stains.Data.Select(kvp => (kvp, favorites.Contains((StainId)kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp) - .Prepend(new KeyValuePair(0, ("None", 0, false))).ToList(); + => () => stains.Select(kvp => (kvp, favorites.Contains(kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp) + .Prepend(new KeyValuePair(Stain.None.RowIndex, Stain.None)).Select(kvp + => new KeyValuePair(kvp.Key.Id, (kvp.Value.Name, kvp.Value.RgbaColor, kvp.Value.Gloss))).ToList(); } diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 4ddbbaa..9e1790a 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -23,8 +23,8 @@ public sealed class ItemCombo : FilterComboCache private ItemId _currentItem; private float _innerWidth; - public SetId CustomSetId { get; private set; } - public Variant CustomVariant { get; private set; } + public PrimaryId CustomSetId { get; private set; } + public Variant CustomVariant { get; private set; } public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites) : base(() => GetItems(favorites, items, slot), log) @@ -83,7 +83,7 @@ public sealed class ItemCombo : FilterComboCache } protected override bool IsVisible(int globalIndex, LowerString filter) - => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Id.ToString()); + => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString()); protected override string ToString(EquipItem obj) => obj.Name; @@ -111,7 +111,7 @@ public sealed class ItemCombo : FilterComboCache private static IReadOnlyList GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot) { var nothing = ItemManager.NothingItem(slot); - if (!items.ItemService.AwaitedService.TryGetValue(slot.ToEquipType(), out var list)) + if (!items.ItemData.ByType.TryGetValue(slot.ToEquipType(), out var list)) return new[] { nothing, diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index 5a1792e..027a211 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -60,12 +60,12 @@ public sealed class WeaponCombo : FilterComboCache var ret = ImGui.Selectable(name, selected); ImGui.SameLine(); using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080); - ImGuiUtil.RightAlign($"({obj.ModelId.Id}-{obj.WeaponType.Id}-{obj.Variant})"); + ImGuiUtil.RightAlign($"({obj.PrimaryId.Id}-{obj.SecondaryId.Id}-{obj.Variant})"); return ret; } protected override bool IsVisible(int globalIndex, LowerString filter) - => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Id.ToString()); + => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString()); protected override string ToString(EquipItem obj) => obj.Name; @@ -80,14 +80,14 @@ public sealed class WeaponCombo : FilterComboCache var enumerable = Array.Empty().AsEnumerable(); foreach (var t in Enum.GetValues().Where(e => e.ToSlot() is EquipSlot.MainHand)) { - if (items.ItemService.AwaitedService.TryGetValue(t, out var l)) + if (items.ItemData.ByType.TryGetValue(t, out var l)) enumerable = enumerable.Concat(l); } return enumerable.OrderBy(e => e.Name).ToList(); } - if (!items.ItemService.AwaitedService.TryGetValue(type, out var list)) + if (!items.ItemData.ByType.TryGetValue(type, out var list)) return Array.Empty(); if (type.AllowsNothing()) diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index d4e19b2..a219134 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -8,6 +8,7 @@ using Glamourer.Events; using Glamourer.Gui.Tabs; using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Gui.Tabs.AutomationTab; +using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.UnlocksTab; using ImGuiNET; @@ -65,8 +66,8 @@ public class MainWindow : Window, IDisposable Messages = messages; _quickBar = quickBar; _config = config; - _tabs = new ITab[] - { + _tabs = + [ settings, actors, designs, @@ -74,7 +75,7 @@ public class MainWindow : Window, IDisposable unlocks, messages, debugTab, - }; + ]; _event.Subscribe(OnTabSelected, TabSelected.Priority.MainWindow); IsOpen = _config.OpenWindowAtStart; } diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index 8eb763e..f244fd5 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -166,7 +166,7 @@ public class PenumbraChangedItemTooltip : IDisposable { case ChangedItemType.ItemOffhand: case ChangedItemType.Item: - if (!_items.ItemService.AwaitedService.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) + if (!_items.ItemData.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return; CreateTooltip(item, "[Glamourer] ", false); @@ -177,7 +177,7 @@ public class PenumbraChangedItemTooltip : IDisposable private bool CanApplyWeapon(EquipSlot slot, EquipItem item) { var main = _objects.Player.GetMainhand(); - var mainItem = _items.Identify(slot, main.Set, main.Type, main.Variant); + var mainItem = _items.Identify(slot, main.X, main.Y, main.Variant); if (slot == EquipSlot.MainHand) return item.Type == mainItem.Type; @@ -197,7 +197,7 @@ public class PenumbraChangedItemTooltip : IDisposable if (!Player(out var state)) return; - if (!_items.ItemService.AwaitedService.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) + if (!_items.ItemData.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return; ApplyItem(state, item); diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index a294b08..15d3725 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -13,7 +13,6 @@ using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop; using Glamourer.Interop.Structs; -using Glamourer.Services; using Glamourer.State; using Glamourer.Structs; using ImGuiNET; @@ -21,14 +20,24 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using Penumbra.GameData.Actors; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorPanel(ActorSelector _selector, StateManager _stateManager, CustomizationDrawer _customizationDrawer, - EquipmentDrawer _equipmentDrawer, IdentifierService _identification, AutoDesignApplier _autoDesignApplier, - Configuration _config, DesignConverter _converter, ObjectManager _objects, DesignManager _designManager, ImportService _importService, - ICondition _conditions) +public class ActorPanel( + ActorSelector _selector, + StateManager _stateManager, + CustomizationDrawer _customizationDrawer, + EquipmentDrawer _equipmentDrawer, + AutoDesignApplier _autoDesignApplier, + Configuration _config, + DesignConverter _converter, + ObjectManager _objects, + DesignManager _designManager, + ImportService _importService, + ICondition _conditions, + DictModelChara _modelChara) { private ActorIdentifier _identifier; private string _actorName = string.Empty; @@ -154,7 +163,7 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus } var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); - var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand); + var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand); _equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose()); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); @@ -187,7 +196,7 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus private void DrawMonsterPanel() { - var names = _identification.AwaitedService.ModelCharaNames(_state!.ModelData.ModelId); + var names = _modelChara[_state!.ModelData.ModelId]; var turnHuman = ImGui.Button("Turn Human"); ImGui.Separator(); using (var box = ImRaii.ListBox("##MonsterList", @@ -295,9 +304,9 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus private void SaveDesignOpen() { ImGui.OpenPopup("Save as Design"); - _newName = _state!.Identifier.ToName(); + _newName = _state!.Identifier.ToName(); var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); - _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest); + _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest); } private void SaveDesignDrawPopup() diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index b8d1ba4..934a19d 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -17,11 +17,11 @@ public class ActorSelector { private readonly EphemeralConfig _config; private readonly ObjectManager _objects; - private readonly ActorService _actors; + private readonly ActorManager _actors; private ActorIdentifier _identifier = ActorIdentifier.Invalid; - public ActorSelector(ObjectManager objects, ActorService actors, EphemeralConfig config) + public ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config) { _objects = objects; _actors = actors; @@ -93,7 +93,7 @@ public class ActorSelector if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth , "Select the local player character.", !_objects.Player, true)) - _identifier = _objects.Player.GetIdentifier(_actors.AwaitedService); + _identifier = _objects.Player.GetIdentifier(_actors); ImGui.SameLine(); var (id, data) = _objects.TargetData; diff --git a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs index da4fda1..c3781fc 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs @@ -3,23 +3,23 @@ using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Utility; -using Glamourer.Services; using ImGuiNET; using OtterGui.Custom; using OtterGui.Log; using OtterGui.Widgets; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; namespace Glamourer.Gui.Tabs.AutomationTab; -public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)> +public sealed class HumanNpcCombo( + string label, + DictModelChara modelCharaDict, + DictBNpcNames bNpcNames, + DictBNpc bNpcs, + HumanModelList humans, + Logger log) + : FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)>(() => CreateList(modelCharaDict, bNpcNames, bNpcs, humans), log) { - private readonly string _label; - - public HumanNpcCombo(string label, IdentifierService service, HumanModelList humans, Logger log) - : base(() => CreateList(service, humans), log) - => _label = label; - protected override string ToString((string Name, ObjectKind Kind, uint[] Ids) obj) => obj.Name; @@ -36,7 +36,8 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki } public bool Draw(float width) - => Draw(_label, CurrentSelection.Name.IsNullOrEmpty() ? "Human Non-Player-Characters..." : CurrentSelection.Name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); + => Draw(label, CurrentSelection.Name.IsNullOrEmpty() ? "Human Non-Player-Characters..." : CurrentSelection.Name, string.Empty, width, + ImGui.GetTextLineHeightWithSpacing()); /// Compare strings in a way that letters and numbers are sorted before any special symbols. @@ -61,15 +62,16 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki } } - private static IReadOnlyList<(string Name, ObjectKind Kind, uint[] Ids)> CreateList(IdentifierService service, HumanModelList humans) + private static IReadOnlyList<(string Name, ObjectKind Kind, uint[] Ids)> CreateList(DictModelChara modelCharaDict, DictBNpcNames bNpcNames, + DictBNpc bNpcs, HumanModelList humans) { var ret = new List<(string Name, ObjectKind Kind, uint Id)>(1024); - for (var modelChara = 0u; modelChara < service.AwaitedService.NumModelChara; ++modelChara) + for (var modelChara = 0u; modelChara < modelCharaDict.Count; ++modelChara) { if (!humans.IsHuman(modelChara)) continue; - var list = service.AwaitedService.ModelCharaNames(modelChara); + var list = modelCharaDict[modelChara]; if (list.Count == 0) continue; @@ -78,8 +80,8 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki switch (kind) { case ObjectKind.BattleNpc: - var nameIds = service.AwaitedService.GetBnpcNames(id); - ret.AddRange(nameIds.Select(nameId => (service.AwaitedService.Name(ObjectKind.BattleNpc, nameId), kind, nameId.Id))); + var nameIds = bNpcNames[id]; + ret.AddRange(nameIds.Select(nameId => (bNpcs[nameId], kind, nameId.Id))); break; case ObjectKind.EventNpc: ret.Add((name, kind, id)); diff --git a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs index e88c55b..8498bc1 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs @@ -1,9 +1,9 @@ using Dalamud.Game.ClientState.Objects.Enums; -using Glamourer.Services; using ImGuiNET; -using OtterGui.Custom; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Gui; +using Penumbra.GameData.Structs; using Penumbra.String; namespace Glamourer.Gui.Tabs.AutomationTab; @@ -12,7 +12,7 @@ public class IdentifierDrawer { private readonly WorldCombo _worldCombo; private readonly HumanNpcCombo _humanNpcCombo; - private readonly ActorService _actors; + private readonly ActorManager _actors; private string _characterName = string.Empty; @@ -21,11 +21,12 @@ public class IdentifierDrawer public ActorIdentifier RetainerIdentifier { get; private set; } = ActorIdentifier.Invalid; public ActorIdentifier MannequinIdentifier { get; private set; } = ActorIdentifier.Invalid; - public IdentifierDrawer(ActorService actors, IdentifierService identifier, HumanModelList humans) + public IdentifierDrawer(ActorManager actors, DictWorld dictWorld, DictModelChara dictModelChara, DictBNpcNames bNpcNames, DictBNpc bNpc, + HumanModelList humans) { _actors = actors; - _worldCombo = new WorldCombo(actors.AwaitedService.Data.Worlds, Glamourer.Log); - _humanNpcCombo = new HumanNpcCombo("##npcs", identifier, humans, Glamourer.Log); + _worldCombo = new WorldCombo(dictWorld, Glamourer.Log); + _humanNpcCombo = new HumanNpcCombo("##npcs", dictModelChara, bNpcNames, bNpc, humans, Glamourer.Log); } public void DrawName(float width) @@ -63,13 +64,13 @@ public class IdentifierDrawer { if (ByteString.FromString(_characterName, out var byteName)) { - PlayerIdentifier = _actors.AwaitedService.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key); - RetainerIdentifier = _actors.AwaitedService.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell); - MannequinIdentifier = _actors.AwaitedService.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin); + PlayerIdentifier = _actors.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key); + RetainerIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell); + MannequinIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin); } NpcIdentifier = _humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc - ? _actors.AwaitedService.CreateNpc(_humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0]) + ? _actors.CreateNpc(_humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0]) : ActorIdentifier.Invalid; } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 6b0e301..3eba0cd 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -333,7 +333,7 @@ public class SetPanel if (!design.Design.DesignData.IsHuman) sb.AppendLine("The base model id can not be changed automatically to something non-human."); - var set = _customizations.AwaitedService.GetList(customize.Clan, customize.Gender); + var set = _customizations.Service.GetList(customize.Clan, customize.Gender); foreach (var type in CustomizationExtensions.All) { var flag = type.ToFlag(); diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 5ca3adf..c1544b2 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -7,11 +7,11 @@ using Dalamud.Interface.Utility; using Glamourer.Automation; using Glamourer.Events; using Glamourer.Interop; -using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using Penumbra.GameData.Actors; using Penumbra.String; using ImGuiClip = OtterGui.ImGuiClip; @@ -22,9 +22,9 @@ public class SetSelector : IDisposable private readonly Configuration _config; private readonly AutoDesignManager _manager; private readonly AutomationChanged _event; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly ObjectManager _objects; - private readonly List<(AutoDesignSet, int)> _list = new(); + private readonly List<(AutoDesignSet, int)> _list = []; public AutoDesignSet? Selection { get; private set; } public int SelectionIndex { get; private set; } = -1; @@ -44,7 +44,7 @@ public class SetSelector : IDisposable internal int _dragDesignIndex = -1; - public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorService actors, ObjectManager objects) + public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorManager actors, ObjectManager objects) { _manager = manager; _event = @event; @@ -289,9 +289,9 @@ public class SetSelector : IDisposable private void NewSetButton(Vector2 size) { - var id = _actors.AwaitedService.GetCurrentPlayer(); + var id = _actors.GetCurrentPlayer(); if (!id.IsValid) - id = _actors.AwaitedService.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue); + id = _actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, $"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true)) _manager.AddDesignSet("New Design", id); diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 71df7b9..298fe0e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -69,7 +69,7 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM static string ItemString(in DesignData data, EquipSlot slot) { var item = data.Item(slot); - return $"{item.Name} ({item.ModelId.Id}{(item.WeaponType != 0 ? $"-{item.WeaponType.Id}" : string.Empty)}-{item.Variant})"; + return $"{item.Name} ({item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; } PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]); diff --git a/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs index 42fc52b..976b5c4 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs @@ -1,23 +1,23 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Numerics; using Dalamud.Interface.Utility; -using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Actors; +using Penumbra.GameData.DataContainers; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class ActorServicePanel(ActorService _actors, ItemManager _items) : IDebugTabTree +public class ActorManagerPanel(ActorManager _actors, DictBNpcNames _bNpcNames) : IDebugTabTree { public string Label => "Actor Service"; public bool Disabled - => !_actors.Valid; + => !_actors.Awaiter.IsCompletedSuccessfully; private string _bnpcFilter = string.Empty; private string _enpcFilter = string.Empty; @@ -29,11 +29,11 @@ public class ActorServicePanel(ActorService _actors, ItemManager _items) : IDebu public void Draw() { DrawBnpcTable(); - DebugTab.DrawNameTable("ENPCs", ref _enpcFilter, _actors.AwaitedService.Data.ENpcs.Select(kvp => (kvp.Key, kvp.Value))); - DebugTab.DrawNameTable("Companions", ref _companionFilter, _actors.AwaitedService.Data.Companions.Select(kvp => (kvp.Key, kvp.Value))); - DebugTab.DrawNameTable("Mounts", ref _mountFilter, _actors.AwaitedService.Data.Mounts.Select(kvp => (kvp.Key, kvp.Value))); - DebugTab.DrawNameTable("Ornaments", ref _ornamentFilter, _actors.AwaitedService.Data.Ornaments.Select(kvp => (kvp.Key, kvp.Value))); - DebugTab.DrawNameTable("Worlds", ref _worldFilter, _actors.AwaitedService.Data.Worlds.Select(kvp => ((uint)kvp.Key, kvp.Value))); + DebugTab.DrawNameTable("ENPCs", ref _enpcFilter, _actors.Data.ENpcs.Select(kvp => (kvp.Key.Id, kvp.Value))); + DebugTab.DrawNameTable("Companions", ref _companionFilter, _actors.Data.Companions.Select(kvp => (kvp.Key.Id, kvp.Value))); + DebugTab.DrawNameTable("Mounts", ref _mountFilter, _actors.Data.Mounts.Select(kvp => (kvp.Key.Id, kvp.Value))); + DebugTab.DrawNameTable("Ornaments", ref _ornamentFilter, _actors.Data.Ornaments.Select(kvp => (kvp.Key.Id, kvp.Value))); + DebugTab.DrawNameTable("Worlds", ref _worldFilter, _actors.Data.Worlds.Select(kvp => ((uint)kvp.Key.Id, kvp.Value))); } private void DrawBnpcTable() @@ -58,15 +58,15 @@ public class ActorServicePanel(ActorService _actors, ItemManager _items) : IDebu ImGui.TableNextColumn(); var skips = ImGuiClip.GetNecessarySkips(height); ImGui.TableNextRow(); - var data = _actors.AwaitedService.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Key.ToString("D5"), kvp.Value)); + var data = _actors.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Key.Id.ToString("D5"), kvp.Value)); var remainder = ImGuiClip.FilteredClippedDraw(data, skips, - p => p.Item2.Contains(_bnpcFilter) || p.Item3.Contains(_bnpcFilter, StringComparison.OrdinalIgnoreCase), + p => p.Item2.Contains(_bnpcFilter) || p.Value.Contains(_bnpcFilter, StringComparison.OrdinalIgnoreCase), p => { ImGuiUtil.DrawTableColumn(p.Item2); - ImGuiUtil.DrawTableColumn(p.Item3); - var bnpcs = _items.IdentifierService.AwaitedService.GetBnpcsFromName(p.Item1); - ImGuiUtil.DrawTableColumn(string.Join(", ", bnpcs.Select(b => b.Id.ToString()))); + ImGuiUtil.DrawTableColumn(p.Value); + var bNpcs = _bNpcNames.GetBNpcsFromName(p.Key.BNpcNameId); + ImGuiUtil.DrawTableColumn(string.Join(", ", bNpcs.Select(b => b.Id.ToString()))); }); ImGuiClip.DrawEndDummy(remainder, height); } diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index 6088651..9906387 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -13,15 +13,15 @@ public class CustomizationServicePanel(CustomizationService _customization) : ID => "Customization Service"; public bool Disabled - => !_customization.Valid; + => !_customization.Awaiter.IsCompletedSuccessfully; public void Draw() { - foreach (var clan in _customization.AwaitedService.Clans) + foreach (var clan in _customization.Service.Clans) { - foreach (var gender in _customization.AwaitedService.Genders) + foreach (var gender in _customization.Service.Genders) { - var set = _customization.AwaitedService.GetList(clan, gender); + var set = _customization.Service.GetList(clan, gender); DrawCustomizationInfo(set); DrawNpcCustomizationInfo(set); } diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index e010b2a..bd0b98f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using OtterGui.Raii; +using OtterGui.Services; namespace Glamourer.Gui.Tabs.DebugTab; -public interface IDebugTabTree +public interface IDebugTabTree : IService { public string Label { get; } public void Draw(); @@ -54,7 +55,7 @@ public class DebugTabHeader(string label, params IDebugTabTree[] subTrees) "Game Data", provider.GetRequiredService(), provider.GetRequiredService(), - provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index 481006e..03ba048 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -8,7 +8,7 @@ using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs index b17f35a..9a0503e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs @@ -3,18 +3,19 @@ using Dalamud.Interface.Utility; using Glamourer.Services; using ImGuiNET; using OtterGui; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; -public class IdentifierPanel(ItemManager _items) : IDebugTabTree +public class IdentifierPanel(ItemManager _items, GamePathParser _gamePathParser) : IDebugTabTree { public string Label => "Identifier Service"; public bool Disabled - => !_items.IdentifierService.Valid; + => !_items.ObjectIdentification.Awaiter.IsCompletedSuccessfully; private string _gamePath = string.Empty; private int _setId; @@ -33,10 +34,10 @@ public class IdentifierPanel(ItemManager _items) : IDebugTabTree ImGui.SameLine(); ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); ImGui.InputTextWithHint("##gamePath", "Enter game path...", ref _gamePath, 256); - var fileInfo = _items.IdentifierService.AwaitedService.GamePathParser.GetFileInfo(_gamePath); + var fileInfo = _gamePathParser.GetFileInfo(_gamePath); ImGui.TextUnformatted( $"{fileInfo.ObjectType} {fileInfo.EquipSlot} {fileInfo.PrimaryId} {fileInfo.SecondaryId} {fileInfo.Variant} {fileInfo.BodySlot} {fileInfo.CustomizationType}"); - Text(string.Join("\n", _items.IdentifierService.AwaitedService.Identify(_gamePath).Keys)); + Text(string.Join("\n", _items.ObjectIdentification.Identify(_gamePath).Keys)); ImGui.Separator(); ImGui.AlignTextToFramePadding(); @@ -46,16 +47,16 @@ public class IdentifierPanel(ItemManager _items) : IDebugTabTree foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var identified = _items.Identify(slot, (SetId)_setId, (Variant)_variant); + var identified = _items.Identify(slot, (PrimaryId)_setId, 0, (Variant)_variant); Text(identified.Name); ImGuiUtil.HoverTooltip(string.Join("\n", - _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (Variant)_variant, slot) + _items.ObjectIdentification.Identify((PrimaryId)_setId, 0, (Variant)_variant, slot) .Select(i => $"{i.Name} {i.Id} {i.ItemId} {i.IconId}"))); } - var weapon = _items.Identify(EquipSlot.MainHand, (SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant); + var weapon = _items.Identify(EquipSlot.MainHand, (PrimaryId)_setId, (SecondaryId)_secondaryId, (Variant)_variant); Text(weapon.Name); ImGuiUtil.HoverTooltip(string.Join("\n", - _items.IdentifierService.AwaitedService.Identify((SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant, EquipSlot.MainHand))); + _items.ObjectIdentification.Identify((PrimaryId)_setId, (SecondaryId)_secondaryId, (Variant)_variant, EquipSlot.MainHand))); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs index 7ef34b6..9fbc931 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs @@ -13,7 +13,7 @@ public class ItemManagerPanel(ItemManager _items) : IDebugTabTree => "Item Manager"; public bool Disabled - => !_items.ItemService.Valid; + => !_items.ItemData.Awaiter.IsCompletedSuccessfully; private string _itemFilter = string.Empty; @@ -22,18 +22,18 @@ public class ItemManagerPanel(ItemManager _items) : IDebugTabTree ImRaii.TreeNode($"Default Sword: {_items.DefaultSword.Name} ({_items.DefaultSword.ItemId}) ({_items.DefaultSword.Weapon()})", ImGuiTreeNodeFlags.Leaf).Dispose(); DebugTab.DrawNameTable("All Items (Main)", ref _itemFilter, - _items.ItemService.AwaitedService.AllItems(true).Select(p => (p.Item1.Id, - $"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) + _items.ItemData.AllItems(true).Select(p => (p.Item1.Id, + $"{p.Item2.Name} ({(p.Item2.SecondaryId == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) .OrderBy(p => p.Item1)); DebugTab.DrawNameTable("All Items (Off)", ref _itemFilter, - _items.ItemService.AwaitedService.AllItems(false).Select(p => (p.Item1.Id, - $"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) + _items.ItemData.AllItems(false).Select(p => (p.Item1.Id, + $"{p.Item2.Name} ({(p.Item2.SecondaryId == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) .OrderBy(p => p.Item1)); foreach (var type in Enum.GetValues().Skip(1)) { DebugTab.DrawNameTable(type.ToName(), ref _itemFilter, - _items.ItemService.AwaitedService[type] - .Select(p => (p.ItemId.Id, $"{p.Name} ({(p.WeaponType == 0 ? p.Armor().ToString() : p.Weapon().ToString())})"))); + _items.ItemData.ByType[type] + .Select(p => (p.ItemId.Id, $"{p.Name} ({(p.SecondaryId.Id == 0 ? p.Armor().ToString() : p.Weapon().ToString())})"))); } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs index a218d96..73194c4 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs @@ -39,7 +39,7 @@ public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) var remainder = ImGuiClip.ClippedDraw(_itemUnlocks, skips, t => { ImGuiUtil.DrawTableColumn(t.Key.ToString()); - if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) + if (_items.ItemData.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) { ImGuiUtil.DrawTableColumn(equip.Name); ImGuiUtil.DrawTableColumn(equip.Type.ToName()); diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index 0b0526b..e71c69a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -199,7 +199,7 @@ public unsafe class ModelEvaluationPanel( if (ImGui.SmallButton("Change Piece")) _updateSlotService.UpdateArmor(model, slot, - new CharacterArmor((SetId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0)); + new CharacterArmor((PrimaryId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0)); ImGui.SameLine(); if (ImGui.SmallButton("Change Stain")) _updateSlotService.UpdateStain(model, slot, 5); @@ -213,11 +213,11 @@ public unsafe class ModelEvaluationPanel( { using var id = ImRaii.PushId("Customize"); var actorCustomize = new Customize(actor.IsCharacter - ? *(Penumbra.GameData.Structs.CustomizeData*)&actor.AsCharacter->DrawData.CustomizeData - : new Penumbra.GameData.Structs.CustomizeData()); + ? *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData + : new CustomizeArray()); var modelCustomize = new Customize(model.IsHuman - ? *(Penumbra.GameData.Structs.CustomizeData*)model.AsHuman->Customize.Data - : new Penumbra.GameData.Structs.CustomizeData()); + ? *(CustomizeArray*)model.AsHuman->Customize.Data + : new CustomizeArray()); foreach (var type in Enum.GetValues()) { using var id2 = ImRaii.PushId((int)type); diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 53af228..90fc0f5 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -55,7 +55,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM return; - void Draw(CustomizationNpcOptions.NpcData data) + void Draw(NpcData data) { using var id = ImRaii.PushId(idx++); var disabled = !_state.GetOrCreate(_objectManager.Player, out var state); diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs index cf9e443..b868140 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -3,14 +3,14 @@ using System.Globalization; using System.Linq; using System.Numerics; using Glamourer.Interop; -using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Actors; namespace Glamourer.Gui.Tabs.DebugTab; -public class ObjectManagerPanel(ObjectManager _objectManager, ActorService _actors) : IDebugTabTree +public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _actors) : IDebugTabTree { public string Label => "Object Manager"; @@ -33,7 +33,7 @@ public class ObjectManagerPanel(ObjectManager _objectManager, ActorService _acto ImGui.TableNextColumn(); ImGuiUtil.DrawTableColumn("World"); - ImGuiUtil.DrawTableColumn(_actors.Valid ? _actors.AwaitedService.Data.ToWorldName(_objectManager.World) : "Service Missing"); + ImGuiUtil.DrawTableColumn(_actors.Awaiter.IsCompletedSuccessfully ? _actors.Data.ToWorldName(_objectManager.World) : "Service Missing"); ImGuiUtil.DrawTableColumn(_objectManager.World.ToString()); ImGuiUtil.DrawTableColumn("Player Character"); diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 6352412..cf5b92a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -59,7 +59,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); ImGui.InputInt("##CutsceneIndex", ref _gameObjectIndex, 0, 0); ImGuiUtil.DrawTableColumn(_penumbra.Available - ? _penumbra.CutsceneParent(_gameObjectIndex).ToString() + ? _penumbra.CutsceneParent((ushort) _gameObjectIndex).ToString() : "Penumbra Unavailable"); ImGuiUtil.DrawTableColumn("Redraw Object"); diff --git a/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs b/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs index 7e1e600..0f27eff 100644 --- a/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs @@ -32,7 +32,7 @@ public class RestrictedGearPanel(ItemManager _items) : IDebugTabTree foreach (var slot in EquipSlotExtensions.EqdpSlots) { var (replaced, model) = - _items.RestrictedGear.ResolveRestricted(new CharacterArmor((SetId)_setId, (Variant)_variant, 0), slot, race, gender); + _items.RestrictedGear.ResolveRestricted(new CharacterArmor((PrimaryId)_setId, (Variant)_variant, 0), slot, race, gender); if (replaced) ImGui.TextUnformatted($"{race.ToName()} - {gender} - {slot.ToName()} resolves to {model}."); } diff --git a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs index cfd00fa..e658c99 100644 --- a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs @@ -40,7 +40,7 @@ public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _i var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlockable, skips, t => { ImGuiUtil.DrawTableColumn(t.Key.ToString()); - if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) + if (_items.ItemData.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) { ImGuiUtil.DrawTableColumn(equip.Name); ImGuiUtil.DrawTableColumn(equip.Type.ToName()); diff --git a/Glamourer/Gui/Tabs/NpcCombo.cs b/Glamourer/Gui/Tabs/NpcCombo.cs index ab8d08d..91f0db0 100644 --- a/Glamourer/Gui/Tabs/NpcCombo.cs +++ b/Glamourer/Gui/Tabs/NpcCombo.cs @@ -1,36 +1,11 @@ -using System.Collections; -using System.Collections.Generic; -using System.Threading.Tasks; -using Dalamud.Plugin.Services; -using Glamourer.Customization; -using Glamourer.Services; +using Glamourer.Customization; using OtterGui.Widgets; -using Penumbra.GameData; namespace Glamourer.Gui.Tabs; -public class NpcCombo(ActorService actorManager, IdentifierService identifier, IDataManager data) - : FilterComboBase(new LazyList(actorManager, identifier, data), false, Glamourer.Log) +public class NpcCombo(NpcCustomizeSet npcCustomizeSet) + : FilterComboCache(npcCustomizeSet, Glamourer.Log) { - private class LazyList(ActorService actorManager, IdentifierService identifier, IDataManager data) - : IReadOnlyList - { - private readonly Task> _task - = Task.Run(() => CustomizationNpcOptions.CreateNpcData(actorManager.AwaitedService.Data.ENpcs, actorManager.AwaitedService.Data.BNpcs, identifier.AwaitedService, data)); - - public IEnumerator GetEnumerator() - => _task.Result.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - - public int Count - => _task.Result.Count; - - public CustomizationNpcOptions.NpcData this[int index] - => _task.Result[index]; - } - - protected override string ToString(CustomizationNpcOptions.NpcData obj) + protected override string ToString(NpcData obj) => obj.Name; } diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index f8395fe..1b0e27d 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -60,7 +60,7 @@ public class SettingsTab : ITab if (!child) return; - Checkbox("Enable Auto Designs", "Enable the application of designs associated to characters to be applied automatically.", + Checkbox("Enable Auto Designs", "Enable the application of designs associated to characters in the Automation tab to be applied automatically.", _config.EnableAutoDesigns, v => _config.EnableAutoDesigns = v); ImGui.NewLine(); ImGui.NewLine(); diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 2bd79d8..154e930 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -41,7 +41,7 @@ public class UnlockOverview foreach (var type in Enum.GetValues()) { - if (type.IsOffhandType() || !_items.ItemService.AwaitedService.TryGetValue(type, out var items) || items.Count == 0) + if (type.IsOffhandType() || !_items.ItemData.ByType.TryGetValue(type, out var items) || items.Count == 0) continue; if (ImGui.Selectable(type.ToName(), _selected1 == type)) @@ -52,11 +52,11 @@ public class UnlockOverview } } - foreach (var clan in _customizations.AwaitedService.Clans) + foreach (var clan in _customizations.Service.Clans) { - foreach (var gender in _customizations.AwaitedService.Genders) + foreach (var gender in _customizations.Service.Genders) { - if (_customizations.AwaitedService.GetList(clan, gender).HairStyles.Count == 0) + if (_customizations.Service.GetList(clan, gender).HairStyles.Count == 0) continue; if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint", @@ -107,7 +107,7 @@ public class UnlockOverview private void DrawCustomizations() { - var set = _customizations.AwaitedService.GetList(_selected2, _selected3); + var set = _customizations.Service.GetList(_selected2, _selected3); var spacing = IconSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -121,7 +121,7 @@ public class UnlockOverview continue; var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time); - var icon = _customizations.AwaitedService.GetIcon(customize.IconId); + var icon = _customizations.Service.GetIcon(customize.IconId); ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint); @@ -150,7 +150,7 @@ public class UnlockOverview private void DrawItems() { - if (!_items.ItemService.AwaitedService.TryGetValue(_selected1, out var items)) + if (!_items.ItemData.ByType.TryGetValue(_selected1, out var items)) return; var spacing = IconSpacing; @@ -160,6 +160,30 @@ public class UnlockOverview var numRows = (items.Count + iconsPerRow - 1) / iconsPerRow; var numVisibleRows = (int)(Math.Ceiling(ImGui.GetContentRegionAvail().Y / (iconSize.Y + spacing.Y)) + 0.5f) + 1; + var skips = ImGuiClip.GetNecessarySkips(iconSize.Y + spacing.Y); + var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, items.Count); + var counter = 0; + for (var idx = skips * iconsPerRow; idx < end; ++idx) + { + DrawItem(items[idx]); + if (counter != iconsPerRow - 1) + { + ImGui.SameLine(); + ++counter; + } + else + { + counter = 0; + } + } + + if (ImGui.GetCursorPosX() != 0) + ImGui.NewLine(); + var remainder = numRows - numVisibleRows - skips; + if (remainder > 0) + ImGuiClip.DrawEndDummy(remainder, iconSize.Y + spacing.Y); + return; + void DrawItem(EquipItem item) { var unlocked = _itemUnlocks.IsUnlocked(item.Id, out var time); @@ -189,7 +213,7 @@ public class UnlockOverview ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})"); if (item.Type.ValidOffhand().IsOffhandType()) ImGui.TextUnformatted( - $"{item.Weapon()}{(_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}"); + $"{item.Weapon()}{(_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}"); else ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}"); ImGui.TextUnformatted( @@ -219,29 +243,6 @@ public class UnlockOverview _tooltip.CreateTooltip(item, string.Empty, false); } } - - var skips = ImGuiClip.GetNecessarySkips(iconSize.Y + spacing.Y); - var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, items.Count); - var counter = 0; - for (var idx = skips * iconsPerRow; idx < end; ++idx) - { - DrawItem(items[idx]); - if (counter != iconsPerRow - 1) - { - ImGui.SameLine(); - ++counter; - } - else - { - counter = 0; - } - } - - if (ImGui.GetCursorPosX() != 0) - ImGui.NewLine(); - var remainder = numRows - numVisibleRows - skips; - if (remainder > 0) - ImGuiClip.DrawEndDummy(remainder, iconSize.Y + spacing.Y); } private static Vector2 IconSpacing diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 5ac087f..3f7531c 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -278,7 +278,7 @@ public class UnlockTable : Table, IDisposable ImGuiUtil.RightAlign(item.ModelString); if (ImGui.IsItemHovered() && item.Type.ValidOffhand().IsOffhandType() - && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) + && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) { using var tt = ImRaii.Tooltip(); ImGui.TextUnformatted("Offhand: " + offhand.ModelString); @@ -297,7 +297,7 @@ public class UnlockTable : Table, IDisposable return true; if (item.Type.ValidOffhand().IsOffhandType() - && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) + && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) return FilterRegex?.IsMatch(offhand.ModelString) ?? offhand.ModelString.Contains(FilterValue, StringComparison.OrdinalIgnoreCase); @@ -411,21 +411,16 @@ public class UnlockTable : Table, IDisposable => item.Flags.HasFlag(ItemFlags.IsCrestWorthy); } - private sealed class ItemList : IReadOnlyCollection + private sealed class ItemList(ItemManager items) : IReadOnlyCollection { - private readonly ItemManager _items; - - public ItemList(ItemManager items) - => _items = items; - public IEnumerator GetEnumerator() - => _items.ItemService.AwaitedService.AllItems(true).Select(i => i.Item2).GetEnumerator(); + => items.ItemData.AllItems(true).Select(i => i.Item2).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public int Count - => _items.ItemService.AwaitedService.TotalItemCount(true); + => items.ItemData.Primary.Count; } private void OnObjectUnlock(ObjectUnlocked.Type _1, uint _2, DateTimeOffset _3) diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 6e83838..2c64b42 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -27,7 +27,7 @@ public static class UiHelpers public static void DrawIcon(this EquipItem item, TextureService textures, Vector2 size, EquipSlot slot) { - var isEmpty = item.ModelId.Id == 0; + var isEmpty = item.PrimaryId.Id == 0; var (ptr, textureSize, empty) = textures.GetIcon(item, slot); if (empty) { diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 9e9a043..ef7e762 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -1,5 +1,4 @@ using System; -using System.Threading; using Dalamud.Hooking; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; @@ -7,7 +6,7 @@ using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop.Structs; using OtterGui.Classes; -using CustomizeData = Penumbra.GameData.Structs.CustomizeData; +using Penumbra.GameData.Structs; namespace Glamourer.Interop; @@ -64,7 +63,7 @@ public unsafe class ChangeCustomizeService : EventWrapper _changeCustomizeHook; - public bool UpdateCustomize(Model model, CustomizeData customize) + public bool UpdateCustomize(Model model, CustomizeArray customize) { if (!model.IsHuman) return false; @@ -75,14 +74,14 @@ public unsafe class ChangeCustomizeService : EventWrapper UpdateCustomize(actor.Model, customize); private bool ChangeCustomizeDetour(Human* human, byte* data, byte skipEquipment) { if (!InUpdate.InMethod) { - var customize = new Ref(new Customize(*(CustomizeData*)data)); + var customize = new Ref(new Customize(*(CustomizeArray*)data)); Invoke(this, (Model)human, customize); ((Customize*)data)->Load(customize.Value); } diff --git a/Glamourer/Interop/CharaFile/CmaFile.cs b/Glamourer/Interop/CharaFile/CmaFile.cs index 15b8af1..c916ad8 100644 --- a/Glamourer/Interop/CharaFile/CmaFile.cs +++ b/Glamourer/Interop/CharaFile/CmaFile.cs @@ -78,8 +78,8 @@ public sealed class CmaFile return; } - var set = mainhand["Item1"]?.ToObject() ?? items.DefaultSword.ModelId; - var type = mainhand["Item2"]?.ToObject() ?? items.DefaultSword.WeaponType; + var set = mainhand["Item1"]?.ToObject() ?? items.DefaultSword.PrimaryId; + var type = mainhand["Item2"]?.ToObject() ?? items.DefaultSword.SecondaryId; var variant = mainhand["Item3"]?.ToObject() ?? items.DefaultSword.Variant; var stain = mainhand["Item4"]?.ToObject() ?? 0; var item = items.Identify(EquipSlot.MainHand, set, type, variant); @@ -95,17 +95,17 @@ public sealed class CmaFile if (offhand == null) { data.SetItem(EquipSlot.MainHand, defaultOffhand); - data.SetStain(EquipSlot.MainHand, defaultOffhand.ModelId.Id == 0 ? 0 : data.Stain(EquipSlot.MainHand)); + data.SetStain(EquipSlot.MainHand, defaultOffhand.PrimaryId.Id == 0 ? 0 : data.Stain(EquipSlot.MainHand)); return; } - var set = offhand["Item1"]?.ToObject() ?? items.DefaultSword.ModelId; - var type = offhand["Item2"]?.ToObject() ?? items.DefaultSword.WeaponType; + var set = offhand["Item1"]?.ToObject() ?? items.DefaultSword.PrimaryId; + var type = offhand["Item2"]?.ToObject() ?? items.DefaultSword.SecondaryId; var variant = offhand["Item3"]?.ToObject() ?? items.DefaultSword.Variant; var stain = offhand["Item4"]?.ToObject() ?? 0; var item = items.Identify(EquipSlot.OffHand, set, type, variant, data.MainhandType); data.SetItem(EquipSlot.OffHand, item.Valid ? item : defaultOffhand); - data.SetStain(EquipSlot.OffHand, defaultOffhand.ModelId.Id == 0 ? 0 : (StainId)stain); + data.SetStain(EquipSlot.OffHand, defaultOffhand.PrimaryId.Id == 0 ? 0 : (StainId)stain); } } diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index c210f1f..96dc107 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -68,7 +68,7 @@ public class ContextMenuService : IDisposable if (itemId > 500000) itemId -= 500000; - if (!_items.ItemService.AwaitedService.TryGetValue(itemId, EquipSlot.MainHand, out var item)) + if (!_items.ItemData.TryGetValue(itemId, EquipSlot.MainHand, out var item)) return null; return new InventoryContextMenuItem(TryOnString, GetInventoryAction(item)); @@ -80,7 +80,7 @@ public class ContextMenuService : IDisposable if (itemId > 500000) itemId -= 500000; - if (!_items.ItemService.AwaitedService.TryGetValue(itemId, EquipSlot.MainHand, out var item)) + if (!_items.ItemData.TryGetValue(itemId, EquipSlot.MainHand, out var item)) return null; return new GameObjectContextMenuItem(TryOnString, GetGameObjectAction(item)); @@ -122,10 +122,10 @@ public class ContextMenuService : IDisposable _state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual); if (item.Type.ValidOffhand().IsOffhandType()) { - if (item.ModelId.Id is > 1600 and < 1651 - && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) + if (item.PrimaryId.Id is > 1600 and < 1651 + && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual); - if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) + if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual); } }; @@ -146,10 +146,10 @@ public class ContextMenuService : IDisposable _state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual); if (item.Type.ValidOffhand().IsOffhandType()) { - if (item.ModelId.Id is > 1600 and < 1651 - && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) + if (item.PrimaryId.Id is > 1600 and < 1651 + && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual); - if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) + if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual); } }; diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 217b5fd..86f2343 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -178,7 +178,7 @@ public class ImportService(CustomizationService _customizations, IDragDropManage if (input.BodyType.Value != 1) return false; - var set = _customizations.AwaitedService.GetList(input.Clan, input.Gender); + var set = _customizations.Service.GetList(input.Clan, input.Gender); voice = set.Voices[0]; if (inputVoice.HasValue && !set.Voices.Contains(inputVoice.Value)) return false; diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index 52dd397..5db7400 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -5,8 +5,8 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Control; using Glamourer.Interop.Structs; -using Glamourer.Services; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; namespace Glamourer.Interop; @@ -15,13 +15,13 @@ public class ObjectManager : IReadOnlyDictionary private readonly IFramework _framework; private readonly IClientState _clientState; private readonly IObjectTable _objects; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly ITargetManager _targets; public IObjectTable Objects => _objects; - public ObjectManager(IFramework framework, IClientState clientState, IObjectTable objects, ActorService actors, ITargetManager targets) + public ObjectManager(IFramework framework, IClientState clientState, IObjectTable objects, ActorManager actors, ITargetManager targets) { _framework = framework; _clientState = clientState; @@ -57,7 +57,7 @@ public class ObjectManager : IReadOnlyDictionary for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i) { Actor character = _objects.GetObjectAddress(i); - if (character.Identifier(_actors.AwaitedService, out var identifier)) + if (character.Identifier(_actors, out var identifier)) HandleIdentifier(identifier, character); } @@ -70,13 +70,13 @@ public class ObjectManager : IReadOnlyDictionary if (!character.Valid && i == (int)ScreenActor.CutsceneStart) break; - HandleIdentifier(character.GetIdentifier(_actors.AwaitedService), character); + HandleIdentifier(character.GetIdentifier(_actors), character); } void AddSpecial(ScreenActor idx, string label) { Actor actor = _objects.GetObjectAddress((int)idx); - if (actor.Identifier(_actors.AwaitedService, out var ident)) + if (actor.Identifier(_actors, out var ident)) { var data = new ActorData(actor, label); _identifiers.Add(ident, data); @@ -95,7 +95,7 @@ public class ObjectManager : IReadOnlyDictionary for (var i = (int)ScreenActor.ScreenEnd; i < _objects.Length; ++i) { Actor character = _objects.GetObjectAddress(i); - if (character.Identifier(_actors.AwaitedService, out var identifier)) + if (character.Identifier(_actors, out var identifier)) HandleIdentifier(identifier, character); } @@ -120,7 +120,7 @@ public class ObjectManager : IReadOnlyDictionary if (identifier.Type is IdentifierType.Player or IdentifierType.Owned) { - var allWorld = _actors.AwaitedService.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, + var allWorld = _actors.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind, identifier.DataId); @@ -137,7 +137,7 @@ public class ObjectManager : IReadOnlyDictionary if (identifier.Type is IdentifierType.Owned) { - var nonOwned = _actors.AwaitedService.CreateNpc(identifier.Kind, identifier.DataId); + var nonOwned = _actors.CreateNpc(identifier.Kind, identifier.DataId); if (!_nonOwnedIdentifiers.TryGetValue(nonOwned, out var nonOwnedData)) { nonOwnedData = new ActorData(character, nonOwned.ToString()); @@ -170,7 +170,7 @@ public class ObjectManager : IReadOnlyDictionary get { Update(); - return Player.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data) + return Player.Identifier(_actors, out var ident) && _identifiers.TryGetValue(ident, out var data) ? (ident, data) : (ident, ActorData.Invalid); } @@ -181,7 +181,7 @@ public class ObjectManager : IReadOnlyDictionary get { Update(); - return Target.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data) + return Target.Identifier(_actors, out var ident) && _identifiers.TryGetValue(ident, out var data) ? (ident, data) : (ident, ActorData.Invalid); } diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 4515b7c..2629ad7 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -221,8 +221,8 @@ public unsafe class PenumbraService : IDisposable => Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null; /// Obtain the parent of a cutscene actor if it is known. - public int CutsceneParent(int idx) - => Available ? _cutsceneParent.Invoke(idx) : -1; + public short CutsceneParent(ushort idx) + => (short) (Available ? _cutsceneParent.Invoke(idx) : -1); /// Try to redraw the given actor. public void RedrawObject(Actor actor, RedrawType settings) diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 389a05f..544fe32 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -76,7 +76,7 @@ public unsafe class WeaponService : IDisposable if (tmpWeapon.Value != weapon.Value) { - if (tmpWeapon.Set.Id == 0) + if (tmpWeapon.X.Id == 0) tmpWeapon.Stain = 0; _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); } @@ -119,7 +119,7 @@ public unsafe class WeaponService : IDisposable var mdl = character.Model; var (_, _, mh, oh) = mdl.GetWeapons(character); var value = slot == EquipSlot.OffHand ? oh : mh; - var weapon = value.With(value.Set.Id == 0 ? 0 : stain); + var weapon = value.With(value.X.Id == 0 ? 0 : stain); LoadWeapon(character, slot, weapon); } } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 253442f..417f1b9 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -16,6 +16,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Classes; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; namespace Glamourer.Services; @@ -27,7 +28,7 @@ public class CommandService : IDisposable private readonly ICommandManager _commands; private readonly MainWindow _mainWindow; private readonly IChatGui _chat; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly ObjectManager _objects; private readonly StateManager _stateManager; private readonly AutoDesignApplier _autoDesignApplier; @@ -37,7 +38,7 @@ public class CommandService : IDisposable private readonly DesignFileSystem _designFileSystem; private readonly Configuration _config; - public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorService actors, ObjectManager objects, + 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) { @@ -402,7 +403,7 @@ public class CommandService : IDisposable { foreach (var actor in actors.Objects) { - if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors.AwaitedService), actor, out var state)) + if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); } } @@ -537,7 +538,7 @@ public class CommandService : IDisposable { if (_objects.GetName(argument.ToLowerInvariant(), out var obj)) { - var identifier = _actors.AwaitedService.FromObject(obj.AsObject, out _, true, true, true); + var identifier = _actors.FromObject(obj.AsObject, out _, true, true, true); if (!identifier.IsValid) { _chat.Print(new SeStringBuilder().AddText("The placeholder ").AddGreen(argument) @@ -547,7 +548,7 @@ public class CommandService : IDisposable } if (allowIndex && identifier.Type is IdentifierType.Npc) - identifier = _actors.AwaitedService.CreateNpc(identifier.Kind, identifier.DataId, obj.Index); + identifier = _actors.CreateNpc(identifier.Kind, identifier.DataId, obj.Index); identifiers = new[] { identifier, @@ -555,7 +556,7 @@ public class CommandService : IDisposable } else { - identifiers = _actors.AwaitedService.FromUserString(argument, allowIndex); + identifiers = _actors.FromUserString(argument, allowIndex); if (!allowAnyWorld && identifiers[0].Type is IdentifierType.Player or IdentifierType.Owned && identifiers[0].HomeWorld == ushort.MaxValue) diff --git a/Glamourer/Services/CustomizationService.cs b/Glamourer/Services/CustomizationService.cs index 1b3f6e2..b5f474f 100644 --- a/Glamourer/Services/CustomizationService.cs +++ b/Glamourer/Services/CustomizationService.cs @@ -1,19 +1,34 @@ using System.Linq; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using Dalamud.Plugin.Services; using Glamourer.Customization; -using Penumbra.GameData.Data; +using OtterGui.Services; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; namespace Glamourer.Services; -public sealed class CustomizationService : AsyncServiceWrapper +public sealed class CustomizationService( + ITextureProvider textures, + IDataManager gameData, + HumanModelList humanModels, + IPluginLog log, + NpcCustomizeSet npcCustomizeSet) + : IAsyncService { - public readonly HumanModelList HumanModels; + public readonly HumanModelList HumanModels = humanModels; - public CustomizationService(ITextureProvider textures, IDataManager gameData, HumanModelList humanModels, IPluginLog log) - : base(nameof(CustomizationService), () => CustomizationManager.Create(textures, gameData, log)) - => HumanModels = humanModels; + private ICustomizationManager? _service; + + private readonly Task _task = Task.WhenAll(humanModels.Awaiter, npcCustomizeSet.Awaiter) + .ContinueWith(_ => CustomizationManager.Create(textures, gameData, log, npcCustomizeSet)); + + public ICustomizationManager Service + => _service ??= _task.Result; + + public Task Awaiter + => _task; public (Customize NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(Customize oldValues, Customize newValues, CustomizeFlag applyWhich, bool allowUnknown) @@ -36,7 +51,7 @@ public sealed class CustomizationService : AsyncServiceWrapper AwaitedService.GetName(CustomName.MidlanderM), - (Gender.Male, SubRace.Highlander) => AwaitedService.GetName(CustomName.HighlanderM), - (Gender.Male, SubRace.Wildwood) => AwaitedService.GetName(CustomName.WildwoodM), - (Gender.Male, SubRace.Duskwight) => AwaitedService.GetName(CustomName.DuskwightM), - (Gender.Male, SubRace.Plainsfolk) => AwaitedService.GetName(CustomName.PlainsfolkM), - (Gender.Male, SubRace.Dunesfolk) => AwaitedService.GetName(CustomName.DunesfolkM), - (Gender.Male, SubRace.SeekerOfTheSun) => AwaitedService.GetName(CustomName.SeekerOfTheSunM), - (Gender.Male, SubRace.KeeperOfTheMoon) => AwaitedService.GetName(CustomName.KeeperOfTheMoonM), - (Gender.Male, SubRace.Seawolf) => AwaitedService.GetName(CustomName.SeawolfM), - (Gender.Male, SubRace.Hellsguard) => AwaitedService.GetName(CustomName.HellsguardM), - (Gender.Male, SubRace.Raen) => AwaitedService.GetName(CustomName.RaenM), - (Gender.Male, SubRace.Xaela) => AwaitedService.GetName(CustomName.XaelaM), - (Gender.Male, SubRace.Helion) => AwaitedService.GetName(CustomName.HelionM), - (Gender.Male, SubRace.Lost) => AwaitedService.GetName(CustomName.LostM), - (Gender.Male, SubRace.Rava) => AwaitedService.GetName(CustomName.RavaM), - (Gender.Male, SubRace.Veena) => AwaitedService.GetName(CustomName.VeenaM), - (Gender.Female, SubRace.Midlander) => AwaitedService.GetName(CustomName.MidlanderF), - (Gender.Female, SubRace.Highlander) => AwaitedService.GetName(CustomName.HighlanderF), - (Gender.Female, SubRace.Wildwood) => AwaitedService.GetName(CustomName.WildwoodF), - (Gender.Female, SubRace.Duskwight) => AwaitedService.GetName(CustomName.DuskwightF), - (Gender.Female, SubRace.Plainsfolk) => AwaitedService.GetName(CustomName.PlainsfolkF), - (Gender.Female, SubRace.Dunesfolk) => AwaitedService.GetName(CustomName.DunesfolkF), - (Gender.Female, SubRace.SeekerOfTheSun) => AwaitedService.GetName(CustomName.SeekerOfTheSunF), - (Gender.Female, SubRace.KeeperOfTheMoon) => AwaitedService.GetName(CustomName.KeeperOfTheMoonF), - (Gender.Female, SubRace.Seawolf) => AwaitedService.GetName(CustomName.SeawolfF), - (Gender.Female, SubRace.Hellsguard) => AwaitedService.GetName(CustomName.HellsguardF), - (Gender.Female, SubRace.Raen) => AwaitedService.GetName(CustomName.RaenF), - (Gender.Female, SubRace.Xaela) => AwaitedService.GetName(CustomName.XaelaF), - (Gender.Female, SubRace.Helion) => AwaitedService.GetName(CustomName.HelionM), - (Gender.Female, SubRace.Lost) => AwaitedService.GetName(CustomName.LostM), - (Gender.Female, SubRace.Rava) => AwaitedService.GetName(CustomName.RavaF), - (Gender.Female, SubRace.Veena) => AwaitedService.GetName(CustomName.VeenaF), + (Gender.Male, SubRace.Midlander) => Service.GetName(CustomName.MidlanderM), + (Gender.Male, SubRace.Highlander) => Service.GetName(CustomName.HighlanderM), + (Gender.Male, SubRace.Wildwood) => Service.GetName(CustomName.WildwoodM), + (Gender.Male, SubRace.Duskwight) => Service.GetName(CustomName.DuskwightM), + (Gender.Male, SubRace.Plainsfolk) => Service.GetName(CustomName.PlainsfolkM), + (Gender.Male, SubRace.Dunesfolk) => Service.GetName(CustomName.DunesfolkM), + (Gender.Male, SubRace.SeekerOfTheSun) => Service.GetName(CustomName.SeekerOfTheSunM), + (Gender.Male, SubRace.KeeperOfTheMoon) => Service.GetName(CustomName.KeeperOfTheMoonM), + (Gender.Male, SubRace.Seawolf) => Service.GetName(CustomName.SeawolfM), + (Gender.Male, SubRace.Hellsguard) => Service.GetName(CustomName.HellsguardM), + (Gender.Male, SubRace.Raen) => Service.GetName(CustomName.RaenM), + (Gender.Male, SubRace.Xaela) => Service.GetName(CustomName.XaelaM), + (Gender.Male, SubRace.Helion) => Service.GetName(CustomName.HelionM), + (Gender.Male, SubRace.Lost) => Service.GetName(CustomName.LostM), + (Gender.Male, SubRace.Rava) => Service.GetName(CustomName.RavaM), + (Gender.Male, SubRace.Veena) => Service.GetName(CustomName.VeenaM), + (Gender.Female, SubRace.Midlander) => Service.GetName(CustomName.MidlanderF), + (Gender.Female, SubRace.Highlander) => Service.GetName(CustomName.HighlanderF), + (Gender.Female, SubRace.Wildwood) => Service.GetName(CustomName.WildwoodF), + (Gender.Female, SubRace.Duskwight) => Service.GetName(CustomName.DuskwightF), + (Gender.Female, SubRace.Plainsfolk) => Service.GetName(CustomName.PlainsfolkF), + (Gender.Female, SubRace.Dunesfolk) => Service.GetName(CustomName.DunesfolkF), + (Gender.Female, SubRace.SeekerOfTheSun) => Service.GetName(CustomName.SeekerOfTheSunF), + (Gender.Female, SubRace.KeeperOfTheMoon) => Service.GetName(CustomName.KeeperOfTheMoonF), + (Gender.Female, SubRace.Seawolf) => Service.GetName(CustomName.SeawolfF), + (Gender.Female, SubRace.Hellsguard) => Service.GetName(CustomName.HellsguardF), + (Gender.Female, SubRace.Raen) => Service.GetName(CustomName.RaenF), + (Gender.Female, SubRace.Xaela) => Service.GetName(CustomName.XaelaF), + (Gender.Female, SubRace.Helion) => Service.GetName(CustomName.HelionM), + (Gender.Female, SubRace.Lost) => Service.GetName(CustomName.LostM), + (Gender.Female, SubRace.Rava) => Service.GetName(CustomName.RavaF), + (Gender.Female, SubRace.Veena) => Service.GetName(CustomName.VeenaF), _ => "Unknown", }; } @@ -105,12 +120,12 @@ public sealed class CustomizationService : AsyncServiceWrapper Returns whether a clan is valid. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsClanValid(SubRace clan) - => AwaitedService.Clans.Contains(clan); + => Service.Clans.Contains(clan); /// Returns whether a gender is valid for the given race. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsGenderValid(Race race, Gender gender) - => race is Race.Hrothgar ? gender == Gender.Male : AwaitedService.Genders.Contains(gender); + => race is Race.Hrothgar ? gender == Gender.Male : Service.Genders.Contains(gender); /// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -126,7 +141,7 @@ public sealed class CustomizationService : AsyncServiceWrapper Returns whether a customization value is valid for a given clan, gender and face. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsCustomizationValid(SubRace race, Gender gender, CustomizeValue face, CustomizeIndex type, CustomizeValue value) - => IsCustomizationValid(AwaitedService.GetList(race, gender), face, type, value); + => IsCustomizationValid(Service.GetList(race, gender), face, type, value); /// /// Check that the given race and clan are valid. @@ -145,10 +160,10 @@ public sealed class CustomizationService : AsyncServiceWrapper c.ToRace() == race, SubRace.Unknown); + actualClan = Service.Clans.FirstOrDefault(c => c.ToRace() == race, SubRace.Unknown); // This should not happen. if (actualClan == SubRace.Unknown) { @@ -174,7 +189,7 @@ public sealed class CustomizationService : AsyncServiceWrapper public string ValidateGender(Race race, Gender gender, out Gender actualGender) { - if (!AwaitedService.Genders.Contains(gender)) + if (!Service.Genders.Contains(gender)) { actualGender = Gender.Male; return $"The gender {gender.ToName()} is unknown, reset to {Gender.Male.ToName()}."; @@ -251,7 +266,7 @@ public sealed class CustomizationService : AsyncServiceWrapper(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); + services.AddDalamudService(pi); } - - public void AddServices(IServiceCollection services) - { - services.AddSingleton(PluginInterface); - services.AddSingleton(Commands); - services.AddSingleton(GameData); - services.AddSingleton(ClientState); - services.AddSingleton(Condition); - services.AddSingleton(GameGui); - services.AddSingleton(Chat); - services.AddSingleton(Framework); - services.AddSingleton(Targets); - services.AddSingleton(Objects); - services.AddSingleton(KeyState); - services.AddSingleton(this); - services.AddSingleton(PluginInterface.UiBuilder); - services.AddSingleton(DragDropManager); - services.AddSingleton(TextureProvider); - services.AddSingleton(Log); - services.AddSingleton(Interop); - } - - // @formatter:off - [PluginService][RequiredVersion("1.0")] public DalamudPluginInterface PluginInterface { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public ICommandManager Commands { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IDataManager GameData { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IClientState ClientState { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public ICondition Condition { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IGameGui GameGui { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IChatGui Chat { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IFramework Framework { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public ITargetManager Targets { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IObjectTable Objects { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IKeyState KeyState { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IDragDropManager DragDropManager { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public ITextureProvider TextureProvider { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IPluginLog Log { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public IGameInteropProvider Interop { get; private set; } = null!; - // @formatter:on } diff --git a/Glamourer/Services/IGamePathParser.cs b/Glamourer/Services/IGamePathParser.cs new file mode 100644 index 0000000..28364d1 --- /dev/null +++ b/Glamourer/Services/IGamePathParser.cs @@ -0,0 +1,6 @@ +namespace Glamourer.Services +{ + internal interface IGamePathParser + { + } +} \ No newline at end of file diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 745469e..20b97f0 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -1,17 +1,17 @@ using System; using System.Linq; using System.Runtime.CompilerServices; -using Dalamud.Plugin; using Dalamud.Plugin.Services; using Lumina.Excel; using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.Services; -public class ItemManager : IDisposable +public class ItemManager { public const string Nothing = "Nothing"; public const string SmallClothesNpc = "Smallclothes (NPC)"; @@ -19,30 +19,24 @@ public class ItemManager : IDisposable private readonly Configuration _config; - public readonly IdentifierService IdentifierService; + public readonly ObjectIdentification ObjectIdentification; public readonly ExcelSheet ItemSheet; - public readonly StainData Stains; - public readonly ItemService ItemService; + public readonly DictStains Stains; + public readonly ItemData ItemData; public readonly RestrictedGear RestrictedGear; public readonly EquipItem DefaultSword; - public ItemManager(Configuration config, DalamudPluginInterface pi, IDataManager gameData, IdentifierService identifierService, - ItemService itemService, IPluginLog log) + public ItemManager(Configuration config, IDataManager gameData, ObjectIdentification objectIdentification, + ItemData itemData, DictStains stains, RestrictedGear restrictedGear) { - _config = config; - ItemSheet = gameData.GetExcelSheet()!; - IdentifierService = identifierService; - Stains = new StainData(pi, gameData, gameData.Language, log); - ItemService = itemService; - RestrictedGear = new RestrictedGear(pi, gameData.Language, gameData, log); - DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)!); // Weathered Shortsword - } - - public void Dispose() - { - Stains.Dispose(); - RestrictedGear.Dispose(); + _config = config; + ItemSheet = gameData.GetExcelSheet()!; + ObjectIdentification = objectIdentification; + ItemData = itemData; + Stains = stains; + RestrictedGear = restrictedGear; + DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)!); // Weathered Shortsword } public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender) @@ -74,11 +68,12 @@ public class ItemManager : IDisposable if (itemId == SmallclothesId(slot)) return SmallClothesItem(slot); - if (!itemId.IsItem || !ItemService.AwaitedService.TryGetValue(itemId.Item, slot, out var item)) + if (!itemId.IsItem || !ItemData.TryGetValue(itemId.Item, slot, out var item)) return EquipItem.FromId(itemId); if (item.Type.ToSlot() != slot) - return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.ModelId, item.WeaponType, item.Variant, 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; @@ -89,12 +84,13 @@ public class ItemManager : IDisposable if (itemId == NothingId(type)) return NothingItem(type); - if (!ItemService.AwaitedService.TryGetValue(itemId, type is FullEquipType.Shield ? EquipSlot.MainHand : EquipSlot.OffHand, + if (!ItemData.TryGetValue(itemId, type is FullEquipType.Shield ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return EquipItem.FromId(itemId); if (item.Type != type) - return new EquipItem(string.Intern($"Invalid #{itemId}"), itemId, item.IconId, item.ModelId, item.WeaponType, item.Variant, 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; @@ -103,7 +99,7 @@ public class ItemManager : IDisposable public EquipItem Resolve(FullEquipType type, CustomItemId id) => id.IsItem ? Resolve(type, id.Item) : EquipItem.FromId(id); - public EquipItem Identify(EquipSlot slot, SetId id, Variant variant) + public EquipItem Identify(EquipSlot slot, PrimaryId id, Variant variant) { slot = slot.ToSlot(); if (slot.ToIndex() == uint.MaxValue) @@ -114,7 +110,7 @@ public class ItemManager : IDisposable case 0: return NothingItem(slot); case SmallClothesNpcModel: return SmallClothesItem(slot); default: - var item = IdentifierService.AwaitedService.Identify(id, variant, slot).FirstOrDefault(); + var item = ObjectIdentification.Identify(id, 0, variant, slot).FirstOrDefault(); return item.Valid ? item : EquipItem.FromIds(0, 0, id, 0, variant, slot.ToEquipType()); @@ -131,7 +127,8 @@ public class ItemManager : IDisposable return NothingItem(offhandType); } - public EquipItem Identify(EquipSlot slot, SetId id, WeaponType type, Variant variant, FullEquipType mainhandType = FullEquipType.Unknown) + public EquipItem Identify(EquipSlot slot, PrimaryId id, SecondaryId type, Variant variant, + FullEquipType mainhandType = FullEquipType.Unknown) { if (slot is EquipSlot.OffHand) { @@ -143,7 +140,7 @@ public class ItemManager : IDisposable if (slot is not EquipSlot.MainHand and not EquipSlot.OffHand) return new EquipItem($"Invalid ({id.Id}-{type.Id}-{variant})", 0, 0, id, type, variant, 0, 0, 0, 0); - var item = IdentifierService.AwaitedService.Identify(id, type, variant, slot).FirstOrDefault(i => i.Type.ToSlot() == slot); + var item = ObjectIdentification.Identify(id, type, variant, slot).FirstOrDefault(i => i.Type.ToSlot() == slot); return item.Valid ? item : EquipItem.FromIds(0, 0, id, type, variant, slot.ToEquipType()); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 8dc4c8d..d88a90c 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Reflection; -using Dalamud.Plugin; +using Dalamud.Plugin; using Glamourer.Api; using Glamourer.Automation; using Glamourer.Designs; @@ -18,22 +15,26 @@ using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.State; +using Glamourer.Structs; using Glamourer.Unlocks; using Microsoft.Extensions.DependencyInjection; using OtterGui.Classes; using OtterGui.Log; +using OtterGui.Services; +using Penumbra.GameData.Actors; using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Structs; namespace Glamourer.Services; -public static class ServiceManager +public static class ServiceManagerA { - public static ServiceProvider CreateProvider(DalamudPluginInterface pi, Logger log) + public static ServiceManager CreateProvider(DalamudPluginInterface pi, Logger log) { EventWrapper.ChangeLogger(log); - var services = new ServiceCollection() - .AddSingleton(log) - .AddDalamud(pi) + var services = new ServiceManager(log) + .AddExistingService(log) .AddMeta() .AddInterop() .AddEvents() @@ -41,19 +42,16 @@ public static class ServiceManager .AddDesigns() .AddState() .AddUi() - .AddApi() - .AddDebug(); - - return services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true }); - } - - private static IServiceCollection AddDalamud(this IServiceCollection services, DalamudPluginInterface pi) - { - new DalamudServices(pi).AddServices(services); + .AddApi(); + DalamudServices.AddServices(services, pi); + services.AddIServices(typeof(EquipItem).Assembly); + services.AddIServices(typeof(Glamourer).Assembly); + services.AddIServices(typeof(EquipFlag).Assembly); + services.CreateProvider(); return services; } - private static IServiceCollection AddMeta(this IServiceCollection services) + private static ServiceManager AddMeta(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() @@ -66,7 +64,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton(); - private static IServiceCollection AddEvents(this IServiceCollection services) + private static ServiceManager AddEvents(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() @@ -82,21 +80,23 @@ public static class ServiceManager .AddSingleton() .AddSingleton(); - private static IServiceCollection AddData(this IServiceCollection services) - => services.AddSingleton() - .AddSingleton() - .AddSingleton() + private static ServiceManager AddData(this ServiceManager services) + => services.AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton(); - private static IServiceCollection AddInterop(this IServiceCollection services) + private static ServiceManager AddInterop(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton(p => new CutsceneResolver(p.GetRequiredService().CutsceneParent)) .AddSingleton() .AddSingleton() .AddSingleton() @@ -108,7 +108,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton(); - private static IServiceCollection AddDesigns(this IServiceCollection services) + private static ServiceManager AddDesigns(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() @@ -117,14 +117,14 @@ public static class ServiceManager .AddSingleton() .AddSingleton(); - private static IServiceCollection AddState(this IServiceCollection services) + private static ServiceManager AddState(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); - private static IServiceCollection AddUi(this IServiceCollection services) + private static ServiceManager AddUi(this ServiceManager services) => services.AddSingleton() .AddSingleton() .AddSingleton() @@ -157,16 +157,7 @@ public static class ServiceManager .AddSingleton() .AddSingleton(); - private static IServiceCollection AddDebug(this IServiceCollection services) - { - services.AddSingleton(p => new DebugTab(p)); - var iType = typeof(IDebugTabTree); - foreach (var type in Assembly.GetAssembly(iType)!.GetTypes().Where(t => !t.IsInterface && iType.IsAssignableFrom(t))) - services.AddSingleton(type); - return services; - } - - private static IServiceCollection AddApi(this IServiceCollection services) + private static ServiceManager AddApi(this ServiceManager services) => services.AddSingleton() .AddSingleton(); } diff --git a/Glamourer/Services/ServiceWrapper.cs b/Glamourer/Services/ServiceWrapper.cs deleted file mode 100644 index ded8ef0..0000000 --- a/Glamourer/Services/ServiceWrapper.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Dalamud.Plugin; -using Penumbra.GameData.Actors; -using System; -using System.Threading.Tasks; -using Dalamud.Plugin.Services; -using Glamourer.Interop.Penumbra; -using Penumbra.GameData.Data; -using Penumbra.GameData; - -namespace Glamourer.Services; - -public abstract class AsyncServiceWrapper : IDisposable -{ - public string Name { get; } - public T? Service { get; private set; } - - public T AwaitedService - { - get - { - _task?.Wait(); - return Service!; - } - } - - public bool Valid - => Service != null && !_isDisposed; - - public event Action? FinishedCreation; - private Task? _task; - - private bool _isDisposed; - - protected AsyncServiceWrapper(string name, Func factory) - { - Name = name; - _task = Task.Run(() => - { - var service = factory(); - if (_isDisposed) - { - if (service is IDisposable d) - d.Dispose(); - } - else - { - Service = service; - Glamourer.Log.Verbose($"[{Name}] Created."); - _task = null; - } - }); - _task.ContinueWith((t, x) => - { - if (!_isDisposed) - FinishedCreation?.Invoke(); - }, null); - } - - public void Dispose() - { - if (_isDisposed) - return; - - _isDisposed = true; - _task = null; - if (Service is IDisposable d) - d.Dispose(); - Glamourer.Log.Verbose($"[{Name}] Disposed."); - } -} - -public sealed class IdentifierService : AsyncServiceWrapper -{ - public IdentifierService(DalamudPluginInterface pi, IDataManager data, ItemService itemService, IPluginLog log) - : base(nameof(IdentifierService), () => Penumbra.GameData.GameData.GetIdentifier(pi, data, itemService.AwaitedService, log)) - { } -} - -public sealed class ItemService : AsyncServiceWrapper -{ - public ItemService(DalamudPluginInterface pi, IDataManager gameData, IPluginLog log) - : base(nameof(ItemService), () => new ItemData(pi, gameData, gameData.Language, log)) - { } -} - -public sealed class ActorService : AsyncServiceWrapper -{ - public ActorService(DalamudPluginInterface pi, IObjectTable objects, IClientState clientState, IFramework framework, IGameInteropProvider interop, IDataManager gameData, - IGameGui gui, PenumbraService penumbra, IPluginLog log) - : base(nameof(ActorService), - () => new ActorManager(pi, objects, clientState, framework, interop, gameData, gui, idx => (short)penumbra.CutsceneParent(idx), log)) - { } -} diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index b570d45..5ab3f41 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -164,20 +164,21 @@ public unsafe class FunModule : IDisposable if (!_codes.EnabledEmperor) return; - void SetItem(EquipSlot slot2, ref CharacterArmor armor) - { - var list = _items.ItemService.AwaitedService[slot2.ToEquipType()]; - var rng = _rng.Next(0, list.Count - 1); - var item = list[rng]; - armor.Set = item.ModelId; - armor.Variant = item.Variant; - } - if (armors.Length == 1) SetItem(slot, ref armors[0]); else for (var i = 0u; i < armors.Length; ++i) SetItem(i.ToEquipSlot(), ref armors[(int)i]); + return; + + void SetItem(EquipSlot slot2, ref CharacterArmor armor) + { + var list = _items.ItemData.ByType[slot2.ToEquipType()]; + var rng = _rng.Next(0, list.Count - 1); + var item = list[rng]; + armor.Set = item.PrimaryId; + armor.Variant = item.Variant; + } } public void ApplyOops(ref Customize customize) @@ -197,7 +198,7 @@ public unsafe class FunModule : IDisposable if (!_codes.EnabledIndividual) return; - var set = _customizations.AwaitedService.GetList(customize.Clan, customize.Gender); + var set = _customizations.Service.GetList(customize.Clan, customize.Gender); foreach (var index in Enum.GetValues()) { if (index is CustomizeIndex.Face || !set.IsAvailable(index)) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 5eaf672..a91a1eb 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -188,7 +188,7 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We /// Apply a weapon to the offhand. public void ChangeOffhand(ActorData data, EquipItem weapon, StainId stain) { - stain = weapon.ModelId.Id == 0 ? 0 : stain; + stain = weapon.PrimaryId.Id == 0 ? 0 : stain; foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain)); } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 3b9d0cb..ebc2661 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -5,7 +5,7 @@ using Glamourer.Customization; using Glamourer.Events; using Glamourer.Services; using Glamourer.Structs; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -74,7 +74,7 @@ public class StateEditor state[CustomizeIndex.Clan] = source; state[CustomizeIndex.Gender] = source; - var set = _customizations.AwaitedService.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); + var set = _customizations.Service.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); foreach (var index in Enum.GetValues().Where(set.IsAvailable)) state[index] = source; } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index ae6de2f..7cc8b82 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -7,13 +7,13 @@ using Glamourer.Interop.Structs; using Glamourer.Services; using OtterGui.Classes; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; using Glamourer.Structs; +using Penumbra.GameData.DataContainers; namespace Glamourer.State; @@ -25,7 +25,7 @@ namespace Glamourer.State; public class StateListener : IDisposable { private readonly Configuration _config; - private readonly ActorService _actors; + private readonly ActorManager _actors; private readonly ObjectManager _objects; private readonly StateManager _manager; private readonly StateApplier _applier; @@ -50,7 +50,7 @@ public class StateListener : IDisposable private ActorState? _creatingState; private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty; - public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config, + public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, @@ -111,7 +111,7 @@ public class StateListener : IDisposable if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; - _creatingIdentifier = actor.GetIdentifier(_actors.AwaitedService); + _creatingIdentifier = actor.GetIdentifier(_actors); ref var modelId = ref *(uint*)modelPtr; ref var customize = ref *(Customize*)customizePtr; @@ -149,7 +149,7 @@ public class StateListener : IDisposable if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors.AwaitedService, out var identifier) + if (!actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; @@ -169,7 +169,7 @@ public class StateListener : IDisposable return; } - var set = _customizations.AwaitedService.GetList(model.Clan, model.Gender); + var set = _customizations.Service.GetList(model.Clan, model.Gender); foreach (var index in CustomizationExtensions.AllBasic) { if (state[index] is not StateChanged.Source.Fixed) @@ -211,7 +211,7 @@ public class StateListener : IDisposable // then we do not want to use our restricted gear protection // since we assume the player has that gear modded to availability. var locked = false; - if (actor.Identifier(_actors.AwaitedService, out var identifier) + if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor.Value); @@ -238,7 +238,7 @@ public class StateListener : IDisposable var currentItem = state.BaseData.Item(slot); var model = state.ModelData.Weapon(slot); var current = currentItem.Weapon(state.BaseData.Stain(slot)); - if (model.Value == current.Value || !_items.ItemService.AwaitedService.TryGetValue(item, EquipSlot.MainHand, out var changedItem)) + if (model.Value == current.Value || !_items.ItemData.TryGetValue(item, EquipSlot.MainHand, out var changedItem)) continue; var changed = changedItem.Weapon(stain); @@ -272,10 +272,10 @@ public class StateListener : IDisposable return; // Fist weapon gauntlet hack. - if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Set.Id != 0 && _lastFistOffhand.Set.Id != 0) + if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Y.Id != 0 && _lastFistOffhand.Y.Id != 0) weapon.Value = _lastFistOffhand; - if (!actor.Identifier(_actors.AwaitedService, out var identifier) + if (!actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; @@ -308,13 +308,13 @@ public class StateListener : IDisposable var newWeapon = state.ModelData.Weapon(slot); if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene) actorWeapon = newWeapon; - else if (actorWeapon.Set.Id != 0) + else if (actorWeapon.X.Id != 0) actorWeapon = actorWeapon.With(newWeapon.Stain); } // Fist Weapon Offhand hack. - if (slot is EquipSlot.MainHand && weapon.Value.Set.Id is > 1600 and < 1651) - _lastFistOffhand = new CharacterWeapon((SetId)(weapon.Value.Set.Id + 50), weapon.Value.Type, weapon.Value.Variant, + if (slot is EquipSlot.MainHand && weapon.Value.X.Id is > 1600 and < 1651) + _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Value.X.Id + 50), weapon.Value.Y, weapon.Value.Variant, weapon.Value.Stain); _funModule.ApplyFun(actor, ref weapon.Value, slot); @@ -329,7 +329,7 @@ public class StateListener : IDisposable return false; var offhand = actor.GetOffhand(); - return offhand.Variant == 0 && offhand.Set.Id != 0 && armor.Set.Id == offhand.Set.Id; + return offhand.Variant == 0 && offhand.Y.Id != 0 && armor.Set.Id == offhand.Y.Id; } var actorArmor = actor.GetArmor(slot); @@ -413,7 +413,7 @@ public class StateListener : IDisposable if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors.AwaitedService, out var identifier) + if (!actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; @@ -439,7 +439,7 @@ public class StateListener : IDisposable if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors.AwaitedService, out var identifier) + if (!actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; @@ -474,7 +474,7 @@ public class StateListener : IDisposable var change = UpdateState.NoChange; // Fist weapon bug hack - if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().Set.Id is > 1600 and < 1651) + if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().X.Id is > 1600 and < 1651) return UpdateState.NoChange; if (baseData.Stain != weapon.Stain) @@ -483,9 +483,9 @@ public class StateListener : IDisposable change = UpdateState.Change; } - if (baseData.Set.Id != weapon.Set.Id || baseData.Type.Id != weapon.Type.Id || baseData.Variant != weapon.Variant) + if (baseData.X.Id != weapon.X.Id || baseData.Y.Id != weapon.Y.Id || baseData.Variant != weapon.Variant) { - var item = _items.Identify(slot, weapon.Set, weapon.Type, weapon.Variant, + var item = _items.Identify(slot, weapon.X, weapon.Y, weapon.Variant, slot is EquipSlot.OffHand ? state.BaseData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown); state.BaseData.SetItem(slot, item); change = UpdateState.Change; @@ -555,7 +555,7 @@ public class StateListener : IDisposable if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; - if (!actor.Identifier(_actors.AwaitedService, out var identifier)) + if (!actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) @@ -588,7 +588,7 @@ public class StateListener : IDisposable // We do not need to handle fixed designs, // if there is no model that caused a fixed design to exist yet, // we also do not care about the invisible model. - if (!actor.Identifier(_actors.AwaitedService, out var identifier)) + if (!actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) @@ -621,7 +621,7 @@ public class StateListener : IDisposable // We do not need to handle fixed designs, // if there is no model that caused a fixed design to exist yet, // we also do not care about the invisible model. - if (!actor.Identifier(_actors.AwaitedService, out var identifier)) + if (!actor.Identifier(_actors, out var identifier)) return; if (!_manager.TryGetValue(identifier, out var state)) diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 68c9cfc..8446288 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -12,17 +12,17 @@ using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.Structs; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.State; -public class StateManager(ActorService _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor, +public class StateManager(ActorManager _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor, HumanModelList _humans, ICondition _condition, IClientState _clientState) : IReadOnlyDictionary { - private readonly Dictionary _states = new(); + private readonly Dictionary _states = []; public IEnumerator> GetEnumerator() => _states.GetEnumerator(); @@ -50,7 +50,7 @@ public class StateManager(ActorService _actors, ItemManager _items, StateChanged /// public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state) - => GetOrCreate(actor.GetIdentifier(_actors.AwaitedService), actor, out state); + => GetOrCreate(actor.GetIdentifier(_actors), actor, out state); /// Try to obtain or create a new state for an existing actor. Returns false if no state could be created. public unsafe bool GetOrCreate(ActorIdentifier identifier, Actor actor, [NotNullWhen(true)] out ActorState? state) @@ -170,8 +170,8 @@ public class StateManager(ActorService _actors, ItemManager _items, StateChanged } // Set the weapons regardless of source. - var mainItem = _items.Identify(EquipSlot.MainHand, main.Set, main.Type, main.Variant); - var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, off.Variant, mainItem.Type); + var mainItem = _items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant); + var offItem = _items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type); ret.SetItem(EquipSlot.MainHand, mainItem); ret.SetStain(EquipSlot.MainHand, main.Stain); ret.SetItem(EquipSlot.OffHand, offItem); @@ -190,13 +190,13 @@ public class StateManager(ActorService _actors, ItemManager _items, StateChanged /// This is hardcoded in the game. private void FistWeaponHack(ref DesignData ret, ref CharacterWeapon mainhand, ref CharacterWeapon offhand) { - if (mainhand.Set.Id is < 1601 or >= 1651) + if (mainhand.Skeleton.Id is < 1601 or >= 1651) return; - var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, (Variant)offhand.Type.Id); - offhand.Set = (SetId)(mainhand.Set.Id + 50); + var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id); + offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50); offhand.Variant = mainhand.Variant; - offhand.Type = mainhand.Type; + offhand.Weapon = mainhand.Weapon; ret.SetItem(EquipSlot.Hands, gauntlets); ret.SetStain(EquipSlot.Hands, mainhand.Stain); } diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index 19a3f6c..8764438 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -178,11 +178,11 @@ public class CustomizeUnlockManager : IDisposable, ISavable { var ret = new Dictionary(); var sheet = gameData.GetExcelSheet(ClientLanguage.English)!; - foreach (var clan in customizations.AwaitedService.Clans) + foreach (var clan in customizations.Service.Clans) { - foreach (var gender in customizations.AwaitedService.Genders) + foreach (var gender in customizations.Service.Genders) { - var list = customizations.AwaitedService.GetList(clan, gender); + var list = customizations.Service.GetList(clan, gender); foreach (var hair in list.HairStyles) { var x = sheet.FirstOrDefault(f => f.FeatureID == hair.Value.Value); diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index 972a393..fa7d74a 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.UI; using Glamourer.Events; using Glamourer.Services; using Lumina.Excel.GeneratedSheets; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Cabinet = Lumina.Excel.GeneratedSheets.Cabinet; @@ -22,7 +23,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary _unlocked = new(); @@ -45,7 +46,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary Unlockable; public ItemUnlockManager(SaveService saveService, ItemManager items, IClientState clientState, IDataManager gameData, IFramework framework, - ObjectUnlocked @event, IdentifierService identifier, IGameInteropProvider interop) + ObjectUnlocked @event, ObjectIdentification identifier, IGameInteropProvider interop) { interop.InitializeFromAttributes(this); _saveService = saveService; @@ -100,12 +101,12 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary _items.ItemService.AwaitedService.TryGetValue(id, EquipSlot.MainHand, out _), "item"); + id => _items.ItemData.TryGetValue(id, EquipSlot.MainHand, out _), "item"); UpdateModels(version); } @@ -282,7 +283,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary()!; foreach (var row in cabinet) { - if (items.ItemService.AwaitedService.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item)) + if (items.ItemData.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item)) ret.TryAdd(item.ItemId, new UnlockRequirements(row.RowId, 0, 0, 0, UnlockType.Cabinet)); } @@ -290,7 +291,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary()!; foreach (var row in gilShopItem) { - if (!items.ItemService.AwaitedService.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item)) + if (!items.ItemData.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item)) continue; var quest1 = row.QuestRequired[0].Row; @@ -323,10 +324,10 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary Date: Thu, 21 Dec 2023 15:22:31 +0100 Subject: [PATCH 096/786] Again. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 37b9bcf..3787e82 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 37b9bcf6727bd902fdb19a0a8b9d80b94f4cdd10 +Subproject commit 3787e82d1b84d2542b6e4238060d75383a4b12a1 From f2ea5283165b89f83db5372fe8c6f02a4d9594d7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 21 Dec 2023 15:29:36 +0100 Subject: [PATCH 097/786] 1.0.7.0 --- Glamourer/Gui/GlamourerChangelog.cs | 11 +++++++++++ Glamourer/Gui/MainWindow.cs | 1 + 2 files changed, 12 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 3411bf2..ae9227f 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -24,6 +24,7 @@ public class GlamourerChangelog Add1_0_4_0(Changelog); Add1_0_5_0(Changelog); Add1_0_6_0(Changelog); + Add1_0_7_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -44,6 +45,16 @@ public class GlamourerChangelog } } + private static void Add1_0_7_0(Changelog log) + => log.NextVersion("Version 1.0.7.0") + .RegisterHighlight("Glamourer now can set the free company crests on body slots, head slots and shields.") + .RegisterEntry("Fixed an issue with tooltips in certain combo selectors.") + .RegisterEntry("Fixed some issues with Hide Hat Gear and monsters turned into humans.") + .RegisterEntry( + "Hopefully fixed issues with icons used by Glamourer that are modified through Penumbra preventing Glamourer to even start in some cases.") + .RegisterEntry("Those icons might still not appear if they fail to load, but Glamourer should at least still work.", 1) + .RegisterEntry("Pre-emptively fixed a potential issue for the holidays."); + private static void Add1_0_6_0(Changelog log) => log.NextVersion("Version 1.0.6.0") .RegisterHighlight("Added the option to define custom color groups and associate designs with them.") diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index d4e19b2..df50fb5 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -8,6 +8,7 @@ using Glamourer.Events; using Glamourer.Gui.Tabs; using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Gui.Tabs.AutomationTab; +using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.UnlocksTab; using ImGuiNET; From 181f58f75f647051b7187390f758bfa560dc130f Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 21 Dec 2023 14:31:33 +0000 Subject: [PATCH 098/786] [CI] Updating repo.json for 1.0.7.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index cbeeea3..46cd000 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.6.1", - "TestingAssemblyVersion": "1.0.6.3", + "AssemblyVersion": "1.0.7.0", + "TestingAssemblyVersion": "1.0.7.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.6.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.6.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 648b3d4515468d9fd40748f504c6d8d66dbb8643 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 21 Dec 2023 17:05:24 +0100 Subject: [PATCH 099/786] Huh. --- Glamourer/Designs/DesignBase64Migration.cs | 10 +++++----- Glamourer/Designs/DesignConverter.cs | 4 ++-- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 2 +- Glamourer/Interop/WeaponService.cs | 4 ++-- Glamourer/State/StateListener.cs | 16 ++++++++-------- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index c756395..c36c1ea 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -121,9 +121,9 @@ public class DesignBase64Migration data.SetStain(slot, mdl.Stain); } - var main = cur[0].X.Id == 0 + var main = cur[0].Skeleton.Id == 0 ? items.DefaultSword - : items.Identify(EquipSlot.MainHand, cur[0].X, cur[0].Y, cur[0].Variant); + : items.Identify(EquipSlot.MainHand, cur[0].Skeleton, cur[0].Weapon, cur[0].Variant); if (!main.Valid) { Glamourer.Log.Warning("Base64 string invalid, weapon could not be identified."); @@ -138,7 +138,7 @@ public class DesignBase64Migration if (main.PrimaryId.Id is > 1600 and < 1651 && cur[1].Variant == 0) { off = items.Identify(EquipSlot.OffHand, (PrimaryId)(main.PrimaryId.Id + 50), main.SecondaryId, main.Variant, main.Type); - var gauntlet = items.Identify(EquipSlot.Hands, cur[1].X, (Variant)cur[1].Y.Id); + var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Skeleton, (Variant)cur[1].Weapon.Id); if (gauntlet.Valid) { data.SetItem(EquipSlot.Hands, gauntlet); @@ -147,9 +147,9 @@ public class DesignBase64Migration } else { - off = cur[0].X.Id == 0 + off = cur[0].Skeleton.Id == 0 ? ItemManager.NothingItem(FullEquipType.Shield) - : items.Identify(EquipSlot.OffHand, cur[1].X, cur[1].Y, cur[1].Variant, main.Type); + : items.Identify(EquipSlot.OffHand, cur[1].Skeleton, cur[1].Weapon, cur[1].Variant, main.Type); } if (main.Type.ValidOffhand() != FullEquipType.Unknown && !off.Valid) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 038509d..86fd0ce 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -165,7 +165,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (slot, item, armor.Stain); } - var mh = _items.Identify(EquipSlot.MainHand, mainhand.X, mainhand.Y, mainhand.Variant, FullEquipType.Unknown); + var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant, FullEquipType.Unknown); if (!mh.Valid) { Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); @@ -174,7 +174,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (EquipSlot.MainHand, mh, mainhand.Stain); - var oh = _items.Identify(EquipSlot.OffHand, offhand.X, offhand.Y, offhand.Variant, mh.Type); + var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type); if (!oh.Valid) { Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified."); diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index f244fd5..e70cd5d 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -177,7 +177,7 @@ public class PenumbraChangedItemTooltip : IDisposable private bool CanApplyWeapon(EquipSlot slot, EquipItem item) { var main = _objects.Player.GetMainhand(); - var mainItem = _items.Identify(slot, main.X, main.Y, main.Variant); + var mainItem = _items.Identify(slot, main.Skeleton, main.Weapon, main.Variant); if (slot == EquipSlot.MainHand) return item.Type == mainItem.Type; diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 544fe32..7ccf963 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -76,7 +76,7 @@ public unsafe class WeaponService : IDisposable if (tmpWeapon.Value != weapon.Value) { - if (tmpWeapon.X.Id == 0) + if (tmpWeapon.Skeleton.Id == 0) tmpWeapon.Stain = 0; _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); } @@ -119,7 +119,7 @@ public unsafe class WeaponService : IDisposable var mdl = character.Model; var (_, _, mh, oh) = mdl.GetWeapons(character); var value = slot == EquipSlot.OffHand ? oh : mh; - var weapon = value.With(value.X.Id == 0 ? 0 : stain); + var weapon = value.With(value.Skeleton.Id == 0 ? 0 : stain); LoadWeapon(character, slot, weapon); } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 7cc8b82..f5f2e3b 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -272,7 +272,7 @@ public class StateListener : IDisposable return; // Fist weapon gauntlet hack. - if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Y.Id != 0 && _lastFistOffhand.Y.Id != 0) + if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Weapon.Id != 0 && _lastFistOffhand.Weapon.Id != 0) weapon.Value = _lastFistOffhand; if (!actor.Identifier(_actors, out var identifier) @@ -308,13 +308,13 @@ public class StateListener : IDisposable var newWeapon = state.ModelData.Weapon(slot); if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene) actorWeapon = newWeapon; - else if (actorWeapon.X.Id != 0) + else if (actorWeapon.Skeleton.Id != 0) actorWeapon = actorWeapon.With(newWeapon.Stain); } // Fist Weapon Offhand hack. - if (slot is EquipSlot.MainHand && weapon.Value.X.Id is > 1600 and < 1651) - _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Value.X.Id + 50), weapon.Value.Y, weapon.Value.Variant, + if (slot is EquipSlot.MainHand && weapon.Value.Skeleton.Id is > 1600 and < 1651) + _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Value.Skeleton.Id + 50), weapon.Value.Weapon, weapon.Value.Variant, weapon.Value.Stain); _funModule.ApplyFun(actor, ref weapon.Value, slot); @@ -329,7 +329,7 @@ public class StateListener : IDisposable return false; var offhand = actor.GetOffhand(); - return offhand.Variant == 0 && offhand.Y.Id != 0 && armor.Set.Id == offhand.Y.Id; + return offhand.Variant == 0 && offhand.Weapon.Id != 0 && armor.Set.Id == offhand.Weapon.Id; } var actorArmor = actor.GetArmor(slot); @@ -474,7 +474,7 @@ public class StateListener : IDisposable var change = UpdateState.NoChange; // Fist weapon bug hack - if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().X.Id is > 1600 and < 1651) + if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().Skeleton.Id is > 1600 and < 1651) return UpdateState.NoChange; if (baseData.Stain != weapon.Stain) @@ -483,9 +483,9 @@ public class StateListener : IDisposable change = UpdateState.Change; } - if (baseData.X.Id != weapon.X.Id || baseData.Y.Id != weapon.Y.Id || baseData.Variant != weapon.Variant) + if (baseData.Skeleton.Id != weapon.Skeleton.Id || baseData.Weapon.Id != weapon.Weapon.Id || baseData.Variant != weapon.Variant) { - var item = _items.Identify(slot, weapon.X, weapon.Y, weapon.Variant, + var item = _items.Identify(slot, weapon.Skeleton, weapon.Weapon, weapon.Variant, slot is EquipSlot.OffHand ? state.BaseData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown); state.BaseData.SetItem(slot, item); change = UpdateState.Change; From 36970e3275419e127173f1f4bc0cdef99eae2ff8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 21 Dec 2023 23:26:30 +0100 Subject: [PATCH 100/786] Fix weapon changing in designs not working right. --- Glamourer/Gui/Equipment/EquipDrawData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index ce2ba04..8ea3972 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -28,7 +28,7 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot) => new(slot, design.DesignData) { - ItemSetter = i => manager.ChangeEquip(design, slot, i), + ItemSetter = slot.IsEquipmentPiece() ? i => manager.ChangeEquip(design, slot, i) : i => manager.ChangeWeapon(design, slot, i), StainSetter = i => manager.ChangeStain(design, slot, i), ApplySetter = b => manager.ChangeApplyEquip(design, slot, b), ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b), From 987c26a51d619700cfc3ddc0b0ff276c8291b3aa Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Dec 2023 14:20:50 +0100 Subject: [PATCH 101/786] Remove GameData, move a bunch of customization data to Penumbra.GameData and the rest to Glamourer, update accordingly. Some reformatting and cleanup. --- Glamourer.GameData/Customization/CmpFile.cs | 46 ----- Glamourer.GameData/Customization/Customize.cs | 124 ------------ .../Customization/CustomizeFlag.cs | 114 ----------- .../Customization/CustomizeIndex.cs | 183 ------------------ .../Customization/CustomizeValue.cs | 34 ---- .../Customization/DatCharacterFile.cs | 149 -------------- Glamourer.GameData/GameData.cs | 88 --------- .../Glamourer - Backup.GameData.csproj | 61 ------ Glamourer.GameData/Glamourer.GameData.csproj | 72 ------- Glamourer.GameData/Offsets.cs | 7 - Glamourer.GameData/Structs/CrestFlag.cs | 102 ---------- Glamourer.GameData/Structs/EquipFlag.cs | 93 --------- Glamourer.GameData/Structs/Job.cs | 29 --- Glamourer.GameData/Structs/JobGroup.cs | 61 ------ Glamourer.sln | 6 - .../Api/GlamourerIpc.GetCustomization.cs | 6 +- Glamourer/Automation/AutoDesign.cs | 6 +- Glamourer/Automation/AutoDesignApplier.cs | 4 +- Glamourer/Automation/AutoDesignManager.cs | 3 +- Glamourer/Automation/FixedDesignMigrator.cs | 26 +-- Glamourer/Designs/DesignBase.cs | 9 +- Glamourer/Designs/DesignBase64Migration.cs | 12 +- Glamourer/Designs/DesignConverter.cs | 4 +- Glamourer/Designs/DesignData.cs | 56 +++--- Glamourer/Designs/DesignManager.cs | 10 +- .../GameData}/CharaMakeParams.cs | 8 +- Glamourer/GameData/ColorParameters.cs | 50 +++++ .../GameData}/CustomName.cs | 4 +- .../GameData}/CustomizationManager.cs | 2 +- .../GameData}/CustomizationNpcOptions.cs | 6 +- .../GameData}/CustomizationOptions.cs | 56 +++--- .../GameData}/CustomizationSet.cs | 10 +- .../GameData}/CustomizeData.cs | 4 +- .../GameData}/ICustomizationManager.cs | 2 +- .../GameData}/NpcCustomizeSet.cs | 120 ++++++------ .../GameData}/NpcData.cs | 4 +- Glamourer/Glamourer.csproj | 5 +- .../CustomizationDrawer.Color.cs | 12 +- .../CustomizationDrawer.GenderRace.cs | 2 +- .../Customization/CustomizationDrawer.Icon.cs | 20 +- .../CustomizationDrawer.Simple.cs | 8 +- .../Gui/Customization/CustomizationDrawer.cs | 29 ++- Glamourer/Gui/DesignCombo.cs | 3 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 5 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 28 ++- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 95 ++++----- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 2 +- .../DebugTab/CustomizationServicePanel.cs | 3 +- .../Tabs/DebugTab/CustomizationUnlockPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs | 5 +- .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 2 - .../Gui/Tabs/DebugTab/DesignTesterPanel.cs | 8 +- Glamourer/Gui/Tabs/DebugTab/JobPanel.cs | 4 +- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 30 ++- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 17 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 - Glamourer/Gui/Tabs/NpcCombo.cs | 2 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 2 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 1 - Glamourer/Gui/ToggleDrawData.cs | 1 - Glamourer/Gui/UiHelpers.cs | 4 +- Glamourer/Interop/ChangeCustomizeService.cs | 8 +- Glamourer/Interop/CharaFile/CharaFile.cs | 16 +- Glamourer/Interop/CharaFile/CmaFile.cs | 4 +- Glamourer/Interop/CrestService.cs | 2 - Glamourer/Interop/ImportService.cs | 9 +- Glamourer/Interop/JobService.cs | 19 +- Glamourer/Interop/Structs/Actor.cs | 6 +- Glamourer/Interop/Structs/Model.cs | 5 +- Glamourer/Interop/UpdateSlotService.cs | 1 + Glamourer/Interop/WeaponService.cs | 10 +- Glamourer/Services/CommandService.cs | 2 - Glamourer/Services/CustomizationService.cs | 14 +- Glamourer/Services/ServiceManager.cs | 2 +- Glamourer/State/ActorState.cs | 8 +- Glamourer/State/FunModule.cs | 13 +- Glamourer/State/StateApplier.cs | 14 +- Glamourer/State/StateEditor.cs | 10 +- Glamourer/State/StateListener.cs | 14 +- Glamourer/State/StateManager.cs | 36 ++-- Glamourer/State/WorldSets.cs | 1 - Glamourer/Unlocks/CustomizeUnlockManager.cs | 5 +- Penumbra.GameData | 2 +- 83 files changed, 444 insertions(+), 1620 deletions(-) delete mode 100644 Glamourer.GameData/Customization/CmpFile.cs delete mode 100644 Glamourer.GameData/Customization/Customize.cs delete mode 100644 Glamourer.GameData/Customization/CustomizeFlag.cs delete mode 100644 Glamourer.GameData/Customization/CustomizeIndex.cs delete mode 100644 Glamourer.GameData/Customization/CustomizeValue.cs delete mode 100644 Glamourer.GameData/Customization/DatCharacterFile.cs delete mode 100644 Glamourer.GameData/GameData.cs delete mode 100644 Glamourer.GameData/Glamourer - Backup.GameData.csproj delete mode 100644 Glamourer.GameData/Glamourer.GameData.csproj delete mode 100644 Glamourer.GameData/Offsets.cs delete mode 100644 Glamourer.GameData/Structs/CrestFlag.cs delete mode 100644 Glamourer.GameData/Structs/EquipFlag.cs delete mode 100644 Glamourer.GameData/Structs/Job.cs delete mode 100644 Glamourer.GameData/Structs/JobGroup.cs rename {Glamourer.GameData/Customization => Glamourer/GameData}/CharaMakeParams.cs (94%) create mode 100644 Glamourer/GameData/ColorParameters.cs rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomName.cs (78%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomizationManager.cs (93%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomizationNpcOptions.cs (92%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomizationOptions.cs (94%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomizationSet.cs (95%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/CustomizeData.cs (89%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/ICustomizationManager.cs (90%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/NpcCustomizeSet.cs (63%) rename {Glamourer.GameData/Customization => Glamourer/GameData}/NpcData.cs (93%) diff --git a/Glamourer.GameData/Customization/CmpFile.cs b/Glamourer.GameData/Customization/CmpFile.cs deleted file mode 100644 index d62e5da..0000000 --- a/Glamourer.GameData/Customization/CmpFile.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Dalamud.Logging; -using Dalamud.Plugin.Services; - -namespace Glamourer.Customization; - -// Convert the Human.Cmp file into color sets. -// If the file can not be read due to TexTools corruption, create a 0-array of size MinSize. -internal class CmpFile -{ - private readonly Lumina.Data.FileResource? _file; - private readonly uint[] _rgbaColors; - - // No error checking since only called internally. - public IEnumerable GetSlice(int offset, int count) - => _rgbaColors.Length >= offset + count ? _rgbaColors.Skip(offset).Take(count) : Enumerable.Repeat(0u, count); - - public bool Valid - => _file != null; - - public CmpFile(IDataManager gameData, IPluginLog log) - { - try - { - _file = gameData.GetFile("chara/xls/charamake/human.cmp")!; - _rgbaColors = new uint[_file.Data.Length >> 2]; - for (var i = 0; i < _file.Data.Length; i += 4) - { - _rgbaColors[i >> 2] = _file.Data[i] - | (uint)(_file.Data[i + 1] << 8) - | (uint)(_file.Data[i + 2] << 16) - | (uint)(_file.Data[i + 3] << 24); - } - } - catch (Exception e) - { - log.Error("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n" - + "======== This usually indicates an error with your index files caused by TexTools modifications.\n" - + "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e); - _file = null; - _rgbaColors = Array.Empty(); - } - } -} diff --git a/Glamourer.GameData/Customization/Customize.cs b/Glamourer.GameData/Customization/Customize.cs deleted file mode 100644 index b19ef22..0000000 --- a/Glamourer.GameData/Customization/Customize.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer.Customization; - -public unsafe struct Customize -{ - public CustomizeArray Data; - - public Customize(in CustomizeArray data) - { - Data = data.Clone(); - } - - public Race Race - { - get => (Race)Data.Get(CustomizeIndex.Race).Value; - set => Data.Set(CustomizeIndex.Race, (CustomizeValue)(byte)value); - } - - public Gender Gender - { - get => (Gender)Data.Get(CustomizeIndex.Gender).Value + 1; - set => Data.Set(CustomizeIndex.Gender, (CustomizeValue)(byte)value - 1); - } - - public CustomizeValue BodyType - { - get => Data.Get(CustomizeIndex.BodyType); - set => Data.Set(CustomizeIndex.BodyType, value); - } - - public SubRace Clan - { - get => (SubRace)Data.Get(CustomizeIndex.Clan).Value; - set => Data.Set(CustomizeIndex.Clan, (CustomizeValue)(byte)value); - } - - public CustomizeValue Face - { - get => Data.Get(CustomizeIndex.Face); - set => Data.Set(CustomizeIndex.Face, value); - } - - - public static readonly Customize Default = GenerateDefault(); - public static readonly Customize Empty = new(); - - public CustomizeValue Get(CustomizeIndex index) - => Data.Get(index); - - public bool Set(CustomizeIndex flag, CustomizeValue index) - => Data.Set(flag, index); - - public bool Equals(Customize other) - => Equals(Data, other.Data); - - public CustomizeValue this[CustomizeIndex index] - { - get => Get(index); - set => Set(index, value); - } - - private static Customize GenerateDefault() - { - var ret = new Customize - { - Race = Race.Hyur, - Clan = SubRace.Midlander, - Gender = Gender.Male, - }; - ret.Set(CustomizeIndex.BodyType, (CustomizeValue)1); - ret.Set(CustomizeIndex.Height, (CustomizeValue)50); - ret.Set(CustomizeIndex.Face, (CustomizeValue)1); - ret.Set(CustomizeIndex.Hairstyle, (CustomizeValue)1); - ret.Set(CustomizeIndex.SkinColor, (CustomizeValue)1); - ret.Set(CustomizeIndex.EyeColorRight, (CustomizeValue)1); - ret.Set(CustomizeIndex.HighlightsColor, (CustomizeValue)1); - ret.Set(CustomizeIndex.TattooColor, (CustomizeValue)1); - ret.Set(CustomizeIndex.Eyebrows, (CustomizeValue)1); - ret.Set(CustomizeIndex.EyeColorLeft, (CustomizeValue)1); - ret.Set(CustomizeIndex.EyeShape, (CustomizeValue)1); - ret.Set(CustomizeIndex.Nose, (CustomizeValue)1); - ret.Set(CustomizeIndex.Jaw, (CustomizeValue)1); - ret.Set(CustomizeIndex.Mouth, (CustomizeValue)1); - ret.Set(CustomizeIndex.LipColor, (CustomizeValue)1); - ret.Set(CustomizeIndex.MuscleMass, (CustomizeValue)50); - ret.Set(CustomizeIndex.TailShape, (CustomizeValue)1); - ret.Set(CustomizeIndex.BustSize, (CustomizeValue)50); - ret.Set(CustomizeIndex.FacePaint, (CustomizeValue)1); - ret.Set(CustomizeIndex.FacePaintColor, (CustomizeValue)1); - return ret; - } - - public void Load(Customize other) - => Data.Read(&other.Data); - - public readonly void Write(nint target) - => Data.Write((void*)target); - - public bool LoadBase64(string data) - => Data.LoadBase64(data); - - public readonly string WriteBase64() - => Data.WriteBase64(); - - public static CustomizeFlag Compare(Customize lhs, Customize rhs) - { - CustomizeFlag ret = 0; - foreach (var idx in Enum.GetValues()) - { - var l = lhs[idx]; - var r = rhs[idx]; - if (l.Value != r.Value) - ret |= idx.ToFlag(); - } - - return ret; - } - - public override string ToString() - => Data.ToString(); -} diff --git a/Glamourer.GameData/Customization/CustomizeFlag.cs b/Glamourer.GameData/Customization/CustomizeFlag.cs deleted file mode 100644 index 44b6cee..0000000 --- a/Glamourer.GameData/Customization/CustomizeFlag.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Runtime.CompilerServices; - -namespace Glamourer.Customization; - -[Flags] -public enum CustomizeFlag : ulong -{ - Invalid = 0, - Race = 1ul << CustomizeIndex.Race, - Gender = 1ul << CustomizeIndex.Gender, - BodyType = 1ul << CustomizeIndex.BodyType, - Height = 1ul << CustomizeIndex.Height, - Clan = 1ul << CustomizeIndex.Clan, - Face = 1ul << CustomizeIndex.Face, - Hairstyle = 1ul << CustomizeIndex.Hairstyle, - Highlights = 1ul << CustomizeIndex.Highlights, - SkinColor = 1ul << CustomizeIndex.SkinColor, - EyeColorRight = 1ul << CustomizeIndex.EyeColorRight, - HairColor = 1ul << CustomizeIndex.HairColor, - HighlightsColor = 1ul << CustomizeIndex.HighlightsColor, - FacialFeature1 = 1ul << CustomizeIndex.FacialFeature1, - FacialFeature2 = 1ul << CustomizeIndex.FacialFeature2, - FacialFeature3 = 1ul << CustomizeIndex.FacialFeature3, - FacialFeature4 = 1ul << CustomizeIndex.FacialFeature4, - FacialFeature5 = 1ul << CustomizeIndex.FacialFeature5, - FacialFeature6 = 1ul << CustomizeIndex.FacialFeature6, - FacialFeature7 = 1ul << CustomizeIndex.FacialFeature7, - LegacyTattoo = 1ul << CustomizeIndex.LegacyTattoo, - TattooColor = 1ul << CustomizeIndex.TattooColor, - Eyebrows = 1ul << CustomizeIndex.Eyebrows, - EyeColorLeft = 1ul << CustomizeIndex.EyeColorLeft, - EyeShape = 1ul << CustomizeIndex.EyeShape, - SmallIris = 1ul << CustomizeIndex.SmallIris, - Nose = 1ul << CustomizeIndex.Nose, - Jaw = 1ul << CustomizeIndex.Jaw, - Mouth = 1ul << CustomizeIndex.Mouth, - Lipstick = 1ul << CustomizeIndex.Lipstick, - LipColor = 1ul << CustomizeIndex.LipColor, - MuscleMass = 1ul << CustomizeIndex.MuscleMass, - TailShape = 1ul << CustomizeIndex.TailShape, - BustSize = 1ul << CustomizeIndex.BustSize, - FacePaint = 1ul << CustomizeIndex.FacePaint, - FacePaintReversed = 1ul << CustomizeIndex.FacePaintReversed, - FacePaintColor = 1ul << CustomizeIndex.FacePaintColor, -} - -public static class CustomizeFlagExtensions -{ - public const CustomizeFlag All = (CustomizeFlag)(((ulong)CustomizeFlag.FacePaintColor << 1) - 1ul); - public const CustomizeFlag AllRelevant = All & ~CustomizeFlag.BodyType & ~CustomizeFlag.Race; - - public const CustomizeFlag RedrawRequired = - CustomizeFlag.Race | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.Face | CustomizeFlag.BodyType; - - public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizationSet set) - => flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender); - - public static bool RequiresRedraw(this CustomizeFlag flags) - => (flags & RedrawRequired) != 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static CustomizeIndex ToIndex(this CustomizeFlag flag) - => flag switch - { - CustomizeFlag.Race => CustomizeIndex.Race, - CustomizeFlag.Gender => CustomizeIndex.Gender, - CustomizeFlag.BodyType => CustomizeIndex.BodyType, - CustomizeFlag.Height => CustomizeIndex.Height, - CustomizeFlag.Clan => CustomizeIndex.Clan, - CustomizeFlag.Face => CustomizeIndex.Face, - CustomizeFlag.Hairstyle => CustomizeIndex.Hairstyle, - CustomizeFlag.Highlights => CustomizeIndex.Highlights, - CustomizeFlag.SkinColor => CustomizeIndex.SkinColor, - CustomizeFlag.EyeColorRight => CustomizeIndex.EyeColorRight, - CustomizeFlag.HairColor => CustomizeIndex.HairColor, - CustomizeFlag.HighlightsColor => CustomizeIndex.HighlightsColor, - CustomizeFlag.FacialFeature1 => CustomizeIndex.FacialFeature1, - CustomizeFlag.FacialFeature2 => CustomizeIndex.FacialFeature2, - CustomizeFlag.FacialFeature3 => CustomizeIndex.FacialFeature3, - CustomizeFlag.FacialFeature4 => CustomizeIndex.FacialFeature4, - CustomizeFlag.FacialFeature5 => CustomizeIndex.FacialFeature5, - CustomizeFlag.FacialFeature6 => CustomizeIndex.FacialFeature6, - CustomizeFlag.FacialFeature7 => CustomizeIndex.FacialFeature7, - CustomizeFlag.LegacyTattoo => CustomizeIndex.LegacyTattoo, - CustomizeFlag.TattooColor => CustomizeIndex.TattooColor, - CustomizeFlag.Eyebrows => CustomizeIndex.Eyebrows, - CustomizeFlag.EyeColorLeft => CustomizeIndex.EyeColorLeft, - CustomizeFlag.EyeShape => CustomizeIndex.EyeShape, - CustomizeFlag.SmallIris => CustomizeIndex.SmallIris, - CustomizeFlag.Nose => CustomizeIndex.Nose, - CustomizeFlag.Jaw => CustomizeIndex.Jaw, - CustomizeFlag.Mouth => CustomizeIndex.Mouth, - CustomizeFlag.Lipstick => CustomizeIndex.Lipstick, - CustomizeFlag.LipColor => CustomizeIndex.LipColor, - CustomizeFlag.MuscleMass => CustomizeIndex.MuscleMass, - CustomizeFlag.TailShape => CustomizeIndex.TailShape, - CustomizeFlag.BustSize => CustomizeIndex.BustSize, - CustomizeFlag.FacePaint => CustomizeIndex.FacePaint, - CustomizeFlag.FacePaintReversed => CustomizeIndex.FacePaintReversed, - CustomizeFlag.FacePaintColor => CustomizeIndex.FacePaintColor, - _ => (CustomizeIndex)byte.MaxValue, - }; - - public static bool SetIfDifferent(ref this CustomizeFlag flags, CustomizeFlag flag, bool value) - { - var newValue = value ? flags | flag : flags & ~flag; - if (newValue == flags) - return false; - - flags = newValue; - return true; - } -} diff --git a/Glamourer.GameData/Customization/CustomizeIndex.cs b/Glamourer.GameData/Customization/CustomizeIndex.cs deleted file mode 100644 index cbd22ed..0000000 --- a/Glamourer.GameData/Customization/CustomizeIndex.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace Glamourer.Customization; - -public enum CustomizeIndex : byte -{ - Race, - Gender, - BodyType, - Height, - Clan, - Face, - Hairstyle, - Highlights, - SkinColor, - EyeColorRight, - HairColor, - HighlightsColor, - FacialFeature1, - FacialFeature2, - FacialFeature3, - FacialFeature4, - FacialFeature5, - FacialFeature6, - FacialFeature7, - LegacyTattoo, - TattooColor, - Eyebrows, - EyeColorLeft, - EyeShape, - SmallIris, - Nose, - Jaw, - Mouth, - Lipstick, - LipColor, - MuscleMass, - TailShape, - BustSize, - FacePaint, - FacePaintReversed, - FacePaintColor, -} - -public static class CustomizationExtensions -{ - public const int NumIndices = (int)CustomizeIndex.FacePaintColor + 1; - - public static readonly CustomizeIndex[] All = Enum.GetValues() - .Where(v => v is not CustomizeIndex.Race and not CustomizeIndex.BodyType).ToArray(); - - public static readonly CustomizeIndex[] AllBasic = All - .Where(v => v is not CustomizeIndex.Gender and not CustomizeIndex.Clan).ToArray(); - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static (int ByteIdx, byte Mask) ToByteAndMask(this CustomizeIndex index) - => index switch - { - CustomizeIndex.Race => (0, 0xFF), - CustomizeIndex.Gender => (1, 0xFF), - CustomizeIndex.BodyType => (2, 0xFF), - CustomizeIndex.Height => (3, 0xFF), - CustomizeIndex.Clan => (4, 0xFF), - CustomizeIndex.Face => (5, 0xFF), - CustomizeIndex.Hairstyle => (6, 0xFF), - CustomizeIndex.Highlights => (7, 0x80), - CustomizeIndex.SkinColor => (8, 0xFF), - CustomizeIndex.EyeColorRight => (9, 0xFF), - CustomizeIndex.HairColor => (10, 0xFF), - CustomizeIndex.HighlightsColor => (11, 0xFF), - CustomizeIndex.FacialFeature1 => (12, 0x01), - CustomizeIndex.FacialFeature2 => (12, 0x02), - CustomizeIndex.FacialFeature3 => (12, 0x04), - CustomizeIndex.FacialFeature4 => (12, 0x08), - CustomizeIndex.FacialFeature5 => (12, 0x10), - CustomizeIndex.FacialFeature6 => (12, 0x20), - CustomizeIndex.FacialFeature7 => (12, 0x40), - CustomizeIndex.LegacyTattoo => (12, 0x80), - CustomizeIndex.TattooColor => (13, 0xFF), - CustomizeIndex.Eyebrows => (14, 0xFF), - CustomizeIndex.EyeColorLeft => (15, 0xFF), - CustomizeIndex.EyeShape => (16, 0x7F), - CustomizeIndex.SmallIris => (16, 0x80), - CustomizeIndex.Nose => (17, 0xFF), - CustomizeIndex.Jaw => (18, 0xFF), - CustomizeIndex.Mouth => (19, 0x7F), - CustomizeIndex.Lipstick => (19, 0x80), - CustomizeIndex.LipColor => (20, 0xFF), - CustomizeIndex.MuscleMass => (21, 0xFF), - CustomizeIndex.TailShape => (22, 0xFF), - CustomizeIndex.BustSize => (23, 0xFF), - CustomizeIndex.FacePaint => (24, 0x7F), - CustomizeIndex.FacePaintReversed => (24, 0x80), - CustomizeIndex.FacePaintColor => (25, 0xFF), - _ => (0, 0x00), - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static CustomizeFlag ToFlag(this CustomizeIndex index) - => (CustomizeFlag)(1ul << (int)index); - - public static string ToDefaultName(this CustomizeIndex customizeIndex) - => customizeIndex switch - { - CustomizeIndex.Race => "Race", - CustomizeIndex.Gender => "Gender", - CustomizeIndex.BodyType => "Body Type", - CustomizeIndex.Height => "Height", - CustomizeIndex.Clan => "Clan", - CustomizeIndex.Face => "Head Style", - CustomizeIndex.Hairstyle => "Hair Style", - CustomizeIndex.Highlights => "Enable Highlights", - CustomizeIndex.SkinColor => "Skin Color", - CustomizeIndex.EyeColorRight => "Left Eye", // inverted due to compatibility fuckup. - CustomizeIndex.HairColor => "Hair Color", - CustomizeIndex.HighlightsColor => "Highlights Color", - CustomizeIndex.TattooColor => "Tattoo Color", - CustomizeIndex.Eyebrows => "Eyebrow Style", - CustomizeIndex.EyeColorLeft => "Right Eye", // inverted due to compatibility fuckup. - CustomizeIndex.EyeShape => "Small Pupils", - CustomizeIndex.Nose => "Nose Style", - CustomizeIndex.Jaw => "Jaw Style", - CustomizeIndex.Mouth => "Mouth Style", - CustomizeIndex.MuscleMass => "Muscle Tone", - CustomizeIndex.TailShape => "Tail Shape", - CustomizeIndex.BustSize => "Bust Size", - CustomizeIndex.FacePaint => "Face Paint", - CustomizeIndex.FacePaintColor => "Face Paint Color", - CustomizeIndex.LipColor => "Lip Color", - CustomizeIndex.FacialFeature1 => "Facial Feature 1", - CustomizeIndex.FacialFeature2 => "Facial Feature 2", - CustomizeIndex.FacialFeature3 => "Facial Feature 3", - CustomizeIndex.FacialFeature4 => "Facial Feature 4", - CustomizeIndex.FacialFeature5 => "Facial Feature 5", - CustomizeIndex.FacialFeature6 => "Facial Feature 6", - CustomizeIndex.FacialFeature7 => "Facial Feature 7", - CustomizeIndex.LegacyTattoo => "Legacy Tattoo", - CustomizeIndex.SmallIris => "Small Iris", - CustomizeIndex.Lipstick => "Enable Lipstick", - CustomizeIndex.FacePaintReversed => "Reverse Face Paint", - _ => string.Empty, - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static unsafe CustomizeValue Get(this in Penumbra.GameData.Structs.CustomizeArray data, CustomizeIndex index) - { - var (offset, mask) = index.ToByteAndMask(); - return (CustomizeValue)(data.Data[offset] & mask); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static unsafe bool Set(this ref Penumbra.GameData.Structs.CustomizeArray data, CustomizeIndex index, CustomizeValue value) - { - var (offset, mask) = index.ToByteAndMask(); - return mask != 0xFF - ? SetIfDifferentMasked(ref data.Data[offset], value, mask) - : SetIfDifferent(ref data.Data[offset], value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static bool SetIfDifferentMasked(ref byte oldValue, CustomizeValue newValue, byte mask) - { - var tmp = (byte)(newValue.Value & mask); - tmp = (byte)(tmp | (oldValue & ~mask)); - if (oldValue == tmp) - return false; - - oldValue = tmp; - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static bool SetIfDifferent(ref byte oldValue, CustomizeValue newValue) - { - if (oldValue == newValue.Value) - return false; - - oldValue = newValue.Value; - return true; - } -} diff --git a/Glamourer.GameData/Customization/CustomizeValue.cs b/Glamourer.GameData/Customization/CustomizeValue.cs deleted file mode 100644 index 25b3406..0000000 --- a/Glamourer.GameData/Customization/CustomizeValue.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Glamourer.Customization; - -public record struct CustomizeValue(byte Value) -{ - public static readonly CustomizeValue Zero = new(0); - public static readonly CustomizeValue Max = new(0xFF); - - public static CustomizeValue Bool(bool b) - => b ? Max : Zero; - - public static explicit operator CustomizeValue(byte value) - => new(value); - - public static CustomizeValue operator ++(CustomizeValue v) - => new(++v.Value); - - public static CustomizeValue operator --(CustomizeValue v) - => new(--v.Value); - - public static bool operator <(CustomizeValue v, int count) - => v.Value < count; - - public static bool operator >(CustomizeValue v, int count) - => v.Value > count; - - public static CustomizeValue operator +(CustomizeValue v, int rhs) - => new((byte)(v.Value + rhs)); - - public static CustomizeValue operator -(CustomizeValue v, int rhs) - => new((byte)(v.Value - rhs)); - - public override string ToString() - => Value.ToString(); -} diff --git a/Glamourer.GameData/Customization/DatCharacterFile.cs b/Glamourer.GameData/Customization/DatCharacterFile.cs deleted file mode 100644 index f33af69..0000000 --- a/Glamourer.GameData/Customization/DatCharacterFile.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using Dalamud.Memory; -using Penumbra.GameData.Structs; - -namespace Glamourer.Customization; - -[StructLayout(LayoutKind.Explicit, Size = Size)] -public unsafe struct DatCharacterFile -{ - public const int Size = 4 + 4 + 4 + 4 + CustomizeArray.Size + 2 + 4 + 41 * 4; // 212 - - [FieldOffset(0)] - private fixed byte _data[Size]; - - [FieldOffset(0)] - public readonly uint Magic = 0x2013FF14; - - [FieldOffset(4)] - public readonly uint Version = 0x05; - - [FieldOffset(8)] - private uint _checksum; - - [FieldOffset(12)] - private readonly uint _padding = 0; - - [FieldOffset(16)] - private CustomizeArray _customize; - - [FieldOffset(16 + CustomizeArray.Size)] - private ushort _voice; - - [FieldOffset(16 + CustomizeArray.Size + 2)] - private uint _timeStamp; - - [FieldOffset(Size - 41 * 4)] - private fixed byte _description[41 * 4]; - - public readonly void Write(Stream stream) - { - for (var i = 0; i < Size; ++i) - stream.WriteByte(_data[i]); - } - - public static bool Read(Stream stream, out DatCharacterFile file) - { - if (stream.Length - stream.Position != Size) - { - file = default; - return false; - } - - file = new DatCharacterFile(stream); - return true; - } - - private DatCharacterFile(Stream stream) - { - for (var i = 0; i < Size; ++i) - _data[i] = (byte)stream.ReadByte(); - } - - public DatCharacterFile(in Customize customize, byte voice, string text) - { - SetCustomize(customize); - SetVoice(voice); - SetTime(DateTimeOffset.UtcNow); - SetDescription(text); - _checksum = CalculateChecksum(); - } - - public readonly uint CalculateChecksum() - { - var ret = 0u; - for (var i = 16; i < Size; i++) - ret ^= (uint)(_data[i] << ((i - 16) % 24)); - return ret; - } - - public readonly uint Checksum - => _checksum; - - public Customize Customize - { - readonly get => new(_customize); - set - { - SetCustomize(value); - _checksum = CalculateChecksum(); - } - } - - public ushort Voice - { - readonly get => _voice; - set - { - SetVoice(value); - _checksum = CalculateChecksum(); - } - } - - public string Description - { - readonly get - { - fixed (byte* ptr = _description) - { - return MemoryHelper.ReadStringNullTerminated((nint)ptr); - } - } - set - { - SetDescription(value); - _checksum = CalculateChecksum(); - } - } - - public DateTimeOffset Time - { - readonly get => DateTimeOffset.FromUnixTimeSeconds(_timeStamp); - set - { - SetTime(value); - _checksum = CalculateChecksum(); - } - } - - private void SetTime(DateTimeOffset time) - => _timeStamp = (uint)time.ToUnixTimeSeconds(); - - private void SetCustomize(in Customize customize) - => _customize = customize.Data.Clone(); - - private void SetVoice(ushort voice) - => _voice = voice; - - private void SetDescription(string text) - { - fixed (byte* ptr = _description) - { - var span = new Span(ptr, 41 * 4); - Encoding.UTF8.GetBytes(text.AsSpan(0, Math.Min(40, text.Length)), span); - } - } -} diff --git a/Glamourer.GameData/GameData.cs b/Glamourer.GameData/GameData.cs deleted file mode 100644 index d5638b0..0000000 --- a/Glamourer.GameData/GameData.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Dalamud; -using Dalamud.Plugin.Services; -using Glamourer.Structs; -using Lumina.Excel.GeneratedSheets; - -namespace Glamourer; - -public static class GameData -{ - private static Dictionary? _jobs; - private static Dictionary? _jobGroups; - private static JobGroup[]? _allJobGroups; - - public static IReadOnlyDictionary Jobs(IDataManager dataManager) - { - if (_jobs != null) - return _jobs; - - var sheet = dataManager.GetExcelSheet()!; - _jobs = sheet.Where(j => j.Abbreviation.RawData.Length > 0).ToDictionary(j => (byte)j.RowId, j => new Job(j)); - return _jobs; - } - - public static IReadOnlyList AllJobGroups(IDataManager dataManager) - { - if (_allJobGroups != null) - return _allJobGroups; - - var sheet = dataManager.GetExcelSheet()!; - var jobs = dataManager.GetExcelSheet(ClientLanguage.English)!; - _allJobGroups = sheet.Select(j => new JobGroup(j, jobs)).ToArray(); - return _allJobGroups; - } - - public static IReadOnlyDictionary JobGroups(IDataManager dataManager) - { - if (_jobGroups != null) - return _jobGroups; - - static bool ValidIndex(uint idx) - { - if (idx is > 0 and < 36) - return true; - - return idx switch - { - // Single jobs and big groups - 91 => true, - 92 => true, - 96 => true, - 98 => true, - 99 => true, - 111 => true, - 112 => true, - 129 => true, - 149 => true, - 150 => true, - 156 => true, - 157 => true, - 158 => true, - 159 => true, - 180 => true, - 181 => true, - 188 => true, - 189 => true, - - // Class + Job - 38 => true, - 41 => true, - 44 => true, - 47 => true, - 50 => true, - 53 => true, - 55 => true, - 69 => true, - 68 => true, - 93 => true, - _ => false, - }; - } - - _jobGroups = AllJobGroups(dataManager).Where(j => ValidIndex(j.Id)) - .ToDictionary(j => (ushort) j.Id, j => j); - return _jobGroups; - } -} diff --git a/Glamourer.GameData/Glamourer - Backup.GameData.csproj b/Glamourer.GameData/Glamourer - Backup.GameData.csproj deleted file mode 100644 index 74d1588..0000000 --- a/Glamourer.GameData/Glamourer - Backup.GameData.csproj +++ /dev/null @@ -1,61 +0,0 @@ - - - net472 - preview - Glamourer - Glamourer.GameData - 1.0.0.0 - 1.0.0.0 - SoftOtter - Glamourer - Copyright © 2020 - true - Library - 4 - true - enable - bin\$(Configuration)\ - $(MSBuildWarningsAsMessages);MSB3277 - - - - full - DEBUG;TRACE - - - - pdbonly - - - - $(MSBuildWarningsAsMessages);MSB3277 - - - - - $(DALAMUD_ROOT)\Dalamud.dll - ..\libs\Dalamud.dll - $(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll - False - - - $(DALAMUD_ROOT)\Lumina.dll - ..\libs\Lumina.dll - $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll - False - - - $(DALAMUD_ROOT)\Lumina.Excel.dll - ..\libs\Lumina.Excel.dll - $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll - False - - - ..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.GameData.dll - - - - - - - diff --git a/Glamourer.GameData/Glamourer.GameData.csproj b/Glamourer.GameData/Glamourer.GameData.csproj deleted file mode 100644 index 92bf301..0000000 --- a/Glamourer.GameData/Glamourer.GameData.csproj +++ /dev/null @@ -1,72 +0,0 @@ - - - net7.0-windows - preview - x64 - Glamourer - Glamourer.GameData - 1.0.0.0 - 1.0.0.0 - SoftOtter - Glamourer - Copyright © 2020 - true - Library - 4 - true - enable - bin\$(Configuration)\ - $(MSBuildWarningsAsMessages);MSB3277 - false - false - - - - full - DEBUG;TRACE - - - - pdbonly - - - - $(MSBuildWarningsAsMessages);MSB3277 - - - - $(AppData)\XIVLauncher\addon\Hooks\dev\ - - - - - $(DalamudLibPath)Dalamud.dll - False - - - $(DalamudLibPath)FFXIVClientStructs.dll - False - - - $(DalamudLibPath)Lumina.dll - False - - - $(DalamudLibPath)Lumina.Excel.dll - False - - - $(DalamudLibPath)Newtonsoft.Json.dll - False - - - $(DalamudLibPath)ImGuiScene.dll - False - - - - - - - - \ No newline at end of file diff --git a/Glamourer.GameData/Offsets.cs b/Glamourer.GameData/Offsets.cs deleted file mode 100644 index 71151d9..0000000 --- a/Glamourer.GameData/Offsets.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Glamourer; - -public static class Sigs -{ - public const string ChangeJob = "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 80 61"; - public const string FlagSlotForUpdate = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A"; -} diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs deleted file mode 100644 index 61ccc7e..0000000 --- a/Glamourer.GameData/Structs/CrestFlag.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Penumbra.GameData.Enums; - -namespace Glamourer.Structs; - -[Flags] -public enum CrestFlag : ushort -{ - OffHand = 0x0001, - Head = 0x0002, - Body = 0x0004, - Hands = 0x0008, - Legs = 0x0010, - Feet = 0x0020, - Ears = 0x0040, - Neck = 0x0080, - Wrists = 0x0100, - RFinger = 0x0200, - LFinger = 0x0400, - MainHand = 0x0800, -} - -public enum CrestType : byte -{ - None, - Human, - Mainhand, - Offhand, -}; - -public static class CrestExtensions -{ - public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Mainhand << 1) - 1); - public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand; - - public static readonly IReadOnlyList AllRelevantSet = Enum.GetValues().Where(f => AllRelevant.HasFlag(f)).ToArray(); - - public static int ToInternalIndex(this CrestFlag flag) - => flag switch - { - CrestFlag.Head => 0, - CrestFlag.Body => 1, - CrestFlag.OffHand => 2, - _ => -1, - }; - - public static (CrestType Type, byte Index) ToIndex(this CrestFlag flag) - => flag switch - { - CrestFlag.Head => (CrestType.Human, 0), - CrestFlag.Body => (CrestType.Human, 1), - CrestFlag.Hands => (CrestType.Human, 2), - CrestFlag.Legs => (CrestType.Human, 3), - CrestFlag.Feet => (CrestType.Human, 4), - CrestFlag.Ears => (CrestType.None, 0), - CrestFlag.Neck => (CrestType.None, 0), - CrestFlag.Wrists => (CrestType.None, 0), - CrestFlag.RFinger => (CrestType.None, 0), - CrestFlag.LFinger => (CrestType.None, 0), - CrestFlag.MainHand => (CrestType.None, 0), - CrestFlag.OffHand => (CrestType.Offhand, 0), - _ => (CrestType.None, 0), - }; - - public static CrestFlag ToCrestFlag(this EquipSlot slot) - => slot switch - { - EquipSlot.MainHand => CrestFlag.MainHand, - EquipSlot.OffHand => CrestFlag.OffHand, - EquipSlot.Head => CrestFlag.Head, - EquipSlot.Body => CrestFlag.Body, - EquipSlot.Hands => CrestFlag.Hands, - EquipSlot.Legs => CrestFlag.Legs, - EquipSlot.Feet => CrestFlag.Feet, - EquipSlot.Ears => CrestFlag.Ears, - EquipSlot.Neck => CrestFlag.Neck, - EquipSlot.Wrists => CrestFlag.Wrists, - EquipSlot.RFinger => CrestFlag.RFinger, - EquipSlot.LFinger => CrestFlag.LFinger, - _ => 0, - }; - - public static string ToLabel(this CrestFlag flag) - => flag switch - { - CrestFlag.Head => "Head", - CrestFlag.Body => "Chest", - CrestFlag.Hands => "Gauntlets", - CrestFlag.Legs => "Pants", - CrestFlag.Feet => "Boot", - CrestFlag.Ears => "Earrings", - CrestFlag.Neck => "Necklace", - CrestFlag.Wrists => "Bracelet", - CrestFlag.RFinger => "Right Ring", - CrestFlag.LFinger => "Left Ring", - CrestFlag.MainHand => "Weapon", - CrestFlag.OffHand => "Shield", - _ => string.Empty, - }; -} diff --git a/Glamourer.GameData/Structs/EquipFlag.cs b/Glamourer.GameData/Structs/EquipFlag.cs deleted file mode 100644 index eaacbac..0000000 --- a/Glamourer.GameData/Structs/EquipFlag.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using Penumbra.GameData.Enums; - -namespace Glamourer.Structs; - -[Flags] -public enum EquipFlag : uint -{ - Head = 0x00000001, - Body = 0x00000002, - Hands = 0x00000004, - Legs = 0x00000008, - Feet = 0x00000010, - Ears = 0x00000020, - Neck = 0x00000040, - Wrist = 0x00000080, - RFinger = 0x00000100, - LFinger = 0x00000200, - Mainhand = 0x00000400, - Offhand = 0x00000800, - HeadStain = 0x00001000, - BodyStain = 0x00002000, - HandsStain = 0x00004000, - LegsStain = 0x00008000, - FeetStain = 0x00010000, - EarsStain = 0x00020000, - NeckStain = 0x00040000, - WristStain = 0x00080000, - RFingerStain = 0x00100000, - LFingerStain = 0x00200000, - MainhandStain = 0x00400000, - OffhandStain = 0x00800000, -} - -public static class EquipFlagExtensions -{ - public const EquipFlag All = (EquipFlag)(((uint)EquipFlag.OffhandStain << 1) - 1); - public const int NumEquipFlags = 24; - - public static EquipFlag ToFlag(this EquipSlot slot) - => slot switch - { - EquipSlot.MainHand => EquipFlag.Mainhand, - EquipSlot.OffHand => EquipFlag.Offhand, - EquipSlot.Head => EquipFlag.Head, - EquipSlot.Body => EquipFlag.Body, - EquipSlot.Hands => EquipFlag.Hands, - EquipSlot.Legs => EquipFlag.Legs, - EquipSlot.Feet => EquipFlag.Feet, - EquipSlot.Ears => EquipFlag.Ears, - EquipSlot.Neck => EquipFlag.Neck, - EquipSlot.Wrists => EquipFlag.Wrist, - EquipSlot.RFinger => EquipFlag.RFinger, - EquipSlot.LFinger => EquipFlag.LFinger, - _ => 0, - }; - - public static EquipFlag ToStainFlag(this EquipSlot slot) - => slot switch - { - EquipSlot.MainHand => EquipFlag.MainhandStain, - EquipSlot.OffHand => EquipFlag.OffhandStain, - EquipSlot.Head => EquipFlag.HeadStain, - EquipSlot.Body => EquipFlag.BodyStain, - EquipSlot.Hands => EquipFlag.HandsStain, - EquipSlot.Legs => EquipFlag.LegsStain, - EquipSlot.Feet => EquipFlag.FeetStain, - EquipSlot.Ears => EquipFlag.EarsStain, - EquipSlot.Neck => EquipFlag.NeckStain, - EquipSlot.Wrists => EquipFlag.WristStain, - EquipSlot.RFinger => EquipFlag.RFingerStain, - EquipSlot.LFinger => EquipFlag.LFingerStain, - _ => 0, - }; - - public static EquipFlag ToBothFlags(this EquipSlot slot) - => slot switch - { - EquipSlot.MainHand => EquipFlag.Mainhand | EquipFlag.MainhandStain, - EquipSlot.OffHand => EquipFlag.Offhand | EquipFlag.OffhandStain, - EquipSlot.Head => EquipFlag.Head | EquipFlag.HeadStain, - EquipSlot.Body => EquipFlag.Body | EquipFlag.BodyStain, - EquipSlot.Hands => EquipFlag.Hands | EquipFlag.HandsStain, - EquipSlot.Legs => EquipFlag.Legs | EquipFlag.LegsStain, - EquipSlot.Feet => EquipFlag.Feet | EquipFlag.FeetStain, - EquipSlot.Ears => EquipFlag.Ears | EquipFlag.EarsStain, - EquipSlot.Neck => EquipFlag.Neck | EquipFlag.NeckStain, - EquipSlot.Wrists => EquipFlag.Wrist | EquipFlag.WristStain, - EquipSlot.RFinger => EquipFlag.RFinger | EquipFlag.RFingerStain, - EquipSlot.LFinger => EquipFlag.LFinger | EquipFlag.LFingerStain, - _ => 0, - }; -} diff --git a/Glamourer.GameData/Structs/Job.cs b/Glamourer.GameData/Structs/Job.cs deleted file mode 100644 index 1d2873d..0000000 --- a/Glamourer.GameData/Structs/Job.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Dalamud.Utility; -using Lumina.Excel.GeneratedSheets; - -namespace Glamourer.Structs; - -// A struct containing the different jobs the game supports. -// Also contains the jobs Name and Abbreviation as strings. -public readonly struct Job -{ - public readonly string Name; - public readonly string Abbreviation; - public readonly ClassJob Base; - - public uint Id - => Base.RowId; - - public JobFlag Flag - => (JobFlag)(1ul << (int)Base.RowId); - - public Job(ClassJob job) - { - Base = job; - Name = job.Name.ToDalamudString().ToString(); - Abbreviation = job.Abbreviation.ToDalamudString().ToString(); - } - - public override string ToString() - => Name; -} diff --git a/Glamourer.GameData/Structs/JobGroup.cs b/Glamourer.GameData/Structs/JobGroup.cs deleted file mode 100644 index 5471e7f..0000000 --- a/Glamourer.GameData/Structs/JobGroup.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Diagnostics; -using Lumina.Excel; -using Lumina.Excel.GeneratedSheets; - -namespace Glamourer.Structs; - -[Flags] -public enum JobFlag : ulong -{ } - -// The game specifies different job groups that can contain specific jobs or not. -public readonly struct JobGroup -{ - public readonly string Name; - public readonly int Count; - public readonly uint Id; - private readonly JobFlag _flags; - - // Create a job group from a given category and the ClassJob sheet. - // It looks up the different jobs contained in the category and sets the flags appropriately. - public JobGroup(ClassJobCategory group, ExcelSheet jobs) - { - Count = 0; - _flags = 0ul; - Id = group.RowId; - Name = group.Name.ToString(); - - Debug.Assert(jobs.RowCount < 64, $"Number of Jobs exceeded 63 ({jobs.RowCount})."); - foreach (var job in jobs) - { - var abbr = job.Abbreviation.ToString(); - if (abbr.Length == 0) - continue; - - var prop = group.GetType().GetProperty(abbr); - Debug.Assert(prop != null, $"Could not get job abbreviation {abbr} property."); - - if (!(bool)prop.GetValue(group)!) - continue; - - ++Count; - _flags |= (JobFlag)(1ul << (int)job.RowId); - } - } - - // Check if a job is contained inside this group. - public bool Fits(Job job) - => _flags.HasFlag(job.Flag); - - // Check if any of the jobs in the given flags fit this group. - public bool Fits(JobFlag flag) - => (_flags & flag) != 0; - - // Check if a job is contained inside this group. - public bool Fits(uint jobId) - { - var flag = (JobFlag)(1ul << (int)jobId); - return _flags.HasFlag(flag); - } -} diff --git a/Glamourer.sln b/Glamourer.sln index 5f555ca..9acdd6c 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -9,8 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution repo.json = repo.json EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.GameData", "Glamourer.GameData\Glamourer.GameData.csproj", "{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer", "Glamourer\Glamourer.csproj", "{01EB903D-871F-4285-A8CF-6486561D5B5B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}" @@ -27,10 +25,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|Any CPU.Build.0 = Release|Any CPU {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|Any CPU {01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Glamourer/Api/GlamourerIpc.GetCustomization.cs b/Glamourer/Api/GlamourerIpc.GetCustomization.cs index 3e8794c..cdec349 100644 --- a/Glamourer/Api/GlamourerIpc.GetCustomization.cs +++ b/Glamourer/Api/GlamourerIpc.GetCustomization.cs @@ -1,11 +1,7 @@ -using System.Buffers.Text; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; -using Glamourer.Customization; -using Glamourer.Designs; -using Glamourer.Structs; using Penumbra.Api.Helpers; using Penumbra.GameData.Actors; diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 0a26759..029a2ec 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -1,10 +1,10 @@ using System; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Interop.Structs; using Glamourer.State; -using Glamourer.Structs; using Newtonsoft.Json.Linq; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Automation; @@ -74,7 +74,7 @@ public class AutoDesign var ret = new JObject { ["Gearset"] = GearsetIndex, - ["JobGroup"] = Jobs.Id, + ["JobGroup"] = Jobs.Id.Id, }; return ret; diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 023b2c0..9f3e286 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -4,14 +4,12 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Misc; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.State; -using Glamourer.Structs; using Glamourer.Unlocks; using OtterGui.Classes; using Penumbra.GameData.Actors; @@ -209,7 +207,7 @@ public class AutoDesignApplier : IDisposable if (!GetPlayerSet(id, out var set)) { if (_state.TryGetValue(id, out var s)) - s.LastJob = (byte)newJob.Id; + s.LastJob = newJob.Id; return; } diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index e997a78..e205784 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -10,7 +10,6 @@ using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Services; -using Glamourer.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; @@ -516,7 +515,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos var jobs = conditions["JobGroup"]?.ToObject() ?? -1; if (jobs >= 0) { - if (!_jobs.JobGroups.TryGetValue((ushort)jobs, out var jobGroup)) + if (!_jobs.JobGroups.TryGetValue((JobGroupId)jobs, out var jobGroup)) { Glamourer.Messager.NotificationMessage( $"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.", diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index d30271e..4ed98d3 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -3,37 +3,30 @@ using System.Linq; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Interop; -using Glamourer.Services; -using Glamourer.Structs; using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.GameData.Actors; +using Penumbra.GameData.Structs; using Penumbra.String; namespace Glamourer.Automation; -public class FixedDesignMigrator +public class FixedDesignMigrator(JobService jobs) { - private readonly JobService _jobs; - private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData; - - public FixedDesignMigrator(JobService jobs) - => _jobs = jobs; + private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData; public void ConsumeMigratedData(ActorManager actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager) { if (_migratedData == null) return; - foreach (var data in _migratedData) + foreach (var (name, data) in _migratedData) { - var allEnabled = true; - var name = data.Name; if (autoManager.Any(d => name == d.Name)) continue; var id = ActorIdentifier.Invalid; - if (ByteString.FromString(data.Name, out var byteString, false)) + if (ByteString.FromString(name, out var byteString)) { id = actors.CreatePlayer(byteString, ushort.MaxValue); if (!id.IsValid) @@ -46,16 +39,15 @@ public class FixedDesignMigrator id = actors.CreatePlayer(byteString, actors.Data.Worlds.First().Key); if (!id.IsValid) { - Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {data.Name}.", NotificationType.Error); - allEnabled = false; + Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {name}.", NotificationType.Error); continue; } } autoManager.AddDesignSet(name, id); - autoManager.SetState(autoManager.Count - 1, allEnabled); + autoManager.SetState(autoManager.Count - 1, true); var set = autoManager[^1]; - foreach (var design in data.Data.AsEnumerable().Reverse()) + foreach (var design in data.AsEnumerable().Reverse()) { if (!designFileSystem.Find(design.Item1, out var child) || child is not DesignFileSystem.Leaf leaf) { @@ -96,7 +88,7 @@ public class FixedDesignMigrator } var job = obj["JobGroups"]?.ToObject() ?? -1; - if (job < 0 || !_jobs.JobGroups.TryGetValue((ushort)job, out var group)) + if (job < 0 || !jobs.JobGroups.TryGetValue((JobGroupId)job, out var group)) { Glamourer.Messager.NotificationMessage("Could not semi-migrate fixed design: Invalid job group specified.", NotificationType.Warning); diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index c9662ef..8e11ad8 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -1,7 +1,6 @@ using Dalamud.Interface.Internal.Notifications; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Services; -using Glamourer.Structs; using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.GameData.Enums; @@ -85,12 +84,12 @@ public class DesignBase internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; - public bool SetCustomize(CustomizationService customizationService, Customize customize) + public bool SetCustomize(CustomizationService customizationService, CustomizeArray customize) { if (customize.Equals(_designData.Customize)) return false; - _designData.Customize.Load(customize); + _designData.Customize = customize; CustomizationSet = customizationService.Service.GetList(customize.Clan, customize.Gender); return true; } @@ -443,7 +442,7 @@ public class DesignBase { design._designData.ModelId = 0; design._designData.IsHuman = true; - design.SetCustomize(customizations, Customize.Default); + design.SetCustomize(customizations, CustomizeArray.Default); Glamourer.Messager.NotificationMessage("The loaded design does not contain any customization data, reset to default.", NotificationType.Warning); return; diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index c36c1ea..d4352e6 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,7 +1,5 @@ using System; -using Glamourer.Customization; using Glamourer.Services; -using Glamourer.Structs; using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -62,7 +60,7 @@ public class DesignBase64Migration data.SetHatVisible((bytes[90] & 0x01) == 0); data.SetVisor((bytes[90] & 0x10) != 0); data.SetWeaponVisible((bytes[90] & 0x02) == 0); - data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24); + data.ModelId = bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24); break; } case 5: @@ -73,7 +71,7 @@ public class DesignBase64Migration data.SetHatVisible((bytes[90] & 0x01) == 0); data.SetVisor((bytes[90] & 0x10) != 0); data.SetWeaponVisible((bytes[90] & 0x02) == 0); - data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24); + data.ModelId = bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24); break; default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}."); } @@ -102,11 +100,11 @@ public class DesignBase64Migration if (!humans.IsHuman(data.ModelId)) { - data.LoadNonHuman(data.ModelId, *(Customize*)(ptr + 4), (nint)eq); + data.LoadNonHuman(data.ModelId, *(CustomizeArray*)(ptr + 4), (nint)eq); return data; } - data.Customize.Load(*(Customize*)(ptr + 4)); + data.Customize = *(CustomizeArray*)(ptr + 4); foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) { var mdl = eq[idx]; @@ -187,7 +185,7 @@ public class DesignBase64Migration | (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0) | (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0) | (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0)); - save.Customize.Write((nint)data + 4); + save.Customize.Write(data + 4); ((CharacterWeapon*)(data + 30))[0] = save.Item(EquipSlot.MainHand).Weapon(save.Stain(EquipSlot.MainHand)); ((CharacterWeapon*)(data + 30))[1] = save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand)); foreach (var slot in EquipSlotExtensions.EqdpSlots) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 86fd0ce..282ab0a 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -2,10 +2,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; -using Glamourer.Customization; using Glamourer.Services; using Glamourer.State; -using Glamourer.Structs; using Glamourer.Utility; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -165,7 +163,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (slot, item, armor.Stain); } - var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant, FullEquipType.Unknown); + var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant); if (!mh.Valid) { Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 68fa154..9e324ae 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -1,8 +1,6 @@ using System; using System.Runtime.CompilerServices; -using Glamourer.Customization; using Glamourer.Services; -using Glamourer.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -12,30 +10,30 @@ namespace Glamourer.Designs; public unsafe struct DesignData { - private string _nameHead = string.Empty; - private string _nameBody = string.Empty; - private string _nameHands = string.Empty; - private string _nameLegs = string.Empty; - private string _nameFeet = string.Empty; - private string _nameEars = string.Empty; - private string _nameNeck = string.Empty; - private string _nameWrists = string.Empty; - private string _nameRFinger = string.Empty; - private string _nameLFinger = string.Empty; - private string _nameMainhand = string.Empty; - private string _nameOffhand = string.Empty; - private fixed uint _itemIds[12]; - private fixed ushort _iconIds[12]; - private fixed byte _equipmentBytes[48]; - public Customize Customize = Customize.Default; - public uint ModelId; - public CrestFlag CrestVisibility; - private SecondaryId _secondaryMainhand; - private SecondaryId _secondaryOffhand; - private FullEquipType _typeMainhand; - private FullEquipType _typeOffhand; - private byte _states; - public bool IsHuman = true; + private string _nameHead = string.Empty; + private string _nameBody = string.Empty; + private string _nameHands = string.Empty; + private string _nameLegs = string.Empty; + private string _nameFeet = string.Empty; + private string _nameEars = string.Empty; + private string _nameNeck = string.Empty; + private string _nameWrists = string.Empty; + private string _nameRFinger = string.Empty; + private string _nameLFinger = string.Empty; + private string _nameMainhand = string.Empty; + private string _nameOffhand = string.Empty; + private fixed uint _itemIds[12]; + private fixed ushort _iconIds[12]; + private fixed byte _equipmentBytes[48]; + public CustomizeArray Customize = CustomizeArray.Default; + public uint ModelId; + public CrestFlag CrestVisibility; + private SecondaryId _secondaryMainhand; + private SecondaryId _secondaryOffhand; + private FullEquipType _typeMainhand; + private FullEquipType _typeOffhand; + private byte _states; + public bool IsHuman = true; public DesignData() { } @@ -255,11 +253,11 @@ public unsafe struct DesignData } - public bool LoadNonHuman(uint modelId, Customize customize, nint equipData) + public bool LoadNonHuman(uint modelId, CustomizeArray customize, nint equipData) { ModelId = modelId; IsHuman = false; - Customize.Load(customize); + Customize.Read(customize.Data); fixed (byte* ptr = _equipmentBytes) { MemoryUtility.MemCpyUnchecked(ptr, (byte*)equipData, 40); @@ -294,7 +292,7 @@ public unsafe struct DesignData public readonly byte[] GetCustomizeBytes() { var ret = new byte[CustomizeArray.Size]; - fixed (byte* retPtr = ret, inPtr = Customize.Data.Data) + fixed (byte* retPtr = ret, inPtr = Customize.Data) { MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length); } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 6b8578e..1320d6a 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -3,12 +3,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Dalamud.Utility; -using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; -using Glamourer.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; @@ -25,8 +23,8 @@ public class DesignManager private readonly HumanModelList _humans; private readonly SaveService _saveService; private readonly DesignChanged _event; - private readonly List _designs = new(); - private readonly Dictionary _undoStore = new(); + private readonly List _designs = []; + private readonly Dictionary _undoStore = []; public IReadOnlyList Designs => _designs; @@ -298,7 +296,7 @@ public class DesignManager return; case CustomizeIndex.Clan: { - var customize = new Customize(design.DesignData.Customize.Data.Clone()); + var customize = design.DesignData.Customize; if (_customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0) return; if (!design.SetCustomize(_customizations, customize)) @@ -308,7 +306,7 @@ public class DesignManager } case CustomizeIndex.Gender: { - var customize = new Customize(design.DesignData.Customize.Data.Clone()); + var customize = design.DesignData.Customize; if (_customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0) return; if (!design.SetCustomize(_customizations, customize)) diff --git a/Glamourer.GameData/Customization/CharaMakeParams.cs b/Glamourer/GameData/CharaMakeParams.cs similarity index 94% rename from Glamourer.GameData/Customization/CharaMakeParams.cs rename to Glamourer/GameData/CharaMakeParams.cs index 63806c0..12dedf9 100644 --- a/Glamourer.GameData/Customization/CharaMakeParams.cs +++ b/Glamourer/GameData/CharaMakeParams.cs @@ -2,9 +2,11 @@ using Lumina.Excel; using Lumina.Excel.GeneratedSheets; -namespace Glamourer.Customization; +namespace Glamourer.GameData; -// A custom version of CharaMakeParams that is easier to parse. +/// +/// A custom version of CharaMakeParams that is easier to parse. +/// [Sheet("CharaMakeParams")] public class CharaMakeParams : ExcelRow { @@ -64,7 +66,7 @@ public class CharaMakeParams : ExcelRow Race = new LazyRow(gameData, parser.ReadColumn(0), language); Tribe = new LazyRow(gameData, parser.ReadColumn(1), language); Gender = parser.ReadColumn(2); - var currentOffset = 0; + int currentOffset; for (var i = 0; i < NumMenus; ++i) { currentOffset = 3 + i; diff --git a/Glamourer/GameData/ColorParameters.cs b/Glamourer/GameData/ColorParameters.cs new file mode 100644 index 0000000..630a5a7 --- /dev/null +++ b/Glamourer/GameData/ColorParameters.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Dalamud.Plugin.Services; +using Penumbra.String.Functions; + +namespace Glamourer.GameData; + +public class ColorParameters : IReadOnlyList +{ + private readonly uint[] _rgbaColors; + + public ReadOnlySpan GetSlice(int offset, int count) + => _rgbaColors.AsSpan(offset, count); + + public unsafe ColorParameters(IDataManager gameData, IPluginLog log) + { + try + { + var file = gameData.GetFile("chara/xls/charamake/human.cmp")!; + _rgbaColors = new uint[file.Data.Length >> 2]; + fixed (byte* ptr1 = file.Data) + { + fixed (uint* ptr2 = _rgbaColors) + { + MemoryUtility.MemCpyUnchecked(ptr2, ptr1, file.Data.Length); + } + } + } + catch (Exception e) + { + log.Error("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n" + + "======== This usually indicates an error with your index files caused by TexTools modifications.\n" + + "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e); + _rgbaColors = Array.Empty(); + } + } + + public IEnumerator GetEnumerator() + => (IEnumerator)_rgbaColors.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _rgbaColors.Length; + + public uint this[int index] + => _rgbaColors[index]; +} diff --git a/Glamourer.GameData/Customization/CustomName.cs b/Glamourer/GameData/CustomName.cs similarity index 78% rename from Glamourer.GameData/Customization/CustomName.cs rename to Glamourer/GameData/CustomName.cs index 040c476..c7d74a1 100644 --- a/Glamourer.GameData/Customization/CustomName.cs +++ b/Glamourer/GameData/CustomName.cs @@ -1,6 +1,6 @@ -namespace Glamourer.Customization; +namespace Glamourer.GameData; -// Localization from the game files directly. +/// For localization from the game files directly. public enum CustomName { MidlanderM, diff --git a/Glamourer.GameData/Customization/CustomizationManager.cs b/Glamourer/GameData/CustomizationManager.cs similarity index 93% rename from Glamourer.GameData/Customization/CustomizationManager.cs rename to Glamourer/GameData/CustomizationManager.cs index b02498c..f249bf6 100644 --- a/Glamourer.GameData/Customization/CustomizationManager.cs +++ b/Glamourer/GameData/CustomizationManager.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.Internal; using Dalamud.Plugin.Services; using Penumbra.GameData.Enums; -namespace Glamourer.Customization; +namespace Glamourer.GameData; public class CustomizationManager : ICustomizationManager { diff --git a/Glamourer.GameData/Customization/CustomizationNpcOptions.cs b/Glamourer/GameData/CustomizationNpcOptions.cs similarity index 92% rename from Glamourer.GameData/Customization/CustomizationNpcOptions.cs rename to Glamourer/GameData/CustomizationNpcOptions.cs index 043ccf8..84509be 100644 --- a/Glamourer.GameData/Customization/CustomizationNpcOptions.cs +++ b/Glamourer/GameData/CustomizationNpcOptions.cs @@ -1,12 +1,14 @@ using Penumbra.GameData.Enums; using System.Collections.Generic; using System.Linq; +using Penumbra.GameData.Structs; -namespace Glamourer.Customization; +namespace Glamourer.GameData; public static class CustomizationNpcOptions { - public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, NpcCustomizeSet npcCustomizeSet) + public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, + NpcCustomizeSet npcCustomizeSet) { var dict = new Dictionary<(SubRace, Gender), HashSet<(CustomizeIndex, CustomizeValue)>>(); var customizeIndices = new[] diff --git a/Glamourer.GameData/Customization/CustomizationOptions.cs b/Glamourer/GameData/CustomizationOptions.cs similarity index 94% rename from Glamourer.GameData/Customization/CustomizationOptions.cs rename to Glamourer/GameData/CustomizationOptions.cs index 3580ef8..6fc3b03 100644 --- a/Glamourer.GameData/Customization/CustomizationOptions.cs +++ b/Glamourer/GameData/CustomizationOptions.cs @@ -10,9 +10,10 @@ using Lumina.Excel; using Lumina.Excel.GeneratedSheets; using OtterGui.Classes; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; -namespace Glamourer.Customization; +namespace Glamourer.GameData; // Generate everything about customization per tribe and gender. public partial class CustomizationOptions @@ -66,7 +67,7 @@ public partial class CustomizationOptions { var tmp = new TemporaryData(gameData, this, log); _icons = new IconStorage(textures, gameData); - SetNames(gameData, tmp); + SetNames(gameData); foreach (var race in Clans) { foreach (var gender in Genders) @@ -79,7 +80,7 @@ public partial class CustomizationOptions // Obtain localized names of customization options and race names from the game data. private readonly string[] _names = new string[Enum.GetValues().Length]; - private void SetNames(IDataManager gameData, TemporaryData tmp) + private void SetNames(IDataManager gameData) { var subRace = gameData.GetExcelSheet()!; @@ -122,9 +123,6 @@ public partial class CustomizationOptions private class TemporaryData { - public bool Valid - => _cmpFile.Valid; - public CustomizationSet GetSet(SubRace race, Gender gender) { var (skin, hair) = GetColors(race, gender); @@ -177,10 +175,8 @@ public partial class CustomizationOptions public TemporaryData(IDataManager gameData, CustomizationOptions options, IPluginLog log) { _options = options; - _cmpFile = new CmpFile(gameData, log); + _cmpFile = new ColorParameters(gameData, log); _customizeSheet = gameData.GetExcelSheet(ClientLanguage.English)!; - _bnpcCustomize = gameData.GetExcelSheet(ClientLanguage.English)!; - _enpcBase = gameData.GetExcelSheet(ClientLanguage.English)!; Lobby = gameData.GetExcelSheet(ClientLanguage.English)!; var tmp = gameData.Excel.GetType().GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)? .MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[] @@ -204,10 +200,8 @@ public partial class CustomizationOptions private readonly ExcelSheet _customizeSheet; private readonly ExcelSheet _listSheet; private readonly ExcelSheet _hairSheet; - private readonly ExcelSheet _bnpcCustomize; - private readonly ExcelSheet _enpcBase; public readonly ExcelSheet Lobby; - private readonly CmpFile _cmpFile; + private readonly ColorParameters _cmpFile; // Those values are shared between all races. private readonly CustomizeData[] _highlightPicker; @@ -222,9 +216,17 @@ public partial class CustomizationOptions private CustomizeData[] CreateColorPicker(CustomizeIndex index, int offset, int num, bool light = false) - => _cmpFile.GetSlice(offset, num) - .Select((c, i) => new CustomizeData(index, (CustomizeValue)(light ? 128 + i : 0 + i), c, (ushort)(offset + i))) - .ToArray(); + { + var ret = new CustomizeData[num]; + var idx = 0; + foreach (var value in _cmpFile.GetSlice(offset, num)) + { + ret[idx] = new CustomizeData(index, (CustomizeValue)(light ? 128 + idx : idx), value, (ushort)(offset + idx)); + ++idx; + } + + return ret; + } private void SetHairByFace(CustomizationSet set) @@ -299,15 +301,9 @@ public partial class CustomizationOptions // Set customizations available if they have any options. private static void SetAvailability(CustomizationSet set, CharaMakeParams row) { - if (set.Race == Race.Hrothgar && set.Gender == Gender.Female) + if (set is { Race: Race.Hrothgar, Gender: Gender.Female }) return; - void Set(bool available, CustomizeIndex flag) - { - if (available) - set.SetAvailable(flag); - } - Set(true, CustomizeIndex.Height); Set(set.Faces.Count > 0, CustomizeIndex.Face); Set(true, CustomizeIndex.Hairstyle); @@ -340,6 +336,13 @@ public partial class CustomizationOptions Set(true, CustomizeIndex.SmallIris); Set(set.Race != Race.Hrothgar, CustomizeIndex.Lipstick); Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintReversed); + return; + + void Set(bool available, CustomizeIndex flag) + { + if (available) + set.SetAvailable(flag); + } } // Create a list of lists of facial features and the legacy tattoo. @@ -348,9 +351,6 @@ public partial class CustomizationOptions var count = set.Faces.Count; set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count); - static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data) - => (new CustomizeData(i, CustomizeValue.Zero, data, 0), new CustomizeData(i, CustomizeValue.Max, data, 1)); - set.LegacyTattoo = Create(CustomizeIndex.LegacyTattoo, 137905); var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray(); @@ -373,6 +373,10 @@ public partial class CustomizationOptions set.FacialFeature5 = tmp[4]; set.FacialFeature6 = tmp[5]; set.FacialFeature7 = tmp[6]; + return; + + static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data) + => (new CustomizeData(i, CustomizeValue.Zero, data), new CustomizeData(i, CustomizeValue.Max, data, 1)); } // Set the names for the given set of parameters. @@ -414,7 +418,7 @@ public partial class CustomizationOptions nameArray[(int)CustomizeIndex.SmallIris] = CustomizeIndex.SmallIris.ToDefaultName(); nameArray[(int)CustomizeIndex.Lipstick] = CustomizeIndex.Lipstick.ToDefaultName(); nameArray[(int)CustomizeIndex.FacePaintReversed] = CustomizeIndex.FacePaintReversed.ToDefaultName(); - set.OptionName = nameArray; + set.OptionName = nameArray; } // Obtain available skin and hair colors for the given subrace and gender. diff --git a/Glamourer.GameData/Customization/CustomizationSet.cs b/Glamourer/GameData/CustomizationSet.cs similarity index 95% rename from Glamourer.GameData/Customization/CustomizationSet.cs rename to Glamourer/GameData/CustomizationSet.cs index b958fdc..2a79c66 100644 --- a/Glamourer.GameData/Customization/CustomizationSet.cs +++ b/Glamourer/GameData/CustomizationSet.cs @@ -4,8 +4,9 @@ using System.Linq; using System.Runtime.CompilerServices; using OtterGui; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; -namespace Glamourer.Customization; +namespace Glamourer.GameData; // Each Subrace and Gender combo has a customization set. // This describes the available customizations, their types and their names. @@ -300,3 +301,10 @@ public class CustomizationSet private CustomizeValue HrothgarFaceHack(CustomizeValue value) => Race == Race.Hrothgar && value.Value is > 4 and < 9 ? value - 4 : value; } + +public static class CustomizationSetExtensions +{ + /// Return only the available customizations in this set and Clan or Gender. + public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizationSet set) + => flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender); +} diff --git a/Glamourer.GameData/Customization/CustomizeData.cs b/Glamourer/GameData/CustomizeData.cs similarity index 89% rename from Glamourer.GameData/Customization/CustomizeData.cs rename to Glamourer/GameData/CustomizeData.cs index 8e9f914..3a3e89c 100644 --- a/Glamourer.GameData/Customization/CustomizeData.cs +++ b/Glamourer/GameData/CustomizeData.cs @@ -1,7 +1,9 @@ using System; using System.Runtime.InteropServices; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; -namespace Glamourer.Customization; +namespace Glamourer.GameData; // Any customization value can be represented in 8 bytes by its ID, // a byte value, an optional value-id and an optional icon or color. diff --git a/Glamourer.GameData/Customization/ICustomizationManager.cs b/Glamourer/GameData/ICustomizationManager.cs similarity index 90% rename from Glamourer.GameData/Customization/ICustomizationManager.cs rename to Glamourer/GameData/ICustomizationManager.cs index e946e3d..2d884cd 100644 --- a/Glamourer.GameData/Customization/ICustomizationManager.cs +++ b/Glamourer/GameData/ICustomizationManager.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.Internal; using Penumbra.GameData.Enums; -namespace Glamourer.Customization; +namespace Glamourer.GameData; public interface ICustomizationManager { diff --git a/Glamourer.GameData/Customization/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs similarity index 63% rename from Glamourer.GameData/Customization/NpcCustomizeSet.cs rename to Glamourer/GameData/NpcCustomizeSet.cs index 3ba64a0..fd261c6 100644 --- a/Glamourer.GameData/Customization/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -11,7 +11,7 @@ using OtterGui.Services; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Structs; -namespace Glamourer.Customization; +namespace Glamourer.GameData; public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList { @@ -187,83 +187,83 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList data.VisorToggled = row.Visor; } - private static (bool, Customize) FromBnpcCustomize(BNpcCustomize bnpcCustomize) + private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize) { - var customize = new Customize(); - customize.Data.Set(0, (byte)bnpcCustomize.Race.Row); - customize.Data.Set(1, bnpcCustomize.Gender); - customize.Data.Set(2, bnpcCustomize.BodyType); - customize.Data.Set(3, bnpcCustomize.Height); - customize.Data.Set(4, (byte)bnpcCustomize.Tribe.Row); - customize.Data.Set(5, bnpcCustomize.Face); - customize.Data.Set(6, bnpcCustomize.HairStyle); - customize.Data.Set(7, bnpcCustomize.HairHighlight); - customize.Data.Set(8, bnpcCustomize.SkinColor); - customize.Data.Set(9, bnpcCustomize.EyeHeterochromia); - customize.Data.Set(10, bnpcCustomize.HairColor); - customize.Data.Set(11, bnpcCustomize.HairHighlightColor); - customize.Data.Set(12, bnpcCustomize.FacialFeature); - customize.Data.Set(13, bnpcCustomize.FacialFeatureColor); - customize.Data.Set(14, bnpcCustomize.Eyebrows); - customize.Data.Set(15, bnpcCustomize.EyeColor); - customize.Data.Set(16, bnpcCustomize.EyeShape); - customize.Data.Set(17, bnpcCustomize.Nose); - customize.Data.Set(18, bnpcCustomize.Jaw); - customize.Data.Set(19, bnpcCustomize.Mouth); - customize.Data.Set(20, bnpcCustomize.LipColor); - customize.Data.Set(21, bnpcCustomize.BustOrTone1); - customize.Data.Set(22, bnpcCustomize.ExtraFeature1); - customize.Data.Set(23, bnpcCustomize.ExtraFeature2OrBust); - customize.Data.Set(24, bnpcCustomize.FacePaint); - customize.Data.Set(25, bnpcCustomize.FacePaintColor); + var customize = new CustomizeArray(); + customize.SetByIndex(0, (CustomizeValue) (byte)bnpcCustomize.Race.Row); + customize.SetByIndex(1, (CustomizeValue) bnpcCustomize.Gender); + customize.SetByIndex(2, (CustomizeValue) bnpcCustomize.BodyType); + customize.SetByIndex(3, (CustomizeValue) bnpcCustomize.Height); + customize.SetByIndex(4, (CustomizeValue) (byte)bnpcCustomize.Tribe.Row); + customize.SetByIndex(5, (CustomizeValue) bnpcCustomize.Face); + customize.SetByIndex(6, (CustomizeValue) bnpcCustomize.HairStyle); + customize.SetByIndex(7, (CustomizeValue) bnpcCustomize.HairHighlight); + customize.SetByIndex(8, (CustomizeValue) bnpcCustomize.SkinColor); + customize.SetByIndex(9, (CustomizeValue) bnpcCustomize.EyeHeterochromia); + customize.SetByIndex(10, (CustomizeValue) bnpcCustomize.HairColor); + customize.SetByIndex(11, (CustomizeValue) bnpcCustomize.HairHighlightColor); + customize.SetByIndex(12, (CustomizeValue) bnpcCustomize.FacialFeature); + customize.SetByIndex(13, (CustomizeValue) bnpcCustomize.FacialFeatureColor); + customize.SetByIndex(14, (CustomizeValue) bnpcCustomize.Eyebrows); + customize.SetByIndex(15, (CustomizeValue) bnpcCustomize.EyeColor); + customize.SetByIndex(16, (CustomizeValue) bnpcCustomize.EyeShape); + customize.SetByIndex(17, (CustomizeValue) bnpcCustomize.Nose); + customize.SetByIndex(18, (CustomizeValue) bnpcCustomize.Jaw); + customize.SetByIndex(19, (CustomizeValue) bnpcCustomize.Mouth); + customize.SetByIndex(20, (CustomizeValue) bnpcCustomize.LipColor); + customize.SetByIndex(21, (CustomizeValue) bnpcCustomize.BustOrTone1); + customize.SetByIndex(22, (CustomizeValue) bnpcCustomize.ExtraFeature1); + customize.SetByIndex(23, (CustomizeValue) bnpcCustomize.ExtraFeature2OrBust); + customize.SetByIndex(24, (CustomizeValue) bnpcCustomize.FacePaint); + customize.SetByIndex(25, (CustomizeValue) bnpcCustomize.FacePaintColor); if (customize.BodyType.Value != 1 || !CustomizationOptions.Races.Contains(customize.Race) || !CustomizationOptions.Clans.Contains(customize.Clan) || !CustomizationOptions.Genders.Contains(customize.Gender)) - return (false, Customize.Default); + return (false, CustomizeArray.Default); return (true, customize); } - private static (bool, Customize) FromEnpcBase(ENpcBase enpcBase) + private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase) { if (enpcBase.ModelChara.Value?.Type != 1) - return (false, Customize.Default); + return (false, CustomizeArray.Default); - var customize = new Customize(); - customize.Data.Set(0, (byte)enpcBase.Race.Row); - customize.Data.Set(1, enpcBase.Gender); - customize.Data.Set(2, enpcBase.BodyType); - customize.Data.Set(3, enpcBase.Height); - customize.Data.Set(4, (byte)enpcBase.Tribe.Row); - customize.Data.Set(5, enpcBase.Face); - customize.Data.Set(6, enpcBase.HairStyle); - customize.Data.Set(7, enpcBase.HairHighlight); - customize.Data.Set(8, enpcBase.SkinColor); - customize.Data.Set(9, enpcBase.EyeHeterochromia); - customize.Data.Set(10, enpcBase.HairColor); - customize.Data.Set(11, enpcBase.HairHighlightColor); - customize.Data.Set(12, enpcBase.FacialFeature); - customize.Data.Set(13, enpcBase.FacialFeatureColor); - customize.Data.Set(14, enpcBase.Eyebrows); - customize.Data.Set(15, enpcBase.EyeColor); - customize.Data.Set(16, enpcBase.EyeShape); - customize.Data.Set(17, enpcBase.Nose); - customize.Data.Set(18, enpcBase.Jaw); - customize.Data.Set(19, enpcBase.Mouth); - customize.Data.Set(20, enpcBase.LipColor); - customize.Data.Set(21, enpcBase.BustOrTone1); - customize.Data.Set(22, enpcBase.ExtraFeature1); - customize.Data.Set(23, enpcBase.ExtraFeature2OrBust); - customize.Data.Set(24, enpcBase.FacePaint); - customize.Data.Set(25, enpcBase.FacePaintColor); + var customize = new CustomizeArray(); + customize.SetByIndex(0, (CustomizeValue) (byte)enpcBase.Race.Row); + customize.SetByIndex(1, (CustomizeValue) enpcBase.Gender); + customize.SetByIndex(2, (CustomizeValue) enpcBase.BodyType); + customize.SetByIndex(3, (CustomizeValue) enpcBase.Height); + customize.SetByIndex(4, (CustomizeValue) (byte)enpcBase.Tribe.Row); + customize.SetByIndex(5, (CustomizeValue) enpcBase.Face); + customize.SetByIndex(6, (CustomizeValue) enpcBase.HairStyle); + customize.SetByIndex(7, (CustomizeValue) enpcBase.HairHighlight); + customize.SetByIndex(8, (CustomizeValue) enpcBase.SkinColor); + customize.SetByIndex(9, (CustomizeValue) enpcBase.EyeHeterochromia); + customize.SetByIndex(10, (CustomizeValue) enpcBase.HairColor); + customize.SetByIndex(11, (CustomizeValue) enpcBase.HairHighlightColor); + customize.SetByIndex(12, (CustomizeValue) enpcBase.FacialFeature); + customize.SetByIndex(13, (CustomizeValue) enpcBase.FacialFeatureColor); + customize.SetByIndex(14, (CustomizeValue) enpcBase.Eyebrows); + customize.SetByIndex(15, (CustomizeValue) enpcBase.EyeColor); + customize.SetByIndex(16, (CustomizeValue) enpcBase.EyeShape); + customize.SetByIndex(17, (CustomizeValue) enpcBase.Nose); + customize.SetByIndex(18, (CustomizeValue) enpcBase.Jaw); + customize.SetByIndex(19, (CustomizeValue) enpcBase.Mouth); + customize.SetByIndex(20, (CustomizeValue) enpcBase.LipColor); + customize.SetByIndex(21, (CustomizeValue) enpcBase.BustOrTone1); + customize.SetByIndex(22, (CustomizeValue) enpcBase.ExtraFeature1); + customize.SetByIndex(23, (CustomizeValue) enpcBase.ExtraFeature2OrBust); + customize.SetByIndex(24, (CustomizeValue) enpcBase.FacePaint); + customize.SetByIndex(25, (CustomizeValue) enpcBase.FacePaintColor); if (customize.BodyType.Value != 1 || !CustomizationOptions.Races.Contains(customize.Race) || !CustomizationOptions.Clans.Contains(customize.Clan) || !CustomizationOptions.Genders.Contains(customize.Gender)) - return (false, Customize.Default); + return (false, CustomizeArray.Default); return (true, customize); } diff --git a/Glamourer.GameData/Customization/NpcData.cs b/Glamourer/GameData/NpcData.cs similarity index 93% rename from Glamourer.GameData/Customization/NpcData.cs rename to Glamourer/GameData/NpcData.cs index 6942147..70bfe58 100644 --- a/Glamourer.GameData/Customization/NpcData.cs +++ b/Glamourer/GameData/NpcData.cs @@ -3,12 +3,12 @@ using System.Text; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Penumbra.GameData.Structs; -namespace Glamourer.Customization; +namespace Glamourer.GameData; public unsafe struct NpcData { public string Name; - public Customize Customize; + public CustomizeArray Customize; private fixed byte _equip[40]; public CharacterWeapon Mainhand; public CharacterWeapon Offhand; diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 12ec3a7..0d2bb36 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -84,7 +84,6 @@ - @@ -109,8 +108,8 @@ - - + + diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index 127d8c2..5fa28a6 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -1,11 +1,11 @@ using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Utility; -using Glamourer.Customization; +using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using Penumbra.GameData; +using Penumbra.GameData.Enums; namespace Glamourer.Gui.Customization; @@ -15,12 +15,12 @@ public partial class CustomizationDrawer private void DrawColorPicker(CustomizeIndex index) { - using var _ = SetId(index); + using var id = SetId(index); var (current, custom) = GetCurrentCustomization(index); var color = ImGui.ColorConvertU32ToFloat4(current < 0 ? ImGui.GetColorU32(ImGuiCol.FrameBg) : custom.Color); - using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0)) + using (_ = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0)) { if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.None, _framedIconSize)) ImGui.OpenPopup(ColorPickerPopupName); @@ -39,7 +39,7 @@ public partial class CustomizationDrawer ImGui.SameLine(); - using (var group = ImRaii.Group()) + using (_ = ImRaii.Group()) { DataInputInt(current, npc); if (_withApply) @@ -89,7 +89,7 @@ public partial class CustomizationDrawer { var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face); if (_set.IsAvailable(index) && current < 0) - return (current, new CustomizeData(index, _customize[index], 0, 0)); + return (current, new CustomizeData(index, _customize[index])); return (current, custom!.Value); } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index 7866bda..097d99b 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using Dalamud.Interface; -using Glamourer.Customization; +using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index f435ba8..939b0f6 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,9 +1,11 @@ using System; using System.Numerics; -using Glamourer.Customization; +using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; @@ -13,7 +15,7 @@ public partial class CustomizationDrawer private void DrawIconSelector(CustomizeIndex index) { - using var _ = SetId(index); + using var id = SetId(index); using var bigGroup = ImRaii.Group(); var label = _currentOption; @@ -28,7 +30,7 @@ public partial class CustomizationDrawer } var icon = _service.Service.GetIcon(custom!.Value.IconId); - using (var disabled = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) + using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) { if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) ImGui.OpenPopup(IconSelectorPopup); @@ -37,7 +39,7 @@ public partial class CustomizationDrawer ImGuiUtil.HoverIconTooltip(icon, _iconSize); ImGui.SameLine(); - using (var group = ImRaii.Group()) + using (_ = ImRaii.Group()) { DataInputInt(current, npc); if (_lockedRedraw && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) @@ -118,16 +120,16 @@ public partial class CustomizationDrawer ImGui.Dummy(new Vector2(ImGui.GetFrameHeight())); } - var oldValue = _customize.Data.At(_currentIndex.ToByteAndMask().ByteIdx); - var tmp = (int)oldValue; + var oldValue = _customize.AtIndex(_currentIndex.ToByteAndMask().ByteIdx); + var tmp = (int)oldValue.Value; ImGui.SetNextItemWidth(_inputIntSize); if (ImGui.InputInt("##text", ref tmp, 1, 1)) { tmp = Math.Clamp(tmp, 0, byte.MaxValue); - if (tmp != oldValue) + if (tmp != oldValue.Value) { - _customize.Data.Set(_currentIndex.ToByteAndMask().ByteIdx, (byte)tmp); - var changes = (byte)tmp ^ oldValue; + _customize.SetByIndex(_currentIndex.ToByteAndMask().ByteIdx, (CustomizeValue)tmp); + var changes = (byte)tmp ^ oldValue.Value; Changed |= ((changes & 0x01) == 0x01 ? CustomizeFlag.FacialFeature1 : 0) | ((changes & 0x02) == 0x02 ? CustomizeFlag.FacialFeature2 : 0) | ((changes & 0x04) == 0x04 ? CustomizeFlag.FacialFeature3 : 0) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index e9ce9e9..e970fb3 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -1,10 +1,10 @@ using System; using System.Numerics; -using Glamourer.Customization; using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; @@ -91,10 +91,10 @@ public partial class CustomizationDrawer private void DrawListSelector(CustomizeIndex index, bool indexedBy1) { - using var _ = SetId(index); + using var id = SetId(index); using var bigGroup = ImRaii.Group(); - using (var disabled = ImRaii.Disabled(_locked)) + using (_ = ImRaii.Disabled(_locked)) { if (indexedBy1) { @@ -210,7 +210,7 @@ public partial class CustomizationDrawer } else { - using (var disabled = ImRaii.Disabled(_locked)) + using (_ = ImRaii.Disabled(_locked)) { if (ImGui.Checkbox("##toggle", ref tmp)) { diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index a288065..c131cf5 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -4,7 +4,7 @@ using System.Reflection; using Dalamud.Interface.Internal; using Dalamud.Interface.Utility; using Dalamud.Plugin; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Services; using ImGuiNET; using OtterGui; @@ -22,13 +22,12 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio private Exception? _terminate; - private Customize _customize = Customize.Default; + private CustomizeArray _customize = CustomizeArray.Default; private CustomizationSet _set = null!; - public Customize Customize + public CustomizeArray Customize => _customize; - public CustomizeFlag CurrentFlag { get; private set; } public CustomizeFlag Changed { get; private set; } public CustomizeFlag ChangeApply { get; private set; } @@ -47,18 +46,16 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio public void Dispose() => _legacyTattoo?.Dispose(); - public bool Draw(Customize current, bool locked, bool lockedRedraw) + public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw) { - CurrentFlag = CustomizeFlagExtensions.All; _withApply = false; Init(current, locked, lockedRedraw); return DrawInternal(); } - public bool Draw(Customize current, CustomizeFlag apply, bool locked, bool lockedRedraw) + public bool Draw(CustomizeArray current, CustomizeFlag apply, bool locked, bool lockedRedraw) { - CurrentFlag = CustomizeFlagExtensions.All; ChangeApply = apply; _initialApply = apply; _withApply = !_config.HideApplyCheckmarks; @@ -66,12 +63,12 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio return DrawInternal(); } - private void Init(Customize current, bool locked, bool lockedRedraw) + private void Init(CustomizeArray current, bool locked, bool lockedRedraw) { UpdateSizes(); - _terminate = null; - Changed = 0; - _customize.Load(current); + _terminate = null; + Changed = 0; + _customize = current; _locked = locked; _lockedRedraw = lockedRedraw; } @@ -156,20 +153,20 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio for (var i = 0; i < CustomizeArray.Size; ++i) { using var id = ImRaii.PushId(i); - int value = _customize.Data.Data[i]; + int value = _customize.Data[i]; ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); if (ImGui.InputInt(string.Empty, ref value, 0, 0)) { var newValue = (byte)Math.Clamp(value, 0, byte.MaxValue); - if (newValue != _customize.Data.Data[i]) + if (newValue != _customize.Data[i]) foreach (var flag in Enum.GetValues()) { - var (j, mask) = flag.ToByteAndMask(); + var (j, _) = flag.ToByteAndMask(); if (j == i) Changed |= flag.ToFlag(); } - _customize.Data.Data[i] = newValue; + _customize.Data[i] = newValue; } } diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 19d73c4..e82ab3c 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -4,7 +4,7 @@ using System.Linq; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Glamourer.Automation; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Services; @@ -13,6 +13,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Widgets; +using Penumbra.GameData.Enums; namespace Glamourer.Gui; diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index e70cd5d..b499bc1 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -7,7 +7,6 @@ using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; -using Glamourer.Structs; using ImGuiNET; using OtterGui.Raii; using Penumbra.Api.Enums; @@ -76,7 +75,7 @@ public class PenumbraChangedItemTooltip : IDisposable case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item): break; case EquipSlot.RFinger: - using (var tt = !openTooltip ? null : ImRaii.Tooltip()) + using (_ = !openTooltip ? null : ImRaii.Tooltip()) { ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor (Right Finger)."); ImGui.TextUnformatted($"{prefix}Shift + Right-Click to apply to current actor (Left Finger)."); @@ -92,7 +91,7 @@ public class PenumbraChangedItemTooltip : IDisposable break; default: - using (var tt = !openTooltip ? null : ImRaii.Tooltip()) + using (_ = !openTooltip ? null : ImRaii.Tooltip()) { ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor."); if (last.Valid) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 15d3725..b56b314 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -6,7 +6,6 @@ using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Automation; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Gui.Customization; @@ -14,7 +13,6 @@ using Glamourer.Gui.Equipment; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.State; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -173,21 +171,21 @@ public class ActorPanel( private void DrawEquipmentMetaToggles() { - using (var _ = ImRaii.Group()) + using (_ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!)); } ImGui.SameLine(); - using (var _ = ImRaii.Group()) + using (_ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!)); } ImGui.SameLine(); - using (var _ = ImRaii.Group()) + using (_ = ImRaii.Group()) { EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!)); @@ -199,7 +197,7 @@ public class ActorPanel( var names = _modelChara[_state!.ModelData.ModelId]; var turnHuman = ImGui.Button("Turn Human"); ImGui.Separator(); - using (var box = ImRaii.ListBox("##MonsterList", + using (_ = ImRaii.ListBox("##MonsterList", new Vector2(ImGui.GetContentRegionAvail().X, 10 * ImGui.GetTextLineHeightWithSpacing()))) { if (names.Count == 0) @@ -211,14 +209,14 @@ public class ActorPanel( ImGui.Separator(); ImGui.TextUnformatted("Customization Data"); - using (var font = ImRaii.PushFont(UiBuilder.MonoFont)) + using (_ = ImRaii.PushFont(UiBuilder.MonoFont)) { - foreach (var b in _state.ModelData.Customize.Data) + foreach (var b in _state.ModelData.Customize) { - using (var g = ImRaii.Group()) + using (_ = ImRaii.Group()) { - ImGui.TextUnformatted($" {b:X2}"); - ImGui.TextUnformatted($"{b,3}"); + ImGui.TextUnformatted($" {b.Value:X2}"); + ImGui.TextUnformatted($"{b.Value,3}"); } ImGui.SameLine(); @@ -232,11 +230,11 @@ public class ActorPanel( ImGui.Separator(); ImGui.TextUnformatted("Equipment Data"); - using (var font = ImRaii.PushFont(UiBuilder.MonoFont)) + using (_ = ImRaii.PushFont(UiBuilder.MonoFont)) { foreach (var b in _state.ModelData.GetEquipmentBytes()) { - using (var g = ImRaii.Group()) + using (_ = ImRaii.Group()) { ImGui.TextUnformatted($" {b:X2}"); ImGui.TextUnformatted($"{b,3}"); @@ -298,8 +296,8 @@ public class ActorPanel( BorderColor = ColorId.ActorUnavailable.Value(), }; - private string _newName = string.Empty; - private DesignBase? _newDesign = null; + private string _newName = string.Empty; + private DesignBase? _newDesign; private void SaveDesignOpen() { diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 3eba0cd..2a0453c 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -6,10 +6,8 @@ using System.Text; using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Automation; -using Glamourer.Customization; using Glamourer.Interop; using Glamourer.Services; -using Glamourer.Structs; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; @@ -17,44 +15,29 @@ using OtterGui.Log; using OtterGui.Raii; using OtterGui.Widgets; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; using Action = System.Action; -using CustomizeIndex = Glamourer.Customization.CustomizeIndex; namespace Glamourer.Gui.Tabs.AutomationTab; -public class SetPanel +public class SetPanel( + SetSelector _selector, + AutoDesignManager _manager, + JobService _jobs, + ItemUnlockManager _itemUnlocks, + RevertDesignCombo _designCombo, + CustomizeUnlockManager _customizeUnlocks, + CustomizationService _customizations, + IdentifierDrawer _identifierDrawer, + Configuration _config) { - private readonly AutoDesignManager _manager; - private readonly SetSelector _selector; - private readonly ItemUnlockManager _itemUnlocks; - private readonly CustomizeUnlockManager _customizeUnlocks; - private readonly CustomizationService _customizations; - - private readonly Configuration _config; - private readonly RevertDesignCombo _designCombo; - private readonly JobGroupCombo _jobGroupCombo; - private readonly IdentifierDrawer _identifierDrawer; + private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log); private string? _tempName; private int _dragIndex = -1; private Action? _endAction; - public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks, - RevertDesignCombo designCombo, - CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer, Configuration config) - { - _selector = selector; - _manager = manager; - _itemUnlocks = itemUnlocks; - _customizeUnlocks = customizeUnlocks; - _customizations = customizations; - _identifierDrawer = identifierDrawer; - _config = config; - _designCombo = designCombo; - _jobGroupCombo = new JobGroupCombo(manager, jobs, Glamourer.Log); - } - private AutoDesignSet Selection => _selector.Selection!; @@ -77,7 +60,7 @@ public class SetPanel var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; - using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var enabled = Selection.Enabled; if (ImGui.Checkbox("##Enabled", ref enabled)) @@ -87,7 +70,7 @@ public class SetPanel } ImGui.SameLine(); - using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game; if (ImGui.Checkbox("##gameState", ref useGame)) @@ -98,7 +81,7 @@ public class SetPanel } ImGui.SameLine(); - using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var editing = _config.ShowAutomationSetEditing; if (ImGui.Checkbox("##Show Editing", ref editing)) @@ -230,7 +213,7 @@ public class SetPanel if (_config.ShowUnlockedItemWarnings) { ImGui.TableNextColumn(); - DrawWarnings(design, idx); + DrawWarnings(design); } } @@ -278,7 +261,7 @@ public class SetPanel } } - private void DrawWarnings(AutoDesign design, int idx) + private void DrawWarnings(AutoDesign design) { if (design.Revert) return; @@ -301,27 +284,6 @@ public class SetPanel using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale, 0)); - - static void DrawWarning(StringBuilder sb, uint color, Vector2 size, string suffix, string good) - { - using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); - if (sb.Length > 0) - { - sb.Append(suffix); - using (var font = ImRaii.PushFont(UiBuilder.IconFont)) - { - ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color); - } - - ImGuiUtil.HoverTooltip(sb.ToString()); - } - else - { - ImGuiUtil.DrawTextButton(string.Empty, size, 0); - ImGuiUtil.HoverTooltip(good); - } - } - var tt = _config.UnlockedItemMode ? "\nThese items will be skipped when applied automatically.\n\nTo change this, disable the Obtained Item Mode setting." : string.Empty; @@ -355,6 +317,27 @@ public class SetPanel : string.Empty; DrawWarning(sb2, _config.UnlockedItemMode ? 0xA03030F0 : 0x0, size, tt, "All customizations to be applied are unlocked."); ImGui.SameLine(); + return; + + static void DrawWarning(StringBuilder sb, uint color, Vector2 size, string suffix, string good) + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); + if (sb.Length > 0) + { + sb.Append(suffix); + using (_ = ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color); + } + + ImGuiUtil.HoverTooltip(sb.ToString()); + } + else + { + ImGuiUtil.DrawTextButton(string.Empty, size, 0); + ImGuiUtil.HoverTooltip(good); + } + } } private void DrawDragDrop(AutoDesignSet set, int index) @@ -394,7 +377,7 @@ public class SetPanel var newType = design.ApplicationType; var newTypeInt = (uint)newType; style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); - using (var c = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value())) + using (_ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value())) { if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)AutoDesign.Type.All)) newType = (AutoDesign.Type)newTypeInt; diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 298fe0e..bbcfa32 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Numerics; using Dalamud.Interface; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index 9906387..c57c1b5 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -1,9 +1,10 @@ using System; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs index f253923..b062980 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs @@ -1,10 +1,10 @@ using System; using System.Numerics; -using Glamourer.Customization; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs index 85ba96c..ef76b09 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs @@ -1,10 +1,9 @@ using System.IO; using System.Numerics; -using Glamourer.Customization; using Glamourer.Interop; using ImGuiNET; using OtterGui; -using OtterGui.Raii; +using Penumbra.GameData.Files; namespace Glamourer.Gui.Tabs.DebugTab; @@ -35,7 +34,7 @@ public class DatFilePanel(ImportService _importService) : IDebugTabTree ImGui.TextUnformatted(_datFile.Value.Version.ToString()); ImGui.TextUnformatted(_datFile.Value.Time.LocalDateTime.ToString("g")); ImGui.TextUnformatted(_datFile.Value.Voice.ToString()); - ImGui.TextUnformatted(_datFile.Value.Customize.Data.ToString()); + ImGui.TextUnformatted(_datFile.Value.Customize.ToString()); ImGui.TextUnformatted(_datFile.Value.Description); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index af4814a..8b7ae9a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -1,9 +1,7 @@ using System; using System.Linq; using Dalamud.Interface; -using Glamourer.Customization; using Glamourer.Designs; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index 03ba048..400b2b1 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -1,10 +1,8 @@ using System; using System.Linq; using Dalamud.Interface; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Services; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -85,7 +83,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe DrawDesignData(_parse64); using var font = ImRaii.PushFont(UiBuilder.MonoFont); ImGui.TextUnformatted(_base64); - using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 })) + using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 })) { foreach (var (c1, c2) in _restore.Zip(_base64)) { @@ -99,7 +97,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe foreach (var ((b1, b2), idx) in _base64Bytes.Zip(_restoreBytes).WithIndex()) { - using (var group = ImRaii.Group()) + using (_ = ImRaii.Group()) { ImGui.TextUnformatted(idx.ToString("D2")); ImGui.TextUnformatted(b1.ToString("X2")); @@ -121,7 +119,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe using var font = ImRaii.PushFont(UiBuilder.MonoFont); foreach (var (b, idx) in _base64Bytes.WithIndex()) { - using (var group = ImRaii.Group()) + using (_ = ImRaii.Group()) { ImGui.TextUnformatted(idx.ToString("D2")); ImGui.TextUnformatted(b.ToString("X2")); diff --git a/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs b/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs index 0fa8765..2318d98 100644 --- a/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs @@ -32,7 +32,7 @@ public class JobPanel(JobService _jobs) : IDebugTabTree foreach (var (id, job) in _jobs.Jobs) { - ImGuiUtil.DrawTableColumn(id.ToString("D3")); + ImGuiUtil.DrawTableColumn(id.Id.ToString("D3")); ImGuiUtil.DrawTableColumn(job.Name); ImGuiUtil.DrawTableColumn(job.Abbreviation); } @@ -68,7 +68,7 @@ public class JobPanel(JobService _jobs) : IDebugTabTree foreach (var (id, group) in _jobs.JobGroups) { - ImGuiUtil.DrawTableColumn(id.ToString("D3")); + ImGuiUtil.DrawTableColumn(id.Id.ToString("D3")); ImGuiUtil.DrawTableColumn(group.Name); ImGuiUtil.DrawTableColumn(group.Count.ToString()); } diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index e71c69a..596476b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -1,10 +1,8 @@ using System; using System.Numerics; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Glamourer.Customization; using Glamourer.Interop; using Glamourer.Interop.Structs; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -126,15 +124,14 @@ public unsafe class ModelEvaluationPanel( model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty); } - private void DrawWeaponState(Actor actor, Model model) + private static void DrawWeaponState(Actor actor, Model model) { using var id = ImRaii.PushId("WeaponState"); ImGuiUtil.DrawTableColumn("Weapon State"); ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->DrawData.IsWeaponHidden ? "Hidden" : "Visible" : "No Character"); - var text = string.Empty; - + string text; if (!model.IsHuman) { text = "No Model"; @@ -146,19 +143,14 @@ public unsafe class ModelEvaluationPanel( else { var weapon = (DrawObject*)model.AsDrawObject->Object.ChildObject; - if ((weapon->Flags & 0x09) == 0x09) - text = "Visible"; - else - text = "Hidden"; + text = (weapon->Flags & 0x09) == 0x09 ? "Visible" : "Hidden"; } ImGuiUtil.DrawTableColumn(text); ImGui.TableNextColumn(); - if (!model.IsHuman) - return; } - private void DrawWetness(Actor actor, Model model) + private static void DrawWetness(Actor actor, Model model) { using var id = ImRaii.PushId("Wetness"); ImGuiUtil.DrawTableColumn("Wetness"); @@ -212,12 +204,12 @@ public unsafe class ModelEvaluationPanel( private void DrawCustomize(Actor actor, Model model) { using var id = ImRaii.PushId("Customize"); - var actorCustomize = new Customize(actor.IsCharacter + var actorCustomize = actor.IsCharacter ? *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData - : new CustomizeArray()); - var modelCustomize = new Customize(model.IsHuman + : new CustomizeArray(); + var modelCustomize = model.IsHuman ? *(CustomizeArray*)model.AsHuman->Customize.Data - : new CustomizeArray()); + : new CustomizeArray(); foreach (var type in Enum.GetValues()) { using var id2 = ImRaii.PushId((int)type); @@ -235,7 +227,7 @@ public unsafe class ModelEvaluationPanel( var shift = BitOperations.TrailingZeroCount(mask); var newValue = value + (1 << shift); modelCustomize.Set(type, (CustomizeValue)newValue); - _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); + _changeCustomizeService.UpdateCustomize(model, modelCustomize); } ImGui.SameLine(); @@ -246,14 +238,14 @@ public unsafe class ModelEvaluationPanel( var shift = BitOperations.TrailingZeroCount(mask); var newValue = value - (1 << shift); modelCustomize.Set(type, (CustomizeValue)newValue); - _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); + _changeCustomizeService.UpdateCustomize(model, modelCustomize); } ImGui.SameLine(); if (ImGui.SmallButton("Reset")) { modelCustomize.Set(type, actorCustomize[type]); - _changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); + _changeCustomizeService.UpdateCustomize(model, modelCustomize); } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 90fc0f5..ffc4b72 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -3,14 +3,15 @@ using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Object; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; +using Glamourer.GameData; using Glamourer.Interop; using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Enums; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; @@ -24,7 +25,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM => false; private string _npcFilter = string.Empty; - private bool _customizeOrGear = false; + private bool _customizeOrGear; public void Draw() { @@ -48,19 +49,17 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.TableNextRow(); var idx = 0; var remainder = ImGuiClip.FilteredClippedDraw(_npcCombo.Items, skips, - d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), Draw); + d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), DrawData); ImGui.TableNextColumn(); ImGuiClip.DrawEndDummy(remainder, ImGui.GetFrameHeightWithSpacing()); - - return; - void Draw(NpcData data) + void DrawData(NpcData data) { using var id = ImRaii.PushId(idx++); var disabled = !_state.GetOrCreate(_objectManager.Player, out var state); ImGui.TableNextColumn(); - if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled, false)) + if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled)) { foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand)) _state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual); @@ -76,7 +75,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(data.Kind is ObjectKind.BattleNpc ? "B" : "E"); - using (var icon = ImRaii.PushFont(UiBuilder.IconFont)) + using (_ = ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); @@ -86,7 +85,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM using var mono = ImRaii.PushFont(UiBuilder.MonoFont); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(_customizeOrGear ? data.Customize.Data.ToString() : data.WriteGear()); + ImGui.TextUnformatted(_customizeOrGear ? data.Customize.ToString() : data.WriteGear()); } } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index b6e5fa3..cd47606 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -7,14 +7,12 @@ using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Internal.Notifications; using FFXIVClientStructs.FFXIV.Client.System.Framework; using Glamourer.Automation; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop; using Glamourer.State; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Classes; diff --git a/Glamourer/Gui/Tabs/NpcCombo.cs b/Glamourer/Gui/Tabs/NpcCombo.cs index 91f0db0..4b1274c 100644 --- a/Glamourer/Gui/Tabs/NpcCombo.cs +++ b/Glamourer/Gui/Tabs/NpcCombo.cs @@ -1,4 +1,4 @@ -using Glamourer.Customization; +using Glamourer.GameData; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 154e930..8953501 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Numerics; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.Utility; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Services; using Glamourer.Unlocks; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 3f7531c..d7e1ce3 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -8,7 +8,6 @@ using Dalamud.Interface.Utility; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Services; -using Glamourer.Structs; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index dda4584..5e7e813 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -2,7 +2,6 @@ using Glamourer.Designs; using Glamourer.Events; using Glamourer.State; -using Glamourer.Structs; using Penumbra.GameData.Enums; namespace Glamourer.Gui; diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 2c64b42..d08fb18 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -1,9 +1,7 @@ using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Utility; -using Glamourer.Customization; using Glamourer.Services; -using Glamourer.Structs; using Glamourer.Unlocks; using ImGuiNET; using Lumina.Misc; @@ -61,7 +59,7 @@ public static class UiHelpers { var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); - using (var disabled = ImRaii.Disabled(locked)) + using (_ = ImRaii.Disabled(locked)) { if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw( "##" + label, flags, out flags)) diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index ef7e762..947ce43 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -2,7 +2,6 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop.Structs; using OtterGui.Classes; @@ -15,7 +14,7 @@ namespace Glamourer.Interop; /// Changes in Race, body type or Gender are probably ignored. /// This operates on draw objects, not game objects. /// -public unsafe class ChangeCustomizeService : EventWrapper>, ChangeCustomizeService.Priority> +public unsafe class ChangeCustomizeService : EventWrapper>, ChangeCustomizeService.Priority> { private readonly PenumbraReloaded _penumbraReloaded; private readonly IGameInteropProvider _interop; @@ -81,10 +80,11 @@ public unsafe class ChangeCustomizeService : EventWrapper(new Customize(*(CustomizeArray*)data)); + var customize = new Ref(*(CustomizeArray*)data); Invoke(this, (Model)human, customize); - ((Customize*)data)->Load(customize.Value); + *(CustomizeArray*)data = customize.Value; } + return _changeCustomizeHook.Original(human, data, skipEquipment); } } diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs index 6a58809..9ddcc03 100644 --- a/Glamourer/Interop/CharaFile/CharaFile.cs +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -1,8 +1,6 @@ using System; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Services; -using Glamourer.Structs; using Newtonsoft.Json.Linq; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -70,7 +68,7 @@ public sealed class CharaFile return; data.SetItem(slot, item); - data.SetStain(slot, (StainId)dye); + data.SetStain(slot, dye); flags |= slot.ToFlag(); flags |= slot.ToStainFlag(); } @@ -94,7 +92,7 @@ public sealed class CharaFile flags |= slot.ToStainFlag(); } - private static CustomizeFlag ParseCustomize(JObject jObj, ref Customize customize) + private static CustomizeFlag ParseCustomize(JObject jObj, ref CustomizeArray customize) { CustomizeFlag ret = 0; customize.Race = ParseRace(jObj, ref ret); @@ -149,7 +147,7 @@ public sealed class CharaFile return id; } - private static void ParseFacial(JObject jObj, ref Customize customize, ref CustomizeFlag application) + private static void ParseFacial(JObject jObj, ref CustomizeArray customize, ref CustomizeFlag application) { var jTok = jObj["FacialFeatures"]; if (jTok == null) @@ -186,7 +184,7 @@ public sealed class CharaFile customize[CustomizeIndex.LegacyTattoo] = CustomizeValue.Max; } - private static void ParseHighlights(JObject jObj, ref Customize customize, ref CustomizeFlag application) + private static void ParseHighlights(JObject jObj, ref CustomizeArray customize, ref CustomizeFlag application) { var jTok = jObj["EnableHighlights"]; if (jTok == null) @@ -242,15 +240,15 @@ public sealed class CharaFile throw new Exception($"Age {age} != Normal is not supported."); } - private static unsafe void ParseByte(JObject jObj, string property, CustomizeIndex idx, ref Customize customize, + private static unsafe void ParseByte(JObject jObj, string property, CustomizeIndex idx, ref CustomizeArray customize, ref CustomizeFlag application) { var jTok = jObj[property]; if (jTok == null) return; - customize.Data.Data[idx.ToByteAndMask().ByteIdx] = jTok.ToObject(); - application |= idx.ToFlag(); + customize.Data[idx.ToByteAndMask().ByteIdx] = jTok.ToObject(); + application |= idx.ToFlag(); } private static SubRace ParseTribe(JObject jObj, ref CustomizeFlag application) diff --git a/Glamourer/Interop/CharaFile/CmaFile.cs b/Glamourer/Interop/CharaFile/CmaFile.cs index c916ad8..e525e7e 100644 --- a/Glamourer/Interop/CharaFile/CmaFile.cs +++ b/Glamourer/Interop/CharaFile/CmaFile.cs @@ -42,7 +42,7 @@ public sealed class CmaFile var byteData = Convert.FromHexString(bytes); fixed (byte* ptr = byteData) { - data.Customize.Data.Read(ptr); + data.Customize.Read(ptr); } } @@ -64,7 +64,7 @@ public sealed class CmaFile data.SetStain(slot, armor.Stain); } - data.Customize.Data.Read(ptr); + data.Customize.Read(ptr); } } diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 9285ec6..2bfa8d5 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -1,12 +1,10 @@ using System; -using System.Linq; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Interop.Structs; -using Glamourer.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 86f2343..4cac25e 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -4,13 +4,14 @@ using System.IO; using System.Linq; using Dalamud.Interface.DragDrop; using Dalamud.Interface.Internal.Notifications; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Interop.CharaFile; using Glamourer.Services; -using Glamourer.Structs; using ImGuiNET; using OtterGui.Classes; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Files; +using Penumbra.GameData.Structs; namespace Glamourer.Interop; @@ -139,7 +140,7 @@ public class ImportService(CustomizationService _customizations, IDragDropManage return true; } - public bool SaveDesignAsDat(string path, in Customize input, string description) + public bool SaveDesignAsDat(string path, in CustomizeArray input, string description) { if (!Verify(input, out var voice)) return false; @@ -168,7 +169,7 @@ public class ImportService(CustomizationService _customizations, IDragDropManage } } - public bool Verify(in Customize input, out byte voice, byte? inputVoice = null) + public bool Verify(in CustomizeArray input, out byte voice, byte? inputVoice = null) { voice = 0; if (_customizations.ValidateClan(input.Clan, input.Race, out _, out _).Length > 0) diff --git a/Glamourer/Interop/JobService.cs b/Glamourer/Interop/JobService.cs index 2fe322f..5ee9cf6 100644 --- a/Glamourer/Interop/JobService.cs +++ b/Glamourer/Interop/JobService.cs @@ -6,7 +6,9 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Interop.Structs; -using Glamourer.Structs; +using Penumbra.GameData; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Structs; namespace Glamourer.Interop; @@ -14,19 +16,20 @@ public class JobService : IDisposable { private readonly nint _characterDataOffset; - public readonly IReadOnlyDictionary Jobs; - public readonly IReadOnlyDictionary JobGroups; - public readonly IReadOnlyList AllJobGroups; + public readonly DictJob Jobs; + public readonly DictJobGroup JobGroups; + + public IReadOnlyList AllJobGroups + => JobGroups.AllJobGroups; public event Action? JobChanged; - public JobService(IDataManager gameData, IGameInteropProvider interop) + public JobService(DictJob jobs, DictJobGroup jobGroups, IDataManager gameData, IGameInteropProvider interop) { interop.InitializeFromAttributes(this); _characterDataOffset = Marshal.OffsetOf(nameof(Character.CharacterData)); - Jobs = GameData.Jobs(gameData); - AllJobGroups = GameData.AllJobGroups(gameData); - JobGroups = GameData.JobGroups(gameData); + Jobs = jobs; + JobGroups = jobGroups; _changeJobHook.Enable(); } diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 750527b..066d985 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -2,8 +2,6 @@ using System; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; -using Glamourer.Customization; -using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; @@ -116,8 +114,8 @@ public readonly unsafe struct Actor : IEquatable public CharacterWeapon GetOffhand() => new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).ModelId.Value); - public Customize GetCustomize() - => *(Customize*)&AsCharacter->DrawData.CustomizeData; + public CustomizeArray GetCustomize() + => *(CustomizeArray*)&AsCharacter->DrawData.CustomizeData; // TODO remove this when available in ClientStructs internal ref CrestFlag CrestBitfield diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index 77bf24e..9705248 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -1,7 +1,6 @@ using System; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Glamourer.Customization; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object; @@ -91,8 +90,8 @@ public readonly unsafe struct Model : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()]; - public Customize GetCustomize() - => *(Customize*)&AsHuman->Customize; + public CustomizeArray GetCustomize() + => *(CustomizeArray*)&AsHuman->Customize; public (Model Address, CharacterWeapon Data) GetMainhand() { diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index f5a0ec0..31472ff 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -4,6 +4,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using Glamourer.Events; using Glamourer.Interop.Structs; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 7ccf963..4c87430 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -5,7 +5,6 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; using Glamourer.Interop.Structs; -using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -14,18 +13,15 @@ namespace Glamourer.Interop; public unsafe class WeaponService : IDisposable { private readonly WeaponLoading _event; - private readonly CrestService _crestService; private readonly ThreadLocal _inUpdate = new(() => false); private readonly delegate* unmanaged[Stdcall] _original; - - public WeaponService(WeaponLoading @event, IGameInteropProvider interop, CrestService crestService) + public WeaponService(WeaponLoading @event, IGameInteropProvider interop) { - _event = @event; - _crestService = crestService; + _event = @event; _loadWeaponHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour); _original = @@ -73,7 +69,7 @@ public unsafe class WeaponService : IDisposable _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); - + if (tmpWeapon.Value != weapon.Value) { if (tmpWeapon.Skeleton.Id == 0) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 417f1b9..2135357 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -5,13 +5,11 @@ using Dalamud.Game.Command; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Services; using Glamourer.Automation; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Gui; using Glamourer.Interop; using Glamourer.State; -using Glamourer.Structs; using ImGuiNET; using OtterGui; using OtterGui.Classes; diff --git a/Glamourer/Services/CustomizationService.cs b/Glamourer/Services/CustomizationService.cs index b5f474f..9e28e42 100644 --- a/Glamourer/Services/CustomizationService.cs +++ b/Glamourer/Services/CustomizationService.cs @@ -2,10 +2,11 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Dalamud.Plugin.Services; -using Glamourer.Customization; +using Glamourer.GameData; using OtterGui.Services; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Services; @@ -30,13 +31,12 @@ public sealed class CustomizationService( public Task Awaiter => _task; - public (Customize NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(Customize oldValues, Customize newValues, + public (CustomizeArray NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(CustomizeArray oldValues, CustomizeArray newValues, CustomizeFlag applyWhich, bool allowUnknown) { CustomizeFlag applied = 0; CustomizeFlag changed = 0; - Customize ret = default; - ret.Load(oldValues); + var ret = oldValues; if (applyWhich.HasFlag(CustomizeFlag.Clan)) { changed |= ChangeClan(ref ret, newValues.Clan); @@ -247,7 +247,7 @@ public sealed class CustomizationService( } /// Change a clan while keeping all other customizations valid. - public CustomizeFlag ChangeClan(ref Customize customize, SubRace newClan) + public CustomizeFlag ChangeClan(ref CustomizeArray customize, SubRace newClan) { if (customize.Clan == newClan) return 0; @@ -271,7 +271,7 @@ public sealed class CustomizationService( } /// Change a gender while keeping all other customizations valid. - public CustomizeFlag ChangeGender(ref Customize customize, Gender newGender) + public CustomizeFlag ChangeGender(ref CustomizeArray customize, Gender newGender) { if (customize.Gender == newGender) return 0; @@ -288,7 +288,7 @@ public sealed class CustomizationService( return FixValues(set, ref customize) | CustomizeFlag.Gender; } - private static CustomizeFlag FixValues(CustomizationSet set, ref Customize customize) + private static CustomizeFlag FixValues(CustomizationSet set, ref CustomizeArray customize) { CustomizeFlag flags = 0; foreach (var idx in CustomizationExtensions.AllBasic) diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index d88a90c..3afe390 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -15,7 +15,6 @@ using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.State; -using Glamourer.Structs; using Glamourer.Unlocks; using Microsoft.Extensions.DependencyInjection; using OtterGui.Classes; @@ -24,6 +23,7 @@ using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Services; diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index 3cd7cba..484d0a9 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -1,13 +1,11 @@ -using Glamourer.Customization; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Events; -using Glamourer.Structs; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using System.Linq; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; -using CustomizeIndex = Glamourer.Customization.CustomizeIndex; +using Penumbra.GameData.Structs; namespace Glamourer.State; @@ -34,7 +32,7 @@ public class ActorState public DesignData ModelData; /// The last seen job. - public byte LastJob; + public JobId LastJob; /// The Lock-Key locking this state. public uint Combination; diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 5ab3f41..67c9e08 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -2,7 +2,6 @@ using System.Linq; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface.Internal.Notifications; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Gui; using Glamourer.Interop; @@ -12,7 +11,7 @@ using ImGuiNET; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using CustomizeIndex = Glamourer.Customization.CustomizeIndex; +using CustomizeIndex = Penumbra.GameData.Enums.CustomizeIndex; namespace Glamourer.State; @@ -107,7 +106,7 @@ public unsafe class FunModule : IDisposable } } - public void ApplyFun(Actor actor, Span armor, ref Customize customize) + public void ApplyFun(Actor actor, Span armor, ref CustomizeArray customize) { if (!ValidFunTarget(actor)) return; @@ -181,7 +180,7 @@ public unsafe class FunModule : IDisposable } } - public void ApplyOops(ref Customize customize) + public void ApplyOops(ref CustomizeArray customize) { if (_codes.EnabledOops == Race.Unknown) return; @@ -193,7 +192,7 @@ public unsafe class FunModule : IDisposable _customizations.ChangeClan(ref customize, targetClan); } - public void ApplyIndividual(ref Customize customize) + public void ApplyIndividual(ref CustomizeArray customize) { if (!_codes.EnabledIndividual) return; @@ -209,7 +208,7 @@ public unsafe class FunModule : IDisposable } } - public void Apply63(ref Customize customize) + public void Apply63(ref CustomizeArray customize) { if (!_codes.Enabled63 || customize.Race is Race.Hrothgar) // TODO Female Hrothgar return; @@ -217,7 +216,7 @@ public unsafe class FunModule : IDisposable _customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male); } - public void ApplySizing(Actor actor, ref Customize customize) + public void ApplySizing(Actor actor, ref CustomizeArray customize) { if (_codes.EnabledSizing == CodeService.Sizing.None) return; diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index a91a1eb..43c9097 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,11 +1,9 @@ using System.Linq; -using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; -using Glamourer.Structs; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -40,7 +38,7 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We /// Change the customization values of actors either by applying them via update or redrawing, /// this depends on whether the changes include changes to Race, Gender, Body Type or Face. /// - public unsafe void ChangeCustomize(ActorData data, in Customize customize, ActorState? state = null) + public unsafe void ChangeCustomize(ActorData data, in CustomizeArray customize, ActorState? _ = null) { foreach (var actor in data.Objects) { @@ -48,15 +46,15 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We if (!mdl.IsCharacterBase) continue; - var flags = Customize.Compare(mdl.GetCustomize(), customize); + var flags = CustomizeArray.Compare(mdl.GetCustomize(), customize); if (!flags.RequiresRedraw() || !mdl.IsHuman) { - _changeCustomize.UpdateCustomize(mdl, customize.Data); + _changeCustomize.UpdateCustomize(mdl, customize); } else if (data.Objects.Count > 1 && _objects.IsInGPose && !actor.IsGPoseOrCutscene) { - var mdlCustomize = (Customize*)&mdl.AsHuman->Customize; - mdlCustomize->Load(customize); + var mdlCustomize = (CustomizeArray*)&mdl.AsHuman->Customize; + *mdlCustomize = customize; _penumbra.RedrawObject(actor, RedrawType.AfterGPose); } else @@ -66,7 +64,7 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We } } - /// + /// public ActorData ChangeCustomize(ActorState state, bool apply) { var data = GetData(state); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index ebc2661..50b2605 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,10 +1,8 @@ using System; using System.Linq; using Dalamud.Plugin.Services; -using Glamourer.Customization; using Glamourer.Events; using Glamourer.Services; -using Glamourer.Structs; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -30,7 +28,7 @@ public class StateEditor /// Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. /// We currently only allow changing things to humans, not humans to monsters. - public bool ChangeModelId(ActorState state, uint modelId, in Customize customize, nint equipData, StateChanged.Source source, + public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateChanged.Source source, out uint oldModelId, uint key = 0) { oldModelId = state.ModelData.ModelId; @@ -57,7 +55,7 @@ public class StateEditor return false; // Fix up everything else to make sure the result is a valid human. - state.ModelData.Customize = Customize.Default; + state.ModelData.Customize = CustomizeArray.Default; state.ModelData.SetDefaultEquipment(_items); state.ModelData.SetHatVisible(true); state.ModelData.SetWeaponVisible(true); @@ -104,8 +102,8 @@ public class StateEditor } /// Change an entire customization array according to flags. - public bool ChangeHumanCustomize(ActorState state, in Customize customizeInput, CustomizeFlag applyWhich, StateChanged.Source source, - out Customize old, out CustomizeFlag changed, uint key = 0) + public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, StateChanged.Source source, + out CustomizeArray old, out CustomizeFlag changed, uint key = 0) { old = state.ModelData.Customize; changed = 0; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index f5f2e3b..d7f947f 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -1,5 +1,4 @@ using Glamourer.Automation; -using Glamourer.Customization; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -12,7 +11,6 @@ using Penumbra.GameData.Structs; using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; -using Glamourer.Structs; using Penumbra.GameData.DataContainers; namespace Glamourer.State; @@ -114,7 +112,7 @@ public class StateListener : IDisposable _creatingIdentifier = actor.GetIdentifier(_actors); ref var modelId = ref *(uint*)modelPtr; - ref var customize = ref *(Customize*)customizePtr; + ref var customize = ref *(CustomizeArray*)customizePtr; if (_autoDesignApplier.Reduce(actor, _creatingIdentifier, out _creatingState)) { switch (UpdateBaseData(actor, _creatingState, modelId, customizePtr, equipDataPtr)) @@ -140,7 +138,7 @@ public class StateListener : IDisposable ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender); } - private unsafe void OnCustomizeChange(Model model, Ref customize) + private unsafe void OnCustomizeChange(Model model, Ref customize) { if (!model.IsHuman) return; @@ -156,7 +154,7 @@ public class StateListener : IDisposable UpdateCustomize(actor, state, ref customize.Value, false); } - private void UpdateCustomize(Actor actor, ActorState state, ref Customize customize, bool checkTransform) + private void UpdateCustomize(Actor actor, ActorState state, ref CustomizeArray customize, bool checkTransform) { switch (UpdateBaseData(actor, state, customize, checkTransform)) { @@ -515,7 +513,7 @@ public class StateListener : IDisposable if (isHuman) state.BaseData = _manager.FromActor(actor, false, false); else - state.BaseData.LoadNonHuman(modelId, *(Customize*)customizeData, equipData); + state.BaseData.LoadNonHuman(modelId, *(CustomizeArray*)customizeData, equipData); return UpdateState.Change; } @@ -526,7 +524,7 @@ public class StateListener : IDisposable /// only if we kept track of state of someone who went to the aesthetician, /// or if they used other tools to change things. /// - private UpdateState UpdateBaseData(Actor actor, ActorState state, Customize customize, bool checkTransform) + private UpdateState UpdateBaseData(Actor actor, ActorState state, CustomizeArray customize, bool checkTransform) { // Customize array does not agree between game object and draw object => transformation. if (checkTransform && !actor.GetCustomize().Equals(customize)) @@ -537,7 +535,7 @@ public class StateListener : IDisposable return UpdateState.NoChange; // TODO: handle wrong base data. // Update customize base state. - state.BaseData.Customize.Load(customize); + state.BaseData.Customize = customize; return UpdateState.Change; } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 8446288..dc83305 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -4,13 +4,11 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Dalamud.Plugin.Services; -using Glamourer.Customization; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; -using Glamourer.Structs; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -18,8 +16,15 @@ using Penumbra.GameData.Structs; namespace Glamourer.State; -public class StateManager(ActorManager _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor, - HumanModelList _humans, ICondition _condition, IClientState _clientState) +public class StateManager( + ActorManager _actors, + ItemManager _items, + StateChanged _event, + StateApplier _applier, + StateEditor _editor, + HumanModelList _humans, + ICondition _condition, + IClientState _clientState) : IReadOnlyDictionary { private readonly Dictionary _states = []; @@ -102,7 +107,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged // TODO reverse search model data to get model id from model. if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) { - ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(Customize*)&actor.AsCharacter->DrawData.CustomizeData, + ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData, (nint)(&actor.AsCharacter->DrawData.Head)); return ret; } @@ -194,9 +199,9 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged return; var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id); - offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50); - offhand.Variant = mainhand.Variant; - offhand.Weapon = mainhand.Weapon; + offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50); + offhand.Variant = mainhand.Variant; + offhand.Weapon = mainhand.Weapon; ret.SetItem(EquipSlot.Hands, gauntlets); ret.SetStain(EquipSlot.Hands, mainhand.Stain); } @@ -205,10 +210,10 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged /// Turn an actor human. public void TurnHuman(ActorState state, StateChanged.Source source, uint key = 0) - => ChangeModelId(state, 0, Customize.Default, nint.Zero, source, key); + => ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key); /// Turn an actor to. - public void ChangeModelId(ActorState state, uint modelId, Customize customize, nint equipData, StateChanged.Source source, + public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateChanged.Source source, uint key = 0) { if (!_editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key)) @@ -233,7 +238,8 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged } /// Change an entire customization array according to flags. - public void ChangeCustomize(ActorState state, in Customize customizeInput, CustomizeFlag apply, StateChanged.Source source, uint key = 0) + public void ChangeCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag apply, StateChanged.Source source, + uint key = 0) { if (!_editor.ChangeHumanCustomize(state, customizeInput, apply, source, out var old, out var applied, key)) return; @@ -447,7 +453,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged var redraw = state.ModelData.ModelId != state.BaseData.ModelId || !state.ModelData.IsHuman - || Customize.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw(); + || CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw(); state.ModelData = state.BaseData; state.ModelData.SetIsWet(false); foreach (var index in Enum.GetValues()) @@ -458,7 +464,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged state[slot, true] = StateChanged.Source.Game; state[slot, false] = StateChanged.Source.Game; } - + foreach (var type in Enum.GetValues()) state[type] = StateChanged.Source.Game; @@ -470,7 +476,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged actors = ApplyAll(state, redraw, true); Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors, null); + _event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors); } public void ResetStateFixed(ActorState state, uint key = 0) @@ -538,7 +544,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged if (!GetOrCreate(actor, out var state)) return; - ApplyAll(state, !actor.Model.IsHuman || Customize.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), + ApplyAll(state, !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); } diff --git a/Glamourer/State/WorldSets.cs b/Glamourer/State/WorldSets.cs index 3f694ee..4464aee 100644 --- a/Glamourer/State/WorldSets.cs +++ b/Glamourer/State/WorldSets.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Glamourer.Interop.Structs; -using Glamourer.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index 8764438..a1e95ef 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -8,10 +8,11 @@ using Dalamud.Plugin.Services; using Dalamud.Utility; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.UI; -using Glamourer.Customization; +using Glamourer.GameData; using Glamourer.Events; using Glamourer.Services; using Lumina.Excel.GeneratedSheets; +using Penumbra.GameData.Enums; namespace Glamourer.Unlocks; @@ -172,7 +173,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable => UnlockDictionaryHelpers.Load(ToFilename(_saveService.FileNames), _unlocked, id => Unlockable.Any(c => c.Value.Data == id), "customization"); - /// Create a list of all unlockable hairstyles and facepaints. + /// Create a list of all unlockable hairstyles and face paints. private static Dictionary CreateUnlockableCustomizations(CustomizationService customizations, IDataManager gameData) { diff --git a/Penumbra.GameData b/Penumbra.GameData index 3787e82..d9a9627 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 3787e82d1b84d2542b6e4238060d75383a4b12a1 +Subproject commit d9a962748364b267df1b186cdaebb9ed9f3fceb6 From 03a0cb5514b4704d15a94c10a985438f10ad3428 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Dec 2023 14:22:39 +0100 Subject: [PATCH 102/786] Update Gamedata. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index d9a9627..ed37f83 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d9a962748364b267df1b186cdaebb9ed9f3fceb6 +Subproject commit ed37f83424c11a5a601e74f4660cd52ebd68a7b3 From aae4141550ab206fc2fbff6022f9a55d0d907988 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 23 Dec 2023 13:08:20 +0100 Subject: [PATCH 103/786] Add IPC to set single items. --- Glamourer/Api/GlamourerIpc.Set.cs | 92 +++++++++++++++++++ Glamourer/Api/GlamourerIpc.cs | 16 +++- Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 48 +++++++++- Penumbra.GameData | 2 +- 4 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 Glamourer/Api/GlamourerIpc.Set.cs diff --git a/Glamourer/Api/GlamourerIpc.Set.cs b/Glamourer/Api/GlamourerIpc.Set.cs new file mode 100644 index 0000000..f6fe92e --- /dev/null +++ b/Glamourer/Api/GlamourerIpc.Set.cs @@ -0,0 +1,92 @@ +using System.Linq; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Plugin; +using Glamourer.Events; +using Glamourer.Services; +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 LabelSetItemByActorName = "Glamourer.SetItemByActorName"; + + + private readonly FuncProvider _setItemProvider; + private readonly FuncProvider _setItemByActorNameProvider; + + public static FuncSubscriber SetItemSubscriber(DalamudPluginInterface pi) + => new(pi, LabelSetItem); + + public static FuncSubscriber SetItemByActorNameSubscriber(DalamudPluginInterface pi) + => new(pi, LabelSetItemByActorName); + + private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, uint key) + { + 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.ChangeItem(state, slot, item, StateChanged.Source.Ipc, key); + return GlamourerErrorCode.Success; + } + + private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, uint key) + { + 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.ChangeItem(state, slot, item, StateChanged.Source.Ipc, key); + found = true; + } + + return found ? GlamourerErrorCode.Success : GlamourerErrorCode.ActorNotFound; + } +} diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 53cfb18..a19dd6f 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -7,6 +7,7 @@ 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; @@ -15,7 +16,7 @@ using Penumbra.String; namespace Glamourer.Api; -public partial class GlamourerIpc : IDisposable +public sealed partial class GlamourerIpc : IDisposable { public const int CurrentApiVersionMajor = 0; public const int CurrentApiVersionMinor = 4; @@ -25,15 +26,18 @@ public partial class GlamourerIpc : IDisposable private readonly ActorManager _actors; private readonly DesignConverter _designConverter; private readonly AutoDesignApplier _autoDesignApplier; + private readonly ItemManager _items; public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorManager actors, - DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier) + DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier, + ItemManager items) { _stateManager = stateManager; _objects = objects; _actors = actors; _designConverter = designConverter; _autoDesignApplier = autoDesignApplier; + _items = items; _gPose = gPose; _stateChangedEvent = stateChangedEvent; _apiVersionProvider = new FuncProvider(pi, LabelApiVersion, ApiVersion); @@ -76,6 +80,11 @@ public partial class GlamourerIpc : IDisposable _stateChangedProvider = new EventProvider>(pi, LabelStateChanged); _gPoseChangedProvider = new EventProvider(pi, LabelGPoseChanged); + _setItemProvider = new FuncProvider(pi, LabelSetItem, + (idx, slot, item, key) => (int)SetItem(idx, (EquipSlot)slot, item, key)); + _setItemByActorNameProvider = new FuncProvider(pi, LabelSetItemByActorName, + (name, slot, item, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, key)); + _stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc); _gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc); } @@ -114,6 +123,9 @@ public partial class GlamourerIpc : IDisposable _stateChangedProvider.Dispose(); _gPose.Unsubscribe(OnGPoseChanged); _gPoseChangedProvider.Dispose(); + + _setItemProvider.Dispose(); + _setItemByActorNameProvider.Dispose(); } private IEnumerable FindActors(string actorName) diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index e42d252..329a2af 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -1,10 +1,14 @@ using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Interface.Utility; 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.Structs; namespace Glamourer.Gui.Tabs.DebugTab; @@ -16,15 +20,20 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag public bool Disabled => false; - private int _gameObjectIndex; - private string _gameObjectName = string.Empty; - private string _base64Apply = string.Empty; + private int _gameObjectIndex; + private CustomItemId _customItemId; + private EquipSlot _slot = EquipSlot.Head; + private string _gameObjectName = string.Empty; + private string _base64Apply = string.Empty; + private GlamourerIpc.GlamourerErrorCode _setItemEc; + private GlamourerIpc.GlamourerErrorCode _setItemByActorNameEc; - public void Draw() + public unsafe 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); + DrawItemIdInput(); using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) return; @@ -104,5 +113,36 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag if (ImGui.Button("Revert##CustomizeCharacter")) GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); + + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItem); + ImGui.TableNextColumn(); + if (ImGui.Button("Set##SetItem")) + _setItemEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemSubscriber(_pluginInterface) + .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, (byte)_slot, _customItemId.Id, 1337); + if (_setItemEc != GlamourerIpc.GlamourerErrorCode.Success) + { + ImGui.SameLine(); + ImGui.TextUnformatted(_setItemEc.ToString()); + } + + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName); + ImGui.TableNextColumn(); + if (ImGui.Button("Set##SetItemByActorName")) + _setItemByActorNameEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface) + .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, 1337); + if (_setItemByActorNameEc != GlamourerIpc.GlamourerErrorCode.Success) + { + ImGui.SameLine(); + ImGui.TextUnformatted(_setItemByActorNameEc.ToString()); + } + } + + private unsafe void DrawItemIdInput() + { + var tmp = _customItemId.Id; + if (ImGui.InputScalar("Custom Item ID", ImGuiDataType.U64, (nint)(&tmp), nint.Zero, nint.Zero)) + _customItemId = (CustomItemId)tmp; + ImGui.SameLine(); + EquipSlotCombo.Draw("Equip Slot", string.Empty, 200 * ImGuiHelpers.GlobalScale, ref _slot); } } diff --git a/Penumbra.GameData b/Penumbra.GameData index ed37f83..4af8775 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit ed37f83424c11a5a601e74f4660cd52ebd68a7b3 +Subproject commit 4af8775f0925ff89e6900c8816b03e0ffeb73f6d From ab76d3508b9a55d05523c99b74b8c45934464d6d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 23 Dec 2023 19:33:50 +0100 Subject: [PATCH 104/786] Rework and improve CustomizationManager and stuff. --- Glamourer/Automation/AutoDesignApplier.cs | 8 +- Glamourer/Designs/Design.cs | 6 +- Glamourer/Designs/DesignBase.cs | 46 +- Glamourer/Designs/DesignConverter.cs | 2 +- Glamourer/Designs/DesignManager.cs | 4 +- Glamourer/GameData/CharaMakeParams.cs | 4 +- Glamourer/GameData/ColorParameters.cs | 9 +- Glamourer/GameData/CustomName.cs | 38 -- Glamourer/GameData/CustomizationManager.cs | 38 -- Glamourer/GameData/CustomizationNpcOptions.cs | 56 -- Glamourer/GameData/CustomizationOptions.cs | 530 ------------------ Glamourer/GameData/CustomizeData.cs | 21 +- Glamourer/GameData/CustomizeManager.cs | 77 +++ .../{CustomizationSet.cs => CustomizeSet.cs} | 142 ++--- Glamourer/GameData/CustomizeSetFactory.cs | 459 +++++++++++++++ Glamourer/GameData/ICustomizationManager.cs | 17 - Glamourer/GameData/NpcCustomizeSet.cs | 164 ++++-- Glamourer/GameData/NpcData.cs | 78 ++- .../Customization/CustomizationDrawer.Icon.cs | 8 +- .../Gui/Customization/CustomizationDrawer.cs | 6 +- Glamourer/Gui/DesignCombo.cs | 4 +- Glamourer/Gui/GenericPopupWindow.cs | 5 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 4 +- .../DebugTab/CustomizationServicePanel.cs | 23 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 29 +- Glamourer/Interop/ImportService.cs | 6 +- ...mizationService.cs => CustomizeService.cs} | 90 +-- Glamourer/Services/ServiceManager.cs | 2 +- Glamourer/State/FunModule.cs | 6 +- Glamourer/State/StateEditor.cs | 6 +- Glamourer/State/StateListener.cs | 6 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 43 +- OtterGui | 2 +- 34 files changed, 916 insertions(+), 1025 deletions(-) delete mode 100644 Glamourer/GameData/CustomName.cs delete mode 100644 Glamourer/GameData/CustomizationManager.cs delete mode 100644 Glamourer/GameData/CustomizationNpcOptions.cs delete mode 100644 Glamourer/GameData/CustomizationOptions.cs create mode 100644 Glamourer/GameData/CustomizeManager.cs rename Glamourer/GameData/{CustomizationSet.cs => CustomizeSet.cs} (94%) create mode 100644 Glamourer/GameData/CustomizeSetFactory.cs delete mode 100644 Glamourer/GameData/ICustomizationManager.cs rename Glamourer/Services/{CustomizationService.cs => CustomizeService.cs} (62%) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 9f3e286..83ce3c4 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -27,7 +27,7 @@ public class AutoDesignApplier : IDisposable private readonly JobService _jobs; private readonly EquippedGearset _equippedGearset; private readonly ActorManager _actors; - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly CustomizeUnlockManager _customizeUnlocks; private readonly ItemUnlockManager _itemUnlocks; private readonly AutomationChanged _event; @@ -48,7 +48,7 @@ public class AutoDesignApplier : IDisposable } public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, - CustomizationService customizations, ActorManager actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, + CustomizeService customizations, ActorManager actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, EquippedGearset equippedGearset) { @@ -468,7 +468,7 @@ public class AutoDesignApplier : IDisposable totalCustomizeFlags |= CustomizeFlag.Face; } - var set = _customizations.Service.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); + var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); var face = state.ModelData.Customize.Face; foreach (var index in Enum.GetValues()) { @@ -477,7 +477,7 @@ public class AutoDesignApplier : IDisposable continue; var value = design.Customize[index]; - if (CustomizationService.IsCustomizationValid(set, face, index, value, out var data)) + if (CustomizeService.IsCustomizationValid(set, face, index, value, out var data)) { if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _)) continue; diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index d10fe29..7382fce 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -15,7 +15,7 @@ public sealed class Design : DesignBase, ISavable { #region Data - internal Design(CustomizationService customize, ItemManager items) + internal Design(CustomizeService customize, ItemManager items) : base(customize, items) { } @@ -98,7 +98,7 @@ public sealed class Design : DesignBase, ISavable #region Deserialization - public static Design LoadDesign(CustomizationService customizations, ItemManager items, JObject json) + public static Design LoadDesign(CustomizeService customizations, ItemManager items, JObject json) { var version = json["FileVersion"]?.ToObject() ?? 0; return version switch @@ -108,7 +108,7 @@ public sealed class Design : DesignBase, ISavable }; } - private static Design LoadDesignV1(CustomizationService customizations, ItemManager items, JObject json) + private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, JObject json) { static string[] ParseTags(JObject json) { diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 8e11ad8..7602f80 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -25,35 +25,35 @@ public class DesignBase public ref DesignData GetDesignDataRef() => ref _designData; - internal DesignBase(CustomizationService customize, ItemManager items) + internal DesignBase(CustomizeService customize, ItemManager items) { _designData.SetDefaultEquipment(items); - CustomizationSet = SetCustomizationSet(customize); + CustomizeSet = SetCustomizationSet(customize); } - internal DesignBase(CustomizationService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) + internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) { _designData = designData; ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; ApplyEquip = equipFlags & EquipFlagExtensions.All; _designFlags = 0; - CustomizationSet = SetCustomizationSet(customize); + CustomizeSet = SetCustomizationSet(customize); } internal DesignBase(DesignBase clone) { _designData = clone._designData; - CustomizationSet = clone.CustomizationSet; + CustomizeSet = clone.CustomizeSet; ApplyCustomize = clone.ApplyCustomizeRaw; ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; _designFlags = clone._designFlags & (DesignFlags)0x0F; } /// Ensure that the customization set is updated when the design data changes. - internal void SetDesignData(CustomizationService customize, in DesignData other) + internal void SetDesignData(CustomizeService customize, in DesignData other) { _designData = other; - CustomizationSet = SetCustomizationSet(customize); + CustomizeSet = SetCustomizationSet(customize); } #region Application Data @@ -69,11 +69,11 @@ public class DesignBase } private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; - public CustomizationSet CustomizationSet { get; private set; } + public CustomizeSet CustomizeSet { get; private set; } internal CustomizeFlag ApplyCustomize { - get => _applyCustomize.FixApplication(CustomizationSet); + get => _applyCustomize.FixApplication(CustomizeSet); set => _applyCustomize = value & CustomizeFlagExtensions.AllRelevant; } @@ -84,13 +84,13 @@ public class DesignBase internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; - public bool SetCustomize(CustomizationService customizationService, CustomizeArray customize) + public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize) { if (customize.Equals(_designData.Customize)) return false; _designData.Customize = customize; - CustomizationSet = customizationService.Service.GetList(customize.Clan, customize.Gender); + CustomizeSet = customizeService.Manager.GetSet(customize.Clan, customize.Gender); return true; } @@ -240,10 +240,10 @@ public class DesignBase } } - private CustomizationSet SetCustomizationSet(CustomizationService customize) + private CustomizeSet SetCustomizationSet(CustomizeService customize) => !_designData.IsHuman - ? customize.Service.GetList(SubRace.Midlander, Gender.Male) - : customize.Service.GetList(_designData.Customize.Clan, _designData.Customize.Gender); + ? customize.Manager.GetSet(SubRace.Midlander, Gender.Male) + : customize.Manager.GetSet(_designData.Customize.Clan, _designData.Customize.Gender); #endregion @@ -330,7 +330,7 @@ public class DesignBase #region Deserialization - public static DesignBase LoadDesignBase(CustomizationService customizations, ItemManager items, JObject json) + public static DesignBase LoadDesignBase(CustomizeService customizations, ItemManager items, JObject json) { var version = json["FileVersion"]?.ToObject() ?? 0; return version switch @@ -340,7 +340,7 @@ public class DesignBase }; } - private static DesignBase LoadDesignV1Base(CustomizationService customizations, ItemManager items, JObject json) + private static DesignBase LoadDesignV1Base(CustomizeService customizations, ItemManager items, JObject json) { var ret = new DesignBase(customizations, items); LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true); @@ -435,7 +435,7 @@ public class DesignBase design._designData.SetVisor(metaValue.ForcedValue); } - protected static void LoadCustomize(CustomizationService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman, + protected static void LoadCustomize(CustomizeService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman, bool allowUnknown) { if (json == null) @@ -473,7 +473,7 @@ public class DesignBase { var arrayText = json["Array"]?.ToObject() ?? string.Empty; design._designData.Customize.LoadBase64(arrayText); - design.CustomizationSet = design.SetCustomizationSet(customizations); + design.CustomizeSet = design.SetCustomizationSet(customizations); return; } @@ -485,18 +485,18 @@ public class DesignBase design._designData.Customize.Race = race; design._designData.Customize.Clan = clan; design._designData.Customize.Gender = gender; - design.CustomizationSet = design.SetCustomizationSet(customizations); + design.CustomizeSet = design.SetCustomizationSet(customizations); design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject() ?? false); - var set = design.CustomizationSet; + var set = design.CustomizeSet; foreach (var idx in CustomizationExtensions.AllBasic) { var tok = json[idx.ToString()]; var data = (CustomizeValue)(tok?["Value"]?.ToObject() ?? 0); if (set.IsAvailable(idx)) - PrintWarning(CustomizationService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data, + PrintWarning(CustomizeService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data, allowUnknown)); var apply = tok?["Apply"]?.ToObject() ?? false; design._designData.Customize[idx] = data; @@ -504,7 +504,7 @@ public class DesignBase } } - public void MigrateBase64(CustomizationService customize, ItemManager items, HumanModelList humans, string base64) + public void MigrateBase64(CustomizeService customize, ItemManager items, HumanModelList humans, string base64) { try { @@ -518,7 +518,7 @@ public class DesignBase SetApplyVisorToggle(applyVisor); SetApplyWeaponVisible(applyWeapon); SetApplyWetness(true); - CustomizationSet = SetCustomizationSet(customize); + CustomizeSet = SetCustomizationSet(customize); } catch (Exception ex) { diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 282ab0a..f7867c4 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -13,7 +13,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Designs; -public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizationService _customize, HumanModelList _humans) +public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans) { public const byte Version = 6; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 1320d6a..80cd0e0 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -18,7 +18,7 @@ namespace Glamourer.Designs; public class DesignManager { - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly ItemManager _items; private readonly HumanModelList _humans; private readonly SaveService _saveService; @@ -29,7 +29,7 @@ public class DesignManager public IReadOnlyList Designs => _designs; - public DesignManager(SaveService saveService, ItemManager items, CustomizationService customizations, + public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, DesignChanged @event, HumanModelList humans) { _saveService = saveService; diff --git a/Glamourer/GameData/CharaMakeParams.cs b/Glamourer/GameData/CharaMakeParams.cs index 12dedf9..4db5825 100644 --- a/Glamourer/GameData/CharaMakeParams.cs +++ b/Glamourer/GameData/CharaMakeParams.cs @@ -4,9 +4,7 @@ using Lumina.Excel.GeneratedSheets; namespace Glamourer.GameData; -/// -/// A custom version of CharaMakeParams that is easier to parse. -/// +/// A custom version of CharaMakeParams that is easier to parse. [Sheet("CharaMakeParams")] public class CharaMakeParams : ExcelRow { diff --git a/Glamourer/GameData/ColorParameters.cs b/Glamourer/GameData/ColorParameters.cs index 630a5a7..975e003 100644 --- a/Glamourer/GameData/ColorParameters.cs +++ b/Glamourer/GameData/ColorParameters.cs @@ -6,10 +6,12 @@ using Penumbra.String.Functions; namespace Glamourer.GameData; +/// Parse the Human.cmp file as a list of 4-byte integer values to obtain colors. public class ColorParameters : IReadOnlyList { private readonly uint[] _rgbaColors; + /// Get a slice of the colors starting at and containing colors. public ReadOnlySpan GetSlice(int offset, int count) => _rgbaColors.AsSpan(offset, count); @@ -18,6 +20,7 @@ public class ColorParameters : IReadOnlyList try { var file = gameData.GetFile("chara/xls/charamake/human.cmp")!; + // Just copy all the data into an uint array. _rgbaColors = new uint[file.Data.Length >> 2]; fixed (byte* ptr1 = file.Data) { @@ -32,19 +35,23 @@ public class ColorParameters : IReadOnlyList log.Error("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n" + "======== This usually indicates an error with your index files caused by TexTools modifications.\n" + "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e); - _rgbaColors = Array.Empty(); + _rgbaColors = []; } } + /// public IEnumerator GetEnumerator() => (IEnumerator)_rgbaColors.GetEnumerator(); + /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// public int Count => _rgbaColors.Length; + /// public uint this[int index] => _rgbaColors[index]; } diff --git a/Glamourer/GameData/CustomName.cs b/Glamourer/GameData/CustomName.cs deleted file mode 100644 index c7d74a1..0000000 --- a/Glamourer/GameData/CustomName.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Glamourer.GameData; - -/// For localization from the game files directly. -public enum CustomName -{ - MidlanderM, - HighlanderM, - WildwoodM, - DuskwightM, - PlainsfolkM, - DunesfolkM, - SeekerOfTheSunM, - KeeperOfTheMoonM, - SeawolfM, - HellsguardM, - RaenM, - XaelaM, - HelionM, - LostM, - RavaM, - VeenaM, - MidlanderF, - HighlanderF, - WildwoodF, - DuskwightF, - PlainsfolkF, - DunesfolkF, - SeekerOfTheSunF, - KeeperOfTheMoonF, - SeawolfF, - HellsguardF, - RaenF, - XaelaF, - HelionF, - LostF, - RavaF, - VeenaF, -} diff --git a/Glamourer/GameData/CustomizationManager.cs b/Glamourer/GameData/CustomizationManager.cs deleted file mode 100644 index f249bf6..0000000 --- a/Glamourer/GameData/CustomizationManager.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using Dalamud.Interface.Internal; -using Dalamud.Plugin.Services; -using Penumbra.GameData.Enums; - -namespace Glamourer.GameData; - -public class CustomizationManager : ICustomizationManager -{ - private static CustomizationOptions? _options; - - private CustomizationManager() - { } - - public static ICustomizationManager Create(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) - { - _options ??= new CustomizationOptions(textures, gameData, log, npcCustomizeSet); - return new CustomizationManager(); - } - - public IReadOnlyList Races - => CustomizationOptions.Races; - - public IReadOnlyList Clans - => CustomizationOptions.Clans; - - public IReadOnlyList Genders - => CustomizationOptions.Genders; - - public CustomizationSet GetList(SubRace clan, Gender gender) - => _options!.GetList(clan, gender); - - public IDalamudTextureWrap GetIcon(uint iconId) - => _options!.GetIcon(iconId); - - public string GetName(CustomName name) - => _options!.GetName(name); -} diff --git a/Glamourer/GameData/CustomizationNpcOptions.cs b/Glamourer/GameData/CustomizationNpcOptions.cs deleted file mode 100644 index 84509be..0000000 --- a/Glamourer/GameData/CustomizationNpcOptions.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Penumbra.GameData.Enums; -using System.Collections.Generic; -using System.Linq; -using Penumbra.GameData.Structs; - -namespace Glamourer.GameData; - -public static class CustomizationNpcOptions -{ - public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, - NpcCustomizeSet npcCustomizeSet) - { - var dict = new Dictionary<(SubRace, Gender), HashSet<(CustomizeIndex, CustomizeValue)>>(); - var customizeIndices = new[] - { - CustomizeIndex.Face, - CustomizeIndex.Hairstyle, - CustomizeIndex.LipColor, - CustomizeIndex.SkinColor, - CustomizeIndex.FacePaintColor, - CustomizeIndex.HighlightsColor, - CustomizeIndex.HairColor, - CustomizeIndex.FacePaint, - CustomizeIndex.TattooColor, - CustomizeIndex.EyeColorLeft, - CustomizeIndex.EyeColorRight, - }; - - foreach (var customize in npcCustomizeSet.Select(s => s.Customize)) - { - var set = sets[CustomizationOptions.ToIndex(customize.Clan, customize.Gender)]; - foreach (var customizeIndex in customizeIndices) - { - var value = customize[customizeIndex]; - if (value == CustomizeValue.Zero) - continue; - - if (set.DataByValue(customizeIndex, value, out _, customize.Face) >= 0) - continue; - - if (!dict.TryGetValue((set.Clan, set.Gender), out var npcSet)) - { - npcSet = [(customizeIndex, value)]; - dict.Add((set.Clan, set.Gender), npcSet); - } - else - { - npcSet.Add((customizeIndex, value)); - } - } - } - - return dict.ToDictionary(kvp => kvp.Key, - kvp => (IReadOnlyList<(CustomizeIndex, CustomizeValue)>)kvp.Value.OrderBy(p => p.Item1).ThenBy(p => p.Item2.Value).ToArray()); - } -} diff --git a/Glamourer/GameData/CustomizationOptions.cs b/Glamourer/GameData/CustomizationOptions.cs deleted file mode 100644 index 6fc3b03..0000000 --- a/Glamourer/GameData/CustomizationOptions.cs +++ /dev/null @@ -1,530 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Dalamud; -using Dalamud.Interface.Internal; -using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Lumina.Excel; -using Lumina.Excel.GeneratedSheets; -using OtterGui.Classes; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Race = Penumbra.GameData.Enums.Race; - -namespace Glamourer.GameData; - -// Generate everything about customization per tribe and gender. -public partial class CustomizationOptions -{ - // All races except for Unknown - internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray(); - - // All tribes except for Unknown - internal static readonly SubRace[] Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray(); - - // Two genders. - internal static readonly Gender[] Genders = - { - Gender.Male, - Gender.Female, - }; - - // Every tribe and gender has a separate set of available customizations. - internal CustomizationSet GetList(SubRace race, Gender gender) - => _customizationSets[ToIndex(race, gender)]; - - // Get specific icons. - internal IDalamudTextureWrap GetIcon(uint id) - => _icons.LoadIcon(id)!; - - private readonly IconStorage _icons; - - private static readonly int ListSize = Clans.Length * Genders.Length; - private readonly CustomizationSet[] _customizationSets = new CustomizationSet[ListSize]; - - - // Get the index for the given pair of tribe and gender. - internal static int ToIndex(SubRace race, Gender gender) - { - var idx = ((int)race - 1) * Genders.Length + (gender == Gender.Female ? 1 : 0); - if (idx < 0 || idx >= ListSize) - ThrowException(race, gender); - return idx; - } - - private static void ThrowException(SubRace race, Gender gender) - => throw new Exception($"Invalid customization requested for {race} {gender}."); -} - -public partial class CustomizationOptions -{ - public string GetName(CustomName name) - => _names[(int)name]; - - internal CustomizationOptions(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) - { - var tmp = new TemporaryData(gameData, this, log); - _icons = new IconStorage(textures, gameData); - SetNames(gameData); - foreach (var race in Clans) - { - foreach (var gender in Genders) - _customizationSets[ToIndex(race, gender)] = tmp.GetSet(race, gender); - } - - tmp.SetNpcData(_customizationSets, npcCustomizeSet); - } - - // Obtain localized names of customization options and race names from the game data. - private readonly string[] _names = new string[Enum.GetValues().Length]; - - private void SetNames(IDataManager gameData) - { - var subRace = gameData.GetExcelSheet()!; - - void Set(CustomName id, Lumina.Text.SeString? s, string def) - => _names[(int)id] = s?.ToDalamudString().TextValue ?? def; - - Set(CustomName.MidlanderM, subRace.GetRow((int)SubRace.Midlander)?.Masculine, SubRace.Midlander.ToName()); - Set(CustomName.MidlanderF, subRace.GetRow((int)SubRace.Midlander)?.Feminine, SubRace.Midlander.ToName()); - Set(CustomName.HighlanderM, subRace.GetRow((int)SubRace.Highlander)?.Masculine, SubRace.Highlander.ToName()); - Set(CustomName.HighlanderF, subRace.GetRow((int)SubRace.Highlander)?.Feminine, SubRace.Highlander.ToName()); - Set(CustomName.WildwoodM, subRace.GetRow((int)SubRace.Wildwood)?.Masculine, SubRace.Wildwood.ToName()); - Set(CustomName.WildwoodF, subRace.GetRow((int)SubRace.Wildwood)?.Feminine, SubRace.Wildwood.ToName()); - Set(CustomName.DuskwightM, subRace.GetRow((int)SubRace.Duskwight)?.Masculine, SubRace.Duskwight.ToName()); - Set(CustomName.DuskwightF, subRace.GetRow((int)SubRace.Duskwight)?.Feminine, SubRace.Duskwight.ToName()); - Set(CustomName.PlainsfolkM, subRace.GetRow((int)SubRace.Plainsfolk)?.Masculine, SubRace.Plainsfolk.ToName()); - Set(CustomName.PlainsfolkF, subRace.GetRow((int)SubRace.Plainsfolk)?.Feminine, SubRace.Plainsfolk.ToName()); - Set(CustomName.DunesfolkM, subRace.GetRow((int)SubRace.Dunesfolk)?.Masculine, SubRace.Dunesfolk.ToName()); - Set(CustomName.DunesfolkF, subRace.GetRow((int)SubRace.Dunesfolk)?.Feminine, SubRace.Dunesfolk.ToName()); - Set(CustomName.SeekerOfTheSunM, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Masculine, SubRace.SeekerOfTheSun.ToName()); - Set(CustomName.SeekerOfTheSunF, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Feminine, SubRace.SeekerOfTheSun.ToName()); - Set(CustomName.KeeperOfTheMoonM, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Masculine, SubRace.KeeperOfTheMoon.ToName()); - Set(CustomName.KeeperOfTheMoonF, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Feminine, SubRace.KeeperOfTheMoon.ToName()); - Set(CustomName.SeawolfM, subRace.GetRow((int)SubRace.Seawolf)?.Masculine, SubRace.Seawolf.ToName()); - Set(CustomName.SeawolfF, subRace.GetRow((int)SubRace.Seawolf)?.Feminine, SubRace.Seawolf.ToName()); - Set(CustomName.HellsguardM, subRace.GetRow((int)SubRace.Hellsguard)?.Masculine, SubRace.Hellsguard.ToName()); - Set(CustomName.HellsguardF, subRace.GetRow((int)SubRace.Hellsguard)?.Feminine, SubRace.Hellsguard.ToName()); - Set(CustomName.RaenM, subRace.GetRow((int)SubRace.Raen)?.Masculine, SubRace.Raen.ToName()); - Set(CustomName.RaenF, subRace.GetRow((int)SubRace.Raen)?.Feminine, SubRace.Raen.ToName()); - Set(CustomName.XaelaM, subRace.GetRow((int)SubRace.Xaela)?.Masculine, SubRace.Xaela.ToName()); - Set(CustomName.XaelaF, subRace.GetRow((int)SubRace.Xaela)?.Feminine, SubRace.Xaela.ToName()); - Set(CustomName.HelionM, subRace.GetRow((int)SubRace.Helion)?.Masculine, SubRace.Helion.ToName()); - Set(CustomName.HelionF, subRace.GetRow((int)SubRace.Helion)?.Feminine, SubRace.Helion.ToName()); - Set(CustomName.LostM, subRace.GetRow((int)SubRace.Lost)?.Masculine, SubRace.Lost.ToName()); - Set(CustomName.LostF, subRace.GetRow((int)SubRace.Lost)?.Feminine, SubRace.Lost.ToName()); - Set(CustomName.RavaM, subRace.GetRow((int)SubRace.Rava)?.Masculine, SubRace.Rava.ToName()); - Set(CustomName.RavaF, subRace.GetRow((int)SubRace.Rava)?.Feminine, SubRace.Rava.ToName()); - Set(CustomName.VeenaM, subRace.GetRow((int)SubRace.Veena)?.Masculine, SubRace.Veena.ToName()); - Set(CustomName.VeenaF, subRace.GetRow((int)SubRace.Veena)?.Feminine, SubRace.Veena.ToName()); - } - - private class TemporaryData - { - public CustomizationSet GetSet(SubRace race, Gender gender) - { - var (skin, hair) = GetColors(race, gender); - var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; - var hrothgar = race.ToRace() == Race.Hrothgar; - // Create the initial set with all the easily accessible parameters available for anyone. - var set = new CustomizationSet(race, gender) - { - Voices = row.Voices, - HairStyles = GetHairStyles(race, gender), - HairColors = hair, - SkinColors = skin, - EyeColors = _eyeColorPicker, - HighlightColors = _highlightPicker, - TattooColors = _tattooColorPicker, - LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark, - LipColorsLight = hrothgar ? Array.Empty() : _lipColorPickerLight, - FacePaintColorsDark = _facePaintColorPickerDark, - FacePaintColorsLight = _facePaintColorPickerLight, - Faces = GetFaces(row), - NumEyebrows = GetListSize(row, CustomizeIndex.Eyebrows), - NumEyeShapes = GetListSize(row, CustomizeIndex.EyeShape), - NumNoseShapes = GetListSize(row, CustomizeIndex.Nose), - NumJawShapes = GetListSize(row, CustomizeIndex.Jaw), - NumMouthShapes = GetListSize(row, CustomizeIndex.Mouth), - FacePaints = GetFacePaints(race, gender), - TailEarShapes = GetTailEarShapes(row), - }; - - SetAvailability(set, row); - SetFacialFeatures(set, row); - SetHairByFace(set); - SetMenuTypes(set, row); - SetNames(set, row); - - return set; - } - - public void SetNpcData(CustomizationSet[] sets, NpcCustomizeSet npcCustomizeSet) - { - var data = CustomizationNpcOptions.CreateNpcData(sets, npcCustomizeSet); - foreach (var set in sets) - { - if (data.TryGetValue((set.Clan, set.Gender), out var npcData)) - set.NpcOptions = npcData.ToArray(); - } - } - - - public TemporaryData(IDataManager gameData, CustomizationOptions options, IPluginLog log) - { - _options = options; - _cmpFile = new ColorParameters(gameData, log); - _customizeSheet = gameData.GetExcelSheet(ClientLanguage.English)!; - Lobby = gameData.GetExcelSheet(ClientLanguage.English)!; - var tmp = gameData.Excel.GetType().GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)? - .MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[] - { - "charamaketype", - gameData.Language.ToLumina(), - null, - }) as ExcelSheet; - _listSheet = tmp!; - _hairSheet = gameData.GetExcelSheet()!; - _highlightPicker = CreateColorPicker(CustomizeIndex.HighlightsColor, 256, 192); - _lipColorPickerDark = CreateColorPicker(CustomizeIndex.LipColor, 512, 96); - _lipColorPickerLight = CreateColorPicker(CustomizeIndex.LipColor, 1024, 96, true); - _eyeColorPicker = CreateColorPicker(CustomizeIndex.EyeColorLeft, 0, 192); - _facePaintColorPickerDark = CreateColorPicker(CustomizeIndex.FacePaintColor, 640, 96); - _facePaintColorPickerLight = CreateColorPicker(CustomizeIndex.FacePaintColor, 1152, 96, true); - _tattooColorPicker = CreateColorPicker(CustomizeIndex.TattooColor, 0, 192); - } - - // Required sheets. - private readonly ExcelSheet _customizeSheet; - private readonly ExcelSheet _listSheet; - private readonly ExcelSheet _hairSheet; - public readonly ExcelSheet Lobby; - private readonly ColorParameters _cmpFile; - - // Those values are shared between all races. - private readonly CustomizeData[] _highlightPicker; - private readonly CustomizeData[] _eyeColorPicker; - private readonly CustomizeData[] _facePaintColorPickerDark; - private readonly CustomizeData[] _facePaintColorPickerLight; - private readonly CustomizeData[] _lipColorPickerDark; - private readonly CustomizeData[] _lipColorPickerLight; - private readonly CustomizeData[] _tattooColorPicker; - - private readonly CustomizationOptions _options; - - - private CustomizeData[] CreateColorPicker(CustomizeIndex index, int offset, int num, bool light = false) - { - var ret = new CustomizeData[num]; - var idx = 0; - foreach (var value in _cmpFile.GetSlice(offset, num)) - { - ret[idx] = new CustomizeData(index, (CustomizeValue)(light ? 128 + idx : idx), value, (ushort)(offset + idx)); - ++idx; - } - - return ret; - } - - - private void SetHairByFace(CustomizationSet set) - { - if (set.Race != Race.Hrothgar) - { - set.HairByFace = Enumerable.Repeat(set.HairStyles, set.Faces.Count + 1).ToArray(); - return; - } - - var tmp = new IReadOnlyList[set.Faces.Count + 1]; - tmp[0] = set.HairStyles; - - for (var i = 1; i <= set.Faces.Count; ++i) - { - bool Valid(CustomizeData c) - { - var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0; - return data == 0 || data == i + set.Faces.Count; - } - - tmp[i] = set.HairStyles.Where(Valid).ToArray(); - } - - set.HairByFace = tmp; - } - - private static void SetMenuTypes(CustomizationSet set, CharaMakeParams row) - { - // Set up the menu types for all customizations. - set.Types = Enum.GetValues().Select(c => - { - // Those types are not correctly given in the menu, so special case them to color pickers. - switch (c) - { - case CustomizeIndex.HighlightsColor: - case CustomizeIndex.EyeColorLeft: - case CustomizeIndex.EyeColorRight: - case CustomizeIndex.FacePaintColor: - return CharaMakeParams.MenuType.ColorPicker; - case CustomizeIndex.BodyType: return CharaMakeParams.MenuType.Nothing; - case CustomizeIndex.FacePaintReversed: - case CustomizeIndex.Highlights: - case CustomizeIndex.SmallIris: - case CustomizeIndex.Lipstick: - return CharaMakeParams.MenuType.Checkmark; - case CustomizeIndex.FacialFeature1: - case CustomizeIndex.FacialFeature2: - case CustomizeIndex.FacialFeature3: - case CustomizeIndex.FacialFeature4: - case CustomizeIndex.FacialFeature5: - case CustomizeIndex.FacialFeature6: - case CustomizeIndex.FacialFeature7: - case CustomizeIndex.LegacyTattoo: - return CharaMakeParams.MenuType.IconCheckmark; - } - - var gameId = c.ToByteAndMask().ByteIdx; - // Otherwise find the first menu corresponding to the id. - // If there is none, assume a list. - var menu = row.Menus - .Cast() - .FirstOrDefault(m => m!.Value.Customize == gameId); - var ret = menu?.Type ?? CharaMakeParams.MenuType.ListSelector; - if (c is CustomizeIndex.TailShape && ret is CharaMakeParams.MenuType.ListSelector) - ret = CharaMakeParams.MenuType.List1Selector; - return ret; - }).ToArray(); - set.Order = CustomizationSet.ComputeOrder(set); - } - - // Set customizations available if they have any options. - private static void SetAvailability(CustomizationSet set, CharaMakeParams row) - { - if (set is { Race: Race.Hrothgar, Gender: Gender.Female }) - return; - - Set(true, CustomizeIndex.Height); - Set(set.Faces.Count > 0, CustomizeIndex.Face); - Set(true, CustomizeIndex.Hairstyle); - Set(true, CustomizeIndex.Highlights); - Set(true, CustomizeIndex.SkinColor); - Set(true, CustomizeIndex.EyeColorRight); - Set(true, CustomizeIndex.HairColor); - Set(true, CustomizeIndex.HighlightsColor); - Set(true, CustomizeIndex.TattooColor); - Set(set.NumEyebrows > 0, CustomizeIndex.Eyebrows); - Set(true, CustomizeIndex.EyeColorLeft); - Set(set.NumEyeShapes > 0, CustomizeIndex.EyeShape); - Set(set.NumNoseShapes > 0, CustomizeIndex.Nose); - Set(set.NumJawShapes > 0, CustomizeIndex.Jaw); - Set(set.NumMouthShapes > 0, CustomizeIndex.Mouth); - Set(set.LipColorsDark.Count > 0, CustomizeIndex.LipColor); - Set(GetListSize(row, CustomizeIndex.MuscleMass) > 0, CustomizeIndex.MuscleMass); - Set(set.TailEarShapes.Count > 0, CustomizeIndex.TailShape); - Set(GetListSize(row, CustomizeIndex.BustSize) > 0, CustomizeIndex.BustSize); - Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaint); - Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintColor); - Set(true, CustomizeIndex.FacialFeature1); - Set(true, CustomizeIndex.FacialFeature2); - Set(true, CustomizeIndex.FacialFeature3); - Set(true, CustomizeIndex.FacialFeature4); - Set(true, CustomizeIndex.FacialFeature5); - Set(true, CustomizeIndex.FacialFeature6); - Set(true, CustomizeIndex.FacialFeature7); - Set(true, CustomizeIndex.LegacyTattoo); - Set(true, CustomizeIndex.SmallIris); - Set(set.Race != Race.Hrothgar, CustomizeIndex.Lipstick); - Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintReversed); - return; - - void Set(bool available, CustomizeIndex flag) - { - if (available) - set.SetAvailable(flag); - } - } - - // Create a list of lists of facial features and the legacy tattoo. - private static void SetFacialFeatures(CustomizationSet set, CharaMakeParams row) - { - var count = set.Faces.Count; - set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count); - - set.LegacyTattoo = Create(CustomizeIndex.LegacyTattoo, 137905); - - var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray(); - for (var i = 0; i < count; ++i) - { - var data = row.FacialFeatureByFace[i].Icons; - tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, data[0]); - tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, data[1]); - tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, data[2]); - tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, data[3]); - tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, data[4]); - tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, data[5]); - tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, data[6]); - } - - set.FacialFeature1 = tmp[0]; - set.FacialFeature2 = tmp[1]; - set.FacialFeature3 = tmp[2]; - set.FacialFeature4 = tmp[3]; - set.FacialFeature5 = tmp[4]; - set.FacialFeature6 = tmp[5]; - set.FacialFeature7 = tmp[6]; - return; - - static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data) - => (new CustomizeData(i, CustomizeValue.Zero, data), new CustomizeData(i, CustomizeValue.Max, data, 1)); - } - - // Set the names for the given set of parameters. - private void SetNames(CustomizationSet set, CharaMakeParams row) - { - var nameArray = Enum.GetValues().Select(c => - { - // Find the first menu that corresponds to the Id. - var byteId = c.ToByteAndMask().ByteIdx; - var menu = row.Menus - .Cast() - .FirstOrDefault(m => m!.Value.Customize == byteId); - if (menu == null) - { - // If none exists and the id corresponds to highlights, set the Highlights name. - if (c == CustomizeIndex.Highlights) - return Lobby.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights"; - - // Otherwise there is an error and we use the default name. - return c.ToDefaultName(); - } - - // Otherwise all is normal, get the menu name or if it does not work the default name. - var textRow = Lobby.GetRow(menu.Value.Id); - return textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName(); - }).ToArray(); - - // Add names for both eye colors. - nameArray[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorLeft.ToDefaultName(); - nameArray[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.EyeColorRight.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature1] = CustomizeIndex.FacialFeature1.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature2] = CustomizeIndex.FacialFeature2.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature3] = CustomizeIndex.FacialFeature3.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature4] = CustomizeIndex.FacialFeature4.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature5] = CustomizeIndex.FacialFeature5.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature6] = CustomizeIndex.FacialFeature6.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacialFeature7] = CustomizeIndex.FacialFeature7.ToDefaultName(); - nameArray[(int)CustomizeIndex.LegacyTattoo] = CustomizeIndex.LegacyTattoo.ToDefaultName(); - nameArray[(int)CustomizeIndex.SmallIris] = CustomizeIndex.SmallIris.ToDefaultName(); - nameArray[(int)CustomizeIndex.Lipstick] = CustomizeIndex.Lipstick.ToDefaultName(); - nameArray[(int)CustomizeIndex.FacePaintReversed] = CustomizeIndex.FacePaintReversed.ToDefaultName(); - set.OptionName = nameArray; - } - - // Obtain available skin and hair colors for the given subrace and gender. - private (CustomizeData[], CustomizeData[]) GetColors(SubRace race, Gender gender) - { - if (race is > SubRace.Veena or SubRace.Unknown) - throw new ArgumentOutOfRangeException(nameof(race), race, null); - - var gv = gender == Gender.Male ? 0 : 1; - var idx = ((int)race * 2 + gv) * 5 + 3; - - return (CreateColorPicker(CustomizeIndex.SkinColor, idx << 8, 192), - CreateColorPicker(CustomizeIndex.HairColor, (idx + 1) << 8, 192)); - } - - // Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender. - private CustomizeData[] GetHairStyles(SubRace race, Gender gender) - { - var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; - // Unknown30 is the number of available hairstyles. - var hairList = new List(row.Unknown30); - // Hairstyles can be found starting at Unknown66. - for (var i = 0; i < row.Unknown30; ++i) - { - var name = $"Unknown{66 + i * 9}"; - var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) - ?? uint.MaxValue; - if (customizeIdx == uint.MaxValue) - continue; - - // Hair Row from CustomizeSheet might not be set in case of unlockable hair. - var hairRow = _customizeSheet.GetRow(customizeIdx); - if (hairRow == null) - hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx)); - else if (_options._icons.IconExists(hairRow.Icon)) - hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon, - (ushort)hairRow.RowId)); - } - - return hairList.OrderBy(h => h.Value.Value).ToArray(); - } - - // Get Features. - private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index) - { - var row = _customizeSheet.GetRow(value); - return row == null - ? new CustomizeData(id, (CustomizeValue)(index + 1), value) - : new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId); - } - - // Get List sizes. - private static int GetListSize(CharaMakeParams row, CustomizeIndex index) - { - var gameId = index.ToByteAndMask().ByteIdx; - var menu = row.Menus.Cast().FirstOrDefault(m => m!.Value.Customize == gameId); - return menu?.Size ?? 0; - } - - // Get face paints from the hair sheet via reflection. - private CustomizeData[] GetFacePaints(SubRace race, Gender gender) - { - var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; - var paintList = new List(row.Unknown37); - // Number of available face paints is at Unknown37. - for (var i = 0; i < row.Unknown37; ++i) - { - // Face paints start at Unknown73. - var name = $"Unknown{73 + i * 9}"; - var customizeIdx = - (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) - ?? uint.MaxValue; - if (customizeIdx == uint.MaxValue) - continue; - - var paintRow = _customizeSheet.GetRow(customizeIdx); - // Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints. - if (paintRow != null) - paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon, - (ushort)paintRow.RowId)); - else - paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)i, customizeIdx)); - } - - return paintList.OrderBy(p => p.Value.Value).ToArray(); - } - - // Specific icons for tails or ears. - private CustomizeData[] GetTailEarShapes(CharaMakeParams row) - => row.Menus.Cast() - .FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx)?.Values - .Select((v, i) => FromValueAndIndex(CustomizeIndex.TailShape, v, i)).ToArray() - ?? Array.Empty(); - - // Specific icons for faces. - private CustomizeData[] GetFaces(CharaMakeParams row) - => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.Face.ToByteAndMask().ByteIdx) - ?.Values - .Select((v, i) => FromValueAndIndex(CustomizeIndex.Face, v, i)).ToArray() - ?? Array.Empty(); - - // Specific icons for Hrothgar patterns. - private CustomizeData[] HrothgarFurPattern(CharaMakeParams row) - => row.Menus.Cast() - .FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.LipColor.ToByteAndMask().ByteIdx)?.Values - .Select((v, i) => FromValueAndIndex(CustomizeIndex.LipColor, v, i)).ToArray() - ?? Array.Empty(); - } -} diff --git a/Glamourer/GameData/CustomizeData.cs b/Glamourer/GameData/CustomizeData.cs index 3a3e89c..4d4e2fd 100644 --- a/Glamourer/GameData/CustomizeData.cs +++ b/Glamourer/GameData/CustomizeData.cs @@ -5,26 +5,34 @@ using Penumbra.GameData.Structs; namespace Glamourer.GameData; -// Any customization value can be represented in 8 bytes by its ID, -// a byte value, an optional value-id and an optional icon or color. +/// +/// Any customization value can be represented in 8 bytes by its ID, +/// a byte value, an optional value-id and an optional icon or color. +/// [StructLayout(LayoutKind.Explicit)] public readonly struct CustomizeData : IEquatable { + /// The index of the option this value is for. [FieldOffset(0)] public readonly CustomizeIndex Index; + /// The value for the option. [FieldOffset(1)] public readonly CustomizeValue Value; + /// The internal ID for sheets. [FieldOffset(2)] public readonly ushort CustomizeId; + /// An ID for an associated icon. [FieldOffset(4)] public readonly uint IconId; + /// An ID for an associated color. [FieldOffset(4)] public readonly uint Color; + /// Construct a CustomizeData from single data values. public CustomizeData(CustomizeIndex index, CustomizeValue value, uint data = 0, ushort customizeId = 0) { Index = index; @@ -34,14 +42,23 @@ public readonly struct CustomizeData : IEquatable CustomizeId = customizeId; } + /// public bool Equals(CustomizeData other) => Index == other.Index && Value.Value == other.Value.Value && CustomizeId == other.CustomizeId; + /// public override bool Equals(object? obj) => obj is CustomizeData other && Equals(other); + /// public override int GetHashCode() => HashCode.Combine((int)Index, Value.Value, CustomizeId); + + public static bool operator ==(CustomizeData left, CustomizeData right) + => left.Equals(right); + + public static bool operator !=(CustomizeData left, CustomizeData right) + => !(left == right); } diff --git a/Glamourer/GameData/CustomizeManager.cs b/Glamourer/GameData/CustomizeManager.cs new file mode 100644 index 0000000..fabc483 --- /dev/null +++ b/Glamourer/GameData/CustomizeManager.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Dalamud.Interface.Internal; +using Dalamud.Plugin.Services; +using OtterGui.Classes; +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Race = Penumbra.GameData.Enums.Race; + +namespace Glamourer.GameData; + +/// Generate everything about customization per tribe and gender. +public class CustomizeManager : IAsyncService +{ + /// All races except for Unknown + public static readonly IReadOnlyList Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray(); + + /// All tribes except for Unknown + public static readonly IReadOnlyList Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray(); + + /// Two genders. + public static readonly IReadOnlyList Genders = + [ + Gender.Male, + Gender.Female, + ]; + + /// Every tribe and gender has a separate set of available customizations. + public CustomizeSet GetSet(SubRace race, Gender gender) + { + if (!Awaiter.IsCompletedSuccessfully) + Awaiter.Wait(); + return _customizationSets[ToIndex(race, gender)]; + } + + /// Get specific icons. + public IDalamudTextureWrap GetIcon(uint id) + => _icons.LoadIcon(id)!; + + /// Iterate over all supported genders and clans. + public static IEnumerable<(SubRace Clan, Gender Gender)> AllSets() + { + foreach (var clan in Clans) + { + yield return (clan, Gender.Male); + yield return (clan, Gender.Female); + } + } + + public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) + { + _icons = new IconStorage(textures, gameData); + var tmpTask = Task.Run(() => new CustomizeSetFactory(gameData, log, _icons, npcCustomizeSet)); + var setTasks = AllSets().Select(p + => tmpTask.ContinueWith(t => _customizationSets[ToIndex(p.Clan, p.Gender)] = t.Result.CreateSet(p.Clan, p.Gender))); + Awaiter = Task.WhenAll(setTasks); + } + + /// + public Task Awaiter { get; } + + private readonly IconStorage _icons; + private static readonly int ListSize = Clans.Count * Genders.Count; + private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize]; + + /// Get the index for the given pair of tribe and gender. + private static int ToIndex(SubRace race, Gender gender) + { + var idx = ((int)race - 1) * Genders.Count + (gender == Gender.Female ? 1 : 0); + if (idx < 0 || idx >= ListSize) + throw new Exception($"Invalid customization requested for {race} {gender}."); + + return idx; + } +} \ No newline at end of file diff --git a/Glamourer/GameData/CustomizationSet.cs b/Glamourer/GameData/CustomizeSet.cs similarity index 94% rename from Glamourer/GameData/CustomizationSet.cs rename to Glamourer/GameData/CustomizeSet.cs index 2a79c66..d2dc8e9 100644 --- a/Glamourer/GameData/CustomizationSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -8,11 +8,13 @@ using Penumbra.GameData.Structs; namespace Glamourer.GameData; -// Each Subrace and Gender combo has a customization set. -// This describes the available customizations, their types and their names. -public class CustomizationSet +/// +/// Each SubRace and Gender combo has a customization set. +/// This describes the available customizations, their types and their names. +/// +public class CustomizeSet { - internal CustomizationSet(SubRace clan, Gender gender) + internal CustomizeSet(SubRace clan, Gender gender) { Gender = gender; Clan = clan; @@ -24,6 +26,8 @@ public class CustomizationSet public SubRace Clan { get; } public Race Race { get; } + public string Name { get; internal init; } = string.Empty; + public CustomizeFlag SettingAvailable { get; internal set; } internal void SetAvailable(CustomizeIndex index) @@ -33,7 +37,7 @@ public class CustomizationSet => SettingAvailable.HasFlag(index.ToFlag()); // Meta - public IReadOnlyList OptionName { get; internal set; } = null!; + public IReadOnlyList OptionName { get; internal init; } = null!; public string Option(CustomizeIndex index) => OptionName[(int)index]; @@ -95,68 +99,6 @@ public class CustomizationSet { var type = Types[(int)index]; - int GetInteger0(out CustomizeData? custom) - { - if (value < Count(index)) - { - custom = new CustomizeData(index, value, 0, value.Value); - return value.Value; - } - - custom = null; - return -1; - } - - int GetInteger1(out CustomizeData? custom) - { - if (value > 0 && value < Count(index) + 1) - { - custom = new CustomizeData(index, value, 0, (ushort)(value.Value - 1)); - return value.Value; - } - - custom = null; - return -1; - } - - static int GetBool(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom) - { - if (value == CustomizeValue.Zero) - { - custom = new CustomizeData(index, CustomizeValue.Zero, 0, 0); - return 0; - } - - var (_, mask) = index.ToByteAndMask(); - if (value.Value == mask) - { - custom = new CustomizeData(index, new CustomizeValue(mask), 0, 1); - return 1; - } - - custom = null; - return -1; - } - - static int Invalid(out CustomizeData? custom) - { - custom = null; - return -1; - } - - int Get(IEnumerable list, CustomizeValue v, out CustomizeData? output) - { - var (val, idx) = list.Cast().WithIndex().FirstOrDefault(p => p.Item1!.Value.Value == v); - if (val == null) - { - output = null; - return -1; - } - - output = val; - return idx; - } - return type switch { CharaMakeParams.MenuType.ListSelector => GetInteger0(out custom), @@ -194,6 +136,68 @@ public class CustomizationSet CharaMakeParams.MenuType.Checkmark => GetBool(index, value, out custom), _ => Invalid(out custom), }; + + int Get(IEnumerable list, CustomizeValue v, out CustomizeData? output) + { + var (val, idx) = list.Cast().WithIndex().FirstOrDefault(p => p.Value!.Value.Value == v); + if (val == null) + { + output = null; + return -1; + } + + output = val; + return idx; + } + + static int Invalid(out CustomizeData? custom) + { + custom = null; + return -1; + } + + static int GetBool(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom) + { + if (value == CustomizeValue.Zero) + { + custom = new CustomizeData(index, CustomizeValue.Zero); + return 0; + } + + var (_, mask) = index.ToByteAndMask(); + if (value.Value == mask) + { + custom = new CustomizeData(index, new CustomizeValue(mask), 0, 1); + return 1; + } + + custom = null; + return -1; + } + + int GetInteger1(out CustomizeData? custom) + { + if (value > 0 && value < Count(index) + 1) + { + custom = new CustomizeData(index, value, 0, (ushort)(value.Value - 1)); + return value.Value; + } + + custom = null; + return -1; + } + + int GetInteger0(out CustomizeData? custom) + { + if (value < Count(index)) + { + custom = new CustomizeData(index, value, 0, value.Value); + return value.Value; + } + + custom = null; + return -1; + } } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -244,7 +248,7 @@ public class CustomizationSet public CharaMakeParams.MenuType Type(CustomizeIndex index) => Types[(int)index]; - internal static IReadOnlyDictionary ComputeOrder(CustomizationSet set) + internal static IReadOnlyDictionary ComputeOrder(CustomizeSet set) { var ret = Enum.GetValues().ToArray(); ret[(int)CustomizeIndex.TattooColor] = CustomizeIndex.EyeColorLeft; @@ -305,6 +309,6 @@ public class CustomizationSet public static class CustomizationSetExtensions { /// Return only the available customizations in this set and Clan or Gender. - public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizationSet set) + public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizeSet set) => flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender); } diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs new file mode 100644 index 0000000..5eaaa58 --- /dev/null +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -0,0 +1,459 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Dalamud; +using Dalamud.Plugin.Services; +using Dalamud.Utility; +using Lumina.Excel; +using Lumina.Excel.GeneratedSheets; +using OtterGui.Classes; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Race = Penumbra.GameData.Enums.Race; + +namespace Glamourer.GameData; + +internal class CustomizeSetFactory( + IDataManager _gameData, + IPluginLog _log, + IconStorage _icons, + NpcCustomizeSet _npcCustomizeSet, + ColorParameters _colors) +{ + public CustomizeSetFactory(IDataManager gameData, IPluginLog log, IconStorage icons, NpcCustomizeSet npcCustomizeSet) + : this(gameData, log, icons, npcCustomizeSet, new ColorParameters(gameData, log)) + { } + + /// Create the set of all available customization options for a given clan and gender. + public CustomizeSet CreateSet(SubRace race, Gender gender) + { + var (skin, hair) = GetSkinHairColors(race, gender); + var row = _charaMakeSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + var hrothgar = race.ToRace() == Race.Hrothgar; + // Create the initial set with all the easily accessible parameters available for anyone. + var set = new CustomizeSet(race, gender) + { + Name = GetName(race, gender), + Voices = row.Voices, + HairStyles = GetHairStyles(race, gender), + HairColors = hair, + SkinColors = skin, + EyeColors = _eyeColorPicker, + HighlightColors = _highlightPicker, + TattooColors = _tattooColorPicker, + LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark, + LipColorsLight = hrothgar ? [] : _lipColorPickerLight, + FacePaintColorsDark = _facePaintColorPickerDark, + FacePaintColorsLight = _facePaintColorPickerLight, + Faces = GetFaces(row), + NumEyebrows = GetListSize(row, CustomizeIndex.Eyebrows), + NumEyeShapes = GetListSize(row, CustomizeIndex.EyeShape), + NumNoseShapes = GetListSize(row, CustomizeIndex.Nose), + NumJawShapes = GetListSize(row, CustomizeIndex.Jaw), + NumMouthShapes = GetListSize(row, CustomizeIndex.Mouth), + FacePaints = GetFacePaints(race, gender), + TailEarShapes = GetTailEarShapes(row), + OptionName = GetOptionNames(row), + Types = GetMenuTypes(row), + }; + SetPostProcessing(set, row); + return set; + } + + /// Some data can not be set independently of the rest, so we need a post-processing step to finalize. + private void SetPostProcessing(CustomizeSet set, CharaMakeParams row) + { + SetAvailability(set, row); + SetFacialFeatures(set, row); + SetHairByFace(set); + SetNpcData(set, set.Clan, set.Gender); + } + + /// Given a customize set with filled data, find all customizations used by valid NPCs that are not regularly available. + private void SetNpcData(CustomizeSet set, SubRace race, Gender gender) + { + var customizeIndices = new[] + { + CustomizeIndex.Face, + CustomizeIndex.Hairstyle, + CustomizeIndex.LipColor, + CustomizeIndex.SkinColor, + CustomizeIndex.FacePaintColor, + CustomizeIndex.HighlightsColor, + CustomizeIndex.HairColor, + CustomizeIndex.FacePaint, + CustomizeIndex.TattooColor, + CustomizeIndex.EyeColorLeft, + CustomizeIndex.EyeColorRight, + }; + + var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>(); + _npcCustomizeSet.Awaiter.Wait(); + foreach (var customize in _npcCustomizeSet.Select(s => s.Customize).Where(c => c.Clan == race && c.Gender == gender)) + { + foreach (var customizeIndex in customizeIndices) + { + var value = customize[customizeIndex]; + if (value == CustomizeValue.Zero) + continue; + + if (set.DataByValue(customizeIndex, value, out _, customize.Face) >= 0) + continue; + + npcCustomizations.Add((customizeIndex, value)); + } + } + + set.NpcOptions = npcCustomizations.OrderBy(p => p.Item1).ThenBy(p => p.Item2.Value).ToArray(); + } + + private readonly ColorParameters _colorParameters = new(_gameData, _log); + private readonly ExcelSheet _customizeSheet = _gameData.GetExcelSheet(ClientLanguage.English)!; + private readonly ExcelSheet _lobbySheet = _gameData.GetExcelSheet(ClientLanguage.English)!; + private readonly ExcelSheet _hairSheet = _gameData.GetExcelSheet(ClientLanguage.English)!; + private readonly ExcelSheet _tribeSheet = _gameData.GetExcelSheet(ClientLanguage.English)!; + + // Those color pickers are shared between all races. + private readonly CustomizeData[] _highlightPicker = CreateColors(_colors, CustomizeIndex.HighlightsColor, 256, 192); + private readonly CustomizeData[] _lipColorPickerDark = CreateColors(_colors, CustomizeIndex.LipColor, 512, 96); + private readonly CustomizeData[] _lipColorPickerLight = CreateColors(_colors, CustomizeIndex.LipColor, 1024, 96, true); + private readonly CustomizeData[] _eyeColorPicker = CreateColors(_colors, CustomizeIndex.EyeColorLeft, 0, 192); + private readonly CustomizeData[] _facePaintColorPickerDark = CreateColors(_colors, CustomizeIndex.FacePaintColor, 640, 96); + private readonly CustomizeData[] _facePaintColorPickerLight = CreateColors(_colors, CustomizeIndex.FacePaintColor, 1152, 96, true); + private readonly CustomizeData[] _tattooColorPicker = CreateColors(_colors, CustomizeIndex.TattooColor, 0, 192); + + private readonly ExcelSheet _charaMakeSheet = _gameData.Excel + .GetType() + .GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)? + .MakeGenericMethod(typeof(CharaMakeParams)) + .Invoke(_gameData.Excel, ["charamaketype", _gameData.Language.ToLumina(), null])! as ExcelSheet + ?? null!; + + /// Obtain available skin and hair colors for the given clan and gender. + private (CustomizeData[] Skin, CustomizeData[] Hair) GetSkinHairColors(SubRace race, Gender gender) + { + if (race is > SubRace.Veena or SubRace.Unknown) + throw new ArgumentOutOfRangeException(nameof(race), race, null); + + var gv = gender == Gender.Male ? 0 : 1; + var idx = ((int)race * 2 + gv) * 5 + 3; + + return (CreateColors(_colorParameters, CustomizeIndex.SkinColor, idx << 8, 192), + CreateColors(_colorParameters, CustomizeIndex.HairColor, (idx + 1) << 8, 192)); + } + + /// Obtain the gender-specific clan name. + private string GetName(SubRace race, Gender gender) + => gender switch + { + Gender.Male => _tribeSheet.GetRow((uint)race)?.Masculine.ToDalamudString().TextValue ?? race.ToName(), + Gender.Female => _tribeSheet.GetRow((uint)race)?.Feminine.ToDalamudString().TextValue ?? race.ToName(), + _ => "Unknown", + }; + + /// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender. + private CustomizeData[] GetHairStyles(SubRace race, Gender gender) + { + var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + // Unknown30 is the number of available hairstyles. + var hairList = new List(row.Unknown30); + // Hairstyles can be found starting at Unknown66. + for (var i = 0; i < row.Unknown30; ++i) + { + var name = $"Unknown{66 + i * 9}"; + var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) + ?? uint.MaxValue; + if (customizeIdx == uint.MaxValue) + continue; + + // Hair Row from CustomizeSheet might not be set in case of unlockable hair. + var hairRow = _customizeSheet.GetRow(customizeIdx); + if (hairRow == null) + hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx)); + else if (_icons.IconExists(hairRow.Icon)) + hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon, + (ushort)hairRow.RowId)); + } + + return [.. hairList.OrderBy(h => h.Value.Value)]; + } + + /// Specific icons for tails or ears. + private CustomizeData[] GetTailEarShapes(CharaMakeParams row) + => row.Menus.Cast() + .FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx)?.Values + .Select((v, i) => FromValueAndIndex(CustomizeIndex.TailShape, v, i)).ToArray() + ?? []; + + /// Specific icons for faces. + private CustomizeData[] GetFaces(CharaMakeParams row) + => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.Face.ToByteAndMask().ByteIdx) + ?.Values + .Select((v, i) => FromValueAndIndex(CustomizeIndex.Face, v, i)).ToArray() + ?? []; + + /// Specific icons for Hrothgar patterns. + private CustomizeData[] HrothgarFurPattern(CharaMakeParams row) + => row.Menus.Cast() + .FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.LipColor.ToByteAndMask().ByteIdx)?.Values + .Select((v, i) => FromValueAndIndex(CustomizeIndex.LipColor, v, i)).ToArray() + ?? []; + + /// Get face paints from the hair sheet via reflection since there are also unlockable face paints. + private CustomizeData[] GetFacePaints(SubRace race, Gender gender) + { + var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + var paintList = new List(row.Unknown37); + // Number of available face paints is at Unknown37. + for (var i = 0; i < row.Unknown37; ++i) + { + // Face paints start at Unknown73. + var name = $"Unknown{73 + i * 9}"; + var customizeIdx = + (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) + ?? uint.MaxValue; + if (customizeIdx == uint.MaxValue) + continue; + + var paintRow = _customizeSheet.GetRow(customizeIdx); + // Face paint Row from CustomizeSheet might not be set in case of unlockable face paints. + if (paintRow != null) + paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon, + (ushort)paintRow.RowId)); + else + paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)i, customizeIdx)); + } + + return [.. paintList.OrderBy(p => p.Value.Value)]; + } + + /// Get List sizes. + private static int GetListSize(CharaMakeParams row, CustomizeIndex index) + { + var gameId = index.ToByteAndMask().ByteIdx; + var menu = row.Menus.Cast().FirstOrDefault(m => m!.Value.Customize == gameId); + return menu?.Size ?? 0; + } + + /// Get generic Features. + private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index) + { + var row = _customizeSheet.GetRow(value); + return row == null + ? new CustomizeData(id, (CustomizeValue)(index + 1), value) + : new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId); + } + + /// Create generic color sets from the parameters. + private static CustomizeData[] CreateColors(ColorParameters colorParameters, CustomizeIndex index, int offset, int num, + bool light = false) + { + var ret = new CustomizeData[num]; + var idx = 0; + foreach (var value in colorParameters.GetSlice(offset, num)) + { + ret[idx] = new CustomizeData(index, (CustomizeValue)(light ? 128 + idx : idx), value, (ushort)(offset + idx)); + ++idx; + } + + return ret; + } + + /// Set the specific option names for the given set of parameters. + private string[] GetOptionNames(CharaMakeParams row) + { + var nameArray = Enum.GetValues().Select(c => + { + // Find the first menu that corresponds to the Id. + var byteId = c.ToByteAndMask().ByteIdx; + var menu = row.Menus + .Cast() + .FirstOrDefault(m => m!.Value.Customize == byteId); + if (menu == null) + { + // If none exists and the id corresponds to highlights, set the Highlights name. + if (c == CustomizeIndex.Highlights) + return _lobbySheet.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights"; + + // Otherwise there is an error and we use the default name. + return c.ToDefaultName(); + } + + // Otherwise all is normal, get the menu name or if it does not work the default name. + var textRow = _lobbySheet.GetRow(menu.Value.Id); + return textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName(); + }).ToArray(); + + // Add names for both eye colors. + nameArray[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorLeft.ToDefaultName(); + nameArray[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.EyeColorRight.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature1] = CustomizeIndex.FacialFeature1.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature2] = CustomizeIndex.FacialFeature2.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature3] = CustomizeIndex.FacialFeature3.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature4] = CustomizeIndex.FacialFeature4.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature5] = CustomizeIndex.FacialFeature5.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature6] = CustomizeIndex.FacialFeature6.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacialFeature7] = CustomizeIndex.FacialFeature7.ToDefaultName(); + nameArray[(int)CustomizeIndex.LegacyTattoo] = CustomizeIndex.LegacyTattoo.ToDefaultName(); + nameArray[(int)CustomizeIndex.SmallIris] = CustomizeIndex.SmallIris.ToDefaultName(); + nameArray[(int)CustomizeIndex.Lipstick] = CustomizeIndex.Lipstick.ToDefaultName(); + nameArray[(int)CustomizeIndex.FacePaintReversed] = CustomizeIndex.FacePaintReversed.ToDefaultName(); + return nameArray; + } + + /// Get the manu types for all available options. + private CharaMakeParams.MenuType[] GetMenuTypes(CharaMakeParams row) + { + // Set up the menu types for all customizations. + return Enum.GetValues().Select(c => + { + // Those types are not correctly given in the menu, so special case them to color pickers. + switch (c) + { + case CustomizeIndex.HighlightsColor: + case CustomizeIndex.EyeColorLeft: + case CustomizeIndex.EyeColorRight: + case CustomizeIndex.FacePaintColor: + return CharaMakeParams.MenuType.ColorPicker; + case CustomizeIndex.BodyType: return CharaMakeParams.MenuType.Nothing; + case CustomizeIndex.FacePaintReversed: + case CustomizeIndex.Highlights: + case CustomizeIndex.SmallIris: + case CustomizeIndex.Lipstick: + return CharaMakeParams.MenuType.Checkmark; + case CustomizeIndex.FacialFeature1: + case CustomizeIndex.FacialFeature2: + case CustomizeIndex.FacialFeature3: + case CustomizeIndex.FacialFeature4: + case CustomizeIndex.FacialFeature5: + case CustomizeIndex.FacialFeature6: + case CustomizeIndex.FacialFeature7: + case CustomizeIndex.LegacyTattoo: + return CharaMakeParams.MenuType.IconCheckmark; + } + + var gameId = c.ToByteAndMask().ByteIdx; + // Otherwise find the first menu corresponding to the id. + // If there is none, assume a list. + var menu = row.Menus + .Cast() + .FirstOrDefault(m => m!.Value.Customize == gameId); + var ret = menu?.Type ?? CharaMakeParams.MenuType.ListSelector; + if (c is CustomizeIndex.TailShape && ret is CharaMakeParams.MenuType.ListSelector) + ret = CharaMakeParams.MenuType.List1Selector; + return ret; + }).ToArray(); + } + + /// Set the availability of options according to actual availability. + private static void SetAvailability(CustomizeSet set, CharaMakeParams row) + { + // TODO: Hrothgar female + if (set is { Race: Race.Hrothgar, Gender: Gender.Female }) + return; + + Set(true, CustomizeIndex.Height); + Set(set.Faces.Count > 0, CustomizeIndex.Face); + Set(true, CustomizeIndex.Hairstyle); + Set(true, CustomizeIndex.Highlights); + Set(true, CustomizeIndex.SkinColor); + Set(true, CustomizeIndex.EyeColorRight); + Set(true, CustomizeIndex.HairColor); + Set(true, CustomizeIndex.HighlightsColor); + Set(true, CustomizeIndex.TattooColor); + Set(set.NumEyebrows > 0, CustomizeIndex.Eyebrows); + Set(true, CustomizeIndex.EyeColorLeft); + Set(set.NumEyeShapes > 0, CustomizeIndex.EyeShape); + Set(set.NumNoseShapes > 0, CustomizeIndex.Nose); + Set(set.NumJawShapes > 0, CustomizeIndex.Jaw); + Set(set.NumMouthShapes > 0, CustomizeIndex.Mouth); + Set(set.LipColorsDark.Count > 0, CustomizeIndex.LipColor); + Set(GetListSize(row, CustomizeIndex.MuscleMass) > 0, CustomizeIndex.MuscleMass); + Set(set.TailEarShapes.Count > 0, CustomizeIndex.TailShape); + Set(GetListSize(row, CustomizeIndex.BustSize) > 0, CustomizeIndex.BustSize); + Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaint); + Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintColor); + Set(true, CustomizeIndex.FacialFeature1); + Set(true, CustomizeIndex.FacialFeature2); + Set(true, CustomizeIndex.FacialFeature3); + Set(true, CustomizeIndex.FacialFeature4); + Set(true, CustomizeIndex.FacialFeature5); + Set(true, CustomizeIndex.FacialFeature6); + Set(true, CustomizeIndex.FacialFeature7); + Set(true, CustomizeIndex.LegacyTattoo); + Set(true, CustomizeIndex.SmallIris); + Set(set.Race != Race.Hrothgar, CustomizeIndex.Lipstick); + Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintReversed); + return; + + void Set(bool available, CustomizeIndex flag) + { + if (available) + set.SetAvailable(flag); + } + } + + /// Set hairstyles per face for Hrothgar and make it simple for non-Hrothgar. + private void SetHairByFace(CustomizeSet set) + { + if (set.Race != Race.Hrothgar) + { + set.HairByFace = Enumerable.Repeat(set.HairStyles, set.Faces.Count + 1).ToArray(); + return; + } + + var tmp = new IReadOnlyList[set.Faces.Count + 1]; + tmp[0] = set.HairStyles; + + for (var i = 1; i <= set.Faces.Count; ++i) + { + tmp[i] = set.HairStyles.Where(Valid).ToArray(); + continue; + + bool Valid(CustomizeData c) + { + var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0; + return data == 0 || data == i + set.Faces.Count; + } + } + + set.HairByFace = tmp; + } + + /// + /// Create a list of lists of facial features and the legacy tattoo. + /// Facial Features are bools in a bitfield, so we supply an "off" and an "on" value for simplicity of use. + /// + private static void SetFacialFeatures(CustomizeSet set, CharaMakeParams row) + { + var count = set.Faces.Count; + set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count); + set.LegacyTattoo = Create(CustomizeIndex.LegacyTattoo, 137905); + + var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray(); + for (var i = 0; i < count; ++i) + { + var data = row.FacialFeatureByFace[i].Icons; + tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, data[0]); + tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, data[1]); + tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, data[2]); + tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, data[3]); + tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, data[4]); + tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, data[5]); + tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, data[6]); + } + + set.FacialFeature1 = tmp[0]; + set.FacialFeature2 = tmp[1]; + set.FacialFeature3 = tmp[2]; + set.FacialFeature4 = tmp[3]; + set.FacialFeature5 = tmp[4]; + set.FacialFeature6 = tmp[5]; + set.FacialFeature7 = tmp[6]; + return; + + static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data) + => (new CustomizeData(i, CustomizeValue.Zero, data), new CustomizeData(i, CustomizeValue.Max, data, 1)); + } +} diff --git a/Glamourer/GameData/ICustomizationManager.cs b/Glamourer/GameData/ICustomizationManager.cs deleted file mode 100644 index 2d884cd..0000000 --- a/Glamourer/GameData/ICustomizationManager.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using Dalamud.Interface.Internal; -using Penumbra.GameData.Enums; - -namespace Glamourer.GameData; - -public interface ICustomizationManager -{ - public IReadOnlyList Races { get; } - public IReadOnlyList Clans { get; } - public IReadOnlyList Genders { get; } - - public CustomizationSet GetList(SubRace race, Gender gender); - - public IDalamudTextureWrap GetIcon(uint iconId); - public string GetName(CustomName name); -} diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index fd261c6..a99448e 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -13,20 +13,30 @@ using Penumbra.GameData.Structs; namespace Glamourer.GameData; +/// Contains a set of all human NPC appearances with their names. public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList { + /// public string Name => nameof(NpcCustomizeSet); - private readonly List _data = []; + /// + public long Time { get; private set; } - public long Time { get; private set; } + /// public long Memory { get; private set; } + + /// public int TotalCount => _data.Count; + /// public Task Awaiter { get; } + /// The list of data. + private readonly List _data = []; + + /// Create the data when ready. public NpcCustomizeSet(IDataManager data, DictENpc eNpcs, DictBNpc bNpcs, DictBNpcNames bNpcNames) { var waitTask = Task.WhenAll(eNpcs.Awaiter, bNpcs.Awaiter, bNpcNames.Awaiter); @@ -40,17 +50,21 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList }); } + /// Create data from event NPCs. private static List CreateEnpcData(IDataManager data, DictENpc eNpcs) { var enpcSheet = data.GetExcelSheet()!; var list = new List(eNpcs.Count); + // Go through all event NPCs already collected into a dictionary. foreach (var (id, name) in eNpcs) { var row = enpcSheet.GetRow(id.Id); + // We only accept NPCs with valid names. if (row == null || name.IsNullOrWhitespace()) continue; + // Check if the customization is a valid human. var (valid, customize) = FromEnpcBase(row); if (!valid) continue; @@ -63,6 +77,8 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList Kind = ObjectKind.EventNpc, }; + // Event NPCs have a reference to NpcEquip but also contain the appearance in their own row. + // Prefer the NpcEquip reference if it is set, otherwise use the own. if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) { ApplyNpcEquip(ref ret, equip); @@ -90,19 +106,25 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList return list; } + /// Create data from battle NPCs. private static List CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames) { var bnpcSheet = data.GetExcelSheet()!; var list = new List((int)bnpcSheet.RowCount); + + // We go through all battle NPCs in the sheet because the dictionary refers to names. foreach (var baseRow in bnpcSheet) { + // Only accept humans. if (baseRow.ModelChara.Value!.Type != 1) continue; var bnpcNameIds = bNpcNames[baseRow.RowId]; + // Only accept battle NPCs with known associated names. if (bnpcNameIds.Count == 0) continue; + // Check if the customization is a valid human. var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!); if (!valid) continue; @@ -115,6 +137,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList Kind = ObjectKind.BattleNpc, }; ApplyNpcEquip(ref ret, equip); + // Add the appearance for each associated name. foreach (var bnpcNameId in bnpcNameIds) { if (bNpcs.TryGetValue(bnpcNameId.Id, out var name) && !name.IsNullOrWhitespace()) @@ -125,13 +148,18 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList return list; } - private void FilterAndOrderNpcData(List eNpcEquip, List bNpcEquip) + /// Given the battle NPC and event NPC lists, order and deduplicate entries. + private void FilterAndOrderNpcData(IReadOnlyCollection eNpcEquip, IReadOnlyCollection bNpcEquip) { _data.Clear(); + // This is a maximum since we deduplicate. _data.EnsureCapacity(eNpcEquip.Count + bNpcEquip.Count); + // Convert the NPCs to a dictionary of lists grouped by name. var groups = eNpcEquip.Concat(bNpcEquip).GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList()); + // Iterate through the sorted list. foreach (var (name, duplicates) in groups.OrderBy(kvp => kvp.Key)) { + // Remove any duplicate entries for a name with identical data. for (var i = 0; i < duplicates.Count; ++i) { var current = duplicates[i]; @@ -145,6 +173,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList } } + // If there is only a single entry, add that. This does not take additional string memory through interning. if (duplicates.Count == 1) { _data.Add(duplicates[0]); @@ -152,24 +181,29 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList } else { + // Add all distinct duplicates with their ID specified in the name. _data.AddRange(duplicates .Select(duplicate => duplicate with { - Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})" + Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})", })); Memory += 96 * duplicates.Count + duplicates.Sum(d => d.Name.Length * 2); } } + // Sort non-alphanumeric entries at the end instead of the beginning. var lastWeird = _data.FindIndex(d => char.IsAsciiLetterOrDigit(d.Name[0])); if (lastWeird != -1) { _data.AddRange(_data.Take(lastWeird)); _data.RemoveRange(0, lastWeird); } + + // Reduce memory footprint. _data.TrimExcess(); } + /// Apply equipment from a NpcEquip row. private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) { data.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); @@ -187,96 +221,102 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList data.VisorToggled = row.Visor; } + /// Obtain customizations from a BNpcCustomize row and check if the human is valid. private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize) { var customize = new CustomizeArray(); - customize.SetByIndex(0, (CustomizeValue) (byte)bnpcCustomize.Race.Row); - customize.SetByIndex(1, (CustomizeValue) bnpcCustomize.Gender); - customize.SetByIndex(2, (CustomizeValue) bnpcCustomize.BodyType); - customize.SetByIndex(3, (CustomizeValue) bnpcCustomize.Height); - customize.SetByIndex(4, (CustomizeValue) (byte)bnpcCustomize.Tribe.Row); - customize.SetByIndex(5, (CustomizeValue) bnpcCustomize.Face); - customize.SetByIndex(6, (CustomizeValue) bnpcCustomize.HairStyle); - customize.SetByIndex(7, (CustomizeValue) bnpcCustomize.HairHighlight); - customize.SetByIndex(8, (CustomizeValue) bnpcCustomize.SkinColor); - customize.SetByIndex(9, (CustomizeValue) bnpcCustomize.EyeHeterochromia); - customize.SetByIndex(10, (CustomizeValue) bnpcCustomize.HairColor); - customize.SetByIndex(11, (CustomizeValue) bnpcCustomize.HairHighlightColor); - customize.SetByIndex(12, (CustomizeValue) bnpcCustomize.FacialFeature); - customize.SetByIndex(13, (CustomizeValue) bnpcCustomize.FacialFeatureColor); - customize.SetByIndex(14, (CustomizeValue) bnpcCustomize.Eyebrows); - customize.SetByIndex(15, (CustomizeValue) bnpcCustomize.EyeColor); - customize.SetByIndex(16, (CustomizeValue) bnpcCustomize.EyeShape); - customize.SetByIndex(17, (CustomizeValue) bnpcCustomize.Nose); - customize.SetByIndex(18, (CustomizeValue) bnpcCustomize.Jaw); - customize.SetByIndex(19, (CustomizeValue) bnpcCustomize.Mouth); - customize.SetByIndex(20, (CustomizeValue) bnpcCustomize.LipColor); - customize.SetByIndex(21, (CustomizeValue) bnpcCustomize.BustOrTone1); - customize.SetByIndex(22, (CustomizeValue) bnpcCustomize.ExtraFeature1); - customize.SetByIndex(23, (CustomizeValue) bnpcCustomize.ExtraFeature2OrBust); - customize.SetByIndex(24, (CustomizeValue) bnpcCustomize.FacePaint); - customize.SetByIndex(25, (CustomizeValue) bnpcCustomize.FacePaintColor); + customize.SetByIndex(0, (CustomizeValue)(byte)bnpcCustomize.Race.Row); + customize.SetByIndex(1, (CustomizeValue)bnpcCustomize.Gender); + customize.SetByIndex(2, (CustomizeValue)bnpcCustomize.BodyType); + customize.SetByIndex(3, (CustomizeValue)bnpcCustomize.Height); + customize.SetByIndex(4, (CustomizeValue)(byte)bnpcCustomize.Tribe.Row); + customize.SetByIndex(5, (CustomizeValue)bnpcCustomize.Face); + customize.SetByIndex(6, (CustomizeValue)bnpcCustomize.HairStyle); + customize.SetByIndex(7, (CustomizeValue)bnpcCustomize.HairHighlight); + customize.SetByIndex(8, (CustomizeValue)bnpcCustomize.SkinColor); + customize.SetByIndex(9, (CustomizeValue)bnpcCustomize.EyeHeterochromia); + customize.SetByIndex(10, (CustomizeValue)bnpcCustomize.HairColor); + customize.SetByIndex(11, (CustomizeValue)bnpcCustomize.HairHighlightColor); + customize.SetByIndex(12, (CustomizeValue)bnpcCustomize.FacialFeature); + customize.SetByIndex(13, (CustomizeValue)bnpcCustomize.FacialFeatureColor); + customize.SetByIndex(14, (CustomizeValue)bnpcCustomize.Eyebrows); + customize.SetByIndex(15, (CustomizeValue)bnpcCustomize.EyeColor); + customize.SetByIndex(16, (CustomizeValue)bnpcCustomize.EyeShape); + customize.SetByIndex(17, (CustomizeValue)bnpcCustomize.Nose); + customize.SetByIndex(18, (CustomizeValue)bnpcCustomize.Jaw); + customize.SetByIndex(19, (CustomizeValue)bnpcCustomize.Mouth); + customize.SetByIndex(20, (CustomizeValue)bnpcCustomize.LipColor); + customize.SetByIndex(21, (CustomizeValue)bnpcCustomize.BustOrTone1); + customize.SetByIndex(22, (CustomizeValue)bnpcCustomize.ExtraFeature1); + customize.SetByIndex(23, (CustomizeValue)bnpcCustomize.ExtraFeature2OrBust); + customize.SetByIndex(24, (CustomizeValue)bnpcCustomize.FacePaint); + customize.SetByIndex(25, (CustomizeValue)bnpcCustomize.FacePaintColor); if (customize.BodyType.Value != 1 - || !CustomizationOptions.Races.Contains(customize.Race) - || !CustomizationOptions.Clans.Contains(customize.Clan) - || !CustomizationOptions.Genders.Contains(customize.Gender)) + || !CustomizeManager.Races.Contains(customize.Race) + || !CustomizeManager.Clans.Contains(customize.Clan) + || !CustomizeManager.Genders.Contains(customize.Gender)) return (false, CustomizeArray.Default); return (true, customize); } + /// Obtain customizations from a ENpcBase row and check if the human is valid. private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase) { if (enpcBase.ModelChara.Value?.Type != 1) return (false, CustomizeArray.Default); var customize = new CustomizeArray(); - customize.SetByIndex(0, (CustomizeValue) (byte)enpcBase.Race.Row); - customize.SetByIndex(1, (CustomizeValue) enpcBase.Gender); - customize.SetByIndex(2, (CustomizeValue) enpcBase.BodyType); - customize.SetByIndex(3, (CustomizeValue) enpcBase.Height); - customize.SetByIndex(4, (CustomizeValue) (byte)enpcBase.Tribe.Row); - customize.SetByIndex(5, (CustomizeValue) enpcBase.Face); - customize.SetByIndex(6, (CustomizeValue) enpcBase.HairStyle); - customize.SetByIndex(7, (CustomizeValue) enpcBase.HairHighlight); - customize.SetByIndex(8, (CustomizeValue) enpcBase.SkinColor); - customize.SetByIndex(9, (CustomizeValue) enpcBase.EyeHeterochromia); - customize.SetByIndex(10, (CustomizeValue) enpcBase.HairColor); - customize.SetByIndex(11, (CustomizeValue) enpcBase.HairHighlightColor); - customize.SetByIndex(12, (CustomizeValue) enpcBase.FacialFeature); - customize.SetByIndex(13, (CustomizeValue) enpcBase.FacialFeatureColor); - customize.SetByIndex(14, (CustomizeValue) enpcBase.Eyebrows); - customize.SetByIndex(15, (CustomizeValue) enpcBase.EyeColor); - customize.SetByIndex(16, (CustomizeValue) enpcBase.EyeShape); - customize.SetByIndex(17, (CustomizeValue) enpcBase.Nose); - customize.SetByIndex(18, (CustomizeValue) enpcBase.Jaw); - customize.SetByIndex(19, (CustomizeValue) enpcBase.Mouth); - customize.SetByIndex(20, (CustomizeValue) enpcBase.LipColor); - customize.SetByIndex(21, (CustomizeValue) enpcBase.BustOrTone1); - customize.SetByIndex(22, (CustomizeValue) enpcBase.ExtraFeature1); - customize.SetByIndex(23, (CustomizeValue) enpcBase.ExtraFeature2OrBust); - customize.SetByIndex(24, (CustomizeValue) enpcBase.FacePaint); - customize.SetByIndex(25, (CustomizeValue) enpcBase.FacePaintColor); + customize.SetByIndex(0, (CustomizeValue)(byte)enpcBase.Race.Row); + customize.SetByIndex(1, (CustomizeValue)enpcBase.Gender); + customize.SetByIndex(2, (CustomizeValue)enpcBase.BodyType); + customize.SetByIndex(3, (CustomizeValue)enpcBase.Height); + customize.SetByIndex(4, (CustomizeValue)(byte)enpcBase.Tribe.Row); + customize.SetByIndex(5, (CustomizeValue)enpcBase.Face); + customize.SetByIndex(6, (CustomizeValue)enpcBase.HairStyle); + customize.SetByIndex(7, (CustomizeValue)enpcBase.HairHighlight); + customize.SetByIndex(8, (CustomizeValue)enpcBase.SkinColor); + customize.SetByIndex(9, (CustomizeValue)enpcBase.EyeHeterochromia); + customize.SetByIndex(10, (CustomizeValue)enpcBase.HairColor); + customize.SetByIndex(11, (CustomizeValue)enpcBase.HairHighlightColor); + customize.SetByIndex(12, (CustomizeValue)enpcBase.FacialFeature); + customize.SetByIndex(13, (CustomizeValue)enpcBase.FacialFeatureColor); + customize.SetByIndex(14, (CustomizeValue)enpcBase.Eyebrows); + customize.SetByIndex(15, (CustomizeValue)enpcBase.EyeColor); + customize.SetByIndex(16, (CustomizeValue)enpcBase.EyeShape); + customize.SetByIndex(17, (CustomizeValue)enpcBase.Nose); + customize.SetByIndex(18, (CustomizeValue)enpcBase.Jaw); + customize.SetByIndex(19, (CustomizeValue)enpcBase.Mouth); + customize.SetByIndex(20, (CustomizeValue)enpcBase.LipColor); + customize.SetByIndex(21, (CustomizeValue)enpcBase.BustOrTone1); + customize.SetByIndex(22, (CustomizeValue)enpcBase.ExtraFeature1); + customize.SetByIndex(23, (CustomizeValue)enpcBase.ExtraFeature2OrBust); + customize.SetByIndex(24, (CustomizeValue)enpcBase.FacePaint); + customize.SetByIndex(25, (CustomizeValue)enpcBase.FacePaintColor); if (customize.BodyType.Value != 1 - || !CustomizationOptions.Races.Contains(customize.Race) - || !CustomizationOptions.Clans.Contains(customize.Clan) - || !CustomizationOptions.Genders.Contains(customize.Gender)) + || !CustomizeManager.Races.Contains(customize.Race) + || !CustomizeManager.Clans.Contains(customize.Clan) + || !CustomizeManager.Genders.Contains(customize.Gender)) return (false, CustomizeArray.Default); return (true, customize); } + /// public IEnumerator GetEnumerator() => _data.GetEnumerator(); + /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// public int Count => _data.Count; + /// public NpcData this[int index] => _data[index]; } diff --git a/Glamourer/GameData/NpcData.cs b/Glamourer/GameData/NpcData.cs index 70bfe58..2db8fb5 100644 --- a/Glamourer/GameData/NpcData.cs +++ b/Glamourer/GameData/NpcData.cs @@ -5,17 +5,34 @@ using Penumbra.GameData.Structs; namespace Glamourer.GameData; +/// A struct containing everything to replicate the appearance of a human NPC. public unsafe struct NpcData { - public string Name; - public CustomizeArray Customize; - private fixed byte _equip[40]; - public CharacterWeapon Mainhand; - public CharacterWeapon Offhand; - public NpcId Id; - public bool VisorToggled; - public ObjectKind Kind; + /// The name of the NPC. + public string Name; + /// The customizations of the NPC. + public CustomizeArray Customize; + + /// The equipment appearance of the NPC, 10 * CharacterArmor. + private fixed byte _equip[40]; + + /// The mainhand weapon appearance of the NPC. + public CharacterWeapon Mainhand; + + /// The offhand weapon appearance of the NPC. + public CharacterWeapon Offhand; + + /// The data ID of the NPC, either event NPC or battle NPC name. + public NpcId Id; + + /// Whether the NPCs visor is toggled. + public bool VisorToggled; + + /// Whether the NPC is an event NPC or a battle NPC. + public ObjectKind Kind; + + /// Obtain the equipment as CharacterArmors. public ReadOnlySpan Equip { get @@ -27,38 +44,40 @@ public unsafe struct NpcData } } + /// Write all the gear appearance to a single string. public string WriteGear() { var sb = new StringBuilder(128); var span = Equip; for (var i = 0; i < 10; ++i) { - sb.Append(span[i].Set.Id.ToString("D4")); - sb.Append('-'); - sb.Append(span[i].Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(span[i].Stain.Id.ToString("D3")); - sb.Append(", "); + sb.Append(span[i].Set.Id.ToString("D4")) + .Append('-') + .Append(span[i].Variant.Id.ToString("D3")) + .Append('-') + .Append(span[i].Stain.Id.ToString("D3")) + .Append(", "); } - sb.Append(Mainhand.Skeleton.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Mainhand.Weapon.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Mainhand.Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(Mainhand.Stain.Id.ToString("D4")); - sb.Append(", "); - sb.Append(Offhand.Skeleton.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Offhand.Weapon.Id.ToString("D4")); - sb.Append('-'); - sb.Append(Offhand.Variant.Id.ToString("D3")); - sb.Append('-'); - sb.Append(Offhand.Stain.Id.ToString("D3")); + sb.Append(Mainhand.Skeleton.Id.ToString("D4")) + .Append('-') + .Append(Mainhand.Weapon.Id.ToString("D4")) + .Append('-') + .Append(Mainhand.Variant.Id.ToString("D3")) + .Append('-') + .Append(Mainhand.Stain.Id.ToString("D4")) + .Append(", ") + .Append(Offhand.Skeleton.Id.ToString("D4")) + .Append('-') + .Append(Offhand.Weapon.Id.ToString("D4")) + .Append('-') + .Append(Offhand.Variant.Id.ToString("D3")) + .Append('-') + .Append(Offhand.Stain.Id.ToString("D3")); return sb.ToString(); } + /// Set an equipment piece to a given value. internal void Set(int idx, uint value) { fixed (byte* ptr = _equip) @@ -67,6 +86,7 @@ public unsafe struct NpcData } } + /// Check if the appearance data, excluding ID and Name, of two NpcData is equal. public bool DataEquals(in NpcData other) { if (VisorToggled != other.VisorToggled) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index 939b0f6..9b35b92 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -29,7 +29,7 @@ public partial class CustomizationDrawer npc = true; } - var icon = _service.Service.GetIcon(custom!.Value.IconId); + var icon = _service.Manager.GetIcon(custom!.Value.IconId); using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) { if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) @@ -69,7 +69,7 @@ public partial class CustomizationDrawer for (var i = 0; i < _currentCount; ++i) { var custom = _set.Data(_currentIndex, i, _customize.Face); - var icon = _service.Service.GetIcon(custom.IconId); + var icon = _service.Manager.GetIcon(custom.IconId); using (var _ = ImRaii.Group()) { using var frameColor = ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed, current == i); @@ -180,8 +180,8 @@ public partial class CustomizationDrawer var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero; var feature = _set.Data(featureIdx, 0, face); var icon = featureIdx == CustomizeIndex.LegacyTattoo - ? _legacyTattoo ?? _service.Service.GetIcon(feature.IconId) - : _service.Service.GetIcon(feature.IconId); + ? _legacyTattoo ?? _service.Manager.GetIcon(feature.IconId) + : _service.Manager.GetIcon(feature.IconId); if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, Vector4.Zero, enabled ? Vector4.One : _redTint)) { diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index c131cf5..90cf5c2 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -14,7 +14,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; -public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizationService _service, CodeService _codes, Configuration _config) +public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeService _service, CodeService _codes, Configuration _config) : IDisposable { private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); @@ -23,7 +23,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio private Exception? _terminate; private CustomizeArray _customize = CustomizeArray.Default; - private CustomizationSet _set = null!; + private CustomizeSet _set = null!; public CustomizeArray Customize => _customize; @@ -117,7 +117,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio return DrawArtisan(); DrawRaceGenderSelector(); - _set = _service.Service.GetList(_customize.Clan, _customize.Gender); + _set = _service.Manager.GetSet(_customize.Clan, _customize.Gender); foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage]) PercentageSelector(id); diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index e82ab3c..d52be90 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -180,7 +180,7 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable private readonly AutoDesignManager _autoDesignManager; public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, - ItemManager items, CustomizationService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, + ItemManager items, CustomizeService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, EphemeralConfig config) : this(designs, fileSystem, tabSelected, designColors, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, config) @@ -210,7 +210,7 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable _autoDesignManager.AddDesign(set, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1); } - private static Design CreateRevertDesign(CustomizationService customize, ItemManager items) + private static Design CreateRevertDesign(CustomizeService customize, ItemManager items) => new(customize, items) { Index = RevertDesignIndex, diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index f1aa9fe..ba16ab9 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System; +using System.Numerics; using Dalamud.Game.ClientState.Conditions; using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; @@ -61,7 +62,7 @@ public class GenericPopupWindow : Window private void DrawFestivalPopup() { var viewportSize = ImGui.GetWindowViewport().Size; - ImGui.SetNextWindowSize(new Vector2(viewportSize.X / 5, viewportSize.Y / 7)); + ImGui.SetNextWindowSize(new Vector2(Math.Max(viewportSize.X / 5, 400), Math.Max(viewportSize.Y / 7, 150))); ImGui.SetNextWindowPos(viewportSize / 2, ImGuiCond.Always, new Vector2(0.5f)); using var popup = ImRaii.Popup("FestivalPopup", ImGuiWindowFlags.Modal); if (!popup) diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 2a0453c..846823a 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -27,7 +27,7 @@ public class SetPanel( ItemUnlockManager _itemUnlocks, RevertDesignCombo _designCombo, CustomizeUnlockManager _customizeUnlocks, - CustomizationService _customizations, + CustomizeService _customizations, IdentifierDrawer _identifierDrawer, Configuration _config) { @@ -295,7 +295,7 @@ public class SetPanel( if (!design.Design.DesignData.IsHuman) sb.AppendLine("The base model id can not be changed automatically to something non-human."); - var set = _customizations.Service.GetList(customize.Clan, customize.Gender); + var set = _customizations.Manager.GetSet(customize.Clan, customize.Gender); foreach (var type in CustomizationExtensions.All) { var flag = type.ToFlag(); diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index c57c1b5..d2b9212 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -8,30 +8,27 @@ using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DebugTab; -public class CustomizationServicePanel(CustomizationService _customization) : IDebugTabTree +public class CustomizationServicePanel(CustomizeService customize) : IDebugTabTree { public string Label => "Customization Service"; public bool Disabled - => !_customization.Awaiter.IsCompletedSuccessfully; + => !customize.Awaiter.IsCompletedSuccessfully; public void Draw() { - foreach (var clan in _customization.Service.Clans) + foreach (var (clan, gender) in CustomizeManager.AllSets()) { - foreach (var gender in _customization.Service.Genders) - { - var set = _customization.Service.GetList(clan, gender); - DrawCustomizationInfo(set); - DrawNpcCustomizationInfo(set); - } + var set = customize.Manager.GetSet(clan, gender); + DrawCustomizationInfo(set); + DrawNpcCustomizationInfo(set); } } - private void DrawCustomizationInfo(CustomizationSet set) + private void DrawCustomizationInfo(CustomizeSet set) { - using var tree = ImRaii.TreeNode($"{_customization.ClanName(set.Clan, set.Gender)} {set.Gender}"); + using var tree = ImRaii.TreeNode($"{customize.ClanName(set.Clan, set.Gender)} {set.Gender}"); if (!tree) return; @@ -49,9 +46,9 @@ public class CustomizationServicePanel(CustomizationService _customization) : ID } } - private void DrawNpcCustomizationInfo(CustomizationSet set) + private void DrawNpcCustomizationInfo(CustomizeSet set) { - using var tree = ImRaii.TreeNode($"{_customization.ClanName(set.Clan, set.Gender)} {set.Gender} (NPC Options)"); + using var tree = ImRaii.TreeNode($"{customize.ClanName(set.Clan, set.Gender)} {set.Gender} (NPC Options)"); if (!tree) return; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index cd47606..5333ba5 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -154,7 +154,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer private void DrawCustomizeApplication() { - var set = _selector.Selected!.CustomizationSet; + var set = _selector.Selected!.CustomizeSet; var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 8953501..10d972f 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -19,7 +19,7 @@ public class UnlockOverview { private readonly ItemManager _items; private readonly ItemUnlockManager _itemUnlocks; - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly CustomizeUnlockManager _customizeUnlocks; private readonly PenumbraChangedItemTooltip _tooltip; private readonly TextureService _textures; @@ -52,25 +52,22 @@ public class UnlockOverview } } - foreach (var clan in _customizations.Service.Clans) + foreach (var (clan, gender) in CustomizeManager.AllSets()) { - foreach (var gender in _customizations.Service.Genders) - { - if (_customizations.Service.GetList(clan, gender).HairStyles.Count == 0) - continue; + if (_customizations.Manager.GetSet(clan, gender).HairStyles.Count == 0) + continue; - if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint", - _selected2 == clan && _selected3 == gender)) - { - _selected1 = FullEquipType.Unknown; - _selected2 = clan; - _selected3 = gender; - } + if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint", + _selected2 == clan && _selected3 == gender)) + { + _selected1 = FullEquipType.Unknown; + _selected2 = clan; + _selected3 = gender; } } } - public UnlockOverview(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks, + public UnlockOverview(ItemManager items, CustomizeService customizations, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureService textures, CodeService codes, JobService jobs, FavoriteManager favorites) { @@ -107,7 +104,7 @@ public class UnlockOverview private void DrawCustomizations() { - var set = _customizations.Service.GetList(_selected2, _selected3); + var set = _customizations.Manager.GetSet(_selected2, _selected3); var spacing = IconSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -121,7 +118,7 @@ public class UnlockOverview continue; var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time); - var icon = _customizations.Service.GetIcon(customize.IconId); + var icon = _customizations.Manager.GetIcon(customize.IconId); ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint); diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 4cac25e..4ec59e9 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -15,7 +15,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop; -public class ImportService(CustomizationService _customizations, IDragDropManager _dragDropManager, ItemManager _items) +public class ImportService(CustomizeService _customizations, IDragDropManager _dragDropManager, ItemManager _items) { public void CreateDatSource() => _dragDropManager.CreateImGuiSource("DatDragger", m => m.Files.Count == 1 && m.Extensions.Contains(".dat"), m => @@ -179,14 +179,14 @@ public class ImportService(CustomizationService _customizations, IDragDropManage if (input.BodyType.Value != 1) return false; - var set = _customizations.Service.GetList(input.Clan, input.Gender); + var set = _customizations.Manager.GetSet(input.Clan, input.Gender); voice = set.Voices[0]; if (inputVoice.HasValue && !set.Voices.Contains(inputVoice.Value)) return false; foreach (var index in CustomizationExtensions.AllBasic) { - if (!CustomizationService.IsCustomizationValid(set, input.Face, index, input[index])) + if (!CustomizeService.IsCustomizationValid(set, input.Face, index, input[index])) return false; } diff --git a/Glamourer/Services/CustomizationService.cs b/Glamourer/Services/CustomizeService.cs similarity index 62% rename from Glamourer/Services/CustomizationService.cs rename to Glamourer/Services/CustomizeService.cs index 9e28e42..1ed6f07 100644 --- a/Glamourer/Services/CustomizationService.cs +++ b/Glamourer/Services/CustomizeService.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; -using Dalamud.Plugin.Services; using Glamourer.GameData; using OtterGui.Services; using Penumbra.GameData.DataContainers; @@ -10,26 +9,18 @@ using Penumbra.GameData.Structs; namespace Glamourer.Services; -public sealed class CustomizationService( - ITextureProvider textures, - IDataManager gameData, +public sealed class CustomizeService( HumanModelList humanModels, - IPluginLog log, - NpcCustomizeSet npcCustomizeSet) + NpcCustomizeSet npcCustomizeSet, + CustomizeManager manager) : IAsyncService { - public readonly HumanModelList HumanModels = humanModels; + public readonly HumanModelList HumanModels = humanModels; + public readonly CustomizeManager Manager = manager; + public readonly NpcCustomizeSet NpcCustomizeSet = npcCustomizeSet; - private ICustomizationManager? _service; - - private readonly Task _task = Task.WhenAll(humanModels.Awaiter, npcCustomizeSet.Awaiter) - .ContinueWith(_ => CustomizationManager.Create(textures, gameData, log, npcCustomizeSet)); - - public ICustomizationManager Service - => _service ??= _task.Result; - - public Task Awaiter - => _task; + public Task Awaiter { get; } + = Task.WhenAll(humanModels.Awaiter, manager.Awaiter, npcCustomizeSet.Awaiter); public (CustomizeArray NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(CustomizeArray oldValues, CustomizeArray newValues, CustomizeFlag applyWhich, bool allowUnknown) @@ -51,7 +42,7 @@ public sealed class CustomizationService( } - var set = Service.GetList(ret.Clan, ret.Gender); + var set = Manager.GetSet(ret.Clan, ret.Gender); applyWhich = applyWhich.FixApplication(set); foreach (var index in CustomizationExtensions.AllBasic) { @@ -79,69 +70,34 @@ public sealed class CustomizationService( gender = Gender.Female; if (gender == Gender.MaleNpc) gender = Gender.Male; - return (gender, race) switch - { - (Gender.Male, SubRace.Midlander) => Service.GetName(CustomName.MidlanderM), - (Gender.Male, SubRace.Highlander) => Service.GetName(CustomName.HighlanderM), - (Gender.Male, SubRace.Wildwood) => Service.GetName(CustomName.WildwoodM), - (Gender.Male, SubRace.Duskwight) => Service.GetName(CustomName.DuskwightM), - (Gender.Male, SubRace.Plainsfolk) => Service.GetName(CustomName.PlainsfolkM), - (Gender.Male, SubRace.Dunesfolk) => Service.GetName(CustomName.DunesfolkM), - (Gender.Male, SubRace.SeekerOfTheSun) => Service.GetName(CustomName.SeekerOfTheSunM), - (Gender.Male, SubRace.KeeperOfTheMoon) => Service.GetName(CustomName.KeeperOfTheMoonM), - (Gender.Male, SubRace.Seawolf) => Service.GetName(CustomName.SeawolfM), - (Gender.Male, SubRace.Hellsguard) => Service.GetName(CustomName.HellsguardM), - (Gender.Male, SubRace.Raen) => Service.GetName(CustomName.RaenM), - (Gender.Male, SubRace.Xaela) => Service.GetName(CustomName.XaelaM), - (Gender.Male, SubRace.Helion) => Service.GetName(CustomName.HelionM), - (Gender.Male, SubRace.Lost) => Service.GetName(CustomName.LostM), - (Gender.Male, SubRace.Rava) => Service.GetName(CustomName.RavaM), - (Gender.Male, SubRace.Veena) => Service.GetName(CustomName.VeenaM), - (Gender.Female, SubRace.Midlander) => Service.GetName(CustomName.MidlanderF), - (Gender.Female, SubRace.Highlander) => Service.GetName(CustomName.HighlanderF), - (Gender.Female, SubRace.Wildwood) => Service.GetName(CustomName.WildwoodF), - (Gender.Female, SubRace.Duskwight) => Service.GetName(CustomName.DuskwightF), - (Gender.Female, SubRace.Plainsfolk) => Service.GetName(CustomName.PlainsfolkF), - (Gender.Female, SubRace.Dunesfolk) => Service.GetName(CustomName.DunesfolkF), - (Gender.Female, SubRace.SeekerOfTheSun) => Service.GetName(CustomName.SeekerOfTheSunF), - (Gender.Female, SubRace.KeeperOfTheMoon) => Service.GetName(CustomName.KeeperOfTheMoonF), - (Gender.Female, SubRace.Seawolf) => Service.GetName(CustomName.SeawolfF), - (Gender.Female, SubRace.Hellsguard) => Service.GetName(CustomName.HellsguardF), - (Gender.Female, SubRace.Raen) => Service.GetName(CustomName.RaenF), - (Gender.Female, SubRace.Xaela) => Service.GetName(CustomName.XaelaF), - (Gender.Female, SubRace.Helion) => Service.GetName(CustomName.HelionM), - (Gender.Female, SubRace.Lost) => Service.GetName(CustomName.LostM), - (Gender.Female, SubRace.Rava) => Service.GetName(CustomName.RavaF), - (Gender.Female, SubRace.Veena) => Service.GetName(CustomName.VeenaF), - _ => "Unknown", - }; + return Manager.GetSet(race, gender).Name; } /// Returns whether a clan is valid. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsClanValid(SubRace clan) - => Service.Clans.Contains(clan); + => CustomizeManager.Clans.Contains(clan); /// Returns whether a gender is valid for the given race. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsGenderValid(Race race, Gender gender) - => race is Race.Hrothgar ? gender == Gender.Male : Service.Genders.Contains(gender); + => race is Race.Hrothgar ? gender == Gender.Male : CustomizeManager.Genders.Contains(gender); - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static bool IsCustomizationValid(CustomizationSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value) + public static bool IsCustomizationValid(CustomizeSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value) => IsCustomizationValid(set, face, type, value, out _); /// Returns whether a customization value is valid for a given clan/gender set and face. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static bool IsCustomizationValid(CustomizationSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value, + public static bool IsCustomizationValid(CustomizeSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value, out CustomizeData? data) => set.Validate(type, value, out data, face); /// Returns whether a customization value is valid for a given clan, gender and face. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsCustomizationValid(SubRace race, Gender gender, CustomizeValue face, CustomizeIndex type, CustomizeValue value) - => IsCustomizationValid(Service.GetList(race, gender), face, type, value); + => IsCustomizationValid(Manager.GetSet(race, gender), face, type, value); /// /// Check that the given race and clan are valid. @@ -160,10 +116,10 @@ public sealed class CustomizationService( return string.Empty; } - if (Service.Races.Contains(race)) + if (CustomizeManager.Races.Contains(race)) { actualRace = race; - actualClan = Service.Clans.FirstOrDefault(c => c.ToRace() == race, SubRace.Unknown); + actualClan = CustomizeManager.Clans.FirstOrDefault(c => c.ToRace() == race, SubRace.Unknown); // This should not happen. if (actualClan == SubRace.Unknown) { @@ -189,7 +145,7 @@ public sealed class CustomizationService( /// public string ValidateGender(Race race, Gender gender, out Gender actualGender) { - if (!Service.Genders.Contains(gender)) + if (!CustomizeManager.Genders.Contains(gender)) { actualGender = Gender.Male; return $"The gender {gender.ToName()} is unknown, reset to {Gender.Male.ToName()}."; @@ -230,7 +186,7 @@ public sealed class CustomizationService( /// The returned actualValue is either the correct value or the one with index 0. /// The return value is an empty string or a warning message. /// - public static string ValidateCustomizeValue(CustomizationSet set, CustomizeValue face, CustomizeIndex index, CustomizeValue value, + public static string ValidateCustomizeValue(CustomizeSet set, CustomizeValue face, CustomizeIndex index, CustomizeValue value, out CustomizeValue actualValue, bool allowUnknown) { if (allowUnknown || IsCustomizationValid(set, face, index, value)) @@ -266,7 +222,7 @@ public sealed class CustomizationService( flags |= CustomizeFlag.Gender; } - var set = Service.GetList(customize.Clan, customize.Gender); + var set = Manager.GetSet(customize.Clan, customize.Gender); return FixValues(set, ref customize) | flags; } @@ -284,11 +240,11 @@ public sealed class CustomizationService( return 0; customize.Gender = newGender; - var set = Service.GetList(customize.Clan, customize.Gender); + var set = Manager.GetSet(customize.Clan, customize.Gender); return FixValues(set, ref customize) | CustomizeFlag.Gender; } - private static CustomizeFlag FixValues(CustomizationSet set, ref CustomizeArray customize) + private static CustomizeFlag FixValues(CustomizeSet set, ref CustomizeArray customize) { CustomizeFlag flags = 0; foreach (var idx in CustomizationExtensions.AllBasic) diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 3afe390..9cbcd01 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -84,7 +84,7 @@ public static class ServiceManagerA => services.AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 67c9e08..deb7771 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -27,7 +27,7 @@ public unsafe class FunModule : IDisposable private readonly WorldSets _worldSets = new(); private readonly ItemManager _items; - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly Configuration _config; private readonly CodeService _codes; private readonly Random _rng; @@ -67,7 +67,7 @@ public unsafe class FunModule : IDisposable internal void ResetFestival() => OnDayChange(DateTime.Now.Day, DateTime.Now.Month, DateTime.Now.Year); - public FunModule(CodeService codes, CustomizationService customizations, ItemManager items, Configuration config, + public FunModule(CodeService codes, CustomizeService customizations, ItemManager items, Configuration config, GenericPopupWindow popupWindow, StateManager stateManager, ObjectManager objects, DesignConverter designConverter, DesignManager designManager) { @@ -197,7 +197,7 @@ public unsafe class FunModule : IDisposable if (!_codes.EnabledIndividual) return; - var set = _customizations.Service.GetList(customize.Clan, customize.Gender); + var set = _customizations.Manager.GetSet(customize.Clan, customize.Gender); foreach (var index in Enum.GetValues()) { if (index is CustomizeIndex.Face || !set.IsAvailable(index)) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 50b2605..0095fe9 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -12,12 +12,12 @@ namespace Glamourer.State; public class StateEditor { private readonly ItemManager _items; - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly HumanModelList _humans; private readonly GPoseService _gPose; private readonly ICondition _condition; - public StateEditor(CustomizationService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition) + public StateEditor(CustomizeService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition) { _customizations = customizations; _humans = humans; @@ -72,7 +72,7 @@ public class StateEditor state[CustomizeIndex.Clan] = source; state[CustomizeIndex.Gender] = source; - var set = _customizations.Service.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); + var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); foreach (var index in Enum.GetValues().Where(set.IsAvailable)) state[index] = source; } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d7f947f..1f3c36b 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -28,7 +28,7 @@ public class StateListener : IDisposable private readonly StateManager _manager; private readonly StateApplier _applier; private readonly ItemManager _items; - private readonly CustomizationService _customizations; + private readonly CustomizeService _customizations; private readonly PenumbraService _penumbra; private readonly SlotUpdating _slotUpdating; private readonly WeaponLoading _weaponLoading; @@ -52,7 +52,7 @@ public class StateListener : IDisposable SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, - ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition, CrestService crestService) + ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, CrestService crestService) { _manager = manager; _items = items; @@ -167,7 +167,7 @@ public class StateListener : IDisposable return; } - var set = _customizations.Service.GetList(model.Clan, model.Gender); + var set = _customizations.Manager.GetSet(model.Clan, model.Gender); foreach (var index in CustomizationExtensions.AllBasic) { if (state[index] is not StateChanged.Source.Fixed) diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index a1e95ef..2bdbb78 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -29,7 +29,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable public IReadOnlyDictionary Unlocked => _unlocked; - public CustomizeUnlockManager(SaveService saveService, CustomizationService customizations, IDataManager gameData, + public CustomizeUnlockManager(SaveService saveService, CustomizeService customizations, IDataManager gameData, IClientState clientState, ObjectUnlocked @event, IGameInteropProvider interop) { interop.InitializeFromAttributes(this); @@ -174,38 +174,35 @@ public class CustomizeUnlockManager : IDisposable, ISavable "customization"); /// Create a list of all unlockable hairstyles and face paints. - private static Dictionary CreateUnlockableCustomizations(CustomizationService customizations, + private static Dictionary CreateUnlockableCustomizations(CustomizeService customizations, IDataManager gameData) { var ret = new Dictionary(); var sheet = gameData.GetExcelSheet(ClientLanguage.English)!; - foreach (var clan in customizations.Service.Clans) + foreach (var (clan, gender) in CustomizeManager.AllSets()) { - foreach (var gender in customizations.Service.Genders) + var list = customizations.Manager.GetSet(clan, gender); + foreach (var hair in list.HairStyles) { - var list = customizations.Service.GetList(clan, gender); - foreach (var hair in list.HairStyles) + var x = sheet.FirstOrDefault(f => f.FeatureID == hair.Value.Value); + if (x?.IsPurchasable == true) { - var x = sheet.FirstOrDefault(f => f.FeatureID == hair.Value.Value); - if (x?.IsPurchasable == true) - { - var name = x.FeatureID == 61 - ? "Eternal Bond" - : x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Aesthetics - ", string.Empty) - ?? string.Empty; - ret.TryAdd(hair, (x.Data, name)); - } + var name = x.FeatureID == 61 + ? "Eternal Bond" + : x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Aesthetics - ", string.Empty) + ?? string.Empty; + ret.TryAdd(hair, (x.Data, name)); } + } - foreach (var paint in list.FacePaints) + foreach (var paint in list.FacePaints) + { + var x = sheet.FirstOrDefault(f => f.FeatureID == paint.Value.Value); + if (x?.IsPurchasable == true) { - var x = sheet.FirstOrDefault(f => f.FeatureID == paint.Value.Value); - if (x?.IsPurchasable == true) - { - var name = x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Cosmetics - ", string.Empty) - ?? string.Empty; - ret.TryAdd(paint, (x.Data, name)); - } + var name = x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Cosmetics - ", string.Empty) + ?? string.Empty; + ret.TryAdd(paint, (x.Data, name)); } } } diff --git a/OtterGui b/OtterGui index 197d23e..4404d62 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 197d23eee167c232000f22ef40a7a2bded913b6c +Subproject commit 4404d62b7442daa7e3436dc417364905e3d5cd2f From 4531cdadbed0a822c9803aa2afe6828c14c0bee2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Dec 2023 12:36:11 +0100 Subject: [PATCH 105/786] Move more DebugUi to GameData. --- Glamourer/GameData/CustomizeManager.cs | 6 +- Glamourer/GameData/NpcCustomizeSet.cs | 4 + Glamourer/Gui/Equipment/EquipmentDrawer.cs | 2 +- .../Gui/Equipment/GlamourerColorCombo.cs | 4 +- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 3 +- .../Gui/Tabs/DebugTab/ActorServicePanel.cs | 73 ------------------ .../Gui/Tabs/DebugTab/AutoDesignPanel.cs | 3 +- .../DebugTab/CustomizationServicePanel.cs | 5 +- .../Tabs/DebugTab/CustomizationUnlockPanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 39 ++++------ .../Gui/Tabs/DebugTab/DesignConverterPanel.cs | 3 +- .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 3 +- .../Gui/Tabs/DebugTab/DesignTesterPanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/FunPanel.cs | 3 +- .../Gui/Tabs/DebugTab/IdentifierPanel.cs | 62 --------------- Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 9 ++- .../Gui/Tabs/DebugTab/ItemManagerPanel.cs | 39 ---------- .../Gui/Tabs/DebugTab/ItemUnlockPanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/JobPanel.cs | 76 ------------------- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 3 +- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 3 +- .../Gui/Tabs/DebugTab/ObjectManagerPanel.cs | 5 +- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 3 +- .../Gui/Tabs/DebugTab/RestrictedGearPanel.cs | 42 ---------- .../Gui/Tabs/DebugTab/RetainedStatePanel.cs | 3 +- Glamourer/Gui/Tabs/DebugTab/StainPanel.cs | 53 ------------- .../Gui/Tabs/DebugTab/UnlockableItemsPanel.cs | 3 +- Glamourer/Services/CustomizeService.cs | 3 + Glamourer/Services/ItemManager.cs | 4 +- OtterGui | 2 +- Penumbra.GameData | 2 +- 33 files changed, 76 insertions(+), 399 deletions(-) delete mode 100644 Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/JobPanel.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs delete mode 100644 Glamourer/Gui/Tabs/DebugTab/StainPanel.cs diff --git a/Glamourer/GameData/CustomizeManager.cs b/Glamourer/GameData/CustomizeManager.cs index fabc483..6f03881 100644 --- a/Glamourer/GameData/CustomizeManager.cs +++ b/Glamourer/GameData/CustomizeManager.cs @@ -30,7 +30,7 @@ public class CustomizeManager : IAsyncService /// Every tribe and gender has a separate set of available customizations. public CustomizeSet GetSet(SubRace race, Gender gender) { - if (!Awaiter.IsCompletedSuccessfully) + if (!Finished) Awaiter.Wait(); return _customizationSets[ToIndex(race, gender)]; } @@ -61,6 +61,10 @@ public class CustomizeManager : IAsyncService /// public Task Awaiter { get; } + /// + public bool Finished + => Awaiter.IsCompletedSuccessfully; + private readonly IconStorage _icons; private static readonly int ListSize = Clans.Count * Genders.Count; private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize]; diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index a99448e..de70bd9 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -33,6 +33,10 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList /// public Task Awaiter { get; } + /// + public bool Finished + => Awaiter.IsCompletedSuccessfully; + /// The list of data. private readonly List _data = []; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index cc94ed0..616e67c 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -24,7 +24,7 @@ public class EquipmentDrawer private readonly ItemManager _items; private readonly GlamourerColorCombo _stainCombo; - private readonly DictStains _stainData; + private readonly DictStain _stainData; private readonly ItemCombo[] _itemCombo; private readonly Dictionary _weaponCombo; private readonly CodeService _codes; diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index 1073ac6..2a3dcec 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -13,7 +13,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public sealed class GlamourerColorCombo(float _comboWidth, DictStains _stains, FavoriteManager _favorites) +public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, FavoriteManager _favorites) : FilterComboColors(_comboWidth, CreateFunc(_stains, _favorites), Glamourer.Log) { protected override bool DrawSelectable(int globalIdx, bool selected) @@ -40,7 +40,7 @@ public sealed class GlamourerColorCombo(float _comboWidth, DictStains _stains, F return base.DrawSelectable(globalIdx, selected); } - private static Func>> CreateFunc(DictStains stains, + private static Func>> CreateFunc(DictStain stains, FavoriteManager favorites) => () => stains.Select(kvp => (kvp, favorites.Contains(kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp) .Prepend(new KeyValuePair(Stain.None.RowIndex, Stain.None)).Select(kvp diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index bbcfa32..ea99620 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -12,10 +12,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IDebugTabTree +public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer { public string Label => $"Active Actors ({_stateManager.Count})###Active Actors"; diff --git a/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs deleted file mode 100644 index 976b5c4..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/ActorServicePanel.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Interface.Utility; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; -using Penumbra.GameData.Actors; -using Penumbra.GameData.DataContainers; -using ImGuiClip = OtterGui.ImGuiClip; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class ActorManagerPanel(ActorManager _actors, DictBNpcNames _bNpcNames) : IDebugTabTree -{ - public string Label - => "Actor Service"; - - public bool Disabled - => !_actors.Awaiter.IsCompletedSuccessfully; - - private string _bnpcFilter = string.Empty; - private string _enpcFilter = string.Empty; - private string _companionFilter = string.Empty; - private string _mountFilter = string.Empty; - private string _ornamentFilter = string.Empty; - private string _worldFilter = string.Empty; - - public void Draw() - { - DrawBnpcTable(); - DebugTab.DrawNameTable("ENPCs", ref _enpcFilter, _actors.Data.ENpcs.Select(kvp => (kvp.Key.Id, kvp.Value))); - DebugTab.DrawNameTable("Companions", ref _companionFilter, _actors.Data.Companions.Select(kvp => (kvp.Key.Id, kvp.Value))); - DebugTab.DrawNameTable("Mounts", ref _mountFilter, _actors.Data.Mounts.Select(kvp => (kvp.Key.Id, kvp.Value))); - DebugTab.DrawNameTable("Ornaments", ref _ornamentFilter, _actors.Data.Ornaments.Select(kvp => (kvp.Key.Id, kvp.Value))); - DebugTab.DrawNameTable("Worlds", ref _worldFilter, _actors.Data.Worlds.Select(kvp => ((uint)kvp.Key.Id, kvp.Value))); - } - - private void DrawBnpcTable() - { - using var _ = ImRaii.PushId(1); - using var tree = ImRaii.TreeNode("BNPCs"); - if (!tree) - return; - - var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref _bnpcFilter, 256); - var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; - using var table = ImRaii.Table("##table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, - new Vector2(-1, 10 * height)); - if (!table) - return; - - if (resetScroll) - ImGui.SetScrollY(0); - ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableSetupColumn("3", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(height); - ImGui.TableNextRow(); - var data = _actors.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Key.Id.ToString("D5"), kvp.Value)); - var remainder = ImGuiClip.FilteredClippedDraw(data, skips, - p => p.Item2.Contains(_bnpcFilter) || p.Value.Contains(_bnpcFilter, StringComparison.OrdinalIgnoreCase), - p => - { - ImGuiUtil.DrawTableColumn(p.Item2); - ImGuiUtil.DrawTableColumn(p.Value); - var bNpcs = _bNpcNames.GetBNpcsFromName(p.Key.BNpcNameId); - ImGuiUtil.DrawTableColumn(string.Join(", ", bNpcs.Select(b => b.Id.ToString()))); - }); - ImGuiClip.DrawEndDummy(remainder, height); - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs index 8e29b8b..cbc0e40 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs @@ -2,10 +2,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IDebugTabTree +public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IGameDataDrawer { public string Label => "Auto Designs"; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index d2b9212..7ab089a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -5,16 +5,17 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class CustomizationServicePanel(CustomizeService customize) : IDebugTabTree +public class CustomizationServicePanel(CustomizeService customize) : IGameDataDrawer { public string Label => "Customization Service"; public bool Disabled - => !customize.Awaiter.IsCompletedSuccessfully; + => !customize.Finished; public void Draw() { diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs index b062980..8703d8c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs @@ -5,10 +5,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class CustomizationUnlockPanel(CustomizeUnlockManager _customizeUnlocks) : IDebugTabTree +public class CustomizationUnlockPanel(CustomizeUnlockManager _customizeUnlocks) : IGameDataDrawer { public string Label => "Customizations"; diff --git a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs index ef76b09..b06e807 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs @@ -4,10 +4,11 @@ using Glamourer.Interop; using ImGuiNET; using OtterGui; using Penumbra.GameData.Files; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class DatFilePanel(ImportService _importService) : IDebugTabTree +public class DatFilePanel(ImportService _importService) : IGameDataDrawer { public string Label => "Character Dat File"; diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index bd0b98f..cb50b88 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -4,21 +4,14 @@ using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using OtterGui.Raii; using OtterGui.Services; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public interface IDebugTabTree : IService +public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) { - public string Label { get; } - public void Draw(); - - public bool Disabled { get; } -} - -public class DebugTabHeader(string label, params IDebugTabTree[] subTrees) -{ - public string Label { get; } = label; - public IReadOnlyList SubTrees { get; } = subTrees; + public string Label { get; } = label; + public IReadOnlyList SubTrees { get; } = subTrees; public void Draw() { @@ -27,14 +20,13 @@ public class DebugTabHeader(string label, params IDebugTabTree[] subTrees) foreach (var subTree in SubTrees) { - using (var disabled = ImRaii.Disabled(subTree.Disabled)) + using var disabled = ImRaii.Disabled(subTree.Disabled); + using var tree = ImRaii.TreeNode(subTree.Label); + if (tree) { - using var tree = ImRaii.TreeNode(subTree.Label); - if (!tree) - continue; + disabled.Dispose(); + subTree.Draw(); } - - subTree.Draw(); } } @@ -53,13 +45,14 @@ public class DebugTabHeader(string label, params IDebugTabTree[] subTrees) => new ( "Game Data", - provider.GetRequiredService(), - provider.GetRequiredService(), - provider.GetRequiredService(), - provider.GetRequiredService(), - provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetRequiredService(), - provider.GetRequiredService(), + provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetRequiredService() ); diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs index 9553f72..0de63ed 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs @@ -8,10 +8,11 @@ using ImGuiNET; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class DesignConverterPanel(DesignConverter _designConverter) : IDebugTabTree +public class DesignConverterPanel(DesignConverter _designConverter) : IGameDataDrawer { public string Label => "Design Converter"; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index 8b7ae9a..8f40388 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -6,10 +6,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _designFileSystem) : IDebugTabTree +public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _designFileSystem) : IGameDataDrawer { public string Label => $"Design Manager ({_designManager.Designs.Count} Designs)###Design Manager"; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index 400b2b1..a4e17ed 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -8,10 +8,11 @@ using OtterGui; using OtterGui.Raii; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDebugTabTree +public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IGameDataDrawer { public string Label => "Base64 Design Tester"; diff --git a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs index 9afc125..c517070 100644 --- a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs @@ -1,9 +1,10 @@ using Glamourer.State; using ImGuiNET; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class FunPanel(FunModule _funModule, Configuration _config) : IDebugTabTree +public class FunPanel(FunModule _funModule, Configuration _config) : IGameDataDrawer { public string Label => "Fun Module"; diff --git a/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs deleted file mode 100644 index 9a0503e..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/IdentifierPanel.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Linq; -using Dalamud.Interface.Utility; -using Glamourer.Services; -using ImGuiNET; -using OtterGui; -using Penumbra.GameData.Data; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class IdentifierPanel(ItemManager _items, GamePathParser _gamePathParser) : IDebugTabTree -{ - public string Label - => "Identifier Service"; - - public bool Disabled - => !_items.ObjectIdentification.Awaiter.IsCompletedSuccessfully; - - private string _gamePath = string.Empty; - private int _setId; - private int _secondaryId; - private int _variant; - - public void Draw() - { - static void Text(string text) - { - if (text.Length > 0) - ImGui.TextUnformatted(text); - } - - ImGui.TextUnformatted("Parse Game Path"); - ImGui.SameLine(); - ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); - ImGui.InputTextWithHint("##gamePath", "Enter game path...", ref _gamePath, 256); - var fileInfo = _gamePathParser.GetFileInfo(_gamePath); - ImGui.TextUnformatted( - $"{fileInfo.ObjectType} {fileInfo.EquipSlot} {fileInfo.PrimaryId} {fileInfo.SecondaryId} {fileInfo.Variant} {fileInfo.BodySlot} {fileInfo.CustomizationType}"); - Text(string.Join("\n", _items.ObjectIdentification.Identify(_gamePath).Keys)); - - ImGui.Separator(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Identify Model"); - ImGui.SameLine(); - DebugTab.DrawInputModelSet(true, ref _setId, ref _secondaryId, ref _variant); - - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - var identified = _items.Identify(slot, (PrimaryId)_setId, 0, (Variant)_variant); - Text(identified.Name); - ImGuiUtil.HoverTooltip(string.Join("\n", - _items.ObjectIdentification.Identify((PrimaryId)_setId, 0, (Variant)_variant, slot) - .Select(i => $"{i.Name} {i.Id} {i.ItemId} {i.IconId}"))); - } - - var weapon = _items.Identify(EquipSlot.MainHand, (PrimaryId)_setId, (SecondaryId)_secondaryId, (Variant)_variant); - Text(weapon.Name); - ImGuiUtil.HoverTooltip(string.Join("\n", - _items.ObjectIdentification.Identify((PrimaryId)_setId, (SecondaryId)_secondaryId, (Variant)_variant, EquipSlot.MainHand))); - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs index 1334f2b..bd447e7 100644 --- a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs @@ -2,10 +2,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public unsafe class InventoryPanel : IDebugTabTree +public unsafe class InventoryPanel : IGameDataDrawer { public string Label => "Inventory"; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index 329a2af..9059d53 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -8,11 +8,12 @@ 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) : IDebugTabTree +public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManager _objectManager) : IGameDataDrawer { public string Label => "IPC Tester"; @@ -137,12 +138,12 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag } } - private unsafe void DrawItemIdInput() + private void DrawItemIdInput() { var tmp = _customItemId.Id; - if (ImGui.InputScalar("Custom Item ID", ImGuiDataType.U64, (nint)(&tmp), nint.Zero, nint.Zero)) + if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp)) _customItemId = (CustomItemId)tmp; ImGui.SameLine(); - EquipSlotCombo.Draw("Equip Slot", string.Empty, 200 * ImGuiHelpers.GlobalScale, ref _slot); + EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs deleted file mode 100644 index 9fbc931..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/ItemManagerPanel.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Linq; -using Glamourer.Services; -using ImGuiNET; -using OtterGui.Raii; -using Penumbra.GameData.Enums; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class ItemManagerPanel(ItemManager _items) : IDebugTabTree -{ - public string Label - => "Item Manager"; - - public bool Disabled - => !_items.ItemData.Awaiter.IsCompletedSuccessfully; - - private string _itemFilter = string.Empty; - - public void Draw() - { - ImRaii.TreeNode($"Default Sword: {_items.DefaultSword.Name} ({_items.DefaultSword.ItemId}) ({_items.DefaultSword.Weapon()})", - ImGuiTreeNodeFlags.Leaf).Dispose(); - DebugTab.DrawNameTable("All Items (Main)", ref _itemFilter, - _items.ItemData.AllItems(true).Select(p => (p.Item1.Id, - $"{p.Item2.Name} ({(p.Item2.SecondaryId == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) - .OrderBy(p => p.Item1)); - DebugTab.DrawNameTable("All Items (Off)", ref _itemFilter, - _items.ItemData.AllItems(false).Select(p => (p.Item1.Id, - $"{p.Item2.Name} ({(p.Item2.SecondaryId == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})")) - .OrderBy(p => p.Item1)); - foreach (var type in Enum.GetValues().Skip(1)) - { - DebugTab.DrawNameTable(type.ToName(), ref _itemFilter, - _items.ItemData.ByType[type] - .Select(p => (p.ItemId.Id, $"{p.Name} ({(p.SecondaryId.Id == 0 ? p.Armor().ToString() : p.Weapon().ToString())})"))); - } - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs index 73194c4..efcc664 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs @@ -7,11 +7,12 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IDebugTabTree +public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IGameDataDrawer { public string Label => "Unlocked Items"; diff --git a/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs b/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs deleted file mode 100644 index 2318d98..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/JobPanel.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Glamourer.Interop; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class JobPanel(JobService _jobs) : IDebugTabTree -{ - public string Label - => "Job Service"; - - public bool Disabled - => false; - - public void Draw() - { - DrawJobs(); - DrawJobGroups(); - DrawValidJobGroups(); - } - - private void DrawJobs() - { - using var t = ImRaii.TreeNode("Jobs"); - if (!t) - return; - - using var table = ImRaii.Table("##jobs", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - return; - - foreach (var (id, job) in _jobs.Jobs) - { - ImGuiUtil.DrawTableColumn(id.Id.ToString("D3")); - ImGuiUtil.DrawTableColumn(job.Name); - ImGuiUtil.DrawTableColumn(job.Abbreviation); - } - } - - private void DrawJobGroups() - { - using var t = ImRaii.TreeNode("All Job Groups"); - if (!t) - return; - - using var table = ImRaii.Table("##groups", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - return; - - foreach (var (group, idx) in _jobs.AllJobGroups.WithIndex()) - { - ImGuiUtil.DrawTableColumn(idx.ToString("D3")); - ImGuiUtil.DrawTableColumn(group.Name); - ImGuiUtil.DrawTableColumn(group.Count.ToString()); - } - } - - private void DrawValidJobGroups() - { - using var t = ImRaii.TreeNode("Valid Job Groups"); - if (!t) - return; - - using var table = ImRaii.Table("##groups", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); - if (!table) - return; - - foreach (var (id, group) in _jobs.JobGroups) - { - ImGuiUtil.DrawTableColumn(id.Id.ToString("D3")); - ImGuiUtil.DrawTableColumn(group.Name); - ImGuiUtil.DrawTableColumn(group.Count.ToString()); - } - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index 596476b..c70396c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -7,6 +7,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; @@ -16,7 +17,7 @@ public unsafe class ModelEvaluationPanel( VisorService _visorService, UpdateSlotService _updateSlotService, ChangeCustomizeService _changeCustomizeService, - CrestService _crestService) : IDebugTabTree + CrestService _crestService) : IGameDataDrawer { public string Label => "Model Evaluation"; diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index ffc4b72..cede0f3 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -12,11 +12,12 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) : IDebugTabTree +public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) : IGameDataDrawer { public string Label => "NPC Appearance"; diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs index b868140..e90cbda 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -7,10 +7,11 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Actors; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _actors) : IDebugTabTree +public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _actors) : IGameDataDrawer { public string Label => "Object Manager"; @@ -33,7 +34,7 @@ public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _acto ImGui.TableNextColumn(); ImGuiUtil.DrawTableColumn("World"); - ImGuiUtil.DrawTableColumn(_actors.Awaiter.IsCompletedSuccessfully ? _actors.Data.ToWorldName(_objectManager.World) : "Service Missing"); + ImGuiUtil.DrawTableColumn(_actors.Finished ? _actors.Data.ToWorldName(_objectManager.World) : "Service Missing"); ImGuiUtil.DrawTableColumn(_objectManager.World.ToString()); ImGuiUtil.DrawTableColumn("Player Character"); diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index cf5b92a..641c16a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -10,11 +10,12 @@ using OtterGui; using OtterGui.Raii; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; -public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItemTooltip _penumbraTooltip) : IDebugTabTree +public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItemTooltip _penumbraTooltip) : IGameDataDrawer { public string Label => "Penumbra Interop"; diff --git a/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs b/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs deleted file mode 100644 index 0f27eff..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/RestrictedGearPanel.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Linq; -using Glamourer.Services; -using ImGuiNET; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class RestrictedGearPanel(ItemManager _items) : IDebugTabTree -{ - public string Label - => "Restricted Gear Service"; - - public bool Disabled - => false; - - private int _setId; - private int _secondaryId; - private int _variant; - - public void Draw() - { - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Resolve Model"); - DebugTab.DrawInputModelSet(false, ref _setId, ref _secondaryId, ref _variant); - foreach (var race in Enum.GetValues().Skip(1)) - { - ReadOnlySpan genders = [Gender.Male, Gender.Female]; - foreach (var gender in genders) - { - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - var (replaced, model) = - _items.RestrictedGear.ResolveRestricted(new CharacterArmor((PrimaryId)_setId, (Variant)_variant, 0), slot, race, gender); - if (replaced) - ImGui.TextUnformatted($"{race.ToName()} - {gender} - {slot.ToName()} resolves to {model}."); - } - } - } - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs index 68841bc..027c316 100644 --- a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs @@ -3,10 +3,11 @@ using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Raii; +using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; -public class RetainedStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IDebugTabTree +public class RetainedStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer { public string Label => "Retained States (Inactive Actors)"; diff --git a/Glamourer/Gui/Tabs/DebugTab/StainPanel.cs b/Glamourer/Gui/Tabs/DebugTab/StainPanel.cs deleted file mode 100644 index 643b6dc..0000000 --- a/Glamourer/Gui/Tabs/DebugTab/StainPanel.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Numerics; -using Dalamud.Interface.Utility; -using Glamourer.Services; -using ImGuiNET; -using OtterGui; -using OtterGui.Raii; -using ImGuiClip = OtterGui.ImGuiClip; - -namespace Glamourer.Gui.Tabs.DebugTab; - -public class StainPanel(ItemManager _items) : IDebugTabTree -{ - public string Label - => "Stain Service"; - - public bool Disabled - => false; - - private string _stainFilter = string.Empty; - - public void Draw() - { - var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref _stainFilter, 256); - var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; - using var table = ImRaii.Table("##table", 4, - ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.SizingFixedFit, - new Vector2(-1, 10 * height)); - if (!table) - return; - - if (resetScroll) - ImGui.SetScrollY(0); - - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(height); - ImGui.TableNextRow(); - var remainder = ImGuiClip.FilteredClippedDraw(_items.Stains, skips, - p => p.Key.Id.ToString().Contains(_stainFilter) || p.Value.Name.Contains(_stainFilter, StringComparison.OrdinalIgnoreCase), - p => - { - ImGuiUtil.DrawTableColumn(p.Key.Id.ToString("D3")); - ImGui.TableNextColumn(); - ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(), - ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight()), - p.Value.RgbaColor, 5 * ImGuiHelpers.GlobalScale); - ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight())); - ImGuiUtil.DrawTableColumn(p.Value.Name); - ImGuiUtil.DrawTableColumn($"#{p.Value.R:X2}{p.Value.G:X2}{p.Value.B:X2}{(p.Value.Gloss ? ", Glossy" : string.Empty)}"); - }); - ImGuiClip.DrawEndDummy(remainder, height); - } -} diff --git a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs index e658c99..ecd8d83 100644 --- a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs @@ -7,11 +7,12 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IDebugTabTree +public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IGameDataDrawer { public string Label => "Unlockable Items"; diff --git a/Glamourer/Services/CustomizeService.cs b/Glamourer/Services/CustomizeService.cs index 1ed6f07..6eeb9db 100644 --- a/Glamourer/Services/CustomizeService.cs +++ b/Glamourer/Services/CustomizeService.cs @@ -22,6 +22,9 @@ public sealed class CustomizeService( public Task Awaiter { get; } = Task.WhenAll(humanModels.Awaiter, manager.Awaiter, npcCustomizeSet.Awaiter); + public bool Finished + => Awaiter.IsCompletedSuccessfully; + public (CustomizeArray NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(CustomizeArray oldValues, CustomizeArray newValues, CustomizeFlag applyWhich, bool allowUnknown) { diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 20b97f0..52709c5 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -21,14 +21,14 @@ public class ItemManager public readonly ObjectIdentification ObjectIdentification; public readonly ExcelSheet ItemSheet; - public readonly DictStains Stains; + public readonly DictStain Stains; public readonly ItemData ItemData; public readonly RestrictedGear RestrictedGear; public readonly EquipItem DefaultSword; public ItemManager(Configuration config, IDataManager gameData, ObjectIdentification objectIdentification, - ItemData itemData, DictStains stains, RestrictedGear restrictedGear) + ItemData itemData, DictStain stains, RestrictedGear restrictedGear) { _config = config; ItemSheet = gameData.GetExcelSheet()!; diff --git a/OtterGui b/OtterGui index 4404d62..bdf053e 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 4404d62b7442daa7e3436dc417364905e3d5cd2f +Subproject commit bdf053ea9e7ac7b96dcd6aceadff8d92c3050b34 diff --git a/Penumbra.GameData b/Penumbra.GameData index 4af8775..a7d2d73 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 4af8775f0925ff89e6900c8816b03e0ffeb73f6d +Subproject commit a7d2d73217113eadf02e21865a82deb92ea9eb53 From 44a65f61fb5ef2c7e31a79978614fac908608aab Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Dec 2023 13:38:29 +0100 Subject: [PATCH 106/786] Add diagnostic display and make CustomizeManager a DataContainer. --- Glamourer/GameData/CustomizeManager.cs | 28 ++++++++++++++++--- Glamourer/GameData/CustomizeSetFactory.cs | 4 +-- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 1 + OtterGui | 2 +- Penumbra.GameData | 2 +- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/Glamourer/GameData/CustomizeManager.cs b/Glamourer/GameData/CustomizeManager.cs index 6f03881..a78cdc0 100644 --- a/Glamourer/GameData/CustomizeManager.cs +++ b/Glamourer/GameData/CustomizeManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Dalamud.Interface.Internal; @@ -12,7 +13,7 @@ using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.GameData; /// Generate everything about customization per tribe and gender. -public class CustomizeManager : IAsyncService +public class CustomizeManager : IAsyncDataContainer { /// All races except for Unknown public static readonly IReadOnlyList Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray(); @@ -52,10 +53,21 @@ public class CustomizeManager : IAsyncService public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) { _icons = new IconStorage(textures, gameData); - var tmpTask = Task.Run(() => new CustomizeSetFactory(gameData, log, _icons, npcCustomizeSet)); + var stopwatch = new Stopwatch(); + var tmpTask = Task.Run(() => + { + stopwatch.Start(); + return new CustomizeSetFactory(gameData, log, _icons, npcCustomizeSet); + }); var setTasks = AllSets().Select(p => tmpTask.ContinueWith(t => _customizationSets[ToIndex(p.Clan, p.Gender)] = t.Result.CreateSet(p.Clan, p.Gender))); - Awaiter = Task.WhenAll(setTasks); + Awaiter = Task.WhenAll(setTasks).ContinueWith(_ => + { + // This is far too hard to estimate sensibly. + TotalCount = 0; + Memory = 0; + Time = stopwatch.ElapsedMilliseconds; + }); } /// @@ -78,4 +90,12 @@ public class CustomizeManager : IAsyncService return idx; } -} \ No newline at end of file + + public long Time { get; private set; } + public long Memory { get; private set; } + + public string Name + => nameof(CustomizeManager); + + public int TotalCount { get; private set; } +} diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 5eaaa58..6099344 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -274,7 +274,7 @@ internal class CustomizeSetFactory( { // If none exists and the id corresponds to highlights, set the Highlights name. if (c == CustomizeIndex.Highlights) - return _lobbySheet.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights"; + return string.Intern(_lobbySheet.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights"); // Otherwise there is an error and we use the default name. return c.ToDefaultName(); @@ -282,7 +282,7 @@ internal class CustomizeSetFactory( // Otherwise all is normal, get the menu name or if it does not work the default name. var textRow = _lobbySheet.GetRow(menu.Value.Id); - return textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName(); + return string.Intern(textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName()); }).ToArray(); // Add names for both eye colors. diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index cb50b88..7c50f4d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -45,6 +45,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) => new ( "Game Data", + provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), diff --git a/OtterGui b/OtterGui index bdf053e..4df65fb 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit bdf053ea9e7ac7b96dcd6aceadff8d92c3050b34 +Subproject commit 4df65fb330f3746b7836c39cb96d1e36a53bcec0 diff --git a/Penumbra.GameData b/Penumbra.GameData index a7d2d73..3073db1 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit a7d2d73217113eadf02e21865a82deb92ea9eb53 +Subproject commit 3073db1fc8a1894a4af8974ea9c22a63acd7316e From d81a6b7f6ff4e59be28ad0879c9ac2c04c48f611 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Dec 2023 14:37:04 +0100 Subject: [PATCH 107/786] Set Order for customize again. --- Glamourer/GameData/CustomizeSet.cs | 13 ------------- Glamourer/GameData/CustomizeSetFactory.cs | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index d2dc8e9..bc4e7a3 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -248,19 +248,6 @@ public class CustomizeSet public CharaMakeParams.MenuType Type(CustomizeIndex index) => Types[(int)index]; - internal static IReadOnlyDictionary ComputeOrder(CustomizeSet set) - { - var ret = Enum.GetValues().ToArray(); - ret[(int)CustomizeIndex.TattooColor] = CustomizeIndex.EyeColorLeft; - ret[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorRight; - ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.TattooColor; - - var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray()); - foreach (var type in Enum.GetValues()) - dict.TryAdd(type, Array.Empty()); - return dict; - } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public int Count(CustomizeIndex index) => Count(index, CustomizeValue.Zero); diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 6099344..22d465b 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -68,6 +68,7 @@ internal class CustomizeSetFactory( SetFacialFeatures(set, row); SetHairByFace(set); SetNpcData(set, set.Clan, set.Gender); + SetOrder(set); } /// Given a customize set with filled data, find all customizations used by valid NPCs that are not regularly available. @@ -303,7 +304,7 @@ internal class CustomizeSetFactory( } /// Get the manu types for all available options. - private CharaMakeParams.MenuType[] GetMenuTypes(CharaMakeParams row) + private static CharaMakeParams.MenuType[] GetMenuTypes(CharaMakeParams row) { // Set up the menu types for all customizations. return Enum.GetValues().Select(c => @@ -394,6 +395,19 @@ internal class CustomizeSetFactory( } } + internal static void SetOrder(CustomizeSet set) + { + var ret = Enum.GetValues().ToArray(); + ret[(int)CustomizeIndex.TattooColor] = CustomizeIndex.EyeColorLeft; + ret[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorRight; + ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.TattooColor; + + var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray()); + foreach (var type in Enum.GetValues()) + dict.TryAdd(type, []); + set.Order = dict; + } + /// Set hairstyles per face for Hrothgar and make it simple for non-Hrothgar. private void SetHairByFace(CustomizeSet set) { From fcca756e20024318ddcbb34a8647591f968a9ca1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Dec 2023 14:37:36 +0100 Subject: [PATCH 108/786] Allow change of bodytype. --- .../CustomizationDrawer.GenderRace.cs | 16 +++++++++++++++- .../Gui/Customization/CustomizationDrawer.cs | 2 ++ Glamourer/Services/CustomizeService.cs | 10 ++++++++++ Penumbra.GameData | 2 +- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index 097d99b..4b9fab7 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -1,11 +1,12 @@ using System; using System.Linq; +using System.Numerics; using Dalamud.Interface; -using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; @@ -75,4 +76,17 @@ public partial class CustomizationDrawer if (_lockedRedraw && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) ImGui.SetTooltip("The race can not be changed as this requires a redraw of the character, which is not supported for this actor."); } + + private void DrawBodyType() + { + if (_customize.BodyType.Value == 1) + return; + + if (!ImGuiUtil.DrawDisabledButton($"Reset Body Type {_customize.BodyType.Value} to Default", + new Vector2(_raceSelectorWidth + _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X, 0), string.Empty, _lockedRedraw)) + return; + + Changed |= CustomizeFlag.BodyType; + _customize.BodyType = (CustomizeValue)1; + } } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 90cf5c2..a56040d 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -117,6 +117,8 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer return DrawArtisan(); DrawRaceGenderSelector(); + DrawBodyType(); + _set = _service.Manager.GetSet(_customize.Clan, _customize.Gender); foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage]) diff --git a/Glamourer/Services/CustomizeService.cs b/Glamourer/Services/CustomizeService.cs index 6eeb9db..0b094f3 100644 --- a/Glamourer/Services/CustomizeService.cs +++ b/Glamourer/Services/CustomizeService.cs @@ -44,6 +44,16 @@ public sealed class CustomizeService( applied |= CustomizeFlag.Gender; } + if (applyWhich.HasFlag(CustomizeFlag.BodyType)) + { + if (oldValues[CustomizeIndex.BodyType] != newValues[CustomizeIndex.BodyType]) + { + ret.Set(CustomizeIndex.BodyType, newValues[CustomizeIndex.BodyType]); + changed |= CustomizeFlag.BodyType; + } + + applied |= CustomizeFlag.BodyType; + } var set = Manager.GetSet(ret.Clan, ret.Gender); applyWhich = applyWhich.FixApplication(set); diff --git a/Penumbra.GameData b/Penumbra.GameData index 3073db1..c1c9fb6 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 3073db1fc8a1894a4af8974ea9c22a63acd7316e +Subproject commit c1c9fb6d83be8b05dfb3f35031c05781daad1fc7 From a900219ede5f50565c3156bef94cdf4958e00c24 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Dec 2023 16:16:37 +0100 Subject: [PATCH 109/786] Make crests more quiet. --- Glamourer/Interop/CrestService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 2bfa8d5..7db9a59 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -77,7 +77,7 @@ public sealed unsafe class CrestService : EventWrapper Date: Sun, 24 Dec 2023 16:17:44 +0100 Subject: [PATCH 110/786] Improve bodytype stuff. --- Glamourer/Designs/DesignBase.cs | 32 ++++---- Glamourer/GameData/CustomizeSet.cs | 2 +- Glamourer/GameData/CustomizeSetFactory.cs | 2 +- Glamourer/GameData/NpcCustomizeSet.cs | 6 +- Glamourer/Gui/Tabs/DebugTab/DebugTab.cs | 75 ++++--------------- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 1 - .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 10 ++- 7 files changed, 40 insertions(+), 88 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 7602f80..f4e7e28 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -33,26 +33,26 @@ public class DesignBase internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) { - _designData = designData; - ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; - ApplyEquip = equipFlags & EquipFlagExtensions.All; - _designFlags = 0; - CustomizeSet = SetCustomizationSet(customize); + _designData = designData; + ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; + ApplyEquip = equipFlags & EquipFlagExtensions.All; + _designFlags = 0; + CustomizeSet = SetCustomizationSet(customize); } internal DesignBase(DesignBase clone) { - _designData = clone._designData; - CustomizeSet = clone.CustomizeSet; - ApplyCustomize = clone.ApplyCustomizeRaw; - ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; - _designFlags = clone._designFlags & (DesignFlags)0x0F; + _designData = clone._designData; + CustomizeSet = clone.CustomizeSet; + ApplyCustomize = clone.ApplyCustomizeRaw; + ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; + _designFlags = clone._designFlags & (DesignFlags)0x0F; } /// Ensure that the customization set is updated when the design data changes. internal void SetDesignData(CustomizeService customize, in DesignData other) { - _designData = other; + _designData = other; CustomizeSet = SetCustomizationSet(customize); } @@ -68,8 +68,8 @@ public class DesignBase WriteProtected = 0x10, } - private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; - public CustomizeSet CustomizeSet { get; private set; } + private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; + public CustomizeSet CustomizeSet { get; private set; } internal CustomizeFlag ApplyCustomize { @@ -90,7 +90,7 @@ public class DesignBase return false; _designData.Customize = customize; - CustomizeSet = customizeService.Manager.GetSet(customize.Clan, customize.Gender); + CustomizeSet = customizeService.Manager.GetSet(customize.Clan, customize.Gender); return true; } @@ -485,7 +485,7 @@ public class DesignBase design._designData.Customize.Race = race; design._designData.Customize.Clan = clan; design._designData.Customize.Gender = gender; - design.CustomizeSet = design.SetCustomizationSet(customizations); + design.CustomizeSet = design.SetCustomizationSet(customizations); design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject() ?? false); design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject() ?? false); @@ -495,7 +495,7 @@ public class DesignBase { var tok = json[idx.ToString()]; var data = (CustomizeValue)(tok?["Value"]?.ToObject() ?? 0); - if (set.IsAvailable(idx)) + if (set.IsAvailable(idx) && design._designData.Customize.BodyType == 1) PrintWarning(CustomizeService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data, allowUnknown)); var apply = tok?["Apply"]?.ToObject() ?? false; diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index bc4e7a3..7c98903 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -297,5 +297,5 @@ public static class CustomizationSetExtensions { /// Return only the available customizations in this set and Clan or Gender. public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizeSet set) - => flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender); + => flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType); } diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 22d465b..90cae8d 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -91,7 +91,7 @@ internal class CustomizeSetFactory( var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>(); _npcCustomizeSet.Awaiter.Wait(); - foreach (var customize in _npcCustomizeSet.Select(s => s.Customize).Where(c => c.Clan == race && c.Gender == gender)) + foreach (var customize in _npcCustomizeSet.Select(s => s.Customize).Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1)) { foreach (var customizeIndex in customizeIndices) { diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index de70bd9..bcd6c54 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -256,8 +256,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList customize.SetByIndex(24, (CustomizeValue)bnpcCustomize.FacePaint); customize.SetByIndex(25, (CustomizeValue)bnpcCustomize.FacePaintColor); - if (customize.BodyType.Value != 1 - || !CustomizeManager.Races.Contains(customize.Race) + if (!CustomizeManager.Races.Contains(customize.Race) || !CustomizeManager.Clans.Contains(customize.Clan) || !CustomizeManager.Genders.Contains(customize.Gender)) return (false, CustomizeArray.Default); @@ -299,8 +298,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList customize.SetByIndex(24, (CustomizeValue)enpcBase.FacePaint); customize.SetByIndex(25, (CustomizeValue)enpcBase.FacePaintColor); - if (customize.BodyType.Value != 1 - || !CustomizeManager.Races.Contains(customize.Race) + if (!CustomizeManager.Races.Contains(customize.Race) || !CustomizeManager.Clans.Contains(customize.Clan) || !CustomizeManager.Genders.Contains(customize.Gender)) return (false, CustomizeArray.Default); diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs index 52c7b53..ffe6945 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs @@ -1,20 +1,14 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Dalamud.Interface.Utility; using ImGuiNET; -using Microsoft.Extensions.DependencyInjection; -using OtterGui; using OtterGui.Raii; +using OtterGui.Services; using OtterGui.Widgets; -using ImGuiClip = Dalamud.Interface.Utility.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public unsafe class DebugTab(IServiceProvider _provider) : ITab +public unsafe class DebugTab(ServiceManager manager) : ITab { - private readonly Configuration _config = _provider.GetRequiredService(); + private readonly Configuration _config = manager.GetService(); public bool IsVisible => _config.DebugMode; @@ -24,11 +18,11 @@ public unsafe class DebugTab(IServiceProvider _provider) : ITab private readonly DebugTabHeader[] _headers = [ - DebugTabHeader.CreateInterop(_provider), - DebugTabHeader.CreateGameData(_provider), - DebugTabHeader.CreateDesigns(_provider), - DebugTabHeader.CreateState(_provider), - DebugTabHeader.CreateUnlocks(_provider), + DebugTabHeader.CreateInterop(manager.Provider!), + DebugTabHeader.CreateGameData(manager.Provider!), + DebugTabHeader.CreateDesigns(manager.Provider!), + DebugTabHeader.CreateState(manager.Provider!), + DebugTabHeader.CreateUnlocks(manager.Provider!), ]; public void DrawContent() @@ -37,55 +31,12 @@ public unsafe class DebugTab(IServiceProvider _provider) : ITab if (!child) return; + if (ImGui.CollapsingHeader("General")) + { + manager.Timers.Draw("Timers"); + } + foreach (var header in _headers) header.Draw(); } - - public static void DrawInputModelSet(bool withWeapon, ref int setId, ref int secondaryId, ref int variant) - { - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - ImGui.InputInt("##SetId", ref setId, 0, 0); - if (withWeapon) - { - ImGui.SameLine(); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - ImGui.InputInt("##TypeId", ref secondaryId, 0, 0); - } - - ImGui.SameLine(); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - ImGui.InputInt("##Variant", ref variant, 0, 0); - } - - public static void DrawNameTable(string label, ref string filter, IEnumerable<(uint, string)> names) - { - using var _ = ImRaii.PushId(label); - using var tree = ImRaii.TreeNode(label); - if (!tree) - return; - - var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref filter, 256); - var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; - using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter, - new Vector2(-1, 10 * height)); - if (!table) - return; - - if (resetScroll) - ImGui.SetScrollY(0); - ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(height); - ImGui.TableNextColumn(); - var f = filter; - var remainder = ImGuiClip.FilteredClippedDraw(names.Select(p => (p.Item1.ToString("D5"), p.Item2)), skips, - p => p.Item1.Contains(f) || p.Item2.Contains(f, StringComparison.OrdinalIgnoreCase), - p => - { - ImGuiUtil.DrawTableColumn(p.Item1); - ImGuiUtil.DrawTableColumn(p.Item2); - }); - ImGuiClip.DrawEndDummy(remainder, height); - } } diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index 7c50f4d..b61b4a6 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using OtterGui.Raii; -using OtterGui.Services; using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index cede0f3..e0c7aa8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -17,7 +17,8 @@ using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) : IGameDataDrawer +public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) + : IGameDataDrawer { public string Label => "NPC Appearance"; @@ -25,20 +26,23 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM public bool Disabled => false; - private string _npcFilter = string.Empty; + private string _npcFilter = string.Empty; private bool _customizeOrGear; public void Draw() { ImGui.Checkbox("Compare Customize (or Gear)", ref _customizeOrGear); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64); + var resetScroll = ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64); using var table = ImRaii.Table("npcs", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, new Vector2(-1, 400 * ImGuiHelpers.GlobalScale)); if (!table) return; + if (resetScroll) + ImGui.SetScrollY(0); + ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300); ImGui.TableSetupColumn("Kind", ImGuiTableColumnFlags.WidthFixed); From deba61d721abb019487b63e8afeffda50dbe0402 Mon Sep 17 00:00:00 2001 From: Limiana <5073202+Limiana@users.noreply.github.com> Date: Mon, 25 Dec 2023 16:01:49 +0300 Subject: [PATCH 111/786] Added certain IPC methods --- Glamourer/Api/GlamourerIpc.ApplyByGUID.cs | 57 +++++++++++++++++++++++ Glamourer/Api/GlamourerIpc.GetDesign.cs | 38 +++++++++++++++ Glamourer/Api/GlamourerIpc.cs | 13 +++++- Glamourer/Gui/MainWindow.cs | 1 + 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 Glamourer/Api/GlamourerIpc.ApplyByGUID.cs create mode 100644 Glamourer/Api/GlamourerIpc.GetDesign.cs diff --git a/Glamourer/Api/GlamourerIpc.ApplyByGUID.cs b/Glamourer/Api/GlamourerIpc.ApplyByGUID.cs new file mode 100644 index 0000000..92088b1 --- /dev/null +++ b/Glamourer/Api/GlamourerIpc.ApplyByGUID.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Plugin; +using Glamourer.Events; +using Glamourer.Interop.Structs; +using Penumbra.Api.Helpers; +using Penumbra.GameData.Actors; + +namespace Glamourer.Api; + +public partial class GlamourerIpc +{ + public const string LabelApplyByGuidAll = "Glamourer.ApplyByGuidAll"; + public const string LabelApplyByGuidAllToCharacter = "Glamourer.ApplyByGuidAllToCharacter"; + + private readonly ActionProvider _applyByGuidAllProvider; + private readonly ActionProvider _applyByGuidAllToCharacterProvider; + + public static ActionSubscriber ApplyByGuidAllSubscriber(DalamudPluginInterface pi) + => new(pi, LabelApplyByGuidAll); + + public static ActionSubscriber ApplyByGuidAllToCharacterSubscriber(DalamudPluginInterface pi) + => new(pi, LabelApplyByGuidAllToCharacter); + + public void ApplyByGuidAll(Guid GUID, string characterName) + => ApplyDesignByGuid(GUID, FindActors(characterName), 0); + + public void ApplyByGuidAllToCharacter(Guid GUID, Character? character) + => ApplyDesignByGuid(GUID, FindActors(character), 0); + + private void ApplyDesignByGuid(Guid GUID, IEnumerable actors, uint lockCode) + { + var design = _designManager.Designs.FirstOrDefault(x => x.Identifier == GUID); + if (design == null) + return; + + var hasModelId = true; + _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(design, state, StateChanged.Source.Ipc, lockCode); + state.Lock(lockCode); + } + } + } +} diff --git a/Glamourer/Api/GlamourerIpc.GetDesign.cs b/Glamourer/Api/GlamourerIpc.GetDesign.cs new file mode 100644 index 0000000..7d655ee --- /dev/null +++ b/Glamourer/Api/GlamourerIpc.GetDesign.cs @@ -0,0 +1,38 @@ +using System; +using System.Buffers.Text; +using System.Collections.Generic; +using System.Linq; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Plugin; +using Glamourer.Customization; +using Glamourer.Designs; +using Glamourer.Structs; +using Penumbra.Api.Helpers; +using Penumbra.GameData.Actors; + +namespace Glamourer.Api; + +public partial class GlamourerIpc +{ + public const string LabelGetDesignList = "Glamourer.GetDesignList"; + + private readonly FuncProvider _getDesignListProvider; + + public static FuncSubscriber GetDesignListSubscriber(DalamudPluginInterface pi) + => new(pi, LabelGetDesignList); + + public DesignListEntry[] GetDesignList() + => _designManager.Designs.Select(x => new DesignListEntry(x)).ToArray(); + + public record class DesignListEntry + { + public string Name; + public Guid Identifier; + + public DesignListEntry(Design design) + { + Name = design.Name; + Identifier = design.Identifier; + } + } +} diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 398cdf2..54c4fa7 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -25,9 +25,10 @@ public partial class GlamourerIpc : IDisposable private readonly ActorService _actors; private readonly DesignConverter _designConverter; private readonly AutoDesignApplier _autoDesignApplier; + private readonly DesignManager _designManager; public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorService actors, - DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier) + DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier, DesignManager designManager) { _stateManager = stateManager; _objects = objects; @@ -36,6 +37,7 @@ public partial class GlamourerIpc : IDisposable _autoDesignApplier = autoDesignApplier; _gPose = gPose; _stateChangedEvent = stateChangedEvent; + _designManager = designManager; _apiVersionProvider = new FuncProvider(pi, LabelApiVersion, ApiVersion); _apiVersionsProvider = new FuncProvider<(int Major, int Minor)>(pi, LabelApiVersions, ApiVersions); @@ -78,6 +80,11 @@ public partial class GlamourerIpc : IDisposable _stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc); _gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc); + + _applyByGuidAllProvider = new ActionProvider(pi, LabelApplyByGuidAll, ApplyByGuidAll); + _applyByGuidAllToCharacterProvider = new ActionProvider(pi, LabelApplyByGuidAllToCharacter, ApplyByGuidAllToCharacter); + + _getDesignListProvider = new FuncProvider(pi, LabelGetDesignList, GetDesignList); } public void Dispose() @@ -114,6 +121,10 @@ public partial class GlamourerIpc : IDisposable _stateChangedProvider.Dispose(); _gPose.Unsubscribe(OnGPoseChanged); _gPoseChangedProvider.Dispose(); + + _applyByGuidAllProvider.Dispose(); + _applyByGuidAllToCharacterProvider.Dispose(); + _getDesignListProvider.Dispose(); } private IEnumerable FindActors(string actorName) diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index d4e19b2..df50fb5 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -8,6 +8,7 @@ using Glamourer.Events; using Glamourer.Gui.Tabs; using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Gui.Tabs.AutomationTab; +using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.UnlocksTab; using ImGuiNET; From dd5c56de9db82c432afcd374a0664e53fcc6c101 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 26 Dec 2023 17:29:45 +0100 Subject: [PATCH 112/786] Rework codes and fun a bit. --- .../Gui/Customization/CustomizationDrawer.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 4 +- Glamourer/Gui/Tabs/SettingsTab.cs | 17 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 4 +- Glamourer/Interop/VisorService.cs | 1 - Glamourer/Services/CodeService.cs | 219 +++++++++++------- Glamourer/Services/ItemManager.cs | 2 +- Glamourer/State/FunModule.cs | 211 ++++++++++------- Glamourer/State/StateListener.cs | 6 +- 9 files changed, 269 insertions(+), 197 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index a56040d..b741f39 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -113,7 +113,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer try { - if (_codes.EnabledArtisan) + if (_codes.Enabled(CodeService.CodeFlag.Artisan)) return DrawArtisan(); DrawRaceGenderSelector(); diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 616e67c..e8840bc 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -94,7 +94,7 @@ public class EquipmentDrawer if (_config.SmallEquip) DrawEquipSmall(equipDrawData); - else if (!equipDrawData.Locked && _codes.EnabledArtisan) + else if (!equipDrawData.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan)) DrawEquipArtisan(equipDrawData); else DrawEquipNormal(equipDrawData); @@ -117,7 +117,7 @@ public class EquipmentDrawer if (_config.SmallEquip) DrawWeaponsSmall(mainhand, offhand, allWeapons); - else if (!mainhand.Locked && _codes.EnabledArtisan) + else if (!mainhand.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan)) DrawWeaponsArtisan(mainhand, offhand); else DrawWeaponsNormal(mainhand, offhand, allWeapons); diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index 1b0e27d..340a9fd 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -247,22 +247,17 @@ public class SettingsTab : ITab if (ImGui.Checkbox(code, ref state)) { action(state); - _config.Codes[i] = (code, state); - _codeService.VerifyState(); - _config.Save(); + _codeService.SaveState(); } } - if (_codeService.EnabledCaptain) - { - if (ImGui.Button("Who am I?!?")) - _funModule.WhoAmI(); + if (ImGui.Button("Who am I?!?")) + _funModule.WhoAmI(); - ImGui.SameLine(); + ImGui.SameLine(); - if (ImGui.Button("Who is that!?!")) - _funModule.WhoIsThat(); - } + if (ImGui.Button("Who is that!?!")) + _funModule.WhoIsThat(); } private void DrawCodeHints() diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 10d972f..65203b9 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -121,7 +121,7 @@ public class UnlockOverview var icon = _customizations.Manager.GetIcon(customize.IconId); ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, - unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint); + unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (ImGui.IsItemHovered()) { using var tt = ImRaii.Tooltip(); @@ -189,7 +189,7 @@ public class UnlockOverview var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); - ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint); + ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (_favorites.Contains(item)) ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 2b49eba..75c5e36 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -18,7 +18,6 @@ public class VisorService : IDisposable { Event = visorStateChanged; _setupVisorHook = interop.HookFromAddress((nint)Human.MemberFunctionPointers.SetupVisor, SetupVisorDetour); - interop.InitializeFromAttributes(this); _setupVisorHook.Enable(); } diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index f065e50..82a6477 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -12,23 +12,66 @@ public class CodeService private readonly Configuration _config; private readonly SHA256 _hasher = SHA256.Create(); - public enum Sizing + [Flags] + public enum CodeFlag : ulong { - None, - Dwarf, - Giant, + Clown = 0x000001, + Emperor = 0x000002, + Individual = 0x000004, + Dwarf = 0x000008, + Giant = 0x000010, + OopsHyur = 0x000020, + OopsElezen = 0x000040, + OopsLalafell = 0x000080, + OopsMiqote = 0x000100, + OopsRoegadyn = 0x000200, + OopsAuRa = 0x000400, + OopsHrothgar = 0x000800, + OopsViera = 0x001000, + Artisan = 0x002000, + SixtyThree = 0x004000, + Shirts = 0x008000, + World = 0x010000, + Elephants = 0x020000, } - public bool EnabledClown { get; private set; } - public bool EnabledEmperor { get; private set; } - public bool EnabledIndividual { get; private set; } - public Sizing EnabledSizing { get; private set; } - public Race EnabledOops { get; private set; } - public bool EnabledArtisan { get; private set; } - public bool EnabledCaptain { get; private set; } - public bool Enabled63 { get; private set; } - public bool EnabledShirts { get; private set; } - public bool EnabledWorld { get; private set; } + public const CodeFlag DyeCodes = CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants; + public const CodeFlag GearCodes = CodeFlag.Emperor | CodeFlag.World | CodeFlag.Elephants; + + public const CodeFlag RaceCodes = CodeFlag.OopsHyur + | CodeFlag.OopsElezen + | CodeFlag.OopsLalafell + | CodeFlag.OopsMiqote + | CodeFlag.OopsRoegadyn + | CodeFlag.OopsAuRa + | CodeFlag.OopsHrothgar; + + public const CodeFlag SizeCodes = CodeFlag.Dwarf | CodeFlag.Giant; + + private CodeFlag _enabled; + + public bool Enabled(CodeFlag flag) + => _enabled.HasFlag(flag); + + public bool AnyEnabled(CodeFlag flag) + => (_enabled & flag) != 0; + + public CodeFlag Masked(CodeFlag mask) + => _enabled & mask; + + public Race GetRace() + => (_enabled & RaceCodes) switch + { + CodeFlag.OopsHyur => Race.Hyur, + CodeFlag.OopsElezen => Race.Elezen, + CodeFlag.OopsLalafell => Race.Lalafell, + CodeFlag.OopsMiqote => Race.Miqote, + CodeFlag.OopsRoegadyn => Race.Roegadyn, + CodeFlag.OopsAuRa => Race.AuRa, + CodeFlag.OopsHrothgar => Race.Hrothgar, + CodeFlag.OopsViera => Race.Viera, + _ => Race.Unknown, + }; public CodeService(Configuration config) { @@ -69,92 +112,94 @@ public class CodeService } public Action? CheckCode(string name) + { + var flag = GetCode(name); + if (flag == 0) + return null; + + var badFlags = ~GetMutuallyExclusive(flag); + return v => _enabled = v ? (_enabled | flag) & badFlags : _enabled & ~flag;; + } + + public CodeFlag GetCode(string name) { using var stream = new MemoryStream(Encoding.UTF8.GetBytes(name)); var sha = (ReadOnlySpan)_hasher.ComputeHash(stream); - return sha switch - { - _ when CodeClown.SequenceEqual(sha) => v => EnabledClown = v, - _ when CodeEmperor.SequenceEqual(sha) => v => EnabledEmperor = v, - _ when CodeIndividual.SequenceEqual(sha) => v => EnabledIndividual = v, - _ when CodeCaptain.SequenceEqual(sha) => v => EnabledCaptain = v, - _ when Code63.SequenceEqual(sha) => v => Enabled63 = v, - _ when CodeDwarf.SequenceEqual(sha) => v => EnabledSizing = v ? Sizing.Dwarf : Sizing.None, - _ when CodeGiant.SequenceEqual(sha) => v => EnabledSizing = v ? Sizing.Giant : Sizing.None, - _ when CodeOops1.SequenceEqual(sha) => v => EnabledOops = v ? Race.Hyur : Race.Unknown, - _ when CodeOops2.SequenceEqual(sha) => v => EnabledOops = v ? Race.Elezen : Race.Unknown, - _ when CodeOops3.SequenceEqual(sha) => v => EnabledOops = v ? Race.Lalafell : Race.Unknown, - _ when CodeOops4.SequenceEqual(sha) => v => EnabledOops = v ? Race.Miqote : Race.Unknown, - _ when CodeOops5.SequenceEqual(sha) => v => EnabledOops = v ? Race.Roegadyn : Race.Unknown, - _ when CodeOops6.SequenceEqual(sha) => v => EnabledOops = v ? Race.AuRa : Race.Unknown, - _ when CodeOops7.SequenceEqual(sha) => v => EnabledOops = v ? Race.Hrothgar : Race.Unknown, - _ when CodeOops8.SequenceEqual(sha) => v => EnabledOops = v ? Race.Viera : Race.Unknown, - _ when CodeArtisan.SequenceEqual(sha) => v => EnabledArtisan = v, - _ when CodeShirts.SequenceEqual(sha) => v => EnabledShirts = v, - _ when CodeWorld.SequenceEqual(sha) => v => EnabledWorld = v, - _ => null, - }; + foreach (var flag in Enum.GetValues()) + { + if (sha.SequenceEqual(GetSha(flag))) + return flag; + } + + return 0; } - public void VerifyState() + /// Update all enabled states in the config. + public void SaveState() { - if (EnabledSizing == Sizing.None && EnabledOops == Race.Unknown) - return; - for (var i = 0; i < _config.Codes.Count; ++i) { - var (code, enabled) = _config.Codes[i]; - if (!enabled) - continue; - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(code)); - var sha = (ReadOnlySpan)_hasher.ComputeHash(stream); - var _ = EnabledSizing switch + var name = _config.Codes[i].Code; + var flag = GetCode(name); + if (flag == 0) { - Sizing.Dwarf when sha.SequenceEqual(CodeGiant) => _config.Codes[i] = (code, false), - Sizing.Giant when sha.SequenceEqual(CodeDwarf) => _config.Codes[i] = (code, false), - _ => (string.Empty, false), - }; + _config.Codes.RemoveAt(i--); + continue; + } - var race = OopsRace(sha); - if (race is not Race.Unknown && race != EnabledOops) - _config.Codes[i] = (code, false); + _config.Codes[i] = (name, Enabled(flag)); } + + _config.Save(); } - private static Race OopsRace(ReadOnlySpan sha) - => sha switch - { - _ when CodeOops1.SequenceEqual(sha) => Race.Hyur, - _ when CodeOops2.SequenceEqual(sha) => Race.Elezen, - _ when CodeOops3.SequenceEqual(sha) => Race.Lalafell, - _ when CodeOops4.SequenceEqual(sha) => Race.Miqote, - _ when CodeOops5.SequenceEqual(sha) => Race.Roegadyn, - _ when CodeOops6.SequenceEqual(sha) => Race.AuRa, - _ when CodeOops7.SequenceEqual(sha) => Race.Hrothgar, - _ when CodeOops8.SequenceEqual(sha) => Race.Viera, - _ => Race.Unknown, - }; - // @formatter:off - private static ReadOnlySpan CodeClown => new byte[] { 0xC4, 0xEE, 0x1D, 0x6F, 0xC5, 0x5D, 0x47, 0xBE, 0x78, 0x63, 0x66, 0x86, 0x81, 0x15, 0xEB, 0xFA, 0xF6, 0x4A, 0x90, 0xEA, 0xC0, 0xE4, 0xEE, 0x86, 0x69, 0x01, 0x8E, 0xDB, 0xCC, 0x69, 0xD1, 0xBD }; - private static ReadOnlySpan CodeEmperor => new byte[] { 0xE2, 0x2D, 0x3E, 0x57, 0x16, 0x82, 0x65, 0x98, 0x7E, 0xE6, 0x8F, 0x45, 0x14, 0x7D, 0x65, 0x31, 0xE9, 0xD8, 0xDB, 0xEA, 0xDC, 0xBF, 0xEE, 0x2A, 0xBA, 0xD5, 0x69, 0x96, 0x78, 0x34, 0x3B, 0x57 }; - private static ReadOnlySpan CodeIndividual => new byte[] { 0x95, 0xA4, 0x71, 0xAC, 0xA3, 0xC2, 0x34, 0x94, 0xC1, 0x65, 0x07, 0xF3, 0x7F, 0x93, 0x57, 0xEE, 0xE3, 0x04, 0xC0, 0xE8, 0x1B, 0xA0, 0xE2, 0x08, 0x68, 0x02, 0x8D, 0xAD, 0x76, 0x03, 0x9B, 0xC5 }; - private static ReadOnlySpan CodeDwarf => new byte[] { 0x55, 0x97, 0xFE, 0xE9, 0x78, 0x64, 0xE8, 0x2F, 0xCD, 0x25, 0xD1, 0xAE, 0xDF, 0x35, 0xE6, 0xED, 0x03, 0x78, 0x54, 0x1D, 0x56, 0x22, 0x34, 0x75, 0x4B, 0x96, 0x6F, 0xBA, 0xAC, 0xEC, 0x00, 0x46 }; - private static ReadOnlySpan CodeGiant => new byte[] { 0x6E, 0xBB, 0x91, 0x1D, 0x67, 0xE3, 0x00, 0x07, 0xA1, 0x0F, 0x2A, 0xF0, 0x26, 0x91, 0x38, 0x63, 0xD3, 0x52, 0x82, 0xF7, 0x5D, 0x93, 0xE8, 0x83, 0xB1, 0xF6, 0xB9, 0x69, 0x78, 0x20, 0xC4, 0xCE }; - private static ReadOnlySpan CodeOops1 => new byte[] { 0x4C, 0x51, 0xE2, 0x38, 0xEF, 0xAD, 0x84, 0x0E, 0x4E, 0x11, 0x0F, 0x5E, 0xDE, 0x45, 0x41, 0x9F, 0x6A, 0xF6, 0x5F, 0x5B, 0xA8, 0x91, 0x64, 0x22, 0xEE, 0x62, 0x97, 0x3C, 0x78, 0x18, 0xCD, 0xAF }; - private static ReadOnlySpan CodeOops2 => new byte[] { 0x3D, 0x5B, 0xA9, 0x62, 0xCE, 0xBE, 0x52, 0xF5, 0x94, 0x2A, 0xF9, 0xB7, 0xCF, 0xD9, 0x24, 0x2B, 0x38, 0xC7, 0x4F, 0x28, 0x97, 0x29, 0x1D, 0x01, 0x13, 0x53, 0x44, 0x11, 0x15, 0x6F, 0x9B, 0x56 }; - private static ReadOnlySpan CodeOops3 => new byte[] { 0x85, 0x8D, 0x5B, 0xC2, 0x66, 0x53, 0x2E, 0xB9, 0xE9, 0x85, 0xE5, 0xF8, 0xD3, 0x75, 0x18, 0x7C, 0x58, 0x55, 0xD4, 0x8C, 0x8E, 0x5F, 0x58, 0x2E, 0xF3, 0xF1, 0xAE, 0xA8, 0xA0, 0x81, 0xC6, 0x0E }; - private static ReadOnlySpan CodeOops4 => new byte[] { 0x44, 0x73, 0x8C, 0x39, 0x5A, 0xF1, 0xDB, 0x5F, 0x62, 0xA1, 0x6E, 0x5F, 0xE6, 0x97, 0x9E, 0x90, 0xD7, 0x5C, 0x97, 0x67, 0xB6, 0xC7, 0x99, 0x61, 0x36, 0xCA, 0x34, 0x7E, 0xB9, 0xAC, 0xC3, 0x76 }; - private static ReadOnlySpan CodeOops5 => new byte[] { 0xB7, 0x25, 0x73, 0xDB, 0xBE, 0xD0, 0x49, 0xFB, 0xFF, 0x9C, 0x32, 0x21, 0xB0, 0x8A, 0x2C, 0x0C, 0x77, 0x46, 0xD5, 0xCF, 0x0E, 0x63, 0x2F, 0x91, 0x85, 0x8B, 0x55, 0x5C, 0x4D, 0xD2, 0xB9, 0xB8 }; - private static ReadOnlySpan CodeOops6 => new byte[] { 0x69, 0x93, 0xAF, 0xE4, 0xB8, 0xEC, 0x5F, 0x40, 0xEB, 0x8A, 0x6F, 0xD1, 0x9B, 0xD9, 0x56, 0x0B, 0xEA, 0x64, 0x79, 0x9B, 0x54, 0xA1, 0x41, 0xED, 0xBC, 0x3E, 0x6E, 0x5C, 0xF1, 0x23, 0x60, 0xF8 }; - private static ReadOnlySpan CodeOops7 => new byte[] { 0x41, 0xEC, 0x65, 0x05, 0x8D, 0x20, 0x68, 0x5A, 0xB7, 0xEB, 0x92, 0x15, 0x43, 0xCF, 0x15, 0x05, 0x27, 0x51, 0xFE, 0x20, 0xC9, 0xB6, 0x2B, 0x84, 0xD9, 0x6A, 0x49, 0x5A, 0x5B, 0x7F, 0x2E, 0xE7 }; - private static ReadOnlySpan CodeOops8 => new byte[] { 0x16, 0xFF, 0x63, 0x85, 0x1C, 0xF5, 0x34, 0x33, 0x67, 0x8C, 0x46, 0x8E, 0x3E, 0xE3, 0xA6, 0x94, 0xF9, 0x74, 0x47, 0xAA, 0xC7, 0x29, 0x59, 0x1F, 0x6C, 0x6E, 0xF2, 0xF5, 0x87, 0x24, 0x9E, 0x2B }; - private static ReadOnlySpan CodeArtisan => new byte[] { 0xDE, 0x01, 0x32, 0x1E, 0x7F, 0x22, 0x80, 0x3D, 0x76, 0xDF, 0x74, 0x0E, 0xEC, 0x33, 0xD3, 0xF4, 0x1A, 0x98, 0x9E, 0x9D, 0x22, 0x5C, 0xAC, 0x3B, 0xFE, 0x0B, 0xC2, 0x13, 0xB9, 0x91, 0x24, 0x61 }; - private static ReadOnlySpan CodeCaptain => new byte[] { 0x5E, 0x0B, 0xDD, 0x86, 0x8F, 0x24, 0xDA, 0x49, 0x1A, 0xD2, 0x59, 0xB9, 0x10, 0x38, 0x29, 0x37, 0x99, 0x9D, 0x53, 0xD9, 0x9B, 0x84, 0x91, 0x5B, 0x6C, 0xCE, 0x3E, 0x2A, 0x38, 0x06, 0x47, 0xE6 }; - private static ReadOnlySpan Code63 => new byte[] { 0xA1, 0x65, 0x60, 0x99, 0xB0, 0x9F, 0xBF, 0xD7, 0x20, 0xC8, 0x29, 0xF6, 0x7B, 0x86, 0x27, 0xF5, 0xBE, 0xA9, 0x5B, 0xB0, 0x20, 0x5E, 0x57, 0x7B, 0xFF, 0xBC, 0x1E, 0x8C, 0x04, 0xF9, 0x35, 0xD3 }; - private static ReadOnlySpan CodeShirts => new byte[] { 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB }; - private static ReadOnlySpan CodeWorld => new byte[] { 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 }; - // @formatter:on + private static CodeFlag GetMutuallyExclusive(CodeFlag flag) + => flag switch + { + CodeFlag.Clown => DyeCodes & ~CodeFlag.Clown, + CodeFlag.Emperor => GearCodes & ~CodeFlag.Emperor, + CodeFlag.Individual => 0, + CodeFlag.Dwarf => SizeCodes & ~CodeFlag.Dwarf, + CodeFlag.Giant => SizeCodes & ~CodeFlag.Giant, + CodeFlag.OopsHyur => RaceCodes & ~CodeFlag.OopsHyur, + CodeFlag.OopsElezen => RaceCodes & ~CodeFlag.OopsElezen, + CodeFlag.OopsLalafell => RaceCodes & ~CodeFlag.OopsLalafell, + CodeFlag.OopsMiqote => RaceCodes & ~CodeFlag.OopsMiqote, + CodeFlag.OopsRoegadyn => RaceCodes & ~CodeFlag.OopsRoegadyn, + CodeFlag.OopsAuRa => RaceCodes & ~CodeFlag.OopsAuRa, + CodeFlag.OopsHrothgar => RaceCodes & ~CodeFlag.OopsHrothgar, + CodeFlag.OopsViera => RaceCodes & ~CodeFlag.OopsViera, + CodeFlag.Artisan => 0, + CodeFlag.SixtyThree => 0, + CodeFlag.Shirts => 0, + CodeFlag.World => (DyeCodes | GearCodes) & ~CodeFlag.World, + CodeFlag.Elephants => (DyeCodes | GearCodes) & ~CodeFlag.Elephants, + _ => 0, + }; + + private static ReadOnlySpan GetSha(CodeFlag flag) + => flag switch + { + CodeFlag.Clown => [ 0xC4, 0xEE, 0x1D, 0x6F, 0xC5, 0x5D, 0x47, 0xBE, 0x78, 0x63, 0x66, 0x86, 0x81, 0x15, 0xEB, 0xFA, 0xF6, 0x4A, 0x90, 0xEA, 0xC0, 0xE4, 0xEE, 0x86, 0x69, 0x01, 0x8E, 0xDB, 0xCC, 0x69, 0xD1, 0xBD ], + CodeFlag.Emperor => [ 0xE2, 0x2D, 0x3E, 0x57, 0x16, 0x82, 0x65, 0x98, 0x7E, 0xE6, 0x8F, 0x45, 0x14, 0x7D, 0x65, 0x31, 0xE9, 0xD8, 0xDB, 0xEA, 0xDC, 0xBF, 0xEE, 0x2A, 0xBA, 0xD5, 0x69, 0x96, 0x78, 0x34, 0x3B, 0x57 ], + CodeFlag.Individual => [ 0x95, 0xA4, 0x71, 0xAC, 0xA3, 0xC2, 0x34, 0x94, 0xC1, 0x65, 0x07, 0xF3, 0x7F, 0x93, 0x57, 0xEE, 0xE3, 0x04, 0xC0, 0xE8, 0x1B, 0xA0, 0xE2, 0x08, 0x68, 0x02, 0x8D, 0xAD, 0x76, 0x03, 0x9B, 0xC5 ], + CodeFlag.Dwarf => [ 0x55, 0x97, 0xFE, 0xE9, 0x78, 0x64, 0xE8, 0x2F, 0xCD, 0x25, 0xD1, 0xAE, 0xDF, 0x35, 0xE6, 0xED, 0x03, 0x78, 0x54, 0x1D, 0x56, 0x22, 0x34, 0x75, 0x4B, 0x96, 0x6F, 0xBA, 0xAC, 0xEC, 0x00, 0x46 ], + CodeFlag.Giant => [ 0x6E, 0xBB, 0x91, 0x1D, 0x67, 0xE3, 0x00, 0x07, 0xA1, 0x0F, 0x2A, 0xF0, 0x26, 0x91, 0x38, 0x63, 0xD3, 0x52, 0x82, 0xF7, 0x5D, 0x93, 0xE8, 0x83, 0xB1, 0xF6, 0xB9, 0x69, 0x78, 0x20, 0xC4, 0xCE ], + CodeFlag.OopsHyur => [ 0x4C, 0x51, 0xE2, 0x38, 0xEF, 0xAD, 0x84, 0x0E, 0x4E, 0x11, 0x0F, 0x5E, 0xDE, 0x45, 0x41, 0x9F, 0x6A, 0xF6, 0x5F, 0x5B, 0xA8, 0x91, 0x64, 0x22, 0xEE, 0x62, 0x97, 0x3C, 0x78, 0x18, 0xCD, 0xAF ], + CodeFlag.OopsElezen => [ 0x3D, 0x5B, 0xA9, 0x62, 0xCE, 0xBE, 0x52, 0xF5, 0x94, 0x2A, 0xF9, 0xB7, 0xCF, 0xD9, 0x24, 0x2B, 0x38, 0xC7, 0x4F, 0x28, 0x97, 0x29, 0x1D, 0x01, 0x13, 0x53, 0x44, 0x11, 0x15, 0x6F, 0x9B, 0x56 ], + CodeFlag.OopsLalafell => [ 0x85, 0x8D, 0x5B, 0xC2, 0x66, 0x53, 0x2E, 0xB9, 0xE9, 0x85, 0xE5, 0xF8, 0xD3, 0x75, 0x18, 0x7C, 0x58, 0x55, 0xD4, 0x8C, 0x8E, 0x5F, 0x58, 0x2E, 0xF3, 0xF1, 0xAE, 0xA8, 0xA0, 0x81, 0xC6, 0x0E ], + CodeFlag.OopsMiqote => [ 0x44, 0x73, 0x8C, 0x39, 0x5A, 0xF1, 0xDB, 0x5F, 0x62, 0xA1, 0x6E, 0x5F, 0xE6, 0x97, 0x9E, 0x90, 0xD7, 0x5C, 0x97, 0x67, 0xB6, 0xC7, 0x99, 0x61, 0x36, 0xCA, 0x34, 0x7E, 0xB9, 0xAC, 0xC3, 0x76 ], + CodeFlag.OopsRoegadyn => [ 0xB7, 0x25, 0x73, 0xDB, 0xBE, 0xD0, 0x49, 0xFB, 0xFF, 0x9C, 0x32, 0x21, 0xB0, 0x8A, 0x2C, 0x0C, 0x77, 0x46, 0xD5, 0xCF, 0x0E, 0x63, 0x2F, 0x91, 0x85, 0x8B, 0x55, 0x5C, 0x4D, 0xD2, 0xB9, 0xB8 ], + CodeFlag.OopsAuRa => [ 0x69, 0x93, 0xAF, 0xE4, 0xB8, 0xEC, 0x5F, 0x40, 0xEB, 0x8A, 0x6F, 0xD1, 0x9B, 0xD9, 0x56, 0x0B, 0xEA, 0x64, 0x79, 0x9B, 0x54, 0xA1, 0x41, 0xED, 0xBC, 0x3E, 0x6E, 0x5C, 0xF1, 0x23, 0x60, 0xF8 ], + CodeFlag.OopsHrothgar => [ 0x41, 0xEC, 0x65, 0x05, 0x8D, 0x20, 0x68, 0x5A, 0xB7, 0xEB, 0x92, 0x15, 0x43, 0xCF, 0x15, 0x05, 0x27, 0x51, 0xFE, 0x20, 0xC9, 0xB6, 0x2B, 0x84, 0xD9, 0x6A, 0x49, 0x5A, 0x5B, 0x7F, 0x2E, 0xE7 ], + CodeFlag.OopsViera => [ 0x16, 0xFF, 0x63, 0x85, 0x1C, 0xF5, 0x34, 0x33, 0x67, 0x8C, 0x46, 0x8E, 0x3E, 0xE3, 0xA6, 0x94, 0xF9, 0x74, 0x47, 0xAA, 0xC7, 0x29, 0x59, 0x1F, 0x6C, 0x6E, 0xF2, 0xF5, 0x87, 0x24, 0x9E, 0x2B ], + CodeFlag.Artisan => [ 0xDE, 0x01, 0x32, 0x1E, 0x7F, 0x22, 0x80, 0x3D, 0x76, 0xDF, 0x74, 0x0E, 0xEC, 0x33, 0xD3, 0xF4, 0x1A, 0x98, 0x9E, 0x9D, 0x22, 0x5C, 0xAC, 0x3B, 0xFE, 0x0B, 0xC2, 0x13, 0xB9, 0x91, 0x24, 0x61 ], + CodeFlag.SixtyThree => [ 0xA1, 0x65, 0x60, 0x99, 0xB0, 0x9F, 0xBF, 0xD7, 0x20, 0xC8, 0x29, 0xF6, 0x7B, 0x86, 0x27, 0xF5, 0xBE, 0xA9, 0x5B, 0xB0, 0x20, 0x5E, 0x57, 0x7B, 0xFF, 0xBC, 0x1E, 0x8C, 0x04, 0xF9, 0x35, 0xD3 ], + CodeFlag.Shirts => [ 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB ], + CodeFlag.World => [ 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 ], + CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ], + _ => [], + }; } diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 52709c5..6e73945 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -21,7 +21,7 @@ public class ItemManager public readonly ObjectIdentification ObjectIdentification; public readonly ExcelSheet ItemSheet; - public readonly DictStain Stains; + public readonly DictStain Stains; public readonly ItemData ItemData; public readonly RestrictedGear RestrictedGear; diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index deb7771..9e0ffa4 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -8,6 +8,7 @@ using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; using ImGuiNET; +using OtterGui; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -25,18 +26,18 @@ public unsafe class FunModule : IDisposable AprilFirst, } - private readonly WorldSets _worldSets = new(); - private readonly ItemManager _items; - private readonly CustomizeService _customizations; - private readonly Configuration _config; - private readonly CodeService _codes; - private readonly Random _rng; - private readonly GenericPopupWindow _popupWindow; - private readonly StateManager _stateManager; - private readonly DesignConverter _designConverter; - private readonly DesignManager _designManager; - private readonly ObjectManager _objects; - private readonly StainId[] _stains; + private readonly WorldSets _worldSets = new(); + private readonly ItemManager _items; + private readonly CustomizeService _customizations; + private readonly Configuration _config; + private readonly CodeService _codes; + private readonly Random _rng; + private readonly GenericPopupWindow _popupWindow; + private readonly StateManager _stateManager; + private readonly DesignConverter _designConverter; + private readonly DesignManager _designManager; + private readonly ObjectManager _objects; + private readonly StainId[] _stains; public FestivalType CurrentFestival { get; private set; } = FestivalType.None; private FunEquipSet? _festivalSet; @@ -89,54 +90,92 @@ public unsafe class FunModule : IDisposable public void Dispose() => DayChangeTracker.DayChanged -= OnDayChange; - public void ApplyFun(Actor actor, ref CharacterArmor armor, EquipSlot slot) + private bool IsInFestival + => _config.DisableFestivals == 0 && _festivalSet != null; + + public void ApplyFunToSlot(Actor actor, ref CharacterArmor armor, EquipSlot slot) { if (!ValidFunTarget(actor)) return; - if (_config.DisableFestivals == 0 && _festivalSet != null - || _codes.EnabledWorld && actor.Index != 0) + if (IsInFestival) { - armor = actor.Model.Valid ? actor.Model.GetArmor(slot) : armor; + KeepOldArmor(actor, slot, ref armor); + return; } - else + + switch (_codes.Masked(CodeService.GearCodes)) { - ApplyEmperor(new Span(ref armor), slot); - ApplyClown(new Span(ref armor)); + case CodeService.CodeFlag.Emperor: + SetRandomItem(slot, ref armor); + break; + case CodeService.CodeFlag.Elephants: + SetElephant(slot, ref armor); + break; + case CodeService.CodeFlag.World when actor.Index != 0: + KeepOldArmor(actor, slot, ref armor); + break; + } + + switch (_codes.Masked(CodeService.DyeCodes)) + { + case CodeService.CodeFlag.Clown: + SetRandomDye(ref armor); + break; } } - public void ApplyFun(Actor actor, Span armor, ref CustomizeArray customize) + public void ApplyFunOnLoad(Actor actor, Span armor, ref CustomizeArray customize) { if (!ValidFunTarget(actor)) return; - if (_config.DisableFestivals == 0 && _festivalSet != null) + // First set the race, if any. + SetRace(ref customize); + // Now apply the gender. + SetGender(ref customize); + // Randomize customizations inside the race and gender combo. + RandomizeCustomize(ref customize); + // Finally, apply forced sizes. + SetSize(actor, ref customize); + + // Apply the festival gear with priority over all gear codes. + if (IsInFestival) { - _festivalSet.Apply(_stains, _rng, armor); - } - else if (_codes.EnabledWorld && actor.Index != 0) - { - _worldSets.Apply(actor, _rng, armor); - } - else - { - ApplyEmperor(armor); - ApplyClown(armor); + _festivalSet!.Apply(_stains, _rng, armor); + return; } - ApplyOops(ref customize); - Apply63(ref customize); - ApplyIndividual(ref customize); - ApplySizing(actor, ref customize); + switch (_codes.Masked(CodeService.GearCodes)) + { + case CodeService.CodeFlag.Emperor: + foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) + SetRandomItem(slot, ref armor[idx]); + break; + case CodeService.CodeFlag.Elephants: + SetElephant(EquipSlot.Body, ref armor[1]); + SetElephant(EquipSlot.Head, ref armor[0]); + break; + case CodeService.CodeFlag.World when actor.Index != 0: + _worldSets.Apply(actor, _rng, armor); + break; + } + + switch (_codes.Masked(CodeService.DyeCodes)) + { + case CodeService.CodeFlag.Clown: + foreach (ref var piece in armor) + SetRandomDye(ref piece); + break; + } } - public void ApplyFun(Actor actor, ref CharacterWeapon weapon, EquipSlot slot) + public void ApplyFunToWeapon(Actor actor, ref CharacterWeapon weapon, EquipSlot slot) { if (!ValidFunTarget(actor)) return; - if (_codes.EnabledWorld) + if (_codes.Enabled(CodeService.CodeFlag.World) && actor.Index != 0) _worldSets.Apply(actor, _rng, ref weapon, slot); } @@ -146,55 +185,58 @@ public unsafe class FunModule : IDisposable && !actor.IsTransformed && actor.AsCharacter->CharacterData.ModelCharaId == 0; - public void ApplyClown(Span armors) - { - if (!_codes.EnabledClown) - return; + private static void KeepOldArmor(Actor actor, EquipSlot slot, ref CharacterArmor armor) + => armor = actor.Model.Valid ? actor.Model.GetArmor(slot) : armor; - foreach (ref var armor in armors) - { - var stainIdx = _rng.Next(0, _stains.Length - 1); - armor.Stain = _stains[stainIdx]; - } + private void SetRandomDye(ref CharacterArmor armor) + { + var stainIdx = _rng.Next(0, _stains.Length - 1); + armor.Stain = _stains[stainIdx]; } - public void ApplyEmperor(Span armors, EquipSlot slot = EquipSlot.Unknown) + private void SetRandomItem(EquipSlot slot, ref CharacterArmor armor) { - if (!_codes.EnabledEmperor) - return; - - if (armors.Length == 1) - SetItem(slot, ref armors[0]); - else - for (var i = 0u; i < armors.Length; ++i) - SetItem(i.ToEquipSlot(), ref armors[(int)i]); - return; - - void SetItem(EquipSlot slot2, ref CharacterArmor armor) - { - var list = _items.ItemData.ByType[slot2.ToEquipType()]; - var rng = _rng.Next(0, list.Count - 1); - var item = list[rng]; - armor.Set = item.PrimaryId; - armor.Variant = item.Variant; - } + var list = _items.ItemData.ByType[slot.ToEquipType()]; + var rng = _rng.Next(0, list.Count - 1); + var item = list[rng]; + armor.Set = item.PrimaryId; + armor.Variant = item.Variant; } - public void ApplyOops(ref CustomizeArray customize) + private void SetElephant(EquipSlot slot, ref CharacterArmor armor) { - if (_codes.EnabledOops == Race.Unknown) + armor = slot switch + { + EquipSlot.Body => new CharacterArmor(6133, 1, 87), + EquipSlot.Head => new CharacterArmor(6133, 1, 87), + _ => armor, + }; + } + + private void SetRace(ref CustomizeArray customize) + { + var race = _codes.GetRace(); + if (race == Race.Unknown) return; - var targetClan = (SubRace)((int)_codes.EnabledOops * 2 - (int)customize.Clan % 2); + var targetClan = (SubRace)((int)race * 2 - (int)customize.Clan % 2); // TODO Female Hrothgar - if (_codes.EnabledOops is Race.Hrothgar && customize.Gender is Gender.Female) + if (race is Race.Hrothgar && customize.Gender is Gender.Female) targetClan = targetClan is SubRace.Lost ? SubRace.Seawolf : SubRace.Hellsguard; _customizations.ChangeClan(ref customize, targetClan); } - public void ApplyIndividual(ref CustomizeArray customize) + private void SetGender(ref CustomizeArray customize) { - if (!_codes.EnabledIndividual) + if (!_codes.Enabled(CodeService.CodeFlag.SixtyThree) || customize.Race is Race.Hrothgar) // TODO Female Hrothgar + return; + + _customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male); + } + + private void RandomizeCustomize(ref CustomizeArray customize) + { + if (!_codes.Enabled(CodeService.CodeFlag.Individual)) return; var set = _customizations.Manager.GetSet(customize.Clan, customize.Gender); @@ -208,27 +250,18 @@ public unsafe class FunModule : IDisposable } } - public void Apply63(ref CustomizeArray customize) + private void SetSize(Actor actor, ref CustomizeArray customize) { - if (!_codes.Enabled63 || customize.Race is Race.Hrothgar) // TODO Female Hrothgar - return; - - _customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male); - } - - public void ApplySizing(Actor actor, ref CustomizeArray customize) - { - if (_codes.EnabledSizing == CodeService.Sizing.None) - return; - - var size = _codes.EnabledSizing switch + var size = _codes.Masked(CodeService.SizeCodes) switch { - CodeService.Sizing.Dwarf when actor.Index == 0 => 0, - CodeService.Sizing.Dwarf when actor.Index != 0 => 100, - CodeService.Sizing.Giant when actor.Index == 0 => 100, - CodeService.Sizing.Giant when actor.Index != 0 => 0, - _ => 0, + CodeService.CodeFlag.Dwarf when actor.Index == 0 => (byte)0, + CodeService.CodeFlag.Dwarf => (byte)100, + CodeService.CodeFlag.Giant when actor.Index == 0 => (byte)100, + CodeService.CodeFlag.Giant => (byte)0, + _ => byte.MaxValue, }; + if (size == byte.MaxValue) + return; if (customize.Gender is Gender.Female) customize[CustomizeIndex.BustSize] = (CustomizeValue)size; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 1f3c36b..c4c5fb1 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -133,7 +133,7 @@ public class StateListener : IDisposable _creatingState.TempUnlock(); } - _funModule.ApplyFun(actor, new Span((void*)equipDataPtr, 10), ref customize); + _funModule.ApplyFunOnLoad(actor, new Span((void*)equipDataPtr, 10), ref customize); if (modelId == 0) ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender); } @@ -216,7 +216,7 @@ public class StateListener : IDisposable locked = state[slot, false] is StateChanged.Source.Ipc; } - _funModule.ApplyFun(actor, ref armor.Value, slot); + _funModule.ApplyFunToSlot(actor, ref armor.Value, slot); if (!_config.UseRestrictedGearProtection || locked) return; @@ -315,7 +315,7 @@ public class StateListener : IDisposable _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Value.Skeleton.Id + 50), weapon.Value.Weapon, weapon.Value.Variant, weapon.Value.Stain); - _funModule.ApplyFun(actor, ref weapon.Value, slot); + _funModule.ApplyFunToWeapon(actor, ref weapon.Value, slot); } /// Update base data for a single changed equipment slot. From 4b242bb3cfef576c1c3cd70942ddfa0108a6e8c0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 28 Dec 2023 14:20:59 +0100 Subject: [PATCH 113/786] Change design loading. --- Glamourer/Designs/DesignManager.cs | 51 ++++++++++++++++++++---------- OtterGui | 2 +- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 80cd0e0..b46e4e0 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -1,7 +1,11 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Dalamud.Utility; using Glamourer.Events; using Glamourer.Interop.Penumbra; @@ -18,7 +22,7 @@ namespace Glamourer.Designs; public class DesignManager { - private readonly CustomizeService _customizations; + private readonly CustomizeService _customizations; private readonly ItemManager _items; private readonly HumanModelList _humans; private readonly SaveService _saveService; @@ -48,29 +52,44 @@ public class DesignManager /// public void LoadDesigns() { + _humans.Awaiter.Wait(); + _customizations.Awaiter.Wait(); + _items.ItemData.Awaiter.Wait(); + + var stopwatch = Stopwatch.StartNew(); _designs.Clear(); - List<(Design, string)> invalidNames = new(); - var skipped = 0; - foreach (var file in _saveService.FileNames.Designs()) + var skipped = 0; + ThreadLocal> designs = new(() => [], true); + Parallel.ForEach(_saveService.FileNames.Designs(), (f, _) => { try { - var text = File.ReadAllText(file.FullName); + var text = File.ReadAllText(f.FullName); var data = JObject.Parse(text); var design = Design.LoadDesign(_customizations, _items, data); - if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(file.Name)) - invalidNames.Add((design, file.FullName)); - if (_designs.Any(f => f.Identifier == design.Identifier)) - throw new Exception($"Identifier {design.Identifier} was not unique."); - - design.Index = _designs.Count; - _designs.Add(design); + designs.Value!.Add((design, f.FullName)); } catch (Exception ex) { Glamourer.Log.Error($"Could not load design, skipped:\n{ex}"); - ++skipped; + Interlocked.Increment(ref skipped); } + }); + + List<(Design, string)> invalidNames = []; + foreach (var (design, path) in designs.Values.SelectMany(v => v)) + { + if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(path)) + invalidNames.Add((design, path)); + if (_designs.Any(d => d.Identifier == design.Identifier)) + { + Glamourer.Log.Error($"Could not load design, skipped: Identifier {design.Identifier} was not unique."); + ++skipped; + continue; + } + + design.Index = _designs.Count; + _designs.Add(design); } var failed = MoveInvalidNames(invalidNames); @@ -79,7 +98,7 @@ public class DesignManager $"Moved {invalidNames.Count - failed} designs to correct names.{(failed > 0 ? $" Failed to move {failed} designs to correct names." : string.Empty)}"); Glamourer.Log.Information( - $"Loaded {_designs.Count} designs.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); + $"Loaded {_designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); _event.Invoke(DesignChanged.Type.ReloadedAll, null!); } @@ -191,10 +210,10 @@ public class DesignManager public void ChangeColor(Design design, string newColor) { var oldColor = design.Color; - if (oldColor == newColor) + if (oldColor == newColor) return; - design.Color = newColor; + design.Color = newColor; design.LastEdit = DateTimeOffset.UtcNow; _saveService.QueueSave(design); Glamourer.Log.Debug($"Changed color of design {design.Identifier}."); diff --git a/OtterGui b/OtterGui index 4df65fb..15203ed 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 4df65fb330f3746b7836c39cb96d1e36a53bcec0 +Subproject commit 15203edf1dba72713f508b798048c56ad969fb95 From 0c1dd50890326189447e3012bdd5b2b7ce5f970e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 28 Dec 2023 19:01:28 +0100 Subject: [PATCH 114/786] Add NPC appearance tab. --- Glamourer/Designs/DesignColors.cs | 15 +- Glamourer/Designs/DesignConverter.cs | 19 +- Glamourer/GameData/NpcCustomizeSet.cs | 13 +- Glamourer/Gui/Colors.cs | 6 +- .../CustomizationDrawer.GenderRace.cs | 7 +- Glamourer/Gui/Equipment/EquipDrawData.cs | 1 + Glamourer/Gui/MainWindow.cs | 10 +- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 9 +- .../Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs | 141 +++++++++ Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs | 45 +++ Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 294 ++++++++++++++++++ Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs | 95 ++++++ Glamourer/Gui/Tabs/NpcTab/NpcTab.cs | 19 ++ Glamourer/Gui/ToggleDrawData.cs | 19 ++ Glamourer/Gui/UiHelpers.cs | 16 +- Glamourer/Services/FilenameService.cs | 3 +- Glamourer/Services/ServiceManager.cs | 5 + OtterGui | 2 +- 18 files changed, 684 insertions(+), 35 deletions(-) create mode 100644 Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs create mode 100644 Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs create mode 100644 Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs create mode 100644 Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs create mode 100644 Glamourer/Gui/Tabs/NpcTab/NpcTab.cs diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index dc36e78..2a4f3f7 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -26,9 +26,9 @@ public class DesignColorUi public DesignColorUi(DesignColors colors, DesignManager designs, Configuration config) { - _colors = colors; - _designs = designs; - _config = config; + _colors = colors; + _designs = designs; + _config = config; } public void Draw() @@ -78,7 +78,7 @@ public class DesignColorUi { using var id = ImRaii.PushId(idx); ImGui.TableNextColumn(); - + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, tt, disabled, true)) { changeString = name; @@ -119,7 +119,7 @@ public class DesignColorUi changeString = _newName; changeValue = 0xFFFFFFFF; } - + if (changeString.Length > 0) { @@ -139,6 +139,7 @@ public class DesignColorUi newColor = color; return false; } + ImGuiUtil.HoverTooltip(tooltip); newColor = ImGui.ColorConvertFloat4ToU32(vec); @@ -148,12 +149,12 @@ public class DesignColorUi public class DesignColors : ISavable, IReadOnlyDictionary { - public const string AutomaticName = "Automatic"; + public const string AutomaticName = "Automatic"; public const string MissingColorName = "Missing Color"; public const uint MissingColorDefault = 0xFF0000D0; private readonly SaveService _saveService; - private readonly Dictionary _colors = new(); + private readonly Dictionary _colors = []; public uint MissingColor { get; private set; } = MissingColorDefault; public event Action? ColorChanged; diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index f7867c4..cd8924f 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -39,12 +39,18 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All); public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) + => ShareBase64(state.ModelData, equipFlags, customizeFlags, crestFlags); + + public string ShareBase64(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { - var design = Convert(state, equipFlags, customizeFlags, crestFlags); + var design = Convert(data, equipFlags, customizeFlags, crestFlags); return ShareBase64(ShareJObject(design)); } public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) + => Convert(state.ModelData, equipFlags, customizeFlags, crestFlags); + + public DesignBase Convert(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) { var design = _designs.CreateTemporary(); design.ApplyEquip = equipFlags & EquipFlagExtensions.All; @@ -54,7 +60,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand)); design.SetApplyWetness(true); - design.SetDesignData(_customize, state.ModelData); + design.SetDesignData(_customize, data); return design; } @@ -144,7 +150,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi } public IEnumerable<(EquipSlot Slot, EquipItem Item, StainId Stain)> FromDrawData(IReadOnlyList armors, - CharacterWeapon mainhand, CharacterWeapon offhand) + CharacterWeapon mainhand, CharacterWeapon offhand, bool skipWarnings) { if (armors.Count != 10) throw new ArgumentException("Invalid length of armor array."); @@ -156,7 +162,8 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi var item = _items.Identify(slot, armor.Set, armor.Variant); if (!item.Valid) { - Glamourer.Log.Warning($"Appearance data {armor} for slot {slot} invalid, item could not be identified."); + if (!skipWarnings) + Glamourer.Log.Warning($"Appearance data {armor} for slot {slot} invalid, item could not be identified."); item = ItemManager.NothingItem(slot); } @@ -164,7 +171,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi } var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant); - if (!mh.Valid) + if (!skipWarnings && !mh.Valid) { Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); mh = _items.DefaultSword; @@ -173,7 +180,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi yield return (EquipSlot.MainHand, mh, mainhand.Stain); var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type); - if (!oh.Valid) + if (!skipWarnings && !oh.Valid) { Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified."); oh = _items.GetDefaultOffhand(mh); diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index bcd6c54..d364c1e 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -161,7 +161,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList // Convert the NPCs to a dictionary of lists grouped by name. var groups = eNpcEquip.Concat(bNpcEquip).GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList()); // Iterate through the sorted list. - foreach (var (name, duplicates) in groups.OrderBy(kvp => kvp.Key)) + foreach (var (_, duplicates) in groups.OrderBy(kvp => kvp.Key)) { // Remove any duplicate entries for a name with identical data. for (var i = 0; i < duplicates.Count; ++i) @@ -177,7 +177,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList } } - // If there is only a single entry, add that. This does not take additional string memory through interning. + // If there is only a single entry, add that. if (duplicates.Count == 1) { _data.Add(duplicates[0]); @@ -185,13 +185,8 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList } else { - // Add all distinct duplicates with their ID specified in the name. - _data.AddRange(duplicates - .Select(duplicate => duplicate with - { - Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})", - })); - Memory += 96 * duplicates.Count + duplicates.Sum(d => d.Name.Length * 2); + _data.AddRange(duplicates); + Memory += 96 * duplicates.Count; } } diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index 873ec38..7addc45 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -28,6 +28,8 @@ public enum ColorId TriStateCheck, TriStateCross, TriStateNeutral, + BattleNpc, + EventNpc, } public static class Colors @@ -60,7 +62,9 @@ public static class Colors ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ), ColorId.TriStateCheck => (0xFF00D000, "Checkmark in Tri-State Checkboxes", "The color of the checkmark indicating positive change in tri-state checkboxes." ), ColorId.TriStateCross => (0xFF0000D0, "Cross in Tri-State Checkboxes", "The color of the cross indicating negative change in tri-state checkboxes." ), - ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes" ), + ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes." ), + ColorId.BattleNpc => (0xFFFFFFFF, "Battle NPC in NPC Tab", "The color of the names of battle NPCs in the NPC tab that do not have a more specific color assigned." ), + ColorId.EventNpc => (0xFFFFFFFF, "Event NPC in NPC Tab", "The color of the names of event NPCs in the NPC tab that do not have a more specific color assigned." ), _ => (0x00000000, string.Empty, string.Empty ), // @formatter:on }; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index 4b9fab7..a50424c 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -82,8 +82,11 @@ public partial class CustomizationDrawer if (_customize.BodyType.Value == 1) return; - if (!ImGuiUtil.DrawDisabledButton($"Reset Body Type {_customize.BodyType.Value} to Default", - new Vector2(_raceSelectorWidth + _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X, 0), string.Empty, _lockedRedraw)) + var label = _lockedRedraw + ? $"Body Type {_customize.BodyType.Value}" + : $"Reset Body Type {_customize.BodyType.Value} to Default"; + if (!ImGuiUtil.DrawDisabledButton(label, new Vector2(_raceSelectorWidth + _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X, 0), + string.Empty, _lockedRedraw)) return; Changed |= CustomizeFlag.BodyType; diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 8ea3972..1edd4ce 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -1,6 +1,7 @@ using System; using Glamourer.Designs; using Glamourer.Events; +using Glamourer.Services; using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index a219134..565ef84 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -10,6 +10,7 @@ using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Gui.Tabs.AutomationTab; using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DesignTab; +using Glamourer.Gui.Tabs.NpcTab; using Glamourer.Gui.Tabs.UnlocksTab; using ImGuiNET; using OtterGui.Custom; @@ -29,6 +30,7 @@ public class MainWindow : Window, IDisposable Automation = 4, Unlocks = 5, Messages = 6, + Npcs = 7, } private readonly Configuration _config; @@ -42,12 +44,14 @@ public class MainWindow : Window, IDisposable public readonly DesignTab Designs; public readonly AutomationTab Automation; public readonly UnlocksTab Unlocks; + public readonly NpcTab Npcs; public readonly MessagesTab Messages; public TabType SelectTab = TabType.None; public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs, - DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar) + DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar, + NpcTab npcs) : base(GetLabel()) { pi.UiBuilder.DisableGposeUiHide = true; @@ -65,6 +69,7 @@ public class MainWindow : Window, IDisposable _event = @event; Messages = messages; _quickBar = quickBar; + Npcs = npcs; _config = config; _tabs = [ @@ -73,6 +78,7 @@ public class MainWindow : Window, IDisposable designs, automation, unlocks, + npcs, messages, debugTab, ]; @@ -117,6 +123,7 @@ public class MainWindow : Window, IDisposable TabType.Automation => Automation.Label, TabType.Unlocks => Unlocks.Label, TabType.Messages => Messages.Label, + TabType.Npcs => Npcs.Label, _ => ReadOnlySpan.Empty, }; @@ -128,6 +135,7 @@ public class MainWindow : Window, IDisposable if (label == Settings.Label) return TabType.Settings; if (label == Automation.Label) return TabType.Automation; if (label == Unlocks.Label) return TabType.Unlocks; + if (label == Npcs.Label) return TabType.Npcs; if (label == Messages.Label) return TabType.Messages; if (label == Debug.Label) return TabType.Debug; // @formatter:on diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index e0c7aa8..2ebf215 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -35,7 +35,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); var resetScroll = ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64); - using var table = ImRaii.Table("npcs", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, + using var table = ImRaii.Table("npcs", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, new Vector2(-1, 400 * ImGuiHelpers.GlobalScale)); if (!table) return; @@ -46,6 +46,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300); ImGui.TableSetupColumn("Kind", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Visor", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Compare", ImGuiTableColumnFlags.WidthStretch); @@ -66,7 +67,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.TableNextColumn(); if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled)) { - foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand)) + foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true)) _state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual); _state.ChangeVisorState(state!, data.VisorToggled, StateChanged.Source.Manual); _state.ChangeCustomize(state!, data.Customize, CustomizeFlagExtensions.All, StateChanged.Source.Manual); @@ -80,6 +81,10 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(data.Kind is ObjectKind.BattleNpc ? "B" : "E"); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(data.Id.Id.ToString()); + using (_ = ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TableNextColumn(); diff --git a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs new file mode 100644 index 0000000..bb22a85 --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.IO; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.Designs; +using Glamourer.GameData; +using Glamourer.Services; +using ImGuiNET; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Glamourer.Gui.Tabs.NpcTab; + +public class LocalNpcAppearanceData : ISavable +{ + private readonly DesignColors _colors; + + public record struct Data(string Color = "", bool Favorite = false); + + private readonly Dictionary _data = []; + + public LocalNpcAppearanceData(DesignColors colors, SaveService saveService) + { + _colors = colors; + Load(saveService); + DataChanged += () => saveService.QueueSave(this); + } + + public bool IsFavorite(in NpcData data) + => _data.TryGetValue(ToKey(data), out var tuple) && tuple.Favorite; + + public (uint Color, bool Favorite) GetData(in NpcData data) + => _data.TryGetValue(ToKey(data), out var t) + ? (GetColor(t.Color, t.Favorite, data.Kind), t.Favorite) + : (GetColor(string.Empty, false, data.Kind), false); + + public string GetColor(in NpcData data) + => _data.TryGetValue(ToKey(data), out var t) ? t.Color : string.Empty; + + private uint GetColor(string color, bool favorite, ObjectKind kind) + { + if (color.Length == 0) + { + if (favorite) + return ColorId.FavoriteStarOn.Value(); + + return kind is ObjectKind.BattleNpc + ? ColorId.BattleNpc.Value() + : ColorId.EventNpc.Value(); + } + + if (_colors.TryGetValue(color, out var value)) + return value == 0 ? ImGui.GetColorU32(ImGuiCol.Text) : value; + + return _colors.MissingColor; + } + + public void ToggleFavorite(in NpcData data) + { + var key = ToKey(data); + if (_data.TryGetValue(key, out var t)) + { + if (t is { Color: "", Favorite: true }) + _data.Remove(key); + else + _data[key] = t with { Favorite = !t.Favorite }; + } + else + { + _data[key] = new Data(string.Empty, true); + } + + DataChanged.Invoke(); + } + + public void SetColor(in NpcData data, string color) + { + var key = ToKey(data); + if (_data.TryGetValue(key, out var t)) + { + if (!t.Favorite && color.Length == 0) + _data.Remove(key); + else + _data[key] = t with { Color = color }; + } + else if (color.Length != 0) + { + _data[key] = new Data(color); + } + + DataChanged.Invoke(); + } + + private static ulong ToKey(in NpcData data) + => (byte)data.Kind | ((ulong)data.Id.Id << 8); + + public event Action DataChanged = null!; + + public string ToFilename(FilenameService fileNames) + => fileNames.NpcAppearanceFile; + + public void Save(StreamWriter writer) + { + var jObj = new JObject() + { + ["Version"] = 1, + ["Data"] = JToken.FromObject(_data), + }; + using var j = new JsonTextWriter(writer); + j.Formatting = Formatting.Indented; + jObj.WriteTo(j); + } + + private void Load(SaveService save) + { + var file = save.FileNames.NpcAppearanceFile; + if (!File.Exists(file)) + return; + + try + { + var text = File.ReadAllText(file); + var jObj = JObject.Parse(text); + var version = jObj["Version"]?.ToObject() ?? 0; + switch (version) + { + case 1: + var data = jObj["Data"]?.ToObject>() ?? []; + _data.EnsureCapacity(data.Count); + foreach (var kvp in data) + _data.Add(kvp.Key, kvp.Value); + return; + default: throw new Exception("Invalid version {version}."); + } + } + catch (Exception ex) + { + Glamourer.Log.Error($"Could not read local NPC appearance data:\n{ex}"); + } + } +} diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs b/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs new file mode 100644 index 0000000..f57985b --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs @@ -0,0 +1,45 @@ +using System; +using Glamourer.Designs; +using Glamourer.GameData; +using OtterGui.Classes; + +namespace Glamourer.Gui.Tabs.NpcTab; + +public sealed class NpcFilter(LocalNpcAppearanceData _favorites) : FilterUtility +{ + protected override string Tooltip + => "Filter NPC appearances for those where their names contain the given substring.\n" + + "Enter i:[number] to filter for NPCs of certain IDs.\n" + + "Enter c:[string] to filter for NPC appearances set to specific colors."; + + protected override (LowerString, long, int) FilterChange(string input) + => input.Length switch + { + 0 => (LowerString.Empty, 0, -1), + > 1 when input[1] == ':' => + input[0] switch + { + 'i' or 'I' => input.Length == 2 ? (LowerString.Empty, 0, -1) : + long.TryParse(input.AsSpan(2), out var r) ? (LowerString.Empty, r, 1) : (LowerString.Empty, 0, -1), + 'c' or 'C' => input.Length == 2 ? (LowerString.Empty, 0, -1) : (new LowerString(input[2..]), 0, 2), + _ => (new LowerString(input), 0, 0), + }, + _ => (new LowerString(input), 0, 0), + }; + + public override bool ApplyFilter(in NpcData value) + => FilterMode switch + { + -1 => false, + 0 => Filter.IsContained(value.Name), + 1 => value.Id.Id == NumericalFilter, + 2 => Filter.IsContained(GetColor(value)), + _ => false, // Should never happen + }; + + private string GetColor(in NpcData value) + { + var color = _favorites.GetColor(value); + return color.Length == 0 ? DesignColors.AutomaticName : color; + } +} diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs new file mode 100644 index 0000000..cd88f97 --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -0,0 +1,294 @@ +using System; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Internal.Notifications; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.Gui.Customization; +using Glamourer.Gui.Equipment; +using Glamourer.Gui.Tabs.DesignTab; +using Glamourer.Interop; +using Glamourer.State; +using ImGuiNET; +using Lumina.Data.Parsing.Scd; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Raii; +using Penumbra.GameData.Enums; + +namespace Glamourer.Gui.Tabs.NpcTab; + +public class NpcPanel( + NpcSelector _selector, + LocalNpcAppearanceData _favorites, + CustomizationDrawer _customizeDrawer, + EquipmentDrawer _equipDrawer, + DesignConverter _converter, + DesignManager _designManager, + StateManager _state, + ObjectManager _objects, + DesignColors _colors) +{ + private readonly DesignColorCombo _colorCombo = new(_colors, true); + private string _newName = string.Empty; + private DesignBase? _newDesign; + + public void Draw() + { + using var group = ImRaii.Group(); + + DrawHeader(); + DrawPanel(); + } + + private void DrawHeader() + { + HeaderDrawer.Draw(_selector.HasSelection ? _selector.Selection.Name : "No Selection", ColorId.NormalDesign.Value(), + ImGui.GetColorU32(ImGuiCol.FrameBg), 2, ExportToClipboardButton(), SaveAsDesignButton(), FavoriteButton()); + SaveDesignDrawPopup(); + } + + private HeaderDrawer.Button FavoriteButton() + { + var (desc, color) = _favorites.IsFavorite(_selector.Selection) + ? ("Remove this NPC appearance from your favorites.", ColorId.FavoriteStarOn.Value()) + : ("Add this NPC Appearance to your favorites.", 0x80000000); + return new HeaderDrawer.Button + { + Icon = FontAwesomeIcon.Star, + OnClick = () => _favorites.ToggleFavorite(_selector.Selection), + Visible = _selector.HasSelection, + Description = desc, + TextColor = color, + }; + } + + private HeaderDrawer.Button ExportToClipboardButton() + => new() + { + Description = + "Copy the current NPCs appearance to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design.", + Icon = FontAwesomeIcon.Copy, + OnClick = ExportToClipboard, + Visible = _selector.HasSelection, + }; + + private HeaderDrawer.Button SaveAsDesignButton() + => new() + { + Description = + "Save this NPCs appearance as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design.", + Icon = FontAwesomeIcon.Save, + OnClick = SaveDesignOpen, + Visible = _selector.HasSelection, + }; + + private void ExportToClipboard() + { + try + { + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + var data = ToDesignData(); + var text = _converter.ShareBase64(data, applyGear, applyCustomize, applyCrest); + ImGui.SetClipboardText(text); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not copy {_selector.Selection.Name}'s data to clipboard.", + $"Could not copy data from NPC appearance {_selector.Selection.Kind} {_selector.Selection.Id.Id} to clipboard", + NotificationType.Error); + } + } + + private void SaveDesignOpen() + { + ImGui.OpenPopup("Save as Design"); + _newName = _selector.Selection.Name; + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + + var data = ToDesignData(); + _newDesign = _converter.Convert(data, applyGear, applyCustomize, applyCrest); + } + + private void SaveDesignDrawPopup() + { + if (!ImGuiUtil.OpenNameField("Save as Design", ref _newName)) + return; + + if (_newDesign != null && _newName.Length > 0) + _designManager.CreateClone(_newDesign, _newName, true); + _newDesign = null; + _newName = string.Empty; + } + + private void DrawPanel() + { + using var child = ImRaii.Child("##Panel", -Vector2.One, true); + if (!child || !_selector.HasSelection) + return; + + DrawButtonRow(); + DrawCustomization(); + DrawEquipment(); + DrawAppearanceInfo(); + } + + private void DrawButtonRow() + { + DrawApplyToSelf(); + ImGui.SameLine(); + DrawApplyToTarget(); + } + + private void DrawCustomization() + { + if (!ImGui.CollapsingHeader("Customization")) + return; + + _customizeDrawer.Draw(_selector.Selection.Customize, true, true); + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + } + + private void DrawEquipment() + { + if (!ImGui.CollapsingHeader("Equipment")) + return; + + _equipDrawer.Prepare(); + var designData = ToDesignData(); + + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var data = new EquipDrawData(slot, designData) { Locked = true }; + _equipDrawer.DrawEquip(data); + } + + var mainhandData = new EquipDrawData(EquipSlot.MainHand, designData) { Locked = true }; + var offhandData = new EquipDrawData(EquipSlot.OffHand, designData) { Locked = true }; + _equipDrawer.DrawWeapons(mainhandData, offhandData, false); + + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(ActorState.MetaIndex.VisorState, _selector.Selection.VisorToggled)); + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + } + + private DesignData ToDesignData() + { + var selection = _selector.Selection; + var items = _converter.FromDrawData(selection.Equip.ToArray(), selection.Mainhand, selection.Offhand, true).ToArray(); + var designData = new DesignData { Customize = selection.Customize }; + foreach (var (slot, item, stain) in items) + { + designData.SetItem(slot, item); + designData.SetStain(slot, stain); + } + + return designData; + } + + private void DrawApplyToSelf() + { + var (id, data) = _objects.PlayerData; + if (!ImGuiUtil.DrawDisabledButton("Apply to Yourself", Vector2.Zero, + "Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations.", + !data.Valid)) + return; + + if (_state.GetOrCreate(id, data.Objects[0], out var state)) + { + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest); + _state.ApplyDesign(design, state, StateChanged.Source.Manual); + } + } + + private void DrawApplyToTarget() + { + var (id, data) = _objects.TargetData; + var tt = id.IsValid + ? data.Valid + ? "Apply the current NPC appearance to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations." + : "The current target can not be manipulated." + : "No valid target selected."; + if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, !data.Valid)) + return; + + if (_state.GetOrCreate(id, data.Objects[0], out var state)) + { + var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest); + _state.ApplyDesign(design, state, StateChanged.Source.Manual); + } + } + + + private void DrawAppearanceInfo() + { + if (!ImGui.CollapsingHeader("Appearance Details")) + return; + + using var table = ImRaii.Table("Details", 2); + if (!table) + return; + + using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); + ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Last Update Datem").X); + ImGui.TableSetupColumn("Data", ImGuiTableColumnFlags.WidthStretch); + + var selection = _selector.Selection; + CopyButton("NPC Name", selection.Name); + CopyButton("NPC ID", selection.Id.Id.ToString()); + ImGuiUtil.DrawFrameColumn("NPC Type"); + ImGui.TableNextColumn(); + var width = ImGui.GetContentRegionAvail().X; + ImGuiUtil.DrawTextButton(selection.Kind is ObjectKind.BattleNpc ? "Battle NPC" : "Event NPC", new Vector2(width, 0), + ImGui.GetColorU32(ImGuiCol.FrameBg)); + + ImGuiUtil.DrawFrameColumn("Color"); + var color = _favorites.GetColor(selection); + var colorName = color.Length == 0 ? DesignColors.AutomaticName : color; + ImGui.TableNextColumn(); + if (_colorCombo.Draw("##colorCombo", colorName, + "Associate a color with this NPC appearance. Right-Click to revert to automatic coloring.", + width - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight()) + && _colorCombo.CurrentSelection != null) + { + color = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection; + _favorites.SetColor(selection, color); + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _favorites.SetColor(selection, string.Empty); + color = string.Empty; + } + + if (_colors.TryGetValue(color, out var currentColor)) + { + ImGui.SameLine(); + if (DesignColorUi.DrawColorButton($"Color associated with {color}", currentColor, out var newColor)) + _colors.SetColor(color, newColor); + } + else if (color.Length != 0) + { + ImGui.SameLine(); + var size = new Vector2(ImGui.GetFrameHeight()); + using var font = ImRaii.PushFont(UiBuilder.IconFont); + ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor); + ImGuiUtil.HoverTooltip("The color associated with this design does not exist."); + } + + return; + + static void CopyButton(string label, string text) + { + ImGuiUtil.DrawFrameColumn(label); + ImGui.TableNextColumn(); + if (ImGui.Button(text, new Vector2(ImGui.GetContentRegionAvail().X, 0))) + ImGui.SetClipboardText(text); + ImGuiUtil.HoverTooltip("Click to copy to clipboard."); + } + } +} diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs new file mode 100644 index 0000000..10d2264 --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Glamourer.GameData; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using ImGuiClip = OtterGui.ImGuiClip; + +namespace Glamourer.Gui.Tabs.NpcTab; + +public class NpcSelector : IDisposable +{ + private readonly NpcCustomizeSet _npcs; + private readonly LocalNpcAppearanceData _favorites; + + private NpcFilter _filter; + private readonly List _visibleOrdered = []; + private int _selectedGlobalIndex; + private bool _listDirty = true; + private Vector2 _defaultItemSpacing; + private float _width; + + + public NpcSelector(NpcCustomizeSet npcs, LocalNpcAppearanceData favorites) + { + _npcs = npcs; + _favorites = favorites; + _filter = new NpcFilter(_favorites); + _favorites.DataChanged += OnFavoriteChange; + } + + public void Dispose() + { + _favorites.DataChanged -= OnFavoriteChange; + } + + private void OnFavoriteChange() + => _listDirty = true; + + public void UpdateList() + { + if (!_listDirty) + return; + + _listDirty = false; + _visibleOrdered.Clear(); + var enumerable = _npcs.WithIndex(); + if (!_filter.IsEmpty) + enumerable = enumerable.Where(d => _filter.ApplyFilter(d.Value)); + var range = enumerable.OrderByDescending(d => _favorites.IsFavorite(d.Value)) + .ThenBy(d => d.Index) + .Select(d => d.Index); + _visibleOrdered.AddRange(range); + } + + public bool HasSelection + => _selectedGlobalIndex >= 0 && _selectedGlobalIndex < _npcs.Count; + + public NpcData Selection + => HasSelection ? _npcs[_selectedGlobalIndex] : default; + + public void Draw(float width) + { + _width = width; + using var group = ImRaii.Group(); + _defaultItemSpacing = ImGui.GetStyle().ItemSpacing; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) + .Push(ImGuiStyleVar.FrameRounding, 0); + + if (_filter.Draw(width)) + _listDirty = true; + UpdateList(); + DrawSelector(); + } + + private void DrawSelector() + { + using var child = ImRaii.Child("##Selector", new Vector2(_width, ImGui.GetContentRegionAvail().Y), true); + if (!child) + return; + + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); + ImGuiClip.ClippedDraw(_visibleOrdered, DrawSelectable, ImGui.GetTextLineHeight()); + } + + private void DrawSelectable(int globalIndex) + { + using var id = ImRaii.PushId(globalIndex); + using var color = ImRaii.PushColor(ImGuiCol.Text, _favorites.GetData(_npcs[globalIndex]).Color); + if (ImGui.Selectable(_npcs[globalIndex].Name, _selectedGlobalIndex == globalIndex, ImGuiSelectableFlags.AllowItemOverlap)) + _selectedGlobalIndex = globalIndex; + } +} diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs new file mode 100644 index 0000000..fb64227 --- /dev/null +++ b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs @@ -0,0 +1,19 @@ +using System; +using Dalamud.Interface.Utility; +using ImGuiNET; +using OtterGui.Widgets; + +namespace Glamourer.Gui.Tabs.NpcTab; + +public class NpcTab(NpcSelector _selector, NpcPanel _panel) : ITab +{ + public ReadOnlySpan Label + => "NPCs"u8; + + public void DrawContent() + { + _selector.Draw(200 * ImGuiHelpers.GlobalScale); + ImGui.SameLine(); + _panel.Draw(); + } +} diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index 5e7e813..3991893 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -100,4 +100,23 @@ public ref struct ToggleDrawData SetValue = setValue, }; } + + public static ToggleDrawData FromValue(ActorState.MetaIndex index, bool value) + { + var (label, tooltip) = index switch + { + ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear."), + ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear."), + ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn."), + ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not."), + _ => throw new Exception("Unsupported meta index."), + }; + return new ToggleDrawData + { + Label = label, + Tooltip = tooltip, + Locked = true, + CurrentValue = value, + }; + } } diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index d08fb18..2bc11c4 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -47,9 +47,15 @@ public static class UiHelpers public static bool DrawCheckbox(string label, string tooltip, bool value, out bool on, bool locked) { - using var disabled = ImRaii.Disabled(locked); - var ret = ImGuiUtil.Checkbox(label, string.Empty, value, v => value = v); - ImGuiUtil.HoverTooltip(tooltip); + bool ret; + using (var disabled = ImRaii.Disabled(locked)) + { + ret = ImGuiUtil.Checkbox("##" + label, string.Empty, value, v => value = v); + } + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(label); + ImGuiUtil.HoverTooltip(tooltip, ImGuiHoveredFlags.AllowWhenDisabled); on = value; return ret; } @@ -58,7 +64,6 @@ public static class UiHelpers out bool newApply, bool locked) { var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0); - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); using (_ = ImRaii.Disabled(locked)) { if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw( @@ -80,7 +85,8 @@ public static class UiHelpers ImGuiUtil.HoverTooltip($"This attribute will be {(currentApply ? currentValue ? "enabled." : "disabled." : "kept as is.")}"); - ImGui.SameLine(); + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(label); return (currentValue != newValue, currentApply != newApply); diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index 2ca573f..7f54f19 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -18,6 +18,7 @@ public class FilenameService public readonly string FavoriteFile; public readonly string DesignColorFile; public readonly string EphemeralConfigFile; + public readonly string NpcAppearanceFile; public FilenameService(DalamudPluginInterface pi) { @@ -32,9 +33,9 @@ public class FilenameService FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json"); DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json"); EphemeralConfigFile = Path.Combine(ConfigDirectory, "ephemeral_config.json"); + NpcAppearanceFile = Path.Combine(ConfigDirectory, "npc_appearance_data.json"); } - public IEnumerable Designs() { if (!Directory.Exists(DesignDirectory)) diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 9cbcd01..db3922c 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -11,6 +11,7 @@ using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Gui.Tabs.AutomationTab; using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DesignTab; +using Glamourer.Gui.Tabs.NpcTab; using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -131,6 +132,10 @@ public static class ServiceManagerA .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/OtterGui b/OtterGui index 15203ed..e58c3c1 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 15203edf1dba72713f508b798048c56ad969fb95 +Subproject commit e58c3c1240cda9d2d2b54f5ab7b8c729c1251fd4 From 29799094eaea92d9bee0676e2a07bf74a63fb7ca Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 28 Dec 2023 19:34:24 +0100 Subject: [PATCH 115/786] Adjust world weights. --- Glamourer/State/WorldSets.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Glamourer/State/WorldSets.cs b/Glamourer/State/WorldSets.cs index 4464aee..4590eec 100644 --- a/Glamourer/State/WorldSets.cs +++ b/Glamourer/State/WorldSets.cs @@ -302,12 +302,12 @@ public class WorldSets private (FunEquipSet.Group, CharacterWeapon, CharacterWeapon)? GetGroup(byte level, byte job, Race race, Gender gender, Random rng) { - const int weight50 = 1200; - const int weight60 = 1500; - const int weight70 = 1700; - const int weight80 = 1800; - const int weight90 = 1900; - const int weight100 = 2000; + const int weight50 = 200; + const int weight60 = 500; + const int weight70 = 700; + const int weight80 = 800; + const int weight90 = 900; + const int weight100 = 1000; if (job >= StarterWeapons.Length) return null; From f4dfe8e89cab420570f3717ca46c36cce0bf523b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 29 Dec 2023 00:12:14 +0100 Subject: [PATCH 116/786] Add Crown Code. --- Glamourer/Interop/Structs/Actor.cs | 17 +++++++++++++ Glamourer/Services/CodeService.cs | 41 ++++++++++++++++-------------- Glamourer/State/FunModule.cs | 27 +++++++++++++++++++- 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 066d985..78dd9fa 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -123,4 +123,21 @@ public readonly unsafe struct Actor : IEquatable public override string ToString() => $"0x{Address:X}"; + + public OnlineStatus OnlineStatus + => (OnlineStatus)AsCharacter->CharacterData.OnlineStatus; +} + +public enum OnlineStatus : byte +{ + Normal = 0x00, + Mentor = 0x1B, + PvEMentor = 0x1C, + TradeMentor = 0x1D, + PvPMentor = 0x1E, + Busy = 0x0C, + Away = 0x11, + MeldMateria = 0x15, + RolePlaying = 0x16, + LookingForGroup = 0x17, } diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 82a6477..7bc5f04 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -33,6 +33,7 @@ public class CodeService Shirts = 0x008000, World = 0x010000, Elephants = 0x020000, + Crown = 0x040000, } public const CodeFlag DyeCodes = CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants; @@ -176,30 +177,32 @@ public class CodeService CodeFlag.Shirts => 0, CodeFlag.World => (DyeCodes | GearCodes) & ~CodeFlag.World, CodeFlag.Elephants => (DyeCodes | GearCodes) & ~CodeFlag.Elephants, + CodeFlag.Crown => 0, _ => 0, }; private static ReadOnlySpan GetSha(CodeFlag flag) => flag switch { - CodeFlag.Clown => [ 0xC4, 0xEE, 0x1D, 0x6F, 0xC5, 0x5D, 0x47, 0xBE, 0x78, 0x63, 0x66, 0x86, 0x81, 0x15, 0xEB, 0xFA, 0xF6, 0x4A, 0x90, 0xEA, 0xC0, 0xE4, 0xEE, 0x86, 0x69, 0x01, 0x8E, 0xDB, 0xCC, 0x69, 0xD1, 0xBD ], - CodeFlag.Emperor => [ 0xE2, 0x2D, 0x3E, 0x57, 0x16, 0x82, 0x65, 0x98, 0x7E, 0xE6, 0x8F, 0x45, 0x14, 0x7D, 0x65, 0x31, 0xE9, 0xD8, 0xDB, 0xEA, 0xDC, 0xBF, 0xEE, 0x2A, 0xBA, 0xD5, 0x69, 0x96, 0x78, 0x34, 0x3B, 0x57 ], - CodeFlag.Individual => [ 0x95, 0xA4, 0x71, 0xAC, 0xA3, 0xC2, 0x34, 0x94, 0xC1, 0x65, 0x07, 0xF3, 0x7F, 0x93, 0x57, 0xEE, 0xE3, 0x04, 0xC0, 0xE8, 0x1B, 0xA0, 0xE2, 0x08, 0x68, 0x02, 0x8D, 0xAD, 0x76, 0x03, 0x9B, 0xC5 ], - CodeFlag.Dwarf => [ 0x55, 0x97, 0xFE, 0xE9, 0x78, 0x64, 0xE8, 0x2F, 0xCD, 0x25, 0xD1, 0xAE, 0xDF, 0x35, 0xE6, 0xED, 0x03, 0x78, 0x54, 0x1D, 0x56, 0x22, 0x34, 0x75, 0x4B, 0x96, 0x6F, 0xBA, 0xAC, 0xEC, 0x00, 0x46 ], - CodeFlag.Giant => [ 0x6E, 0xBB, 0x91, 0x1D, 0x67, 0xE3, 0x00, 0x07, 0xA1, 0x0F, 0x2A, 0xF0, 0x26, 0x91, 0x38, 0x63, 0xD3, 0x52, 0x82, 0xF7, 0x5D, 0x93, 0xE8, 0x83, 0xB1, 0xF6, 0xB9, 0x69, 0x78, 0x20, 0xC4, 0xCE ], - CodeFlag.OopsHyur => [ 0x4C, 0x51, 0xE2, 0x38, 0xEF, 0xAD, 0x84, 0x0E, 0x4E, 0x11, 0x0F, 0x5E, 0xDE, 0x45, 0x41, 0x9F, 0x6A, 0xF6, 0x5F, 0x5B, 0xA8, 0x91, 0x64, 0x22, 0xEE, 0x62, 0x97, 0x3C, 0x78, 0x18, 0xCD, 0xAF ], - CodeFlag.OopsElezen => [ 0x3D, 0x5B, 0xA9, 0x62, 0xCE, 0xBE, 0x52, 0xF5, 0x94, 0x2A, 0xF9, 0xB7, 0xCF, 0xD9, 0x24, 0x2B, 0x38, 0xC7, 0x4F, 0x28, 0x97, 0x29, 0x1D, 0x01, 0x13, 0x53, 0x44, 0x11, 0x15, 0x6F, 0x9B, 0x56 ], - CodeFlag.OopsLalafell => [ 0x85, 0x8D, 0x5B, 0xC2, 0x66, 0x53, 0x2E, 0xB9, 0xE9, 0x85, 0xE5, 0xF8, 0xD3, 0x75, 0x18, 0x7C, 0x58, 0x55, 0xD4, 0x8C, 0x8E, 0x5F, 0x58, 0x2E, 0xF3, 0xF1, 0xAE, 0xA8, 0xA0, 0x81, 0xC6, 0x0E ], - CodeFlag.OopsMiqote => [ 0x44, 0x73, 0x8C, 0x39, 0x5A, 0xF1, 0xDB, 0x5F, 0x62, 0xA1, 0x6E, 0x5F, 0xE6, 0x97, 0x9E, 0x90, 0xD7, 0x5C, 0x97, 0x67, 0xB6, 0xC7, 0x99, 0x61, 0x36, 0xCA, 0x34, 0x7E, 0xB9, 0xAC, 0xC3, 0x76 ], - CodeFlag.OopsRoegadyn => [ 0xB7, 0x25, 0x73, 0xDB, 0xBE, 0xD0, 0x49, 0xFB, 0xFF, 0x9C, 0x32, 0x21, 0xB0, 0x8A, 0x2C, 0x0C, 0x77, 0x46, 0xD5, 0xCF, 0x0E, 0x63, 0x2F, 0x91, 0x85, 0x8B, 0x55, 0x5C, 0x4D, 0xD2, 0xB9, 0xB8 ], - CodeFlag.OopsAuRa => [ 0x69, 0x93, 0xAF, 0xE4, 0xB8, 0xEC, 0x5F, 0x40, 0xEB, 0x8A, 0x6F, 0xD1, 0x9B, 0xD9, 0x56, 0x0B, 0xEA, 0x64, 0x79, 0x9B, 0x54, 0xA1, 0x41, 0xED, 0xBC, 0x3E, 0x6E, 0x5C, 0xF1, 0x23, 0x60, 0xF8 ], - CodeFlag.OopsHrothgar => [ 0x41, 0xEC, 0x65, 0x05, 0x8D, 0x20, 0x68, 0x5A, 0xB7, 0xEB, 0x92, 0x15, 0x43, 0xCF, 0x15, 0x05, 0x27, 0x51, 0xFE, 0x20, 0xC9, 0xB6, 0x2B, 0x84, 0xD9, 0x6A, 0x49, 0x5A, 0x5B, 0x7F, 0x2E, 0xE7 ], - CodeFlag.OopsViera => [ 0x16, 0xFF, 0x63, 0x85, 0x1C, 0xF5, 0x34, 0x33, 0x67, 0x8C, 0x46, 0x8E, 0x3E, 0xE3, 0xA6, 0x94, 0xF9, 0x74, 0x47, 0xAA, 0xC7, 0x29, 0x59, 0x1F, 0x6C, 0x6E, 0xF2, 0xF5, 0x87, 0x24, 0x9E, 0x2B ], - CodeFlag.Artisan => [ 0xDE, 0x01, 0x32, 0x1E, 0x7F, 0x22, 0x80, 0x3D, 0x76, 0xDF, 0x74, 0x0E, 0xEC, 0x33, 0xD3, 0xF4, 0x1A, 0x98, 0x9E, 0x9D, 0x22, 0x5C, 0xAC, 0x3B, 0xFE, 0x0B, 0xC2, 0x13, 0xB9, 0x91, 0x24, 0x61 ], - CodeFlag.SixtyThree => [ 0xA1, 0x65, 0x60, 0x99, 0xB0, 0x9F, 0xBF, 0xD7, 0x20, 0xC8, 0x29, 0xF6, 0x7B, 0x86, 0x27, 0xF5, 0xBE, 0xA9, 0x5B, 0xB0, 0x20, 0x5E, 0x57, 0x7B, 0xFF, 0xBC, 0x1E, 0x8C, 0x04, 0xF9, 0x35, 0xD3 ], - CodeFlag.Shirts => [ 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB ], - CodeFlag.World => [ 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 ], - CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ], - _ => [], + CodeFlag.Clown => [ 0xC4, 0xEE, 0x1D, 0x6F, 0xC5, 0x5D, 0x47, 0xBE, 0x78, 0x63, 0x66, 0x86, 0x81, 0x15, 0xEB, 0xFA, 0xF6, 0x4A, 0x90, 0xEA, 0xC0, 0xE4, 0xEE, 0x86, 0x69, 0x01, 0x8E, 0xDB, 0xCC, 0x69, 0xD1, 0xBD ], + CodeFlag.Emperor => [ 0xE2, 0x2D, 0x3E, 0x57, 0x16, 0x82, 0x65, 0x98, 0x7E, 0xE6, 0x8F, 0x45, 0x14, 0x7D, 0x65, 0x31, 0xE9, 0xD8, 0xDB, 0xEA, 0xDC, 0xBF, 0xEE, 0x2A, 0xBA, 0xD5, 0x69, 0x96, 0x78, 0x34, 0x3B, 0x57 ], + CodeFlag.Individual => [ 0x95, 0xA4, 0x71, 0xAC, 0xA3, 0xC2, 0x34, 0x94, 0xC1, 0x65, 0x07, 0xF3, 0x7F, 0x93, 0x57, 0xEE, 0xE3, 0x04, 0xC0, 0xE8, 0x1B, 0xA0, 0xE2, 0x08, 0x68, 0x02, 0x8D, 0xAD, 0x76, 0x03, 0x9B, 0xC5 ], + CodeFlag.Dwarf => [ 0x55, 0x97, 0xFE, 0xE9, 0x78, 0x64, 0xE8, 0x2F, 0xCD, 0x25, 0xD1, 0xAE, 0xDF, 0x35, 0xE6, 0xED, 0x03, 0x78, 0x54, 0x1D, 0x56, 0x22, 0x34, 0x75, 0x4B, 0x96, 0x6F, 0xBA, 0xAC, 0xEC, 0x00, 0x46 ], + CodeFlag.Giant => [ 0x6E, 0xBB, 0x91, 0x1D, 0x67, 0xE3, 0x00, 0x07, 0xA1, 0x0F, 0x2A, 0xF0, 0x26, 0x91, 0x38, 0x63, 0xD3, 0x52, 0x82, 0xF7, 0x5D, 0x93, 0xE8, 0x83, 0xB1, 0xF6, 0xB9, 0x69, 0x78, 0x20, 0xC4, 0xCE ], + CodeFlag.OopsHyur => [ 0x4C, 0x51, 0xE2, 0x38, 0xEF, 0xAD, 0x84, 0x0E, 0x4E, 0x11, 0x0F, 0x5E, 0xDE, 0x45, 0x41, 0x9F, 0x6A, 0xF6, 0x5F, 0x5B, 0xA8, 0x91, 0x64, 0x22, 0xEE, 0x62, 0x97, 0x3C, 0x78, 0x18, 0xCD, 0xAF ], + CodeFlag.OopsElezen => [ 0x3D, 0x5B, 0xA9, 0x62, 0xCE, 0xBE, 0x52, 0xF5, 0x94, 0x2A, 0xF9, 0xB7, 0xCF, 0xD9, 0x24, 0x2B, 0x38, 0xC7, 0x4F, 0x28, 0x97, 0x29, 0x1D, 0x01, 0x13, 0x53, 0x44, 0x11, 0x15, 0x6F, 0x9B, 0x56 ], + CodeFlag.OopsLalafell => [ 0x85, 0x8D, 0x5B, 0xC2, 0x66, 0x53, 0x2E, 0xB9, 0xE9, 0x85, 0xE5, 0xF8, 0xD3, 0x75, 0x18, 0x7C, 0x58, 0x55, 0xD4, 0x8C, 0x8E, 0x5F, 0x58, 0x2E, 0xF3, 0xF1, 0xAE, 0xA8, 0xA0, 0x81, 0xC6, 0x0E ], + CodeFlag.OopsMiqote => [ 0x44, 0x73, 0x8C, 0x39, 0x5A, 0xF1, 0xDB, 0x5F, 0x62, 0xA1, 0x6E, 0x5F, 0xE6, 0x97, 0x9E, 0x90, 0xD7, 0x5C, 0x97, 0x67, 0xB6, 0xC7, 0x99, 0x61, 0x36, 0xCA, 0x34, 0x7E, 0xB9, 0xAC, 0xC3, 0x76 ], + CodeFlag.OopsRoegadyn => [ 0xB7, 0x25, 0x73, 0xDB, 0xBE, 0xD0, 0x49, 0xFB, 0xFF, 0x9C, 0x32, 0x21, 0xB0, 0x8A, 0x2C, 0x0C, 0x77, 0x46, 0xD5, 0xCF, 0x0E, 0x63, 0x2F, 0x91, 0x85, 0x8B, 0x55, 0x5C, 0x4D, 0xD2, 0xB9, 0xB8 ], + CodeFlag.OopsAuRa => [ 0x69, 0x93, 0xAF, 0xE4, 0xB8, 0xEC, 0x5F, 0x40, 0xEB, 0x8A, 0x6F, 0xD1, 0x9B, 0xD9, 0x56, 0x0B, 0xEA, 0x64, 0x79, 0x9B, 0x54, 0xA1, 0x41, 0xED, 0xBC, 0x3E, 0x6E, 0x5C, 0xF1, 0x23, 0x60, 0xF8 ], + CodeFlag.OopsHrothgar => [ 0x41, 0xEC, 0x65, 0x05, 0x8D, 0x20, 0x68, 0x5A, 0xB7, 0xEB, 0x92, 0x15, 0x43, 0xCF, 0x15, 0x05, 0x27, 0x51, 0xFE, 0x20, 0xC9, 0xB6, 0x2B, 0x84, 0xD9, 0x6A, 0x49, 0x5A, 0x5B, 0x7F, 0x2E, 0xE7 ], + CodeFlag.OopsViera => [ 0x16, 0xFF, 0x63, 0x85, 0x1C, 0xF5, 0x34, 0x33, 0x67, 0x8C, 0x46, 0x8E, 0x3E, 0xE3, 0xA6, 0x94, 0xF9, 0x74, 0x47, 0xAA, 0xC7, 0x29, 0x59, 0x1F, 0x6C, 0x6E, 0xF2, 0xF5, 0x87, 0x24, 0x9E, 0x2B ], + CodeFlag.Artisan => [ 0xDE, 0x01, 0x32, 0x1E, 0x7F, 0x22, 0x80, 0x3D, 0x76, 0xDF, 0x74, 0x0E, 0xEC, 0x33, 0xD3, 0xF4, 0x1A, 0x98, 0x9E, 0x9D, 0x22, 0x5C, 0xAC, 0x3B, 0xFE, 0x0B, 0xC2, 0x13, 0xB9, 0x91, 0x24, 0x61 ], + CodeFlag.SixtyThree => [ 0xA1, 0x65, 0x60, 0x99, 0xB0, 0x9F, 0xBF, 0xD7, 0x20, 0xC8, 0x29, 0xF6, 0x7B, 0x86, 0x27, 0xF5, 0xBE, 0xA9, 0x5B, 0xB0, 0x20, 0x5E, 0x57, 0x7B, 0xFF, 0xBC, 0x1E, 0x8C, 0x04, 0xF9, 0x35, 0xD3 ], + CodeFlag.Shirts => [ 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB ], + CodeFlag.World => [ 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 ], + CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ], + CodeFlag.Crown => [ 0x43, 0x8E, 0x34, 0x56, 0x24, 0xC9, 0xC6, 0xDE, 0x2A, 0x68, 0x3A, 0x5D, 0xF5, 0x8E, 0xCB, 0xEF, 0x0D, 0x4D, 0x5B, 0xDC, 0x23, 0xF9, 0xF9, 0xBD, 0xD9, 0x60, 0xAD, 0x53, 0xC5, 0xA0, 0x33, 0xC4 ], + _ => [], }; } diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 9e0ffa4..43f8f07 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -104,6 +104,14 @@ public unsafe class FunModule : IDisposable return; } + if (_codes.Enabled(CodeService.CodeFlag.Crown) + && actor.OnlineStatus is OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor + && slot.IsEquipment()) + { + armor = new CharacterArmor(6117, 1, 0); + return; + } + switch (_codes.Masked(CodeService.GearCodes)) { case CodeService.CodeFlag.Emperor: @@ -146,6 +154,13 @@ public unsafe class FunModule : IDisposable return; } + if (_codes.Enabled(CodeService.CodeFlag.Crown) + && actor.OnlineStatus is OnlineStatus.Mentor or OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor) + { + SetCrown(armor); + return; + } + switch (_codes.Masked(CodeService.GearCodes)) { case CodeService.CodeFlag.Emperor: @@ -203,7 +218,7 @@ public unsafe class FunModule : IDisposable armor.Variant = item.Variant; } - private void SetElephant(EquipSlot slot, ref CharacterArmor armor) + private static void SetElephant(EquipSlot slot, ref CharacterArmor armor) { armor = slot switch { @@ -213,6 +228,16 @@ public unsafe class FunModule : IDisposable }; } + private static void SetCrown(Span armor) + { + var clown = new CharacterArmor(6117, 1, 0); + armor[0] = clown; + armor[1] = clown; + armor[2] = clown; + armor[3] = clown; + armor[4] = clown; + } + private void SetRace(ref CustomizeArray customize) { var race = _codes.GetRace(); From 5faaf5337e7ca7999a129a7b71b39ad0b0255d74 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 29 Dec 2023 17:28:16 +0100 Subject: [PATCH 117/786] Fix time display. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index c1c9fb6..5f91b29 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit c1c9fb6d83be8b05dfb3f35031c05781daad1fc7 +Subproject commit 5f91b296a0d043aee6add38f707ed570f96b1f9f From ff6905d45e52fce560c97cae491a98fe7325fb27 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 29 Dec 2023 16:32:15 +0000 Subject: [PATCH 118/786] [CI] Updating repo.json for testing_1.0.7.1 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 46cd000..e57050a 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.7.0", - "TestingAssemblyVersion": "1.0.7.0", + "TestingAssemblyVersion": "1.0.7.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.0.7.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 53388739cabf038710e91501866c74647c84e2f2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 29 Dec 2023 18:31:59 +0100 Subject: [PATCH 119/786] Fix labels appearing where unwanted. --- Glamourer/Gui/UiHelpers.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 2bc11c4..916bdaa 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -47,14 +47,20 @@ public static class UiHelpers public static bool DrawCheckbox(string label, string tooltip, bool value, out bool on, bool locked) { + var startsWithHash = label.StartsWith("##"); bool ret; - using (var disabled = ImRaii.Disabled(locked)) + using (_ = ImRaii.Disabled(locked)) { - ret = ImGuiUtil.Checkbox("##" + label, string.Empty, value, v => value = v); + ret = ImGuiUtil.Checkbox(startsWithHash ? label : "##" + label, string.Empty, value, v => value = v); } - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(label); + + if (!startsWithHash) + { + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(label); + } + ImGuiUtil.HoverTooltip(tooltip, ImGuiHoveredFlags.AllowWhenDisabled); on = value; return ret; @@ -63,7 +69,7 @@ public static class UiHelpers public static (bool, bool) DrawMetaToggle(string label, bool currentValue, bool currentApply, out bool newValue, out bool newApply, bool locked) { - var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0); + var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0); using (_ = ImRaii.Disabled(locked)) { if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw( From 24c3a52f6aec0e91cc36dd535c868ef11d57dc0d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 29 Dec 2023 18:37:46 +0100 Subject: [PATCH 120/786] Fix design coloring and Apply All Customization display. --- Glamourer/Designs/DesignBase.cs | 5 ++++- Glamourer/Designs/DesignColors.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index f4e7e28..aeb48e0 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -74,9 +74,12 @@ public class DesignBase internal CustomizeFlag ApplyCustomize { get => _applyCustomize.FixApplication(CustomizeSet); - set => _applyCustomize = value & CustomizeFlagExtensions.AllRelevant; + set => _applyCustomize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType; } + internal CustomizeFlag ApplyCustomizeExcludingBodyType + => _applyCustomize.FixApplication(CustomizeSet) & ~CustomizeFlag.BodyType; + internal CustomizeFlag ApplyCustomizeRaw => _applyCustomize; diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index 2a4f3f7..fa257c6 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -282,7 +282,7 @@ public class DesignColors : ISavable, IReadOnlyDictionary public static uint AutoColor(DesignBase design) { - var customize = design.ApplyCustomize == 0; + var customize = design.ApplyCustomizeExcludingBodyType == 0; var equip = design.ApplyEquip == 0; return (customize, equip) switch { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 5333ba5..848bb58 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -154,9 +154,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer private void DrawCustomizeApplication() { - var set = _selector.Selected!.CustomizeSet; - var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; - var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; + var set = _selector.Selected!.CustomizeSet; + var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; + var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) { var newFlags = flags == 3; From 6c5c202356a4b6a1338394fdfdbc758c3f4e2e85 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 29 Dec 2023 17:42:37 +0000 Subject: [PATCH 121/786] [CI] Updating repo.json for testing_1.0.7.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index e57050a..6b78819 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.7.0", - "TestingAssemblyVersion": "1.0.7.1", + "TestingAssemblyVersion": "1.0.7.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.0.7.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 22ea1e344e009d1453912ba2f3bbd99cc4c0f68f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 29 Dec 2023 19:01:05 +0100 Subject: [PATCH 122/786] do not protect when state is locked. --- Glamourer/State/StateListener.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index c4c5fb1..bbc519f 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -28,7 +28,7 @@ public class StateListener : IDisposable private readonly StateManager _manager; private readonly StateApplier _applier; private readonly ItemManager _items; - private readonly CustomizeService _customizations; + private readonly CustomizeService _customizations; private readonly PenumbraService _penumbra; private readonly SlotUpdating _slotUpdating; private readonly WeaponLoading _weaponLoading; @@ -134,7 +134,7 @@ public class StateListener : IDisposable } _funModule.ApplyFunOnLoad(actor, new Span((void*)equipDataPtr, 10), ref customize); - if (modelId == 0) + if (modelId == 0 && _creatingState is not { IsLocked: true }) ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender); } From 9395072bee06043b66df6ea79ede45f716829f92 Mon Sep 17 00:00:00 2001 From: Limiana <5073202+Limiana@users.noreply.github.com> Date: Fri, 29 Dec 2023 21:33:21 +0300 Subject: [PATCH 123/786] Adjust new IPC methods + added testing methods --- Glamourer/Api/GlamourerIpc.ApplyByGUID.cs | 28 +++++++++---------- Glamourer/Api/GlamourerIpc.GetDesign.cs | 20 +++---------- Glamourer/Api/GlamourerIpc.cs | 10 +++---- Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 24 ++++++++++++++++ 4 files changed, 47 insertions(+), 35 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.ApplyByGUID.cs b/Glamourer/Api/GlamourerIpc.ApplyByGUID.cs index 92088b1..2d816e7 100644 --- a/Glamourer/Api/GlamourerIpc.ApplyByGUID.cs +++ b/Glamourer/Api/GlamourerIpc.ApplyByGUID.cs @@ -12,27 +12,27 @@ namespace Glamourer.Api; public partial class GlamourerIpc { - public const string LabelApplyByGuidAll = "Glamourer.ApplyByGuidAll"; - public const string LabelApplyByGuidAllToCharacter = "Glamourer.ApplyByGuidAllToCharacter"; + public const string LabelApplyByGuid = "Glamourer.ApplyByGuid"; + public const string LabelApplyByGuidToCharacter = "Glamourer.ApplyByGuidToCharacter"; - private readonly ActionProvider _applyByGuidAllProvider; - private readonly ActionProvider _applyByGuidAllToCharacterProvider; + private readonly ActionProvider _applyByGuidProvider; + private readonly ActionProvider _applyByGuidToCharacterProvider; - public static ActionSubscriber ApplyByGuidAllSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuidAll); + public static ActionSubscriber ApplyByGuidSubscriber(DalamudPluginInterface pi) + => new(pi, LabelApplyByGuid); - public static ActionSubscriber ApplyByGuidAllToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuidAllToCharacter); + public static ActionSubscriber ApplyByGuidToCharacterSubscriber(DalamudPluginInterface pi) + => new(pi, LabelApplyByGuidToCharacter); - public void ApplyByGuidAll(Guid GUID, string characterName) - => ApplyDesignByGuid(GUID, FindActors(characterName), 0); + public void ApplyByGuid(Guid Identifier, string characterName) + => ApplyDesignByGuid(Identifier, FindActors(characterName), 0); - public void ApplyByGuidAllToCharacter(Guid GUID, Character? character) - => ApplyDesignByGuid(GUID, FindActors(character), 0); + public void ApplyByGuidToCharacter(Guid Identifier, Character? character) + => ApplyDesignByGuid(Identifier, FindActors(character), 0); - private void ApplyDesignByGuid(Guid GUID, IEnumerable actors, uint lockCode) + private void ApplyDesignByGuid(Guid Identifier, IEnumerable actors, uint lockCode) { - var design = _designManager.Designs.FirstOrDefault(x => x.Identifier == GUID); + var design = _designManager.Designs.FirstOrDefault(x => x.Identifier == Identifier); if (design == null) return; diff --git a/Glamourer/Api/GlamourerIpc.GetDesign.cs b/Glamourer/Api/GlamourerIpc.GetDesign.cs index 7d655ee..2c53f57 100644 --- a/Glamourer/Api/GlamourerIpc.GetDesign.cs +++ b/Glamourer/Api/GlamourerIpc.GetDesign.cs @@ -16,23 +16,11 @@ public partial class GlamourerIpc { public const string LabelGetDesignList = "Glamourer.GetDesignList"; - private readonly FuncProvider _getDesignListProvider; + private readonly FuncProvider<(string Name, Guid Identifier)[]> _getDesignListProvider; - public static FuncSubscriber GetDesignListSubscriber(DalamudPluginInterface pi) + public static FuncSubscriber<(string Name, Guid Identifier)[]> GetDesignListSubscriber(DalamudPluginInterface pi) => new(pi, LabelGetDesignList); - public DesignListEntry[] GetDesignList() - => _designManager.Designs.Select(x => new DesignListEntry(x)).ToArray(); - - public record class DesignListEntry - { - public string Name; - public Guid Identifier; - - public DesignListEntry(Design design) - { - Name = design.Name; - Identifier = design.Identifier; - } - } + public (string Name, Guid Identifier)[] GetDesignList() + => _designManager.Designs.Select(x => (x.Name.Text, x.Identifier)).ToArray(); } diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 54c4fa7..dd4b5bd 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -81,10 +81,10 @@ public partial class GlamourerIpc : IDisposable _stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc); _gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc); - _applyByGuidAllProvider = new ActionProvider(pi, LabelApplyByGuidAll, ApplyByGuidAll); - _applyByGuidAllToCharacterProvider = new ActionProvider(pi, LabelApplyByGuidAllToCharacter, ApplyByGuidAllToCharacter); + _applyByGuidProvider = new ActionProvider(pi, LabelApplyByGuid, ApplyByGuid); + _applyByGuidToCharacterProvider = new ActionProvider(pi, LabelApplyByGuidToCharacter, ApplyByGuidToCharacter); - _getDesignListProvider = new FuncProvider(pi, LabelGetDesignList, GetDesignList); + _getDesignListProvider = new FuncProvider<(string Name, Guid Identifier)[]>(pi, LabelGetDesignList, GetDesignList); } public void Dispose() @@ -122,8 +122,8 @@ public partial class GlamourerIpc : IDisposable _gPose.Unsubscribe(OnGPoseChanged); _gPoseChangedProvider.Dispose(); - _applyByGuidAllProvider.Dispose(); - _applyByGuidAllToCharacterProvider.Dispose(); + _applyByGuidProvider.Dispose(); + _applyByGuidToCharacterProvider.Dispose(); _getDesignListProvider.Dispose(); } diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index e42d252..02a2e21 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -5,6 +5,8 @@ using Glamourer.Interop; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using System; +using System.Linq; namespace Glamourer.Gui.Tabs.DebugTab; @@ -19,12 +21,14 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag private int _gameObjectIndex; private string _gameObjectName = string.Empty; private string _base64Apply = string.Empty; + private string _designIdentifier = string.Empty; 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); using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) return; @@ -50,6 +54,15 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag else ImGui.TextUnformatted("Error"); + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList); + ImGui.TableNextColumn(); + var designList = GlamourerIpc.GetDesignListSubscriber(_pluginInterface) + .Invoke(); + if (designList != null) + ImGuiUtil.CopyOnClickSelectable(string.Join(", ", designList)); + else + ImGui.TextUnformatted("Error"); + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevert); ImGui.TableNextColumn(); if (ImGui.Button("Revert##Name")) @@ -104,5 +117,16 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag if (ImGui.Button("Revert##CustomizeCharacter")) GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); + + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid); + ImGui.TableNextColumn(); + if (ImGui.Button("Apply##ByGuidName")) + GlamourerIpc.ApplyByGuidSubscriber(_pluginInterface).Invoke(Guid.Parse(_designIdentifier), _gameObjectName); + + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidToCharacter); + ImGui.TableNextColumn(); + if (ImGui.Button("Apply##ByGuidCharacter")) + GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface) + .Invoke(Guid.Parse(_designIdentifier), _objectManager.Objects[_gameObjectIndex] as Character); } } From c29b6b5e57f02f107c304080f47ac633b87cbf75 Mon Sep 17 00:00:00 2001 From: X3llus <17010070+X3llus@users.noreply.github.com> Date: Sat, 30 Dec 2023 10:52:59 -0500 Subject: [PATCH 124/786] Created a new glamour command that lets you delete a desgin using the given designs name --- Glamourer/Services/CommandService.cs | 37 +++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 2135357..fc402f7 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -84,7 +84,8 @@ public class CommandService : IDisposable _config.Ephemeral.Save(); return; default: - _chat.Print("Use without argument to toggle the main window."); + _chat.Print("Use without argument to toggle the main w" + + "indow."); _chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer").AddText(" for application commands.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("qdb", "Toggles the quick design bar on or off.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("lock", "Toggles the lock of the main window on or off.").BuiltString); @@ -113,6 +114,7 @@ public class CommandService : IDisposable "automation" => SetAutomation(argument), "copy" => CopyState(argument), "save" => SaveState(argument), + "delete" => Delete(argument), _ => PrintHelp(argumentList[0]), }; } @@ -410,6 +412,39 @@ public class CommandService : IDisposable return true; } + private bool Delete(string argument) + { + if (argument.Length == 0) + { + _chat.Print(new SeStringBuilder().AddText("Use with /glamour delete ").AddYellow("[Design Name]").BuiltString); + _chat.Print(new SeStringBuilder() + .AddText( + " 》 The design name is case-insensitive. If multiple designs of that name up to case exist, the first one is chosen.") + .BuiltString); + _chat.Print(new SeStringBuilder() + .AddText( + " 》 If using the design identifier, you need to specify at least 4 characters for it, and the first one starting with the provided characters is chosen.") + .BuiltString); + return false; + } + + //Design? design = _designManager.Designs.FirstOrDefault(design => design.Name == arguments); + var lower = argument.ToLowerInvariant(); + Design? design = _designManager.Designs.FirstOrDefault(d + => d.Name.Lower == lower || lower.Length > 3 && d.Identifier.ToString().StartsWith(lower)); + + if (design == null) + { + _chat.Print(new SeStringBuilder().AddRed("Error with finding the design.").BuiltString); + return false; + } + + _objects.Update(); + _designManager.Delete(design); + + return true; + } + private bool CopyState(string argument) { if (argument.Length == 0) From 9b82f856e1de984e62911b9095bb2b09cbaaa64b Mon Sep 17 00:00:00 2001 From: X3llus <17010070+X3llus@users.noreply.github.com> Date: Sat, 30 Dec 2023 11:04:47 -0500 Subject: [PATCH 125/786] small formatting fix --- Glamourer/Services/CommandService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index fc402f7..9ff230f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -84,8 +84,7 @@ public class CommandService : IDisposable _config.Ephemeral.Save(); return; default: - _chat.Print("Use without argument to toggle the main w" + - "indow."); + _chat.Print("Use without argument to toggle the main window."); _chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer").AddText(" for application commands.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("qdb", "Toggles the quick design bar on or off.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("lock", "Toggles the lock of the main window on or off.").BuiltString); From 0a3ca2430380a62563e9d0c799c03824bd5e0981 Mon Sep 17 00:00:00 2001 From: X3llus <17010070+X3llus@users.noreply.github.com> Date: Sat, 30 Dec 2023 11:06:46 -0500 Subject: [PATCH 126/786] removed commeted out code --- Glamourer/Services/CommandService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 9ff230f..022712f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -427,7 +427,6 @@ public class CommandService : IDisposable return false; } - //Design? design = _designManager.Designs.FirstOrDefault(design => design.Name == arguments); var lower = argument.ToLowerInvariant(); Design? design = _designManager.Designs.FirstOrDefault(d => d.Name.Lower == lower || lower.Length > 3 && d.Identifier.ToString().StartsWith(lower)); From 060e8047ca4d0f2c0459ce58d6173af2f50de7ab Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 31 Dec 2023 12:59:15 +0100 Subject: [PATCH 127/786] Add Stain to IPC Set. --- Glamourer/Api/GlamourerIpc.Set.cs | 16 ++++++++-------- Glamourer/Api/GlamourerIpc.cs | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.Set.cs b/Glamourer/Api/GlamourerIpc.Set.cs index f6fe92e..5a6405d 100644 --- a/Glamourer/Api/GlamourerIpc.Set.cs +++ b/Glamourer/Api/GlamourerIpc.Set.cs @@ -23,16 +23,16 @@ public partial class GlamourerIpc public const string LabelSetItemByActorName = "Glamourer.SetItemByActorName"; - private readonly FuncProvider _setItemProvider; - private readonly FuncProvider _setItemByActorNameProvider; + private readonly FuncProvider _setItemProvider; + private readonly FuncProvider _setItemByActorNameProvider; - public static FuncSubscriber SetItemSubscriber(DalamudPluginInterface pi) + public static FuncSubscriber SetItemSubscriber(DalamudPluginInterface pi) => new(pi, LabelSetItem); - public static FuncSubscriber SetItemByActorNameSubscriber(DalamudPluginInterface pi) + public static FuncSubscriber SetItemByActorNameSubscriber(DalamudPluginInterface pi) => new(pi, LabelSetItemByActorName); - private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, uint key) + private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key) { if (itemId.Id == 0) itemId = ItemManager.NothingId(slot); @@ -56,11 +56,11 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeItem(state, slot, item, StateChanged.Source.Ipc, key); + _stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key); return GlamourerErrorCode.Success; } - private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, uint key) + private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key) { if (itemId.Id == 0) itemId = ItemManager.NothingId(slot); @@ -83,7 +83,7 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeItem(state, slot, item, StateChanged.Source.Ipc, key); + _stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key); found = true; } diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index a19dd6f..43f39c6 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -80,10 +80,10 @@ public sealed partial class GlamourerIpc : IDisposable _stateChangedProvider = new EventProvider>(pi, LabelStateChanged); _gPoseChangedProvider = new EventProvider(pi, LabelGPoseChanged); - _setItemProvider = new FuncProvider(pi, LabelSetItem, - (idx, slot, item, key) => (int)SetItem(idx, (EquipSlot)slot, item, key)); - _setItemByActorNameProvider = new FuncProvider(pi, LabelSetItemByActorName, - (name, slot, item, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, key)); + _setItemProvider = new FuncProvider(pi, LabelSetItem, + (idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key)); + _setItemByActorNameProvider = new FuncProvider(pi, LabelSetItemByActorName, + (name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key)); _stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc); _gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc); From ea7806535a43b8916df59567cf8fd28efa3290be Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 31 Dec 2023 12:59:28 +0100 Subject: [PATCH 128/786] Fix left rings not being changeable in designs. --- Glamourer/Gui/Equipment/EquipDrawData.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 1edd4ce..f94d997 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -29,7 +29,9 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot) => new(slot, design.DesignData) { - ItemSetter = slot.IsEquipmentPiece() ? i => manager.ChangeEquip(design, slot, i) : i => manager.ChangeWeapon(design, slot, i), + ItemSetter = slot.IsEquipment() || slot.IsAccessory() + ? i => manager.ChangeEquip(design, slot, i) + : i => manager.ChangeWeapon(design, slot, i), StainSetter = i => manager.ChangeStain(design, slot, i), ApplySetter = b => manager.ChangeApplyEquip(design, slot, b), ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b), From 2e5cdc229ddfd729662dbee87762664ded11ddb7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 31 Dec 2023 13:16:39 +0100 Subject: [PATCH 129/786] Improve PR to use GetDesign. --- Glamourer/Services/CommandService.cs | 51 +++++++++++++--------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 022712f..ed60243 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -85,7 +85,8 @@ public class CommandService : IDisposable return; default: _chat.Print("Use without argument to toggle the main window."); - _chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer").AddText(" for application commands.").BuiltString); + _chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer") + .AddText(" for application commands.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("qdb", "Toggles the quick design bar on or off.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("lock", "Toggles the lock of the main window on or off.").BuiltString); return; @@ -415,7 +416,7 @@ public class CommandService : IDisposable { if (argument.Length == 0) { - _chat.Print(new SeStringBuilder().AddText("Use with /glamour delete ").AddYellow("[Design Name]").BuiltString); + _chat.Print(new SeStringBuilder().AddText("Use with /glamour delete ").AddYellow("[Design Name, Path or Identifier]").BuiltString); _chat.Print(new SeStringBuilder() .AddText( " 》 The design name is case-insensitive. If multiple designs of that name up to case exist, the first one is chosen.") @@ -424,23 +425,18 @@ public class CommandService : IDisposable .AddText( " 》 If using the design identifier, you need to specify at least 4 characters for it, and the first one starting with the provided characters is chosen.") .BuiltString); + _chat.Print(new SeStringBuilder() + .AddText(" 》 The design path is the folder path in the selector, with '/' as separators. It is also case-insensitive.") + .BuiltString); return false; - } - - var lower = argument.ToLowerInvariant(); - Design? design = _designManager.Designs.FirstOrDefault(d - => d.Name.Lower == lower || lower.Length > 3 && d.Identifier.ToString().StartsWith(lower)); - - if (design == null) - { - _chat.Print(new SeStringBuilder().AddRed("Error with finding the design.").BuiltString); + } + + if (!GetDesign(argument, out var designBase, false) || designBase is not Design d) return false; - } - - _objects.Update(); - _designManager.Delete(design); - - return true; + + _designManager.Delete(d); + + return true; } private bool CopyState(string argument) @@ -553,14 +549,13 @@ public class CommandService : IDisposable design = leaf.Value; } - if (design == null) - { - _chat.Print(new SeStringBuilder().AddText("The token ").AddYellow(argument, true).AddText(" did not resolve to an existing design.") - .BuiltString); - return false; - } + if (design != null) + return true; + + _chat.Print(new SeStringBuilder().AddText("The token ").AddYellow(argument, true).AddText(" did not resolve to an existing design.") + .BuiltString); + return false; - return true; } private unsafe bool IdentifierHandling(string argument, out ActorIdentifier[] identifiers, bool allowAnyWorld, bool allowIndex) @@ -580,10 +575,10 @@ public class CommandService : IDisposable if (allowIndex && identifier.Type is IdentifierType.Npc) identifier = _actors.CreateNpc(identifier.Kind, identifier.DataId, obj.Index); - identifiers = new[] - { + identifiers = + [ identifier, - }; + ]; } else { @@ -600,7 +595,7 @@ public class CommandService : IDisposable return true; } - catch (ActorManager.IdentifierParseError e) + catch (ActorIdentifierFactory.IdentifierParseError e) { _chat.Print(new SeStringBuilder().AddText("The argument ").AddRed(argument, true) .AddText($" could not be converted to an identifier. {e.Message}") From fca5e838415e5cea2d823291872ee3a449d705f3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 31 Dec 2023 13:16:59 +0100 Subject: [PATCH 130/786] Fix IPC Tester after adding Stain. --- Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 23 ++++++++++++++----- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 8 +------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index 9059d53..cb2720c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -1,4 +1,5 @@ -using Dalamud.Game.ClientState.Objects.Types; +using System; +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.Api; @@ -23,6 +24,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag private int _gameObjectIndex; private CustomItemId _customItemId; + private StainId _stainId; private EquipSlot _slot = EquipSlot.Head; private string _gameObjectName = string.Empty; private string _base64Apply = string.Empty; @@ -34,7 +36,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag 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); - DrawItemIdInput(); + DrawItemInput(); using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) return; @@ -119,7 +121,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Set##SetItem")) _setItemEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, (byte)_slot, _customItemId.Id, 1337); + .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); if (_setItemEc != GlamourerIpc.GlamourerErrorCode.Success) { ImGui.SameLine(); @@ -130,7 +132,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Set##SetItemByActorName")) _setItemByActorNameEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface) - .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, 1337); + .Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); if (_setItemByActorNameEc != GlamourerIpc.GlamourerErrorCode.Success) { ImGui.SameLine(); @@ -138,12 +140,21 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag } } - private void DrawItemIdInput() + private void DrawItemInput() { var tmp = _customItemId.Id; if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp)) _customItemId = (CustomItemId)tmp; - ImGui.SameLine(); + 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/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 641c16a..9c7ccc1 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -1,8 +1,5 @@ using System; -using Dalamud.Interface; using Dalamud.Interface.Utility; -using Glamourer.Gui.Tabs.DebugTab; -using Glamourer.Gui; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using ImGuiNET; @@ -28,9 +25,6 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem public void Draw() { - if (!ImGui.CollapsingHeader("Penumbra")) - return; - using var table = ImRaii.Table("##PenumbraTable", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) return; @@ -68,7 +62,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); ImGui.InputInt("##redrawObject", ref _gameObjectIndex, 0, 0); ImGui.TableNextColumn(); - using (var disabled = ImRaii.Disabled(!_penumbra.Available)) + using (_ = ImRaii.Disabled(!_penumbra.Available)) { if (ImGui.SmallButton("Redraw")) _penumbra.RedrawObject((ObjectIndex)_gameObjectIndex, RedrawType.Redraw); From a5c1e669160f0275bb10cc2424faf45de5e0ed38 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 31 Dec 2023 13:29:42 +0100 Subject: [PATCH 131/786] Rename and move and reuse. --- Glamourer/Api/GlamourerIpc.Apply.cs | 25 +++++++- Glamourer/Api/GlamourerIpc.ApplyByGUID.cs | 57 ------------------- ...rIpc.GetDesign.cs => GlamourerIpc.Data.cs} | 7 --- Glamourer/Api/GlamourerIpc.cs | 13 +++-- 4 files changed, 31 insertions(+), 71 deletions(-) delete mode 100644 Glamourer/Api/GlamourerIpc.ApplyByGUID.cs rename Glamourer/Api/{GlamourerIpc.GetDesign.cs => GlamourerIpc.Data.cs} (73%) diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs index da8e33e..d2bc86f 100644 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ b/Glamourer/Api/GlamourerIpc.Apply.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using Glamourer.Designs; @@ -25,6 +27,9 @@ public partial class GlamourerIpc public const string LabelApplyOnlyCustomizationLock = "Glamourer.ApplyOnlyCustomizationLock"; public const string LabelApplyOnlyCustomizationToCharacterLock = "Glamourer.ApplyOnlyCustomizationToCharacterLock"; + public const string LabelApplyByGuid = "Glamourer.ApplyByGuid"; + public const string LabelApplyByGuidToCharacter = "Glamourer.ApplyByGuidToCharacter"; + private readonly ActionProvider _applyAllProvider; private readonly ActionProvider _applyAllToCharacterProvider; private readonly ActionProvider _applyOnlyEquipmentProvider; @@ -39,6 +44,9 @@ public partial class GlamourerIpc private readonly ActionProvider _applyOnlyCustomizationProviderLock; private readonly ActionProvider _applyOnlyCustomizationToCharacterProviderLock; + private readonly ActionProvider _applyByGuidProvider; + private readonly ActionProvider _applyByGuidToCharacterProvider; + public static ActionSubscriber ApplyAllSubscriber(DalamudPluginInterface pi) => new(pi, LabelApplyAll); @@ -57,6 +65,12 @@ public partial class GlamourerIpc public static ActionSubscriber ApplyOnlyCustomizationToCharacterSubscriber(DalamudPluginInterface pi) => new(pi, LabelApplyOnlyCustomizationToCharacter); + public static ActionSubscriber ApplyByGuidSubscriber(DalamudPluginInterface pi) + => new(pi, LabelApplyByGuid); + + public static ActionSubscriber ApplyByGuidToCharacterSubscriber(DalamudPluginInterface pi) + => new(pi, LabelApplyByGuidToCharacter); + public void ApplyAll(string base64, string characterName) => ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, 0); @@ -95,6 +109,12 @@ public partial class GlamourerIpc => 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); + + public void ApplyByGuidToCharacter(Guid identifier, Character? character) + => ApplyDesignByGuid(identifier, FindActors(character), 0); + private void ApplyDesign(DesignBase? design, IEnumerable actors, byte version, uint lockCode) { if (design == null) @@ -118,4 +138,7 @@ public partial class GlamourerIpc } } } + + private void ApplyDesignByGuid(Guid identifier, IEnumerable actors, uint lockCode) + => ApplyDesign(_designManager.Designs.FirstOrDefault(x => x.Identifier == identifier), actors, DesignConverter.Version, lockCode); } diff --git a/Glamourer/Api/GlamourerIpc.ApplyByGUID.cs b/Glamourer/Api/GlamourerIpc.ApplyByGUID.cs deleted file mode 100644 index 2d816e7..0000000 --- a/Glamourer/Api/GlamourerIpc.ApplyByGUID.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; -using Glamourer.Events; -using Glamourer.Interop.Structs; -using Penumbra.Api.Helpers; -using Penumbra.GameData.Actors; - -namespace Glamourer.Api; - -public partial class GlamourerIpc -{ - public const string LabelApplyByGuid = "Glamourer.ApplyByGuid"; - public const string LabelApplyByGuidToCharacter = "Glamourer.ApplyByGuidToCharacter"; - - private readonly ActionProvider _applyByGuidProvider; - private readonly ActionProvider _applyByGuidToCharacterProvider; - - public static ActionSubscriber ApplyByGuidSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuid); - - public static ActionSubscriber ApplyByGuidToCharacterSubscriber(DalamudPluginInterface pi) - => new(pi, LabelApplyByGuidToCharacter); - - public void ApplyByGuid(Guid Identifier, string characterName) - => ApplyDesignByGuid(Identifier, FindActors(characterName), 0); - - public void ApplyByGuidToCharacter(Guid Identifier, Character? character) - => ApplyDesignByGuid(Identifier, FindActors(character), 0); - - private void ApplyDesignByGuid(Guid Identifier, IEnumerable actors, uint lockCode) - { - var design = _designManager.Designs.FirstOrDefault(x => x.Identifier == Identifier); - if (design == null) - return; - - var hasModelId = true; - _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(design, state, StateChanged.Source.Ipc, lockCode); - state.Lock(lockCode); - } - } - } -} diff --git a/Glamourer/Api/GlamourerIpc.GetDesign.cs b/Glamourer/Api/GlamourerIpc.Data.cs similarity index 73% rename from Glamourer/Api/GlamourerIpc.GetDesign.cs rename to Glamourer/Api/GlamourerIpc.Data.cs index 2c53f57..3acff10 100644 --- a/Glamourer/Api/GlamourerIpc.GetDesign.cs +++ b/Glamourer/Api/GlamourerIpc.Data.cs @@ -1,14 +1,7 @@ using System; -using System.Buffers.Text; -using System.Collections.Generic; using System.Linq; -using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; -using Glamourer.Customization; -using Glamourer.Designs; -using Glamourer.Structs; using Penumbra.Api.Helpers; -using Penumbra.GameData.Actors; namespace Glamourer.Api; diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index dd4b5bd..7e8781e 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -28,7 +28,8 @@ public partial class GlamourerIpc : IDisposable private readonly DesignManager _designManager; public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorService actors, - DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier, DesignManager designManager) + DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier, + DesignManager designManager) { _stateManager = stateManager; _objects = objects; @@ -65,6 +66,9 @@ public partial class GlamourerIpc : IDisposable _applyOnlyCustomizationToCharacterProviderLock = new ActionProvider(pi, LabelApplyOnlyCustomizationToCharacterLock, ApplyOnlyCustomizationToCharacterLock); + _applyByGuidProvider = new ActionProvider(pi, LabelApplyByGuid, ApplyByGuid); + _applyByGuidToCharacterProvider = new ActionProvider(pi, LabelApplyByGuidToCharacter, ApplyByGuidToCharacter); + _revertProvider = new ActionProvider(pi, LabelRevert, Revert); _revertCharacterProvider = new ActionProvider(pi, LabelRevertCharacter, RevertCharacter); _revertProviderLock = new ActionProvider(pi, LabelRevertLock, RevertLock); @@ -81,9 +85,6 @@ public partial class GlamourerIpc : IDisposable _stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc); _gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc); - _applyByGuidProvider = new ActionProvider(pi, LabelApplyByGuid, ApplyByGuid); - _applyByGuidToCharacterProvider = new ActionProvider(pi, LabelApplyByGuidToCharacter, ApplyByGuidToCharacter); - _getDesignListProvider = new FuncProvider<(string Name, Guid Identifier)[]>(pi, LabelGetDesignList, GetDesignList); } @@ -107,6 +108,8 @@ public partial class GlamourerIpc : IDisposable _applyOnlyEquipmentToCharacterProviderLock.Dispose(); _applyOnlyCustomizationProviderLock.Dispose(); _applyOnlyCustomizationToCharacterProviderLock.Dispose(); + _applyByGuidProvider.Dispose(); + _applyByGuidToCharacterProvider.Dispose(); _revertProvider.Dispose(); _revertCharacterProvider.Dispose(); @@ -122,8 +125,6 @@ public partial class GlamourerIpc : IDisposable _gPose.Unsubscribe(OnGPoseChanged); _gPoseChangedProvider.Dispose(); - _applyByGuidProvider.Dispose(); - _applyByGuidToCharacterProvider.Dispose(); _getDesignListProvider.Dispose(); } From c3f2e7d3a1731e622f07a68f741491d2eae5fb0e Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 31 Dec 2023 12:46:43 +0000 Subject: [PATCH 132/786] [CI] Updating repo.json for testing_1.0.7.3 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 6b78819..e030efe 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.7.0", - "TestingAssemblyVersion": "1.0.7.2", + "TestingAssemblyVersion": "1.0.7.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.0.7.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 6130bae81d461f24d28913854871306c0564576b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 3 Jan 2024 22:47:35 +0100 Subject: [PATCH 133/786] I don't know what behavior changed here but this fixes the issue. --- Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs index c3781fc..0eeda59 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs @@ -4,6 +4,7 @@ using System.Linq; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Utility; using ImGuiNET; +using OtterGui; using OtterGui.Custom; using OtterGui.Log; using OtterGui.Widgets; @@ -80,8 +81,10 @@ public sealed class HumanNpcCombo( switch (kind) { case ObjectKind.BattleNpc: - var nameIds = bNpcNames[id]; - ret.AddRange(nameIds.Select(nameId => (bNpcs[nameId], kind, nameId.Id))); + if (!bNpcNames.TryGetValue(id, out var nameIds)) + continue; + + ret.AddRange(nameIds.SelectWhere(nameId => (bNpcs.TryGetValue(nameId, out var s), (s!, kind, nameId.Id)))); break; case ObjectKind.EventNpc: ret.Add((name, kind, id)); From 6ecf06a67126353013b8da40b5dd5f6b87b6e335 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 6 Jan 2024 23:56:19 +0100 Subject: [PATCH 134/786] Update OtterGui --- Glamourer/Automation/AutoDesignApplier.cs | 115 ++++++++++-------- Glamourer/Designs/DesignManager.cs | 6 +- Glamourer/Events/AutomationChanged.cs | 14 +-- Glamourer/Events/DesignChanged.cs | 11 +- Glamourer/Events/EquippedGearset.cs | 11 +- Glamourer/Events/GPoseService.cs | 4 +- Glamourer/Events/HeadGearVisibilityChanged.cs | 15 +-- Glamourer/Events/MovedEquipment.cs | 11 +- Glamourer/Events/ObjectUnlocked.cs | 10 +- Glamourer/Events/PenumbraReloaded.cs | 11 +- Glamourer/Events/SlotUpdating.cs | 16 +-- Glamourer/Events/StateChanged.cs | 12 +- Glamourer/Events/TabSelected.cs | 14 +-- Glamourer/Events/VisorStateChanged.cs | 15 +-- Glamourer/Events/WeaponLoading.cs | 15 +-- Glamourer/Events/WeaponVisibilityChanged.cs | 14 +-- Glamourer/Glamourer.cs | 3 +- Glamourer/Interop/ChangeCustomizeService.cs | 8 +- Glamourer/Interop/CrestService.cs | 13 +- Glamourer/Services/ServiceManager.cs | 2 +- Glamourer/State/StateListener.cs | 72 +++++------ Glamourer/State/StateManager.cs | 2 +- OtterGui | 2 +- 23 files changed, 142 insertions(+), 254 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 83ce3c4..90fb00d 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -11,7 +11,6 @@ using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.State; using Glamourer.Unlocks; -using OtterGui.Classes; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -27,7 +26,7 @@ public class AutoDesignApplier : IDisposable private readonly JobService _jobs; private readonly EquippedGearset _equippedGearset; private readonly ActorManager _actors; - private readonly CustomizeService _customizations; + private readonly CustomizeService _customizations; private readonly CustomizeUnlockManager _customizeUnlocks; private readonly ItemUnlockManager _itemUnlocks; private readonly AutomationChanged _event; @@ -80,7 +79,7 @@ public class AutoDesignApplier : IDisposable _jobs.JobChanged -= OnJobChange; } - private void OnWeaponLoading(Actor actor, EquipSlot slot, Ref weapon) + private void OnWeaponLoading(Actor actor, EquipSlot slot, ref CharacterWeapon weapon) { if (_jobChangeState == null || !_config.EnableAutoDesigns) return; @@ -89,27 +88,33 @@ public class AutoDesignApplier : IDisposable if (id == _jobChangeState.Identifier) { var current = _jobChangeState.BaseData.Item(slot); - if (slot is EquipSlot.MainHand) + switch (slot) { - if (_jobChangeMainhand.TryGetValue(current.Type, out var data)) + case EquipSlot.MainHand: { - Glamourer.Log.Verbose( - $"Changing Mainhand from {_jobChangeState.ModelData.Weapon(EquipSlot.MainHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); - _state.ChangeItem(_jobChangeState, EquipSlot.MainHand, data.Item1, data.Item2); - weapon.Value = _jobChangeState.ModelData.Weapon(EquipSlot.MainHand); - } - } - else if (slot is EquipSlot.OffHand && current.Type == _jobChangeState.BaseData.MainhandType.Offhand()) - { - if (_jobChangeOffhand.TryGetValue(current.Type, out var data)) - { - Glamourer.Log.Verbose( - $"Changing Offhand from {_jobChangeState.ModelData.Weapon(EquipSlot.OffHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); - _state.ChangeItem(_jobChangeState, EquipSlot.OffHand, data.Item1, data.Item2); - weapon.Value = _jobChangeState.ModelData.Weapon(EquipSlot.OffHand); - } + if (_jobChangeMainhand.TryGetValue(current.Type, out var data)) + { + Glamourer.Log.Verbose( + $"Changing Mainhand from {_jobChangeState.ModelData.Weapon(EquipSlot.MainHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); + _state.ChangeItem(_jobChangeState, EquipSlot.MainHand, data.Item1, data.Item2); + weapon = _jobChangeState.ModelData.Weapon(EquipSlot.MainHand); + } - ResetJobChange(); + break; + } + case EquipSlot.OffHand when current.Type == _jobChangeState.BaseData.MainhandType.Offhand(): + { + if (_jobChangeOffhand.TryGetValue(current.Type, out var data)) + { + Glamourer.Log.Verbose( + $"Changing Offhand from {_jobChangeState.ModelData.Weapon(EquipSlot.OffHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); + _state.ChangeItem(_jobChangeState, EquipSlot.OffHand, data.Item1, data.Item2); + weapon = _jobChangeState.ModelData.Weapon(EquipSlot.OffHand); + } + + ResetJobChange(); + break; + } } } else @@ -123,21 +128,32 @@ public class AutoDesignApplier : IDisposable if (!_config.EnableAutoDesigns || set == null) return; - void RemoveOld(ActorIdentifier[]? identifiers) + switch (type) { - if (identifiers == null) - return; - - foreach (var id in identifiers) - { - if (id.Type is IdentifierType.Player && id.HomeWorld == WorldId.AnyWorld) - foreach (var state in _state.Where(kvp => kvp.Key.PlayerName == id.PlayerName).Select(kvp => kvp.Value)) - state.RemoveFixedDesignSources(); - else if (_state.TryGetValue(id, out var state)) - state.RemoveFixedDesignSources(); - } + case AutomationChanged.Type.ToggleSet when !set.Enabled: + case AutomationChanged.Type.DeletedDesign when set.Enabled: + // The automation set was disabled or deleted, no other for those identifiers can be enabled, remove existing Fixed Locks. + RemoveOld(set.Identifiers); + break; + case AutomationChanged.Type.ChangeIdentifier when set.Enabled: + // Remove fixed state from the old identifiers assigned and the old enabled set, if any. + var (oldIds, _, oldSet) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!; + RemoveOld(oldIds); + ApplyNew(set); // Does not need to disable oldSet because same identifiers. + break; + case AutomationChanged.Type.ToggleSet: // Does not need to disable old states because same identifiers. + case AutomationChanged.Type.ChangedBase: + case AutomationChanged.Type.AddedDesign: + case AutomationChanged.Type.MovedDesign: + case AutomationChanged.Type.ChangedDesign: + case AutomationChanged.Type.ChangedConditions: + case AutomationChanged.Type.ChangedType: + ApplyNew(set); + break; } + return; + void ApplyNew(AutoDesignSet? newSet) { if (newSet is not { Enabled: true }) @@ -174,28 +190,19 @@ public class AutoDesignApplier : IDisposable } } - switch (type) + void RemoveOld(ActorIdentifier[]? identifiers) { - case AutomationChanged.Type.ToggleSet when !set.Enabled: - case AutomationChanged.Type.DeletedDesign when set.Enabled: - // The automation set was disabled or deleted, no other for those identifiers can be enabled, remove existing Fixed Locks. - RemoveOld(set.Identifiers); - break; - case AutomationChanged.Type.ChangeIdentifier when set.Enabled: - // Remove fixed state from the old identifiers assigned and the old enabled set, if any. - var (oldIds, _, oldSet) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!; - RemoveOld(oldIds); - ApplyNew(set); // Does not need to disable oldSet because same identifiers. - break; - case AutomationChanged.Type.ToggleSet: // Does not need to disable old states because same identifiers. - case AutomationChanged.Type.ChangedBase: - case AutomationChanged.Type.AddedDesign: - case AutomationChanged.Type.MovedDesign: - case AutomationChanged.Type.ChangedDesign: - case AutomationChanged.Type.ChangedConditions: - case AutomationChanged.Type.ChangedType: - ApplyNew(set); - break; + if (identifiers == null) + return; + + foreach (var id in identifiers) + { + if (id.Type is IdentifierType.Player && id.HomeWorld == WorldId.AnyWorld) + foreach (var state in _state.Where(kvp => kvp.Key.PlayerName == id.PlayerName).Select(kvp => kvp.Value)) + state.RemoveFixedDesignSources(); + else if (_state.TryGetValue(id, out var state)) + state.RemoveFixedDesignSources(); + } } } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index b46e4e0..2d25cc2 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -99,7 +99,7 @@ public class DesignManager Glamourer.Log.Information( $"Loaded {_designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); - _event.Invoke(DesignChanged.Type.ReloadedAll, null!); + _event.Invoke(DesignChanged.Type.ReloadedAll, null!, null); } /// Whether an Undo for the given design is possible. @@ -176,7 +176,7 @@ public class DesignManager --d.Index; _designs.RemoveAt(design.Index); _saveService.ImmediateDelete(design); - _event.Invoke(DesignChanged.Type.Deleted, design); + _event.Invoke(DesignChanged.Type.Deleted, design, null); } /// Rename a design. @@ -723,7 +723,7 @@ public class DesignManager if (!message.IsNullOrEmpty()) Glamourer.Log.Debug(message); _saveService.ImmediateSave(design); - _event.Invoke(DesignChanged.Type.Created, design); + _event.Invoke(DesignChanged.Type.Created, design, null); return true; } diff --git a/Glamourer/Events/AutomationChanged.cs b/Glamourer/Events/AutomationChanged.cs index 4a35e4b..1d45084 100644 --- a/Glamourer/Events/AutomationChanged.cs +++ b/Glamourer/Events/AutomationChanged.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.Automation; +using Glamourer.Automation; using OtterGui.Classes; namespace Glamourer.Events; @@ -12,8 +11,8 @@ namespace Glamourer.Events; /// Parameter is additional data depending on the type of change. /// /// -public sealed class AutomationChanged : EventWrapper, - AutomationChanged.Priority> +public sealed class AutomationChanged() + : EventWrapper(nameof(AutomationChanged)) { public enum Type { @@ -65,11 +64,4 @@ public sealed class AutomationChanged : EventWrapper AutoDesignApplier, } - - public AutomationChanged() - : base(nameof(AutomationChanged)) - { } - - public void Invoke(Type type, AutoDesignSet? set, object? data) - => Invoke(this, type, set, data); } diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index c528fde..76724eb 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -1,4 +1,3 @@ -using System; using Glamourer.Designs; using Glamourer.Gui; using OtterGui.Classes; @@ -13,7 +12,8 @@ namespace Glamourer.Events; /// Parameter is any additional data depending on the type of change. /// /// -public sealed class DesignChanged : EventWrapper, DesignChanged.Priority> +public sealed class DesignChanged() + : EventWrapper(nameof(DesignChanged)) { public enum Type { @@ -98,11 +98,4 @@ public sealed class DesignChanged : EventWrapper DesignCombo = -2, } - - public DesignChanged() - : base(nameof(DesignChanged)) - { } - - public void Invoke(Type type, Design design, object? data = null) - => Invoke(this, type, design, data); } diff --git a/Glamourer/Events/EquippedGearset.cs b/Glamourer/Events/EquippedGearset.cs index a8fafff..c4252eb 100644 --- a/Glamourer/Events/EquippedGearset.cs +++ b/Glamourer/Events/EquippedGearset.cs @@ -1,4 +1,3 @@ -using System; using OtterGui.Classes; namespace Glamourer.Events; @@ -13,18 +12,12 @@ namespace Glamourer.Events; /// Parameter is the job id of the associated job. /// /// -public sealed class EquippedGearset : EventWrapper, EquippedGearset.Priority> +public sealed class EquippedGearset() + : EventWrapper(nameof(EquippedGearset)) { public enum Priority { /// AutoDesignApplier = 0, } - - public EquippedGearset() - : base(nameof(EquippedGearset)) - { } - - public void Invoke(string name, int id, int lastId, byte glamour, byte jobId) - => Invoke(this, name, id, lastId, glamour, jobId); } diff --git a/Glamourer/Events/GPoseService.cs b/Glamourer/Events/GPoseService.cs index 7403754..a87914e 100644 --- a/Glamourer/Events/GPoseService.cs +++ b/Glamourer/Events/GPoseService.cs @@ -5,7 +5,7 @@ using OtterGui.Classes; namespace Glamourer.Events; -public sealed class GPoseService : EventWrapper, GPoseService.Priority> +public sealed class GPoseService : EventWrapper { private readonly IFramework _framework; private readonly IClientState _state; @@ -56,7 +56,7 @@ public sealed class GPoseService : EventWrapper, GPoseService.Prior return; InGPose = inGPose; - Invoke(this, InGPose); + Invoke(InGPose); var actions = InGPose ? _onEnter : _onLeave; foreach (var action in actions) { diff --git a/Glamourer/Events/HeadGearVisibilityChanged.cs b/Glamourer/Events/HeadGearVisibilityChanged.cs index d12cd69..91454b3 100644 --- a/Glamourer/Events/HeadGearVisibilityChanged.cs +++ b/Glamourer/Events/HeadGearVisibilityChanged.cs @@ -1,4 +1,3 @@ -using System; using Glamourer.Interop.Structs; using OtterGui.Classes; @@ -11,22 +10,12 @@ namespace Glamourer.Events; /// Parameter is the new state. /// /// -public sealed class HeadGearVisibilityChanged : EventWrapper>, HeadGearVisibilityChanged.Priority> +public sealed class HeadGearVisibilityChanged() + : EventWrapperRef2(nameof(HeadGearVisibilityChanged)) { public enum Priority { /// StateListener = 0, } - - public HeadGearVisibilityChanged() - : base(nameof(HeadGearVisibilityChanged)) - { } - - public void Invoke(Actor actor, ref bool state) - { - var value = new Ref(state); - Invoke(this, actor, value); - state = value; - } } diff --git a/Glamourer/Events/MovedEquipment.cs b/Glamourer/Events/MovedEquipment.cs index 4548575..53491f1 100644 --- a/Glamourer/Events/MovedEquipment.cs +++ b/Glamourer/Events/MovedEquipment.cs @@ -1,4 +1,3 @@ -using System; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -11,18 +10,12 @@ namespace Glamourer.Events; /// Parameter is an array of slots updated and corresponding item ids and stains. /// /// -public sealed class MovedEquipment : EventWrapper, MovedEquipment.Priority> +public sealed class MovedEquipment() + : EventWrapper<(EquipSlot, uint, StainId)[], MovedEquipment.Priority>(nameof(MovedEquipment)) { public enum Priority { /// StateListener = 0, } - - public MovedEquipment() - : base(nameof(MovedEquipment)) - { } - - public void Invoke((EquipSlot, uint, StainId)[] items) - => Invoke(this, items); } diff --git a/Glamourer/Events/ObjectUnlocked.cs b/Glamourer/Events/ObjectUnlocked.cs index 7b8c120..b31fa3e 100644 --- a/Glamourer/Events/ObjectUnlocked.cs +++ b/Glamourer/Events/ObjectUnlocked.cs @@ -11,7 +11,8 @@ namespace Glamourer.Events; /// Parameter is the timestamp of the unlock. /// /// -public sealed class ObjectUnlocked : EventWrapper, ObjectUnlocked.Priority> +public sealed class ObjectUnlocked() + : EventWrapper(nameof(ObjectUnlocked)) { public enum Type { @@ -25,11 +26,4 @@ public sealed class ObjectUnlocked : EventWrapper Currently used as a hack to make the unlock table dirty in it. If anything else starts using this, rework. UnlockTable = 0, } - - public ObjectUnlocked() - : base(nameof(ObjectUnlocked)) - { } - - public void Invoke(Type type, uint id, DateTimeOffset timestamp) - => Invoke(this, type, id, timestamp); } diff --git a/Glamourer/Events/PenumbraReloaded.cs b/Glamourer/Events/PenumbraReloaded.cs index 40a2527..7df8b3f 100644 --- a/Glamourer/Events/PenumbraReloaded.cs +++ b/Glamourer/Events/PenumbraReloaded.cs @@ -1,4 +1,3 @@ -using System; using OtterGui.Classes; namespace Glamourer.Events; @@ -6,18 +5,12 @@ namespace Glamourer.Events; /// /// Triggered when Penumbra is reloaded. /// -public sealed class PenumbraReloaded : EventWrapper +public sealed class PenumbraReloaded() + : EventWrapper(nameof(PenumbraReloaded)) { public enum Priority { /// ChangeCustomizeService = 0, } - - public PenumbraReloaded() - : base(nameof(PenumbraReloaded)) - { } - - public void Invoke() - => Invoke(this); } diff --git a/Glamourer/Events/SlotUpdating.cs b/Glamourer/Events/SlotUpdating.cs index d83822b..ab6e2f9 100644 --- a/Glamourer/Events/SlotUpdating.cs +++ b/Glamourer/Events/SlotUpdating.cs @@ -15,24 +15,12 @@ namespace Glamourer.Events; /// Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. /// /// -public sealed class SlotUpdating : EventWrapper, Ref>, SlotUpdating.Priority> +public sealed class SlotUpdating() + : EventWrapperRef34(nameof(SlotUpdating)) { public enum Priority { /// StateListener = 0, } - - public SlotUpdating() - : base(nameof(SlotUpdating)) - { } - - public void Invoke(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue) - { - var value = new Ref(armor); - var @return = new Ref(returnValue); - Invoke(this, model, slot, value, @return); - armor = value; - returnValue = @return; - } } diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index e02a6d9..3045b98 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -1,8 +1,6 @@ -using System; using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Classes; -using Penumbra.GameData.Actors; namespace Glamourer.Events; @@ -15,7 +13,8 @@ namespace Glamourer.Events; /// Parameter is any additional data depending on the type of change. /// /// -public sealed class StateChanged : EventWrapper, StateChanged.Priority> +public sealed class StateChanged() + : EventWrapper(nameof(StateChanged)) { public enum Type { @@ -62,11 +61,4 @@ public sealed class StateChanged : EventWrapper Invoke(this, type, source, state, actors, data); } diff --git a/Glamourer/Events/TabSelected.cs b/Glamourer/Events/TabSelected.cs index f43fee0..82a6abb 100644 --- a/Glamourer/Events/TabSelected.cs +++ b/Glamourer/Events/TabSelected.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Gui; using OtterGui.Classes; @@ -12,8 +11,8 @@ namespace Glamourer.Events; /// Parameter is the design to select if the tab is the designs tab. /// /// -public sealed class TabSelected : EventWrapper, - TabSelected.Priority> +public sealed class TabSelected() + : EventWrapper(nameof(TabSelected)) { public enum Priority { @@ -23,11 +22,4 @@ public sealed class TabSelected : EventWrapper MainWindow = 1, } - - public TabSelected() - : base(nameof(TabSelected)) - { } - - public void Invoke(MainWindow.TabType type, Design? design) - => Invoke(this, type, design); } diff --git a/Glamourer/Events/VisorStateChanged.cs b/Glamourer/Events/VisorStateChanged.cs index 0cd83d1..d71eccd 100644 --- a/Glamourer/Events/VisorStateChanged.cs +++ b/Glamourer/Events/VisorStateChanged.cs @@ -1,4 +1,3 @@ -using System; using Glamourer.Interop.Structs; using OtterGui.Classes; @@ -12,22 +11,12 @@ namespace Glamourer.Events; /// Parameter is whether to call the original function. /// /// -public sealed class VisorStateChanged : EventWrapper>, VisorStateChanged.Priority> +public sealed class VisorStateChanged() + : EventWrapperRef2(nameof(VisorStateChanged)) { public enum Priority { /// StateListener = 0, } - - public VisorStateChanged() - : base(nameof(VisorStateChanged)) - { } - - public void Invoke(Model model, ref bool state) - { - var value = new Ref(state); - Invoke(this, model, value); - state = value; - } } diff --git a/Glamourer/Events/WeaponLoading.cs b/Glamourer/Events/WeaponLoading.cs index 1224e7f..41da2aa 100644 --- a/Glamourer/Events/WeaponLoading.cs +++ b/Glamourer/Events/WeaponLoading.cs @@ -1,4 +1,3 @@ -using System; using Glamourer.Interop.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; @@ -14,7 +13,8 @@ namespace Glamourer.Events; /// Parameter is the model values to change the weapon to. /// /// -public sealed class WeaponLoading : EventWrapper>, WeaponLoading.Priority> +public sealed class WeaponLoading() + : EventWrapperRef3(nameof(WeaponLoading)) { public enum Priority { @@ -24,15 +24,4 @@ public sealed class WeaponLoading : EventWrapper AutoDesignApplier = -1, } - - public WeaponLoading() - : base(nameof(WeaponLoading)) - { } - - public void Invoke(Actor actor, EquipSlot slot, ref CharacterWeapon weapon) - { - var value = new Ref(weapon); - Invoke(this, actor, slot, value); - weapon = value; - } } diff --git a/Glamourer/Events/WeaponVisibilityChanged.cs b/Glamourer/Events/WeaponVisibilityChanged.cs index 561c793..a94eabf 100644 --- a/Glamourer/Events/WeaponVisibilityChanged.cs +++ b/Glamourer/Events/WeaponVisibilityChanged.cs @@ -1,4 +1,3 @@ -using System; using Glamourer.Interop.Structs; using OtterGui.Classes; @@ -11,22 +10,11 @@ namespace Glamourer.Events; /// Parameter is the new state. /// /// -public sealed class WeaponVisibilityChanged : EventWrapper>, WeaponVisibilityChanged.Priority> +public sealed class WeaponVisibilityChanged() : EventWrapperRef2(nameof(WeaponVisibilityChanged)) { public enum Priority { /// StateListener = 0, } - - public WeaponVisibilityChanged() - : base(nameof(WeaponVisibilityChanged)) - { } - - public void Invoke(Actor actor, ref bool state) - { - var value = new Ref(state); - Invoke(this, actor, value); - state = value; - } } diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 00b94bb..c9d76ef 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -5,7 +5,6 @@ using Glamourer.Gui; using Glamourer.Interop; using Glamourer.Services; using Glamourer.State; -using Microsoft.Extensions.DependencyInjection; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Services; @@ -34,6 +33,8 @@ public class Glamourer : IDalamudPlugin { _services = ServiceManagerA.CreateProvider(pluginInterface, Log); Messager = _services.GetService(); + _services.EnsureRequiredServices(); + _services.GetService(); _services.GetService(); _services.GetService(); diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 947ce43..9acb418 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -14,7 +14,7 @@ namespace Glamourer.Interop; /// Changes in Race, body type or Gender are probably ignored. /// This operates on draw objects, not game objects. /// -public unsafe class ChangeCustomizeService : EventWrapper>, ChangeCustomizeService.Priority> +public unsafe class ChangeCustomizeService : EventWrapperRef2 { private readonly PenumbraReloaded _penumbraReloaded; private readonly IGameInteropProvider _interop; @@ -79,11 +79,7 @@ public unsafe class ChangeCustomizeService : EventWrapper(*(CustomizeArray*)data); - Invoke(this, (Model)human, customize); - *(CustomizeArray*)data = customize.Value; - } + Invoke(human, ref *(CustomizeArray*)data); return _changeCustomizeHook.Original(human, data, skipEquipment); } diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 7db9a59..7764573 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Hooking; +using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; @@ -15,10 +14,10 @@ namespace Glamourer.Interop; /// /// Parameter is the model with an update. /// Parameter is the equipment slot changed. -/// Parameter is the whether the crest will be shown. +/// Parameter is whether the crest will be shown. /// /// -public sealed unsafe class CrestService : EventWrapper>, CrestService.Priority> +public sealed unsafe class CrestService : EventWrapperRef3 { public enum Priority { @@ -72,9 +71,9 @@ public sealed unsafe class CrestService : EventWrapper(((CrestFlag)crestFlags).HasFlag(slot)); - Invoke(this, actor, slot, newValue); - crestFlags = (byte)(newValue.Value ? crestFlags | (byte)slot : crestFlags & (byte)~slot); + var newValue = ((CrestFlag)crestFlags).HasFlag(slot); + Invoke(actor, slot, ref newValue); + crestFlags = (byte)(newValue ? crestFlags | (byte)slot : crestFlags & (byte)~slot); } Glamourer.Log.Verbose( diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index db3922c..2f64cf8 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -33,7 +33,7 @@ public static class ServiceManagerA { public static ServiceManager CreateProvider(DalamudPluginInterface pi, Logger log) { - EventWrapper.ChangeLogger(log); + EventWrapperBase.ChangeLogger(log); var services = new ServiceManager(log) .AddExistingService(log) .AddMeta() diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index bbc519f..3651553 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -138,7 +138,7 @@ public class StateListener : IDisposable ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender); } - private unsafe void OnCustomizeChange(Model model, Ref customize) + private unsafe void OnCustomizeChange(Model model, ref CustomizeArray customize) { if (!model.IsHuman) return; @@ -151,7 +151,7 @@ public class StateListener : IDisposable || !_manager.TryGetValue(identifier, out var state)) return; - UpdateCustomize(actor, state, ref customize.Value, false); + UpdateCustomize(actor, state, ref customize, false); } private void UpdateCustomize(Actor actor, ActorState state, ref CustomizeArray customize, bool checkTransform) @@ -199,7 +199,7 @@ public class StateListener : IDisposable /// A draw model loads a new equipment piece. /// Update base data, apply or update model data, and protect against restricted gear. /// - private void OnSlotUpdating(Model model, EquipSlot slot, Ref armor, Ref returnValue) + private void OnSlotUpdating(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue) { var actor = _penumbra.GameObjectFromDrawObject(model); if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) @@ -212,16 +212,16 @@ public class StateListener : IDisposable if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) { - HandleEquipSlot(actor, state, slot, ref armor.Value); + HandleEquipSlot(actor, state, slot, ref armor); locked = state[slot, false] is StateChanged.Source.Ipc; } - _funModule.ApplyFunToSlot(actor, ref armor.Value, slot); + _funModule.ApplyFunToSlot(actor, ref armor, slot); if (!_config.UseRestrictedGearProtection || locked) return; var customize = model.GetCustomize(); - (_, armor.Value) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender); + (_, armor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender); } private void OnMovedEquipment((EquipSlot, uint, StainId)[] items) @@ -264,20 +264,20 @@ public class StateListener : IDisposable /// Update base data, apply or update model data. /// Verify consistent weapon types. /// - private void OnWeaponLoading(Actor actor, EquipSlot slot, Ref weapon) + private void OnWeaponLoading(Actor actor, EquipSlot slot, ref CharacterWeapon weapon) { if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; // Fist weapon gauntlet hack. - if (slot is EquipSlot.OffHand && weapon.Value.Variant == 0 && weapon.Value.Weapon.Id != 0 && _lastFistOffhand.Weapon.Id != 0) - weapon.Value = _lastFistOffhand; + if (slot is EquipSlot.OffHand && weapon.Variant == 0 && weapon.Weapon.Id != 0 && _lastFistOffhand.Weapon.Id != 0) + weapon = _lastFistOffhand; if (!actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) return; - ref var actorWeapon = ref weapon.Value; + ref var actorWeapon = ref weapon; var baseType = state.BaseData.Item(slot).Type; var apply = false; switch (UpdateBaseData(actor, state, slot, actorWeapon)) @@ -311,25 +311,16 @@ public class StateListener : IDisposable } // Fist Weapon Offhand hack. - if (slot is EquipSlot.MainHand && weapon.Value.Skeleton.Id is > 1600 and < 1651) - _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Value.Skeleton.Id + 50), weapon.Value.Weapon, weapon.Value.Variant, - weapon.Value.Stain); + if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651) + _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, + weapon.Stain); - _funModule.ApplyFunToWeapon(actor, ref weapon.Value, slot); + _funModule.ApplyFunToWeapon(actor, ref weapon, slot); } /// Update base data for a single changed equipment slot. private UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterArmor armor) { - bool FistWeaponGauntletHack() - { - if (slot is not EquipSlot.Hands) - return false; - - var offhand = actor.GetOffhand(); - return offhand.Variant == 0 && offhand.Weapon.Id != 0 && armor.Set.Id == offhand.Weapon.Id; - } - var actorArmor = actor.GetArmor(slot); var fistWeapon = FistWeaponGauntletHack(); @@ -373,6 +364,15 @@ public class StateListener : IDisposable } return change; + + bool FistWeaponGauntletHack() + { + if (slot is not EquipSlot.Hands) + return false; + + var offhand = actor.GetOffhand(); + return offhand.Variant == 0 && offhand.Weapon.Id != 0 && armor.Set.Id == offhand.Weapon.Id; + } } /// Handle a full equip slot update for base data and model data. @@ -406,7 +406,7 @@ public class StateListener : IDisposable } } - private void OnCrestChange(Actor actor, CrestFlag slot, Ref value) + private void OnCrestChange(Actor actor, CrestFlag slot, ref bool value) { if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; @@ -415,17 +415,17 @@ public class StateListener : IDisposable || !_manager.TryGetValue(identifier, out var state)) return; - switch (UpdateBaseCrest(actor, state, slot, value.Value)) + switch (UpdateBaseCrest(actor, state, slot, value)) { case UpdateState.Change: if (state[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game); else - value.Value = state.ModelData.Crest(slot); + value = state.ModelData.Crest(slot); break; case UpdateState.NoChange: case UpdateState.HatHack: - value.Value = state.ModelData.Crest(slot); + value = state.ModelData.Crest(slot); break; case UpdateState.Transformed: break; } @@ -540,7 +540,7 @@ public class StateListener : IDisposable } /// Handle visor state changes made by the game. - private void OnVisorChange(Model model, Ref value) + private void OnVisorChange(Model model, ref bool value) { // Skip updates when in customize update. if (ChangeCustomizeService.InUpdate.InMethod) @@ -565,19 +565,19 @@ public class StateListener : IDisposable // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) - value.Value = state.ModelData.IsVisorToggled(); + value = state.ModelData.IsVisorToggled(); else _manager.ChangeVisorState(state, value, StateChanged.Source.Game); } else { // if base state did not change, overwrite the value with the model state one. - value.Value = state.ModelData.IsVisorToggled(); + value = state.ModelData.IsVisorToggled(); } } /// Handle Hat Visibility changes. These act on the game object. - private void OnHeadGearVisibilityChange(Actor actor, Ref value) + private void OnHeadGearVisibilityChange(Actor actor, ref bool value) { if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; @@ -598,19 +598,19 @@ public class StateListener : IDisposable // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) - value.Value = state.ModelData.IsHatVisible(); + value = state.ModelData.IsHatVisible(); else _manager.ChangeHatState(state, value, StateChanged.Source.Game); } else { // if base state did not change, overwrite the value with the model state one. - value.Value = state.ModelData.IsHatVisible(); + value = state.ModelData.IsHatVisible(); } } /// Handle Weapon Visibility changes. These act on the game object. - private void OnWeaponVisibilityChange(Actor actor, Ref value) + private void OnWeaponVisibilityChange(Actor actor, ref bool value) { if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; @@ -631,14 +631,14 @@ public class StateListener : IDisposable // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) - value.Value = state.ModelData.IsWeaponVisible(); + value = state.ModelData.IsWeaponVisible(); else _manager.ChangeWeaponState(state, value, StateChanged.Source.Game); } else { // if base state did not change, overwrite the value with the model state one. - value.Value = state.ModelData.IsWeaponVisible(); + value = state.ModelData.IsWeaponVisible(); } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index dc83305..a3f1de6 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -476,7 +476,7 @@ public class StateManager( actors = ApplyAll(state, redraw, true); Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors); + _event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors, null); } public void ResetStateFixed(ActorState state, uint key = 0) diff --git a/OtterGui b/OtterGui index e58c3c1..e4a8261 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit e58c3c1240cda9d2d2b54f5ab7b8c729c1251fd4 +Subproject commit e4a82619332aade2748a381956a1ab8934de6211 From bbf460f5e0ef50878a5ba4bfdbb25d572fc2c30b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 7 Jan 2024 22:21:55 +0100 Subject: [PATCH 135/786] re-fix slot check in restricted gear. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 5f91b29..55535b4 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 5f91b296a0d043aee6add38f707ed570f96b1f9f +Subproject commit 55535b445f51f09ada75d2803484025bb51cca01 From 9361560350ba86c754e3f6633ab9ac1733ae06e2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 8 Jan 2024 22:58:44 +0100 Subject: [PATCH 136/786] Add CustomizeParameter data. --- Glamourer/GameData/CustomizeParameterData.cs | 220 ++++++++++++++++++ Glamourer/GameData/CustomizeParameterFlag.cs | 52 +++++ .../CustomizeParameterDrawData.cs | 38 +++ .../Customization/CustomizeParameterDrawer.cs | 96 ++++++++ 4 files changed, 406 insertions(+) create mode 100644 Glamourer/GameData/CustomizeParameterData.cs create mode 100644 Glamourer/GameData/CustomizeParameterFlag.cs create mode 100644 Glamourer/Gui/Customization/CustomizeParameterDrawData.cs create mode 100644 Glamourer/Gui/Customization/CustomizeParameterDrawer.cs diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs new file mode 100644 index 0000000..16a6dad --- /dev/null +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -0,0 +1,220 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using FFXIVClientStructs.FFXIV.Shader; + +namespace Glamourer.GameData; + +public struct CustomizeParameterData +{ + public Vector3 SkinDiffuse; + public Vector3 SkinSpecular; + public Vector3 LipDiffuse; + public Vector3 HairDiffuse; + public Vector3 HairSpecular; + public Vector3 HairHighlight; + public Vector3 LeftEye; + public Vector3 RightEye; + public Vector3 FeatureColor; + public float FacePaintUvMultiplier; + public float FacePaintUvOffset; + public float MuscleTone; + public float LipOpacity; + + public Vector3 this[CustomizeParameterFlag flag] + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + readonly get + { + return flag switch + { + CustomizeParameterFlag.SkinDiffuse => SkinDiffuse, + CustomizeParameterFlag.MuscleTone => new Vector3(MuscleTone, 0, 0), + CustomizeParameterFlag.SkinSpecular => SkinSpecular, + CustomizeParameterFlag.LipDiffuse => LipDiffuse, + CustomizeParameterFlag.LipOpacity => new Vector3(LipOpacity, 0, 0), + CustomizeParameterFlag.HairDiffuse => HairDiffuse, + CustomizeParameterFlag.HairSpecular => HairSpecular, + CustomizeParameterFlag.HairHighlight => HairHighlight, + CustomizeParameterFlag.LeftEye => LeftEye, + CustomizeParameterFlag.RightEye => RightEye, + CustomizeParameterFlag.FeatureColor => FeatureColor, + CustomizeParameterFlag.FacePaintUvMultiplier => new Vector3(FacePaintUvMultiplier, 0, 0), + CustomizeParameterFlag.FacePaintUvOffset => new Vector3(FacePaintUvOffset, 0, 0), + _ => Vector3.Zero, + }; + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + set => Set(flag, value); + } + + public bool Set(CustomizeParameterFlag flag, Vector3 value) + { + return flag switch + { + CustomizeParameterFlag.SkinDiffuse => SetIfDifferent(ref SkinDiffuse, value), + CustomizeParameterFlag.MuscleTone => SetIfDifferent(ref MuscleTone, Math.Clamp(value[0], -100, 100)), + CustomizeParameterFlag.SkinSpecular => SetIfDifferent(ref SkinSpecular, value), + CustomizeParameterFlag.LipDiffuse => SetIfDifferent(ref LipDiffuse, value), + CustomizeParameterFlag.LipOpacity => SetIfDifferent(ref LipOpacity, Math.Clamp(value[0], -100, 100)), + CustomizeParameterFlag.HairDiffuse => SetIfDifferent(ref HairDiffuse, value), + CustomizeParameterFlag.HairSpecular => SetIfDifferent(ref HairSpecular, value), + CustomizeParameterFlag.HairHighlight => SetIfDifferent(ref HairHighlight, value), + CustomizeParameterFlag.LeftEye => SetIfDifferent(ref LeftEye, value), + CustomizeParameterFlag.RightEye => SetIfDifferent(ref RightEye, value), + CustomizeParameterFlag.FeatureColor => SetIfDifferent(ref FeatureColor, value), + CustomizeParameterFlag.FacePaintUvMultiplier => SetIfDifferent(ref FacePaintUvMultiplier, value[0]), + CustomizeParameterFlag.FacePaintUvOffset => SetIfDifferent(ref FacePaintUvOffset, value[0]), + _ => false, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public readonly void Apply(ref CustomizeParameter parameters, CustomizeParameterFlag flags = CustomizeParameterExtensions.All) + { + if (flags.HasFlag(CustomizeParameterFlag.SkinDiffuse)) + parameters.SkinColor = Convert(SkinDiffuse, parameters.SkinColor.W); + if (flags.HasFlag(CustomizeParameterFlag.MuscleTone)) + parameters.SkinColor.W = MuscleTone; + if (flags.HasFlag(CustomizeParameterFlag.SkinSpecular)) + parameters.SkinFresnelValue0 = Convert(SkinSpecular, 0); + if (flags.HasFlag(CustomizeParameterFlag.LipDiffuse)) + parameters.LipColor = Convert(LipDiffuse, parameters.LipColor.W); + if (flags.HasFlag(CustomizeParameterFlag.LipOpacity)) + parameters.LipColor.W = LipOpacity; + if (flags.HasFlag(CustomizeParameterFlag.HairDiffuse)) + parameters.MainColor = Convert(HairDiffuse); + if (flags.HasFlag(CustomizeParameterFlag.HairSpecular)) + parameters.HairFresnelValue0 = Convert(HairSpecular); + if (flags.HasFlag(CustomizeParameterFlag.HairHighlight)) + parameters.MeshColor = Convert(HairHighlight); + if (flags.HasFlag(CustomizeParameterFlag.LeftEye)) + parameters.LeftColor = Convert(LeftEye, parameters.LeftColor.W); + if (flags.HasFlag(CustomizeParameterFlag.RightEye)) + parameters.RightColor = Convert(RightEye, parameters.RightColor.W); + if (flags.HasFlag(CustomizeParameterFlag.FeatureColor)) + parameters.OptionColor = Convert(FeatureColor); + if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvMultiplier)) + parameters.LeftColor.W = FacePaintUvMultiplier; + if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvOffset)) + parameters.RightColor.W = FacePaintUvOffset; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public readonly void ApplySingle(ref CustomizeParameter parameters, CustomizeParameterFlag flag) + { + switch (flag) + { + case CustomizeParameterFlag.SkinDiffuse: + parameters.SkinColor = Convert(SkinDiffuse, parameters.SkinColor.W); + break; + case CustomizeParameterFlag.MuscleTone: + parameters.SkinColor.W = MuscleTone; + break; + case CustomizeParameterFlag.SkinSpecular: + parameters.SkinFresnelValue0 = Convert(SkinSpecular, 0); + break; + case CustomizeParameterFlag.LipDiffuse: + parameters.LipColor = Convert(LipDiffuse, parameters.LipColor.W); + break; + case CustomizeParameterFlag.LipOpacity: + parameters.LipColor.W = LipOpacity; + break; + case CustomizeParameterFlag.HairDiffuse: + parameters.MainColor = Convert(HairDiffuse); + break; + case CustomizeParameterFlag.HairSpecular: + parameters.HairFresnelValue0 = Convert(HairSpecular); + break; + case CustomizeParameterFlag.HairHighlight: + parameters.MeshColor = Convert(HairHighlight); + break; + case CustomizeParameterFlag.LeftEye: + parameters.LeftColor = Convert(LeftEye, parameters.LeftColor.W); + break; + case CustomizeParameterFlag.RightEye: + parameters.RightColor = Convert(RightEye, parameters.RightColor.W); + break; + case CustomizeParameterFlag.FeatureColor: + parameters.OptionColor = Convert(FeatureColor); + break; + case CustomizeParameterFlag.FacePaintUvMultiplier: + parameters.LeftColor.W = FacePaintUvMultiplier; + break; + case CustomizeParameterFlag.FacePaintUvOffset: + parameters.RightColor.W = FacePaintUvOffset; + break; + } + } + + public static CustomizeParameterData FromParameters(in CustomizeParameter parameter) + => new() + { + FacePaintUvOffset = parameter.RightColor.W, + FacePaintUvMultiplier = parameter.LeftColor.W, + MuscleTone = parameter.SkinColor.W, + LipOpacity = parameter.LipColor.W, + SkinDiffuse = Convert(parameter.SkinColor), + SkinSpecular = Convert(parameter.SkinFresnelValue0), + LipDiffuse = Convert(parameter.LipColor), + HairDiffuse = Convert(parameter.MainColor), + HairSpecular = Convert(parameter.HairFresnelValue0), + HairHighlight = Convert(parameter.MeshColor), + LeftEye = Convert(parameter.LeftColor), + RightEye = Convert(parameter.RightColor), + FeatureColor = Convert(parameter.OptionColor), + }; + + public static Vector3 FromParameter(in CustomizeParameter parameter, CustomizeParameterFlag flag) + => flag switch + { + CustomizeParameterFlag.SkinDiffuse => Convert(parameter.SkinColor), + CustomizeParameterFlag.MuscleTone => new Vector3(parameter.SkinColor.W), + CustomizeParameterFlag.SkinSpecular => Convert(parameter.SkinFresnelValue0), + CustomizeParameterFlag.LipDiffuse => Convert(parameter.LipColor), + CustomizeParameterFlag.LipOpacity => new Vector3(parameter.LipColor.W), + CustomizeParameterFlag.HairDiffuse => Convert(parameter.MainColor), + CustomizeParameterFlag.HairSpecular => Convert(parameter.HairFresnelValue0), + CustomizeParameterFlag.HairHighlight => Convert(parameter.MeshColor), + CustomizeParameterFlag.LeftEye => Convert(parameter.LeftColor), + CustomizeParameterFlag.RightEye => Convert(parameter.RightColor), + CustomizeParameterFlag.FeatureColor => Convert(parameter.OptionColor), + CustomizeParameterFlag.FacePaintUvMultiplier => new Vector3(parameter.LeftColor.W), + CustomizeParameterFlag.FacePaintUvOffset => new Vector3(parameter.RightColor.W), + _ => Vector3.Zero, + }; + + private static FFXIVClientStructs.FFXIV.Common.Math.Vector4 Convert(Vector3 value, float w) + => new(value.X * value.X, value.Y * value.Y, value.Z * value.Z, w); + + private static Vector3 Convert(FFXIVClientStructs.FFXIV.Common.Math.Vector3 value) + => new((float)Math.Sqrt(value.X), (float)Math.Sqrt(value.Y), (float)Math.Sqrt(value.Z)); + + private static Vector3 Convert(FFXIVClientStructs.FFXIV.Common.Math.Vector4 value) + => new((float)Math.Sqrt(value.X), (float)Math.Sqrt(value.Y), (float)Math.Sqrt(value.Z)); + + private static FFXIVClientStructs.FFXIV.Common.Math.Vector3 Convert(Vector3 value) + => new(value.X * value.X, value.Y * value.Y, value.Z * value.Z); + + private static bool SetIfDifferent(ref Vector3 val, Vector3 @new) + { + @new.X = Math.Clamp(@new.X, 0, 1); + @new.Y = Math.Clamp(@new.Y, 0, 1); + @new.Z = Math.Clamp(@new.Z, 0, 1); + + if (@new == val) + return false; + + val = @new; + return true; + } + + private static bool SetIfDifferent(ref T val, T @new) where T : IEqualityOperators + { + if (@new == val) + return false; + + val = @new; + return true; + } +} diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs new file mode 100644 index 0000000..d874538 --- /dev/null +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace Glamourer.GameData; + +[Flags] +public enum CustomizeParameterFlag : ushort +{ + SkinDiffuse = 0x0001, + MuscleTone = 0x0002, + SkinSpecular = 0x0004, + LipDiffuse = 0x0008, + LipOpacity = 0x0010, + HairDiffuse = 0x0020, + HairSpecular = 0x0040, + HairHighlight = 0x0080, + LeftEye = 0x0100, + RightEye = 0x0200, + FeatureColor = 0x0400, + FacePaintUvMultiplier = 0x0800, + FacePaintUvOffset = 0x1000, +} + +public static class CustomizeParameterExtensions +{ + public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x1FFF; + + public const CustomizeParameterFlag Triples = All + & ~(CustomizeParameterFlag.MuscleTone + | CustomizeParameterFlag.LipOpacity + | CustomizeParameterFlag.FacePaintUvOffset + | CustomizeParameterFlag.FacePaintUvMultiplier); + + public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone | CustomizeParameterFlag.LipOpacity; + public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier; + + public static readonly IReadOnlyList AllFlags = [.. Enum.GetValues()]; + public static readonly IReadOnlyList TripleFlags = AllFlags.Where(f => Triples.HasFlag(f)).ToArray(); + public static readonly IReadOnlyList PercentageFlags = AllFlags.Where(f => Percentages.HasFlag(f)).ToArray(); + public static readonly IReadOnlyList ValueFlags = AllFlags.Where(f => Values.HasFlag(f)).ToArray(); + + public static int Count(this CustomizeParameterFlag flag) + => Triples.HasFlag(flag) ? 3 : 1; + + public static IEnumerable Iterate(this CustomizeParameterFlag flags) + => AllFlags.Where(f => flags.HasFlag(f)); + + public static int ToInternalIndex(this CustomizeParameterFlag flag) + => BitOperations.TrailingZeroCount((uint)flag); +} diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs new file mode 100644 index 0000000..09426e2 --- /dev/null +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs @@ -0,0 +1,38 @@ +using System; +using System.Numerics; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.GameData; +using Glamourer.State; + +namespace Glamourer.Gui.Customization; + +public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data) +{ + public readonly CustomizeParameterFlag Flag = flag; + public bool Locked; + public bool DisplayApplication; + + public Action ValueSetter = null!; + public Action ApplySetter = null!; + public Vector3 CurrentValue = data.Parameters[flag]; + public bool CurrentApply; + + public static CustomizeParameterDrawData FromDesign(DesignManager manager, Design design, CustomizeParameterFlag flag) + => new(flag, design.DesignData) + { + Locked = design.WriteProtected(), + DisplayApplication = true, + CurrentApply = design.DoApplyParameter(flag), + ValueSetter = v => manager.ChangeCustomizeParameter(design, flag, v), + ApplySetter = v => manager.ChangeApplyParameter(design, flag, v), + }; + + public static CustomizeParameterDrawData FromState(StateManager manager, ActorState state, CustomizeParameterFlag flag) + => new(flag, state.ModelData) + { + Locked = state.IsLocked, + DisplayApplication = false, + ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, StateChanged.Source.Manual), + }; +} diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs new file mode 100644 index 0000000..41760ed --- /dev/null +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -0,0 +1,96 @@ +using Glamourer.Designs; +using Glamourer.GameData; +using Glamourer.State; +using System.Numerics; +using Dalamud.Interface.Utility.Raii; +using ImGuiNET; +using OtterGui.Services; + +namespace Glamourer.Gui.Customization; + +public class CustomizeParameterDrawer(Configuration config) : IService +{ + public void Draw(DesignManager designManager, Design design) + { + foreach (var flag in CustomizeParameterExtensions.TripleFlags) + DrawColorInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); + + foreach (var flag in CustomizeParameterExtensions.PercentageFlags) + DrawPercentageInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); + + foreach (var flag in CustomizeParameterExtensions.ValueFlags) + DrawValueInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); + } + + public void Draw(StateManager stateManager, ActorState state) + { + foreach (var flag in CustomizeParameterExtensions.TripleFlags) + DrawColorInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + + foreach (var flag in CustomizeParameterExtensions.PercentageFlags) + DrawPercentageInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + + foreach (var flag in CustomizeParameterExtensions.ValueFlags) + DrawValueInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + } + + private void DrawColorInput(in CustomizeParameterDrawData data) + { + using var id = ImRaii.PushId((int)data.Flag); + var value = data.CurrentValue; + using (_ = ImRaii.Disabled(data.Locked)) + { + if (ImGui.ColorEdit3("##value", ref value, ImGuiColorEditFlags.Float)) + data.ValueSetter(value); + } + + DrawApplyAndLabel(data); + } + + private void DrawValueInput(in CustomizeParameterDrawData data) + { + using var id = ImRaii.PushId((int)data.Flag); + var value = data.CurrentValue[0]; + + using (_ = ImRaii.Disabled(data.Locked)) + { + if (ImGui.InputFloat("##value", ref value, 0.1f, 0.5f)) + data.ValueSetter(new Vector3(value)); + } + + DrawApplyAndLabel(data); + } + + private void DrawPercentageInput(in CustomizeParameterDrawData data) + { + using var id = ImRaii.PushId((int)data.Flag); + var value = data.CurrentValue[0] * 100f; + + using (_ = ImRaii.Disabled(data.Locked)) + { + if (ImGui.SliderFloat("##value", ref value, 0, 100, "%.2f", ImGuiSliderFlags.AlwaysClamp)) + data.ValueSetter(new Vector3(value / 100f)); + } + + DrawApplyAndLabel(data); + } + + private static void DrawApply(in CustomizeParameterDrawData data) + { + if (UiHelpers.DrawCheckbox("##apply", "Apply this custom parameter when applying the Design.", data.CurrentApply, out var enabled, + data.Locked)) + data.ApplySetter(enabled); + } + + private void DrawApplyAndLabel(in CustomizeParameterDrawData data) + { + if (data.DisplayApplication && !config.HideApplyCheckmarks) + { + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + DrawApply(data); + } + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.TextUnformatted(data.Flag.ToString()); + } +} From 1a0a0f681f32767de18e8920bf05ef10f342cead Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 8 Jan 2024 23:00:02 +0100 Subject: [PATCH 137/786] Add parameter handling. --- Glamourer/Automation/AutoDesign.cs | 9 +- Glamourer/Automation/AutoDesignApplier.cs | 33 +++- Glamourer/Configuration.cs | 1 + Glamourer/Designs/Design.cs | 32 ++-- Glamourer/Designs/DesignBase.cs | 173 +++++++++++++++--- Glamourer/Designs/DesignConverter.cs | 21 ++- Glamourer/Designs/DesignData.cs | 50 ++--- Glamourer/Designs/DesignManager.cs | 30 +++ Glamourer/Events/DesignChanged.cs | 6 + Glamourer/Events/StateChanged.cs | 3 + Glamourer/Gui/DesignQuickBar.cs | 4 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 30 ++- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 12 ++ .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 24 +++ Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 101 +++++++--- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 18 +- Glamourer/Gui/Tabs/SettingsTab.cs | 13 +- Glamourer/Gui/UiHelpers.cs | 11 +- Glamourer/Interop/ChangeCustomizeService.cs | 22 ++- Glamourer/Interop/Structs/ActorData.cs | 1 - Glamourer/Services/CommandService.cs | 3 +- Glamourer/State/ActorState.cs | 17 +- Glamourer/State/StateApplier.cs | 44 ++++- Glamourer/State/StateEditor.cs | 23 ++- Glamourer/State/StateListener.cs | 63 ++++++- Glamourer/State/StateManager.cs | 42 +++++ 27 files changed, 633 insertions(+), 155 deletions(-) diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 029a2ec..364c358 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -1,5 +1,6 @@ using System; using Glamourer.Designs; +using Glamourer.GameData; using Glamourer.Interop.Structs; using Glamourer.State; using Newtonsoft.Json.Linq; @@ -80,20 +81,24 @@ public class AutoDesign return ret; } - public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat() + public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor, bool + ApplyWeapon, bool ApplyWet) ApplyWhat() { var equipFlags = (ApplicationType.HasFlag(Type.Weapons) ? WeaponFlags : 0) | (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0) | (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0) | (ApplicationType.HasFlag(Type.GearCustomization) ? StainFlags : 0); var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0; + var parameterFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeParameterExtensions.All : 0; var crestFlag = ApplicationType.HasFlag(Type.GearCustomization) ? CrestExtensions.AllRelevant : 0; if (Revert) - return (equipFlags, customizeFlags, crestFlag, ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Armor), + return (equipFlags, customizeFlags, crestFlag, parameterFlags, ApplicationType.HasFlag(Type.Armor), + ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations)); return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, crestFlag & Design.ApplyCrest, + parameterFlags & Design.ApplyParameters, ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(), ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(), ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(), diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 90fb00d..dde2146 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -6,6 +6,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Designs; using Glamourer.Events; +using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; @@ -271,10 +272,11 @@ public class AutoDesignApplier : IDisposable private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange) { - EquipFlag totalEquipFlags = 0; - CustomizeFlag totalCustomizeFlags = 0; - CrestFlag totalCrestFlags = 0; - byte totalMetaFlags = 0; + EquipFlag totalEquipFlags = 0; + CustomizeFlag totalCustomizeFlags = 0; + CrestFlag totalCrestFlags = 0; + CustomizeParameterFlag totalParameterFlags = 0; + byte totalMetaFlags = 0; if (set.BaseState == AutoDesignSet.Base.Game) _state.ResetStateFixed(state); else if (!respectManual) @@ -297,18 +299,19 @@ public class AutoDesignApplier : IDisposable if (!data.IsHuman) continue; - var (equipFlags, customizeFlags, crestFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat(); + var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat(); ReduceMeta(state, data, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual, source); ReduceCustomize(state, data, customizeFlags, ref totalCustomizeFlags, respectManual, source); ReduceEquip(state, data, equipFlags, ref totalEquipFlags, respectManual, source, fromJobChange); ReduceCrests(state, data, crestFlags, ref totalCrestFlags, respectManual, source); + ReduceParameters(state, data, parameterFlags, ref totalParameterFlags, respectManual, source); } if (totalCustomizeFlags != 0) state.ModelData.ModelId = 0; } - /// Get world-specific first and all-world afterwards. + /// Get world-specific first and all-world afterward. private bool GetPlayerSet(ActorIdentifier identifier, [NotNullWhen(true)] out AutoDesignSet? set) { switch (identifier.Type) @@ -349,6 +352,24 @@ public class AutoDesignApplier : IDisposable } } + private void ReduceParameters(ActorState state, in DesignData design, CustomizeParameterFlag parameterFlags, + ref CustomizeParameterFlag totalParameterFlags, bool respectManual, StateChanged.Source source) + { + parameterFlags &= ~totalParameterFlags; + if (parameterFlags == 0) + return; + + foreach (var flag in CustomizeParameterExtensions.AllFlags) + { + if (!parameterFlags.HasFlag(flag)) + continue; + + if (!respectManual || state[flag] is not StateChanged.Source.Manual) + _state.ChangeCustomizeParameter(state, flag, design.Parameters[flag], source); + totalParameterFlags |= flag; + } + } + private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual, StateChanged.Source source, bool fromJobChange) { diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 841fe68..cb093bf 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -38,6 +38,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool RevertManualChangesOnZoneChange { get; set; } = false; public bool ShowQuickBarInTabs { get; set; } = true; public bool OpenWindowAtStart { get; set; } = false; + public bool UseAdvancedParameters { get; set; } = false; 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/Designs/Design.cs b/Glamourer/Designs/Design.cs index 7382fce..709e5ca 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -54,20 +54,21 @@ public sealed class Design : DesignBase, ISavable public new JObject JsonSerialize() { var ret = new JObject() - { - ["FileVersion"] = FileVersion, - ["Identifier"] = Identifier, - ["CreationDate"] = CreationDate, - ["LastEdit"] = LastEdit, - ["Name"] = Name.Text, - ["Description"] = Description, - ["Color"] = Color, - ["Tags"] = JArray.FromObject(Tags), - ["WriteProtected"] = WriteProtected(), - ["Equipment"] = SerializeEquipment(), - ["Customize"] = SerializeCustomize(), - ["Mods"] = SerializeMods(), - }; + { + ["FileVersion"] = FileVersion, + ["Identifier"] = Identifier, + ["CreationDate"] = CreationDate, + ["LastEdit"] = LastEdit, + ["Name"] = Name.Text, + ["Description"] = Description, + ["Color"] = Color, + ["Tags"] = JArray.FromObject(Tags), + ["WriteProtected"] = WriteProtected(), + ["Equipment"] = SerializeEquipment(), + ["Customize"] = SerializeCustomize(), + ["Parameters"] = SerializeParameters(), + ["Mods"] = SerializeMods(), + }; return ret; } @@ -133,7 +134,8 @@ public sealed class Design : DesignBase, ISavable LoadCustomize(customizations, json["Customize"], design, design.Name, true, false); LoadEquip(items, json["Equipment"], design, design.Name, true); LoadMods(json["Mods"], design); - design.Color = json["Color"]?.ToObject() ?? string.Empty; + LoadParameters(json["Parameters"], design, design.Name); + design.Color = json["Color"]?.ToObject() ?? string.Empty; return design; } diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index aeb48e0..aa42834 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -6,7 +6,9 @@ using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Numerics; using Penumbra.GameData.DataContainers; namespace Glamourer.Designs; @@ -71,6 +73,8 @@ public class DesignBase private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; public CustomizeSet CustomizeSet { get; private set; } + public CustomizeParameterFlag ApplyParameters { get; private set; } + internal CustomizeFlag ApplyCustomize { get => _applyCustomize.FixApplication(CustomizeSet); @@ -174,6 +178,9 @@ public class DesignBase public bool DoApplyCrest(CrestFlag slot) => ApplyCrest.HasFlag(slot); + public bool DoApplyParameter(CustomizeParameterFlag flag) + => ApplyParameters.HasFlag(flag); + internal bool SetApplyEquip(EquipSlot slot, bool value) { var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag(); @@ -214,32 +221,48 @@ public class DesignBase return true; } - internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) - => new(this, equipFlags, customizeFlags, crestFlags); + internal bool SetApplyParameter(CustomizeParameterFlag flag, bool value) + { + var newValue = value ? ApplyParameters | flag : ApplyParameters & ~flag; + if (newValue == ApplyParameters) + return false; + + ApplyParameters = newValue; + return true; + } + + internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, + CustomizeParameterFlag parameterFlags) + => new(this, equipFlags, customizeFlags, crestFlags, parameterFlags); internal readonly struct FlagRestrictionResetter : IDisposable { - private readonly DesignBase _design; - private readonly EquipFlag _oldEquipFlags; - private readonly CustomizeFlag _oldCustomizeFlags; - private readonly CrestFlag _oldCrestFlags; + private readonly DesignBase _design; + private readonly EquipFlag _oldEquipFlags; + private readonly CustomizeFlag _oldCustomizeFlags; + private readonly CrestFlag _oldCrestFlags; + private readonly CustomizeParameterFlag _oldParameterFlags; - public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) + public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, + CustomizeParameterFlag parameterFlags) { _design = d; _oldEquipFlags = d.ApplyEquip; _oldCustomizeFlags = d.ApplyCustomizeRaw; _oldCrestFlags = d.ApplyCrest; + _oldParameterFlags = d.ApplyParameters; d.ApplyEquip &= equipFlags; d.ApplyCustomize &= customizeFlags; d.ApplyCrest &= crestFlags; + d.ApplyParameters &= parameterFlags; } public void Dispose() { - _design.ApplyEquip = _oldEquipFlags; - _design.ApplyCustomize = _oldCustomizeFlags; - _design.ApplyCrest = _oldCrestFlags; + _design.ApplyEquip = _oldEquipFlags; + _design.ApplyCustomize = _oldCustomizeFlags; + _design.ApplyCrest = _oldCrestFlags; + _design.ApplyParameters = _oldParameterFlags; } } @@ -256,26 +279,16 @@ public class DesignBase { var ret = new JObject { - ["FileVersion"] = FileVersion, - ["Equipment"] = SerializeEquipment(), - ["Customize"] = SerializeCustomize(), + ["FileVersion"] = FileVersion, + ["Equipment"] = SerializeEquipment(), + ["Customize"] = SerializeCustomize(), + ["Parameters"] = SerializeParameters(), }; return ret; } protected JObject SerializeEquipment() { - static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest) - => new() - { - ["ItemId"] = id.Id, - ["Stain"] = stain.Id, - ["Crest"] = crest, - ["Apply"] = apply, - ["ApplyStain"] = applyStain, - ["ApplyCrest"] = applyCrest, - }; - var ret = new JObject(); if (_designData.IsHuman) { @@ -298,6 +311,17 @@ public class DesignBase } return ret; + + static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest) + => new() + { + ["ItemId"] = id.Id, + ["Stain"] = stain.Id, + ["Crest"] = crest, + ["Apply"] = apply, + ["ApplyStain"] = applyStain, + ["ApplyCrest"] = applyCrest, + }; } protected JObject SerializeCustomize() @@ -329,6 +353,42 @@ public class DesignBase return ret; } + protected JObject SerializeParameters() + { + var ret = new JObject(); + + foreach (var flag in CustomizeParameterExtensions.ValueFlags) + { + ret[flag.ToString()] = new JObject() + { + ["Value"] = DesignData.Parameters[flag][0], + ["Apply"] = DoApplyParameter(flag), + }; + } + + foreach (var flag in CustomizeParameterExtensions.PercentageFlags) + { + ret[flag.ToString()] = new JObject() + { + ["Percentage"] = DesignData.Parameters[flag][0], + ["Apply"] = DoApplyParameter(flag), + }; + } + + foreach (var flag in CustomizeParameterExtensions.TripleFlags) + { + ret[flag.ToString()] = new JObject() + { + ["Red"] = DesignData.Parameters[flag][0], + ["Green"] = DesignData.Parameters[flag][1], + ["Blue"] = DesignData.Parameters[flag][2], + ["Apply"] = DoApplyParameter(flag), + }; + } + + return ret; + } + #endregion #region Deserialization @@ -348,9 +408,68 @@ public class DesignBase var ret = new DesignBase(customizations, items); LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true); LoadEquip(items, json["Equipment"], ret, "Temporary Design", true); + LoadParameters(json["Parameters"], ret, "Temporary Design"); return ret; } + protected static void LoadParameters(JToken? parameters, DesignBase design, string name) + { + if (parameters == null) + { + design.ApplyParameters = 0; + design.GetDesignDataRef().Parameters = default; + return; + } + + + foreach (var flag in CustomizeParameterExtensions.ValueFlags) + { + if (!TryGetToken(flag, out var token)) + continue; + + var value = token["Value"]?.ToObject() ?? 0f; + design.GetDesignDataRef().Parameters[flag] = new Vector3(value); + } + + foreach (var flag in CustomizeParameterExtensions.PercentageFlags) + { + if (!TryGetToken(flag, out var token)) + continue; + + var value = Math.Clamp(token["Percentage"]?.ToObject() ?? 0f, 0f, 1f); + design.GetDesignDataRef().Parameters[flag] = new Vector3(value); + } + + foreach (var flag in CustomizeParameterExtensions.TripleFlags) + { + if (!TryGetToken(flag, out var token)) + continue; + + var r = Math.Clamp(token["Red"]?.ToObject() ?? 0f, 0, 1); + var g = Math.Clamp(token["Green"]?.ToObject() ?? 0f, 0, 1); + var b = Math.Clamp(token["Blue"]?.ToObject() ?? 0f, 0, 1); + design.GetDesignDataRef().Parameters[flag] = new Vector3(r, g, b); + } + + return; + + // Load the token and set application. + bool TryGetToken(CustomizeParameterFlag flag, [NotNullWhen(true)] out JToken? token) + { + token = parameters![flag.ToString()]; + if (token != null) + { + var apply = token["Apply"]?.ToObject() ?? false; + design.SetApplyParameter(flag, apply); + return true; + } + + design.ApplyParameters &= ~flag; + design.GetDesignDataRef().Parameters[flag] = Vector3.Zero; + return false; + } + } + protected static void LoadEquip(ItemManager items, JToken? equip, DesignBase design, string name, bool allowUnknown) { if (equip == null) @@ -514,8 +633,10 @@ public class DesignBase _designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags, out var writeProtected, out var applyHat, out var applyVisor, out var applyWeapon); - ApplyEquip = equipFlags; - ApplyCustomize = customizeFlags; + ApplyEquip = equipFlags; + ApplyCustomize = customizeFlags; + ApplyParameters = 0; + ApplyCrest = 0; SetWriteProtected(writeProtected); SetApplyHatVisible(applyHat); SetApplyVisorToggle(applyVisor); diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index cd8924f..7f8e1f2 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; +using Glamourer.GameData; using Glamourer.Services; using Glamourer.State; using Glamourer.Utility; @@ -23,9 +24,9 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi public JObject ShareJObject(Design design) => design.JsonSerialize(); - public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) + public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags) { - var design = Convert(state, equipFlags, customizeFlags, crestFlags); + var design = Convert(state, equipFlags, customizeFlags, crestFlags, parameterFlags); return ShareJObject(design); } @@ -36,21 +37,21 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi => ShareBase64(ShareJObject(design)); public string ShareBase64(ActorState state) - => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All); + => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All, CustomizeParameterExtensions.All); - public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) - => ShareBase64(state.ModelData, equipFlags, customizeFlags, crestFlags); + public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags) + => ShareBase64(state.ModelData, equipFlags, customizeFlags, crestFlags, parameterFlags); - public string ShareBase64(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) + public string ShareBase64(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags) { - var design = Convert(data, equipFlags, customizeFlags, crestFlags); + var design = Convert(data, equipFlags, customizeFlags, crestFlags, parameterFlags); return ShareBase64(ShareJObject(design)); } - public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) - => Convert(state.ModelData, equipFlags, customizeFlags, crestFlags); + public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags) + => Convert(state.ModelData, equipFlags, customizeFlags, crestFlags, parameterFlags); - public DesignBase Convert(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) + public DesignBase Convert(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags) { var design = _designs.CreateTemporary(); design.ApplyEquip = equipFlags & EquipFlagExtensions.All; diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 9e324ae..f29d42e 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using Glamourer.GameData; using Glamourer.Services; using OtterGui.Classes; using Penumbra.GameData.Enums; @@ -10,30 +11,31 @@ namespace Glamourer.Designs; public unsafe struct DesignData { - private string _nameHead = string.Empty; - private string _nameBody = string.Empty; - private string _nameHands = string.Empty; - private string _nameLegs = string.Empty; - private string _nameFeet = string.Empty; - private string _nameEars = string.Empty; - private string _nameNeck = string.Empty; - private string _nameWrists = string.Empty; - private string _nameRFinger = string.Empty; - private string _nameLFinger = string.Empty; - private string _nameMainhand = string.Empty; - private string _nameOffhand = string.Empty; - private fixed uint _itemIds[12]; - private fixed ushort _iconIds[12]; - private fixed byte _equipmentBytes[48]; - public CustomizeArray Customize = CustomizeArray.Default; - public uint ModelId; - public CrestFlag CrestVisibility; - private SecondaryId _secondaryMainhand; - private SecondaryId _secondaryOffhand; - private FullEquipType _typeMainhand; - private FullEquipType _typeOffhand; - private byte _states; - public bool IsHuman = true; + private string _nameHead = string.Empty; + private string _nameBody = string.Empty; + private string _nameHands = string.Empty; + private string _nameLegs = string.Empty; + private string _nameFeet = string.Empty; + private string _nameEars = string.Empty; + private string _nameNeck = string.Empty; + private string _nameWrists = string.Empty; + private string _nameRFinger = string.Empty; + private string _nameLFinger = string.Empty; + private string _nameMainhand = string.Empty; + private string _nameOffhand = string.Empty; + private fixed uint _itemIds[12]; + private fixed ushort _iconIds[12]; + private fixed byte _equipmentBytes[48]; + public CustomizeParameterData Parameters; + public CustomizeArray Customize = CustomizeArray.Default; + public uint ModelId; + public CrestFlag CrestVisibility; + private SecondaryId _secondaryMainhand; + private SecondaryId _secondaryOffhand; + private FullEquipType _typeMainhand; + private FullEquipType _typeOffhand; + private byte _states; + public bool IsHuman = true; public DesignData() { } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 2d25cc2..a869896 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -4,10 +4,13 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Numerics; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Dalamud.Utility; using Glamourer.Events; +using Glamourer.GameData; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; @@ -17,6 +20,7 @@ using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using static Glamourer.State.ActorState; namespace Glamourer.Designs; @@ -424,6 +428,20 @@ public class DesignManager } } + /// Change a customize parameter. + public void ChangeCustomizeParameter(Design design, CustomizeParameterFlag flag, Vector3 value) + { + var old = design.DesignData.Parameters[flag]; + if (!design.GetDesignDataRef().Parameters.Set(flag, value)) + return; + + var @new = design.DesignData.Parameters[flag]; + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}."); + _saveService.QueueSave(design); + _event.Invoke(DesignChanged.Type.Parameter, design, (old, @new, flag)); + } + /// Change whether to apply a specific equipment piece. public void ChangeApplyEquip(Design design, EquipSlot slot, bool value) { @@ -529,6 +547,18 @@ public class DesignManager _event.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value)); } + /// Change the application value of a customize parameter. + public void ChangeApplyParameter(Design design, CustomizeParameterFlag flag, bool value) + { + if (!design.SetApplyParameter(flag, value)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(design); + Glamourer.Log.Debug($"Set applying of parameter {flag} to {value}."); + _event.Invoke(DesignChanged.Type.ApplyParameter, design, flag); + } + /// Apply an entire design based on its appliance rules piece by piece. public void ApplyDesign(Design design, DesignBase other) { diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 76724eb..4bdb1df 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -65,6 +65,9 @@ public sealed class DesignChanged() /// An existing design had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. Crest, + /// An existing design had a customize parameter changed. Data is the old value, the new value and the flag [(Vector3, Vector3, CustomizeParameterFlag)]. + Parameter, + /// An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. ApplyCustomize, @@ -77,6 +80,9 @@ public sealed class DesignChanged() /// An existing design changed whether a specific crest visibility is applied. Data is the slot of the equipment [EquipSlot]. ApplyCrest, + /// An existing design changed whether a specific customize parameter is applied. Data is the flag for the parameter [CustomizeParameterFlag]. + ApplyParameter, + /// An existing design changed its write protection status. Data is the new value [bool]. WriteProtection, diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 3045b98..50c3d2e 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -39,6 +39,9 @@ public sealed class StateChanged() /// A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. Crest, + /// A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(Vector3, Vector3, CustomizeParameterFlag)]. + Parameter, + /// A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] Design, diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 68abd0a..d808cbe 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -159,8 +159,8 @@ public class DesignQuickBar : Window, IDisposable return; } - var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); - using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); + var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); + using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index b56b314..aad9e52 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -35,7 +35,8 @@ public class ActorPanel( DesignManager _designManager, ImportService _importService, ICondition _conditions, - DictModelChara _modelChara) + DictModelChara _modelChara, + CustomizeParameterDrawer _parameterDrawer) { private ActorIdentifier _identifier; private string _actorName = string.Empty; @@ -127,6 +128,7 @@ public class ActorPanel( { DrawCustomizationsHeader(); DrawEquipmentHeader(); + DrawParameterHeader(); } private void DrawCustomizationsHeader() @@ -169,6 +171,14 @@ public class ActorPanel( ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } + private void DrawParameterHeader() + { + if (!_config.UseAdvancedParameters || !ImGui.CollapsingHeader("Advanced Customizations")) + return; + + _parameterDrawer.Draw(_stateManager, _state!); + } + private void DrawEquipmentMetaToggles() { using (_ = ImRaii.Group()) @@ -302,9 +312,9 @@ public class ActorPanel( private void SaveDesignOpen() { ImGui.OpenPopup("Save as Design"); - _newName = _state!.Identifier.ToName(); - var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); - _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest); + _newName = _state!.Identifier.ToName(); + var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); + _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest, applyParameters); } private void SaveDesignDrawPopup() @@ -339,8 +349,8 @@ public class ActorPanel( { try { - var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); - var text = _converter.ShareBase64(_state!, applyGear, applyCustomize, applyCrest); + var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); + var text = _converter.ShareBase64(_state!, applyGear, applyCustomize, applyCrest, applyParameters); ImGui.SetClipboardText(text); } catch (Exception ex) @@ -379,9 +389,9 @@ public class ActorPanel( !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) return; - var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state, + _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state, StateChanged.Source.Manual); } @@ -397,9 +407,9 @@ public class ActorPanel( !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) return; - var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state, + _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state, StateChanged.Source.Manual); } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 846823a..054ee0a 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -269,7 +269,7 @@ public class SetPanel( var size = new Vector2(ImGui.GetFrameHeight()); size.X += ImGuiHelpers.GlobalScale; - var (equipFlags, customizeFlags, _, _, _, _, _) = design.ApplyWhat(); + var (equipFlags, customizeFlags, _, _, _, _, _, _) = design.ApplyWhat(); var sb = new StringBuilder(); foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)) { diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index ea99620..53cffd6 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -101,6 +101,18 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state[type]); ImGui.TableNextRow(); } + + foreach (var crest in CrestExtensions.AllRelevantSet) + { + PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state[crest]); + ImGui.TableNextRow(); + } + + foreach (var flag in CustomizeParameterExtensions.AllFlags) + { + PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state[flag]); + ImGui.TableNextRow(); + } } else { diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index c70396c..1514647 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -1,6 +1,8 @@ using System; using System.Numerics; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Shader; +using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Structs; using ImGuiNET; @@ -78,6 +80,28 @@ public unsafe class ModelEvaluationPanel( DrawEquip(actor, model); DrawCustomize(actor, model); DrawCrests(actor, model); + DrawParameters(actor, model); + } + + private void DrawParameters(Actor actor, Model model) + { + if (!model.IsHuman) + return; + if (model.AsHuman->CustomizeParameterCBuffer == null) + return; + + var ptr = (CustomizeParameter*)model.AsHuman->CustomizeParameterCBuffer->UnsafeSourcePointer; + if (ptr == null) + return; + + var convert = CustomizeParameterData.FromParameters(*ptr); + foreach (var flag in CustomizeParameterExtensions.AllFlags) + { + ImGuiUtil.DrawTableColumn(flag.ToString()); + ImGuiUtil.DrawTableColumn(string.Empty); + ImGuiUtil.DrawTableColumn(convert[flag].ToString()); + ImGui.TableNextColumn(); + } } private void DrawVisor(Actor actor, Model model) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 848bb58..74ff84f 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Framework; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Events; +using Glamourer.GameData; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop; @@ -21,9 +22,20 @@ using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _customizationDrawer, DesignManager _manager, - ObjectManager _objects, StateManager _state, EquipmentDrawer _equipmentDrawer, ModAssociationsTab _modAssociations, - DesignDetailTab _designDetails, DesignConverter _converter, ImportService _importService, MultiDesignPanel _multiDesignPanel) +public class DesignPanel( + DesignFileSystemSelector _selector, + CustomizationDrawer _customizationDrawer, + DesignManager _manager, + ObjectManager _objects, + StateManager _state, + EquipmentDrawer _equipmentDrawer, + ModAssociationsTab _modAssociations, + Configuration _config, + DesignDetailTab _designDetails, + DesignConverter _converter, + ImportService _importService, + MultiDesignPanel _multiDesignPanel, + CustomizeParameterDrawer _parameterDrawer) { private readonly FileDialogManager _fileDialog = new(); @@ -152,11 +164,20 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } + private void DrawCustomizeParameters() + { + if (!_config.UseAdvancedParameters || !ImGui.CollapsingHeader("Advanced Customization")) + return; + + _parameterDrawer.Draw(_manager, _selector.Selected!); + } + private void DrawCustomizeApplication() { - var set = _selector.Selected!.CustomizeSet; - var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; - var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; + var set = _selector.Selected!.CustomizeSet; + var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; + var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : + (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) { var newFlags = flags == 3; @@ -205,6 +226,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer DrawCustomizeApplication(); ImGui.NewLine(); DrawCrestApplication(); + ImGui.NewLine(); + if (_config.UseAdvancedParameters) + DrawMetaApplication(); } ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); @@ -248,27 +272,47 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer EquipSlotExtensions.FullSlots); ImGui.NewLine(); - const uint all = 0x0Fu; - var flags = (_selector.Selected!.DoApplyHatVisible() ? 0x01u : 0x00) - | (_selector.Selected!.DoApplyVisorToggle() ? 0x02u : 0x00) - | (_selector.Selected!.DoApplyWeaponVisible() ? 0x04u : 0x00) - | (_selector.Selected!.DoApplyWetness() ? 0x08u : 0x00); - var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); - var apply = bigChange ? (flags & 0x01) == 0x01 : _selector.Selected!.DoApplyHatVisible(); - if (ImGui.Checkbox("Apply Hat Visibility", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.HatState, apply); + if (_config.UseAdvancedParameters) + DrawParameterApplication(); + else + DrawMetaApplication(); + } + } - apply = bigChange ? (flags & 0x02) == 0x02 : _selector.Selected!.DoApplyVisorToggle(); - if (ImGui.Checkbox("Apply Visor State", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.VisorState, apply); + private void DrawMetaApplication() + { + const uint all = 0x0Fu; + var flags = (_selector.Selected!.DoApplyHatVisible() ? 0x01u : 0x00) + | (_selector.Selected!.DoApplyVisorToggle() ? 0x02u : 0x00) + | (_selector.Selected!.DoApplyWeaponVisible() ? 0x04u : 0x00) + | (_selector.Selected!.DoApplyWetness() ? 0x08u : 0x00); + var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); + var apply = bigChange ? (flags & 0x01) == 0x01 : _selector.Selected!.DoApplyHatVisible(); + if (ImGui.Checkbox("Apply Hat Visibility", ref apply) || bigChange) + _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.HatState, apply); - apply = bigChange ? (flags & 0x04) == 0x04 : _selector.Selected!.DoApplyWeaponVisible(); - if (ImGui.Checkbox("Apply Weapon Visibility", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.WeaponState, apply); + apply = bigChange ? (flags & 0x02) == 0x02 : _selector.Selected!.DoApplyVisorToggle(); + if (ImGui.Checkbox("Apply Visor State", ref apply) || bigChange) + _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.VisorState, apply); - apply = bigChange ? (flags & 0x08) == 0x08 : _selector.Selected!.DoApplyWetness(); - if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.Wetness, apply); + apply = bigChange ? (flags & 0x04) == 0x04 : _selector.Selected!.DoApplyWeaponVisible(); + if (ImGui.Checkbox("Apply Weapon Visibility", ref apply) || bigChange) + _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.WeaponState, apply); + + apply = bigChange ? (flags & 0x08) == 0x08 : _selector.Selected!.DoApplyWetness(); + if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange) + _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.Wetness, apply); + } + + private void DrawParameterApplication() + { + var flags = (uint)_selector.Selected!.ApplyParameters; + var bigChange = ImGui.CheckboxFlags("Apply All Customize Parameters", ref flags, (uint)CustomizeParameterExtensions.All); + foreach (var flag in CustomizeParameterExtensions.AllFlags) + { + var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : _selector.Selected!.DoApplyParameter(flag); + if (ImGui.Checkbox($"Apply {flag}", ref apply) || bigChange) + _manager.ChangeApplyParameter(_selector.Selected!, flag, apply); } } @@ -316,6 +360,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer DrawButtonRow(); DrawCustomize(); DrawEquipment(); + DrawCustomizeParameters(); _designDetails.Draw(); DrawApplicationRules(); _modAssociations.Draw(); @@ -386,8 +431,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); - using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); + var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); + using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); } } @@ -405,8 +450,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); - using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); + var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); + using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index cd88f97..235b866 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -89,9 +89,9 @@ public class NpcPanel( { try { - var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); var data = ToDesignData(); - var text = _converter.ShareBase64(data, applyGear, applyCustomize, applyCrest); + var text = _converter.ShareBase64(data, applyGear, applyCustomize, applyCrest, applyParameters); ImGui.SetClipboardText(text); } catch (Exception ex) @@ -105,11 +105,11 @@ public class NpcPanel( private void SaveDesignOpen() { ImGui.OpenPopup("Save as Design"); - _newName = _selector.Selection.Name; - var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); + _newName = _selector.Selection.Name; + var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); var data = ToDesignData(); - _newDesign = _converter.Convert(data, applyGear, applyCustomize, applyCrest); + _newDesign = _converter.Convert(data, applyGear, applyCustomize, applyCrest, applyParameters); } private void SaveDesignDrawPopup() @@ -198,8 +198,8 @@ public class NpcPanel( if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); - var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest); + var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); + var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest, applyParameters); _state.ApplyDesign(design, state, StateChanged.Source.Manual); } } @@ -217,8 +217,8 @@ public class NpcPanel( if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); - var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest); + var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); + var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest, applyParameters); _state.ApplyDesign(design, state, StateChanged.Source.Manual); } } diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index 340a9fd..ac3dc1b 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -60,8 +60,9 @@ public class SettingsTab : ITab if (!child) return; - Checkbox("Enable Auto Designs", "Enable the application of designs associated to characters in the Automation tab to be applied automatically.", - _config.EnableAutoDesigns, v => _config.EnableAutoDesigns = v); + Checkbox("Enable Auto Designs", + "Enable the application of designs associated to characters in the Automation tab to be applied automatically.", + _config.EnableAutoDesigns, v => _config.EnableAutoDesigns = v); ImGui.NewLine(); ImGui.NewLine(); ImGui.NewLine(); @@ -97,6 +98,8 @@ public class SettingsTab : ITab Checkbox("Revert Manual Changes on Zone Change", "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone.", _config.RevertManualChangesOnZoneChange, v => _config.RevertManualChangesOnZoneChange = v); + Checkbox("Enable Advanced Customization Options", "Enable the display and editing of advanced customization options like arbitrary colors.", + _config.UseAdvancedParameters, v => _config.UseAdvancedParameters = v); ImGui.NewLine(); } @@ -108,7 +111,8 @@ public class SettingsTab : ITab EphemeralCheckbox("Show Quick Design Bar", "Show a bar separate from the main window that allows you to quickly apply designs or revert your character and target.", _config.Ephemeral.ShowDesignQuickBar, v => _config.Ephemeral.ShowDesignQuickBar = v); - EphemeralCheckbox("Lock Quick Design Bar", "Prevent the quick design bar from being moved and lock it in place.", _config.Ephemeral.LockDesignQuickBar, + EphemeralCheckbox("Lock Quick Design Bar", "Prevent the quick design bar from being moved and lock it in place.", + _config.Ephemeral.LockDesignQuickBar, v => _config.Ephemeral.LockDesignQuickBar = v); if (Widget.ModifiableKeySelector("Hotkey to Toggle Quick Design Bar", "Set a hotkey that opens or closes the quick design bar.", 100 * ImGuiHelpers.GlobalScale, @@ -138,7 +142,8 @@ public class SettingsTab : ITab _config.HideWindowInCutscene = v; _uiBuilder.DisableCutsceneUiHide = !v; }); - EphemeralCheckbox("Lock Main Window", "Prevent the main window from being moved and lock it in place.", _config.Ephemeral.LockMainWindow, + EphemeralCheckbox("Lock Main Window", "Prevent the main window from being moved and lock it in place.", + _config.Ephemeral.LockMainWindow, v => _config.Ephemeral.LockMainWindow = v); Checkbox("Open Main Window at Game Start", "Whether the main Glamourer window should be open or closed after launching the game.", _config.OpenWindowAtStart, v => _config.OpenWindowAtStart = v); diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 916bdaa..d55fde0 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -1,6 +1,7 @@ using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Utility; +using Glamourer.GameData; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; @@ -98,13 +99,13 @@ public static class UiHelpers return (currentValue != newValue, currentApply != newApply); } - public static (EquipFlag, CustomizeFlag, CrestFlag) ConvertKeysToFlags() + public static (EquipFlag, CustomizeFlag, CrestFlag, CustomizeParameterFlag) ConvertKeysToFlags() => (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch { - (false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All), - (true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All), - (true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0, CrestExtensions.All), - (false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant, 0), + (false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All), + (true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All), + (true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0, CrestExtensions.All, 0), + (false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant, 0, CustomizeParameterExtensions.All), }; public static (bool, bool) ConvertKeysToBool() diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 9acb418..060021e 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -19,6 +19,8 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 _original; + private readonly Post _postEvent = new(); + /// Check whether we in a manual customize update, in which case we need to not toggle certain flags. public static readonly InMethodChecker InUpdate = new(); @@ -36,6 +38,7 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 action, Post.Priority priority) + => _postEvent.Subscribe(action, priority); + + public void Unsubscribe(Action action) + => _postEvent.Unsubscribe(action); + + public sealed class Post() : EventWrapper(nameof(ChangeCustomizeService) + '.' + nameof(Post)) + { + public enum Priority + { + /// + StateListener = 0, + } } } diff --git a/Glamourer/Interop/Structs/ActorData.cs b/Glamourer/Interop/Structs/ActorData.cs index ce11e34..d93df6b 100644 --- a/Glamourer/Interop/Structs/ActorData.cs +++ b/Glamourer/Interop/Structs/ActorData.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using OtterGui.Log; -using Penumbra.GameData.Actors; namespace Glamourer.Interop.Structs; diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index ed60243..b1e41f7 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -7,6 +7,7 @@ using Dalamud.Plugin.Services; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Events; +using Glamourer.GameData; using Glamourer.Gui; using Glamourer.Interop; using Glamourer.State; @@ -500,7 +501,7 @@ public class CommandService : IDisposable && _stateManager.GetOrCreate(identifier, data.Objects[0], out state))) continue; - var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All); + var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All); _designManager.CreateClone(design, split[0], true); return true; } diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index 484d0a9..cd5a23a 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -5,6 +5,7 @@ using Penumbra.GameData.Enums; using System.Linq; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using Glamourer.GameData; using Penumbra.GameData.Structs; namespace Glamourer.State; @@ -79,7 +80,11 @@ public class ActorState /// This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. private readonly StateChanged.Source[] _sources = Enumerable .Repeat(StateChanged.Source.Game, - EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + CrestExtensions.AllRelevantSet.Count).ToArray(); + EquipFlagExtensions.NumEquipFlags + + CustomizationExtensions.NumIndices + + 5 + + CrestExtensions.AllRelevantSet.Count + + CustomizeParameterExtensions.AllFlags.Count).ToArray(); internal ActorState(ActorIdentifier identifier) => Identifier = identifier.CreatePermanent(); @@ -96,6 +101,13 @@ public class ActorState public ref StateChanged.Source this[MetaIndex index] => ref _sources[(int)index]; + public ref StateChanged.Source this[CustomizeParameterFlag flag] + => ref _sources[ + EquipFlagExtensions.NumEquipFlags + + CustomizationExtensions.NumIndices + 5 + + CrestExtensions.AllRelevantSet.Count + + flag.ToInternalIndex()]; + public void RemoveFixedDesignSources() { for (var i = 0; i < _sources.Length; ++i) @@ -105,6 +117,9 @@ public class ActorState } } + public CustomizeParameterFlag OnlyChangedParameters() + => CustomizeParameterExtensions.AllFlags.Where(f => this[f] is not StateChanged.Source.Game).Aggregate((CustomizeParameterFlag) 0, (a, b) => a | b); + public bool UpdateTerritory(ushort territory) { if (territory == LastTerritory) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 43c9097..5278f72 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,5 +1,7 @@ using System.Linq; +using FFXIVClientStructs.FFXIV.Shader; using Glamourer.Events; +using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; @@ -14,8 +16,17 @@ namespace Glamourer.State; /// This class applies changes made to state to actual objects in the game. /// It handles applying those changes as well as redrawing the actor if necessary. /// -public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, WeaponService _weapon, ChangeCustomizeService _changeCustomize, - ItemManager _items, PenumbraService _penumbra, MetaService _metaService, ObjectManager _objects, CrestService _crests) +public class StateApplier( + UpdateSlotService _updateSlot, + VisorService _visor, + WeaponService _weapon, + ChangeCustomizeService _changeCustomize, + ItemManager _items, + PenumbraService _penumbra, + MetaService _metaService, + ObjectManager _objects, + CrestService _crests, + Configuration _config) { /// Simply force a redraw regardless of conditions. public void ForceRedraw(ActorData data) @@ -271,6 +282,35 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We return data; } + /// Change the customize parameters on models. Can change multiple at once. + public unsafe void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values) + { + if (!_config.UseAdvancedParameters) + return; + + foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) + { + var buffer = actor.Model.AsHuman->CustomizeParameterCBuffer; + if (buffer == null) + continue; + + var ptr = (CustomizeParameter*)buffer->UnsafeSourcePointer; + if (ptr == null) + continue; + + values.Apply(ref *ptr, flags); + } + } + + /// + public ActorData ChangeParameters(ActorState state, CustomizeParameterFlag flags, bool apply) + { + var data = GetData(state); + if (apply) + ChangeParameters(data, flags, state.ModelData.Parameters); + return data; + } + private ActorData GetData(ActorState state) { _objects.Update(); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 0095fe9..28e631c 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,7 +1,9 @@ using System; using System.Linq; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Common.Math; using Glamourer.Events; +using Glamourer.GameData; using Glamourer.Services; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -11,11 +13,11 @@ namespace Glamourer.State; public class StateEditor { - private readonly ItemManager _items; + private readonly ItemManager _items; private readonly CustomizeService _customizations; - private readonly HumanModelList _humans; - private readonly GPoseService _gPose; - private readonly ICondition _condition; + private readonly HumanModelList _humans; + private readonly GPoseService _gPose; + private readonly ICondition _condition; public StateEditor(CustomizeService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition) { @@ -208,6 +210,19 @@ public class StateEditor return true; } + /// Change the customize flags of a character. + public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, Vector3 value, StateChanged.Source source, out Vector3 oldValue, + uint key = 0) + { + oldValue = state.ModelData.Parameters[flag]; + if (!state.CanUnlock(key)) + return false; + + state.ModelData.Parameters.Set(flag, value); + state[flag] = source; + return true; + } + public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue, uint key = 0) { diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 3651553..5d1b57c 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -4,13 +4,14 @@ using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; -using OtterGui.Classes; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Shader; +using Glamourer.GameData; using Penumbra.GameData.DataContainers; namespace Glamourer.State; @@ -46,6 +47,7 @@ public class StateListener : IDisposable private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid; private ActorState? _creatingState; + private ActorState? _customizeState; private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty; public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, @@ -148,10 +150,10 @@ public class StateListener : IDisposable return; if (!actor.Identifier(_actors, out var identifier) - || !_manager.TryGetValue(identifier, out var state)) + || !_manager.TryGetValue(identifier, out _customizeState)) return; - UpdateCustomize(actor, state, ref customize, false); + UpdateCustomize(actor, _customizeState, ref customize, false); } private void UpdateCustomize(Actor actor, ActorState state, ref CustomizeArray customize, bool checkTransform) @@ -671,6 +673,7 @@ public class StateListener : IDisposable _changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener); _crestService.Subscribe(OnCrestChange, CrestService.Priority.StateListener); _crestService.ModelCrestSetup += OnModelCrestSetup; + _changeCustomizeService.Subscribe(OnCustomizeChanged, ChangeCustomizeService.Post.Priority.StateListener); } private void Unsubscribe() @@ -686,6 +689,7 @@ public class StateListener : IDisposable _changeCustomizeService.Unsubscribe(OnCustomizeChange); _crestService.Unsubscribe(OnCrestChange); _crestService.ModelCrestSetup -= OnModelCrestSetup; + _changeCustomizeService.Unsubscribe(OnCustomizeChanged); } private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject) @@ -700,5 +704,58 @@ public class StateListener : IDisposable _applier.ChangeHatState(data, _creatingState.ModelData.IsHatVisible()); _applier.ChangeWeaponState(data, _creatingState.ModelData.IsWeaponVisible()); _applier.ChangeWetness(data, _creatingState.ModelData.IsWet()); + + ApplyParameters(_creatingState, drawObject); + } + + private void OnCustomizeChanged(Model model) + { + if (_customizeState == null) + { + var actor = _penumbra.GameObjectFromDrawObject(model); + if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + return; + + if (!actor.Identifier(_actors, out var identifier) + || !_manager.TryGetValue(identifier, out _customizeState)) + return; + } + + ApplyParameters(_customizeState, model); + _customizeState = null; + } + + private unsafe void ApplyParameters(ActorState state, Model model) + { + if (!model.IsHuman) + return; + + var cBuffer = model.AsHuman->CustomizeParameterCBuffer; + if (cBuffer == null) + return; + + var ptr = (CustomizeParameter*)cBuffer->UnsafeSourcePointer; + if (ptr == null) + return; + + foreach (var flag in CustomizeParameterExtensions.AllFlags) + { + var newValue = CustomizeParameterData.FromParameter(*ptr, flag); + + switch (state[flag]) + { + case StateChanged.Source.Game: + case StateChanged.Source.Manual: + if (state.BaseData.Parameters.Set(flag, newValue)) + _manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game); + break; + case StateChanged.Source.Fixed: + case StateChanged.Source.Ipc: + state.BaseData.Parameters.Set(flag, newValue); + if (_config.UseAdvancedParameters) + state.ModelData.Parameters.ApplySingle(ref *ptr, flag); + break; + } + } } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index a3f1de6..4039f00 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -3,9 +3,12 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Numerics; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Shader; using Glamourer.Designs; using Glamourer.Events; +using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; @@ -189,6 +192,14 @@ public class StateManager( // Weapon visibility could technically be inferred from the weapon draw objects, // but since we use hat visibility from the game object we can also use weapon visibility from it. ret.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden); + + if (model.IsHuman && model.AsHuman->CustomizeParameterCBuffer != null) + { + var ptr = model.AsHuman->CustomizeParameterCBuffer->UnsafeSourcePointer; + if (ptr != null) + ret.Parameters = CustomizeParameterData.FromParameters(*(CustomizeParameter*)ptr); + } + return ret; } @@ -309,6 +320,19 @@ public class StateManager( _event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot)); } + /// Change the crest of an equipment piece. + public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, Vector3 value, StateChanged.Source source, uint key = 0) + { + if (!_editor.ChangeParameter(state, flag, value, source, out var old, key)) + return; + + var @new = state.ModelData.Parameters[flag]; + var actors = _applier.ChangeParameters(state, flag, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); + Glamourer.Log.Verbose( + $"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); + _event.Invoke(StateChanged.Type.Parameter, source, state, actors, (old, @new, flag)); + } + /// Change hat visibility. public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0) { @@ -390,6 +414,9 @@ public class StateManager( foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest)) _editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key); + + foreach (var flag in CustomizeParameterExtensions.AllFlags.Where(design.DoApplyParameter)) + _editor.ChangeParameter(state, flag, design.DesignData.Parameters[flag], source, out _, key); } var actors = ApplyAll(state, redraw, false); @@ -441,6 +468,7 @@ public class StateManager( _applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible()); _applier.ChangeVisor(actors, state.ModelData.IsVisorToggled()); _applier.ChangeCrests(actors, state.ModelData.CrestVisibility); + _applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters); } return actors; @@ -454,6 +482,7 @@ public class StateManager( var redraw = state.ModelData.ModelId != state.BaseData.ModelId || !state.ModelData.IsHuman || CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw(); + state.ModelData = state.BaseData; state.ModelData.SetIsWet(false); foreach (var index in Enum.GetValues()) @@ -471,9 +500,13 @@ public class StateManager( foreach (var slot in CrestExtensions.AllRelevantSet) state[slot] = StateChanged.Source.Game; + foreach (var flag in CustomizeParameterExtensions.AllFlags) + state[flag] = StateChanged.Source.Game; + var actors = ActorData.Invalid; if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) actors = ApplyAll(state, redraw, true); + Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors, null); @@ -514,6 +547,15 @@ public class StateManager( } } + foreach (var flag in CustomizeParameterExtensions.AllFlags) + { + if (state[flag] is StateChanged.Source.Fixed) + { + state[flag] = StateChanged.Source.Game; + state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag]; + } + } + if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed) { state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game; From d62d7e352f976972df2b4e533cf19e17818b0aa9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 8 Jan 2024 23:25:51 +0100 Subject: [PATCH 138/786] Add option to apply mod associations with /glamour apply. --- Glamourer/Designs/Design.cs | 2 +- Glamourer/Designs/DesignBase.cs | 8 +-- Glamourer/Interop/Penumbra/PenumbraService.cs | 30 ++++++--- Glamourer/Services/CommandService.cs | 62 +++++++++++++++++-- 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 709e5ca..c38669b 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -66,7 +66,7 @@ public sealed class Design : DesignBase, ISavable ["WriteProtected"] = WriteProtected(), ["Equipment"] = SerializeEquipment(), ["Customize"] = SerializeCustomize(), - ["Parameters"] = SerializeParameters(), + ["Parameters"] = SerializeParameters(), ["Mods"] = SerializeMods(), }; return ret; diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index aa42834..3ed2524 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -279,10 +279,10 @@ public class DesignBase { var ret = new JObject { - ["FileVersion"] = FileVersion, - ["Equipment"] = SerializeEquipment(), - ["Customize"] = SerializeCustomize(), - ["Parameters"] = SerializeParameters(), + ["FileVersion"] = FileVersion, + ["Equipment"] = SerializeEquipment(), + ["Customize"] = SerializeCustomize(), + ["Parameters"] = SerializeParameters(), }; return ret; } diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 2629ad7..23ca919 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -148,7 +148,8 @@ public unsafe class PenumbraService : IDisposable public void OpenModPage(Mod mod) { if (_openModPage.Invoke(TabType.Mods, mod.DirectoryName, mod.Name) == PenumbraApiEc.ModMissing) - Glamourer.Messager.NotificationMessage($"Could not open the mod {mod.Name}, no fitting mod was found in your Penumbra install.", NotificationType.Info, false); + 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 @@ -158,7 +159,7 @@ public unsafe class PenumbraService : IDisposable /// Try to set all mod settings as desired. Only sets when the mod should be enabled. /// If it is disabled, ignore all other settings. /// - public string SetMod(Mod mod, ModSettings settings) + public string SetMod(Mod mod, ModSettings settings, string? collection = null) { if (!Available) return "Penumbra is not available."; @@ -166,12 +167,13 @@ public unsafe class PenumbraService : IDisposable var sb = new StringBuilder(); try { - var collection = _currentCollection.Invoke(ApiCollectionType.Current); - var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled); - if (ec is PenumbraApiEc.ModMissing) - return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found."; - - Debug.Assert(ec is not PenumbraApiEc.CollectionMissing, "Missing collection should not be possible."); + collection ??= _currentCollection.Invoke(ApiCollectionType.Current); + var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled); + switch (ec) + { + case PenumbraApiEc.ModMissing: return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found."; + case PenumbraApiEc.CollectionMissing: return $"The collection {collection} could not be found."; + } if (!settings.Enabled) return string.Empty; @@ -216,13 +218,23 @@ public unsafe class PenumbraService : IDisposable return valid ? name : string.Empty; } + /// Obtain the name of the collection currently assigned to the given actor. + public string GetActorCollection(Actor actor) + { + if (!Available) + return string.Empty; + + var (valid, _, name) = _objectCollection.Invoke(actor.Index.Index); + return valid ? name : string.Empty; + } + /// Obtain the game object corresponding to a draw object. public Actor GameObjectFromDrawObject(Model drawObject) => 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) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index b1e41f7..47074f7 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -10,6 +10,8 @@ using Glamourer.Events; using Glamourer.GameData; using Glamourer.Gui; using Glamourer.Interop; +using Glamourer.Interop.Penumbra; +using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; using OtterGui; @@ -36,10 +38,11 @@ public class CommandService : IDisposable private readonly DesignConverter _converter; private readonly DesignFileSystem _designFileSystem; private readonly Configuration _config; + private readonly PenumbraService _penumbra; public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, - DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config) + DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, PenumbraService penumbra) { _commands = commands; _mainWindow = mainWindow; @@ -53,6 +56,7 @@ public class CommandService : IDisposable _designFileSystem = designFileSystem; _autoDesignManager = autoDesignManager; _config = config; + _penumbra = penumbra; _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); _commands.AddHandler(ApplyCommandString, @@ -368,11 +372,14 @@ public class CommandService : IDisposable private bool Apply(string arguments) { var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - if (split.Length != 2) + if (split.Length is not 2) { _chat.Print(new SeStringBuilder().AddText("Use with /glamour apply ").AddYellow("[Design Name, Path or Identifier, or Clipboard]") .AddText(" | ") - .AddGreen("[Character Identifier]").BuiltString); + .AddGreen("[Character Identifier]") + .AddText("; ") + .AddBlue("") + .BuiltString); _chat.Print(new SeStringBuilder() .AddText( " 》 The design name is case-insensitive. If multiple designs of that name up to case exist, the first one is chosen.") @@ -386,10 +393,27 @@ public class CommandService : IDisposable .BuiltString); _chat.Print(new SeStringBuilder() .AddText(" 》 Clipboard as a single word will try to apply a design string currently in your clipboard.").BuiltString); + _chat.Print(new SeStringBuilder() + .AddText(" 》 ").AddBlue("").AddText(" is optional and can be omitted (together with the ;), ").AddBlue("true") + .AddText(" or ").AddBlue("false").AddText(".").BuiltString); + _chat.Print(new SeStringBuilder().AddText("If ").AddBlue("true") + .AddText(", it will try to apply mod associations to the collection assigned to the identified character.").BuiltString); PlayerIdentifierHelp(false, true); } - if (!GetDesign(split[0], out var design, true) || !IdentifierHandling(split[1], out var identifiers, false, true)) + var split2 = split[1].Split(';', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + var applyMods = split2.Length == 2 + && split2[1].ToLowerInvariant() switch + { + "true" => true, + "1" => true, + "t" => true, + "yes" => true, + "y" => true, + _ => false, + }; + if (!GetDesign(split[0], out var design, true) || !IdentifierHandling(split2[0], out var identifiers, false, true)) return false; _objects.Update(); @@ -405,7 +429,10 @@ public class CommandService : IDisposable foreach (var actor in actors.Objects) { if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) + { + ApplyModSettings(design, actor, applyMods); _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); + } } } } @@ -413,6 +440,29 @@ public class CommandService : IDisposable return true; } + private void ApplyModSettings(DesignBase design, Actor actor, bool applyMods) + { + if (!applyMods || design is not Design d) + return; + + var collection = _penumbra.GetActorCollection(actor); + if (collection.Length <= 0) + return; + + var appliedMods = 0; + foreach (var (mod, setting) in d.AssociatedMods) + { + var message = _penumbra.SetMod(mod, setting, collection); + if (message.Length > 0) + Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); + else + ++appliedMods; + } + + if (appliedMods > 0) + Glamourer.Messager.Chat.Print($"Applied {appliedMods} mod settings to {collection}."); + } + private bool Delete(string argument) { if (argument.Length == 0) @@ -501,7 +551,8 @@ public class CommandService : IDisposable && _stateManager.GetOrCreate(identifier, data.Objects[0], out state))) continue; - var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All); + var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, + CustomizeParameterExtensions.All); _designManager.CreateClone(design, split[0], true); return true; } @@ -556,7 +607,6 @@ public class CommandService : IDisposable _chat.Print(new SeStringBuilder().AddText("The token ").AddYellow(argument, true).AddText(" did not resolve to an existing design.") .BuiltString); return false; - } private unsafe bool IdentifierHandling(string argument, out ActorIdentifier[] identifiers, bool allowAnyWorld, bool allowIndex) From a5c33a6311b30b7eff7ae511f75f1755dd2c3a6b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 8 Jan 2024 23:30:55 +0100 Subject: [PATCH 139/786] Use global usings for System headers. --- Glamourer/Api/GlamourerIpc.ApiVersions.cs | 3 +-- Glamourer/Api/GlamourerIpc.Apply.cs | 5 +---- Glamourer/Api/GlamourerIpc.Data.cs | 4 +--- Glamourer/Api/GlamourerIpc.Events.cs | 3 +-- .../Api/GlamourerIpc.GetCustomization.cs | 4 +--- Glamourer/Api/GlamourerIpc.Revert.cs | 3 +-- Glamourer/Api/GlamourerIpc.Set.cs | 3 +-- Glamourer/Api/GlamourerIpc.cs | 3 --- Glamourer/Automation/AutoDesign.cs | 3 +-- Glamourer/Automation/AutoDesignApplier.cs | 6 +----- Glamourer/Automation/AutoDesignManager.cs | 8 +------- Glamourer/Automation/AutoDesignSet.cs | 3 +-- Glamourer/Automation/FixedDesignMigrator.cs | 4 +--- Glamourer/Configuration.cs | 6 +----- Glamourer/Designs/Design.cs | 6 +----- Glamourer/Designs/DesignBase.cs | 4 ---- Glamourer/Designs/DesignBase64Migration.cs | 3 +-- Glamourer/Designs/DesignColors.cs | 5 ----- Glamourer/Designs/DesignConverter.cs | 6 +----- Glamourer/Designs/DesignData.cs | 4 +--- Glamourer/Designs/DesignFileSystem.cs | 8 +------- Glamourer/Designs/DesignManager.cs | 13 +------------ Glamourer/EphemeralConfig.cs | 4 +--- Glamourer/Events/GPoseService.cs | 4 +--- Glamourer/Events/ObjectUnlocked.cs | 1 - Glamourer/Events/SlotUpdating.cs | 1 - Glamourer/GameData/ColorParameters.cs | 5 +---- Glamourer/GameData/CustomizeData.cs | 4 +--- Glamourer/GameData/CustomizeManager.cs | 7 +------ Glamourer/GameData/CustomizeParameterData.cs | 5 +---- Glamourer/GameData/CustomizeParameterFlag.cs | 7 +------ Glamourer/GameData/CustomizeSet.cs | 6 +----- Glamourer/GameData/CustomizeSetFactory.cs | 6 +----- Glamourer/GameData/NpcCustomizeSet.cs | 7 +------ Glamourer/GameData/NpcData.cs | 4 +--- Glamourer/Glamourer.cs | 3 +-- Glamourer/GlobalUsings.cs | 19 +++++++++++++++++++ Glamourer/Gui/Colors.cs | 1 - .../CustomizationDrawer.Color.cs | 3 +-- .../CustomizationDrawer.GenderRace.cs | 5 +---- .../Customization/CustomizationDrawer.Icon.cs | 4 +--- .../CustomizationDrawer.Simple.cs | 4 +--- .../Gui/Customization/CustomizationDrawer.cs | 5 +---- .../CustomizeParameterDrawData.cs | 4 +--- .../Customization/CustomizeParameterDrawer.cs | 1 - Glamourer/Gui/DesignCombo.cs | 5 +---- Glamourer/Gui/DesignQuickBar.cs | 4 +--- Glamourer/Gui/Equipment/EquipDrawData.cs | 3 +-- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 7 +------ .../Gui/Equipment/GlamourerColorCombo.cs | 6 +----- Glamourer/Gui/Equipment/ItemCombo.cs | 5 +---- Glamourer/Gui/Equipment/WeaponCombo.cs | 5 +---- Glamourer/Gui/GenericPopupWindow.cs | 4 +--- Glamourer/Gui/GlamourerWindowSystem.cs | 3 +-- Glamourer/Gui/MainWindow.cs | 4 +--- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 6 +----- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 4 +--- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 5 +---- Glamourer/Gui/Tabs/ActorTab/ActorTab.cs | 3 +-- .../Gui/Tabs/AutomationTab/AutomationTab.cs | 3 +-- .../Gui/Tabs/AutomationTab/HumanNpcCombo.cs | 5 +---- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 7 +------ .../Gui/Tabs/AutomationTab/SetSelector.cs | 6 +----- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 5 +---- .../DebugTab/CustomizationServicePanel.cs | 3 +-- .../Tabs/DebugTab/CustomizationUnlockPanel.cs | 4 +--- Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs | 4 +--- Glamourer/Gui/Tabs/DebugTab/DebugTab.cs | 3 +-- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 4 +--- .../Gui/Tabs/DebugTab/DesignConverterPanel.cs | 5 +---- .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 4 +--- .../Gui/Tabs/DebugTab/DesignTesterPanel.cs | 4 +--- Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 3 +-- .../Gui/Tabs/DebugTab/ItemUnlockPanel.cs | 4 +--- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 4 +--- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 4 +--- .../Gui/Tabs/DebugTab/ObjectManagerPanel.cs | 6 +----- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 3 +-- .../Gui/Tabs/DebugTab/RetainedStatePanel.cs | 3 +-- .../Gui/Tabs/DebugTab/UnlockableItemsPanel.cs | 4 +--- .../Gui/Tabs/DesignTab/DesignColorCombo.cs | 3 +-- .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 5 +---- .../DesignTab/DesignFileSystemSelector.cs | 5 +---- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 6 +----- Glamourer/Gui/Tabs/DesignTab/DesignTab.cs | 3 +-- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 3 +-- Glamourer/Gui/Tabs/DesignTab/ModCombo.cs | 4 +--- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 6 +----- Glamourer/Gui/Tabs/HeaderDrawer.cs | 5 +---- Glamourer/Gui/Tabs/MessageTab.cs | 3 +-- .../Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs | 5 +---- Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs | 3 +-- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 5 +---- Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs | 6 +----- Glamourer/Gui/Tabs/NpcTab/NpcTab.cs | 3 +-- Glamourer/Gui/Tabs/SettingsTab.cs | 6 +----- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 5 +---- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 7 +------ Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs | 4 +--- Glamourer/Gui/ToggleDrawData.cs | 3 +-- Glamourer/Gui/UiHelpers.cs | 1 - Glamourer/Interop/ChangeCustomizeService.cs | 3 +-- Glamourer/Interop/CharaFile/CharaFile.cs | 3 +-- Glamourer/Interop/CharaFile/CmaFile.cs | 3 +-- Glamourer/Interop/ContextMenuService.cs | 3 +-- Glamourer/Interop/ImportService.cs | 6 +----- Glamourer/Interop/InventoryService.cs | 4 +--- Glamourer/Interop/JobService.cs | 3 --- Glamourer/Interop/MetaService.cs | 3 +-- Glamourer/Interop/ObjectManager.cs | 5 +---- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 3 +-- Glamourer/Interop/Penumbra/PenumbraService.cs | 8 +------- Glamourer/Interop/ScalingService.cs | 4 +--- Glamourer/Interop/Structs/Actor.cs | 1 - Glamourer/Interop/Structs/ActorData.cs | 4 +--- Glamourer/Interop/Structs/Model.cs | 3 +-- Glamourer/Interop/UpdateSlotService.cs | 3 +-- Glamourer/Interop/VisorService.cs | 4 +--- Glamourer/Interop/WeaponService.cs | 4 +--- Glamourer/Services/BackupService.cs | 2 -- Glamourer/Services/CodeService.cs | 5 ----- Glamourer/Services/CommandService.cs | 5 +---- Glamourer/Services/ConfigMigrationService.cs | 5 +---- Glamourer/Services/CustomizeService.cs | 3 --- Glamourer/Services/FilenameService.cs | 2 -- Glamourer/Services/ItemManager.cs | 3 --- Glamourer/Services/TextureService.cs | 2 -- Glamourer/State/ActorState.cs | 1 - Glamourer/State/FunEquipSet.cs | 3 +-- Glamourer/State/FunModule.cs | 4 +--- Glamourer/State/StateApplier.cs | 3 +-- Glamourer/State/StateEditor.cs | 5 ++--- Glamourer/State/StateListener.cs | 1 - Glamourer/State/StateManager.cs | 8 +------- Glamourer/State/WorldSets.cs | 4 +--- Glamourer/Unlocks/CustomizeUnlockManager.cs | 6 +----- Glamourer/Unlocks/FavoriteManager.cs | 6 +----- Glamourer/Unlocks/ItemUnlockManager.cs | 7 +------ Glamourer/Unlocks/UnlockDictionaryHelpers.cs | 5 +---- Glamourer/Utility/CompressExtensions.cs | 6 +----- 140 files changed, 141 insertions(+), 472 deletions(-) create mode 100644 Glamourer/GlobalUsings.cs diff --git a/Glamourer/Api/GlamourerIpc.ApiVersions.cs b/Glamourer/Api/GlamourerIpc.ApiVersions.cs index 7c74d62..25aaf57 100644 --- a/Glamourer/Api/GlamourerIpc.ApiVersions.cs +++ b/Glamourer/Api/GlamourerIpc.ApiVersions.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Plugin; +using Dalamud.Plugin; using Penumbra.Api.Helpers; namespace Glamourer.Api; diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs index d2bc86f..47c655e 100644 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ b/Glamourer/Api/GlamourerIpc.Apply.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using Glamourer.Designs; using Glamourer.Events; diff --git a/Glamourer/Api/GlamourerIpc.Data.cs b/Glamourer/Api/GlamourerIpc.Data.cs index 3acff10..c981899 100644 --- a/Glamourer/Api/GlamourerIpc.Data.cs +++ b/Glamourer/Api/GlamourerIpc.Data.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Dalamud.Plugin; +using Dalamud.Plugin; using Penumbra.Api.Helpers; namespace Glamourer.Api; diff --git a/Glamourer/Api/GlamourerIpc.Events.cs b/Glamourer/Api/GlamourerIpc.Events.cs index 0186f6d..44f0775 100644 --- a/Glamourer/Api/GlamourerIpc.Events.cs +++ b/Glamourer/Api/GlamourerIpc.Events.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.Events; +using Glamourer.Events; using Glamourer.Interop.Structs; using Glamourer.State; using Penumbra.Api.Helpers; diff --git a/Glamourer/Api/GlamourerIpc.GetCustomization.cs b/Glamourer/Api/GlamourerIpc.GetCustomization.cs index cdec349..bedf514 100644 --- a/Glamourer/Api/GlamourerIpc.GetCustomization.cs +++ b/Glamourer/Api/GlamourerIpc.GetCustomization.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using Penumbra.Api.Helpers; using Penumbra.GameData.Actors; diff --git a/Glamourer/Api/GlamourerIpc.Revert.cs b/Glamourer/Api/GlamourerIpc.Revert.cs index 9d486ae..8bf47cc 100644 --- a/Glamourer/Api/GlamourerIpc.Revert.cs +++ b/Glamourer/Api/GlamourerIpc.Revert.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using Glamourer.Events; using Penumbra.Api.Helpers; diff --git a/Glamourer/Api/GlamourerIpc.Set.cs b/Glamourer/Api/GlamourerIpc.Set.cs index 5a6405d..1a39bea 100644 --- a/Glamourer/Api/GlamourerIpc.Set.cs +++ b/Glamourer/Api/GlamourerIpc.Set.cs @@ -1,5 +1,4 @@ -using System.Linq; -using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using Glamourer.Events; using Glamourer.Services; diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index b4c29b4..36d248b 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -1,8 +1,5 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; -using System; -using System.Collections.Generic; -using System.Linq; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Events; diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 364c358..7ab3021 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop.Structs; using Glamourer.State; diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index dde2146..aa808d1 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Designs; using Glamourer.Events; diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index e205784..38e4479 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Events; diff --git a/Glamourer/Automation/AutoDesignSet.cs b/Glamourer/Automation/AutoDesignSet.cs index 8657ad7..3e1d713 100644 --- a/Glamourer/Automation/AutoDesignSet.cs +++ b/Glamourer/Automation/AutoDesignSet.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Linq; using Penumbra.GameData.Actors; namespace Glamourer.Automation; diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index 4ed98d3..9809fd8 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Interop; using Newtonsoft.Json.Linq; diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index cb093bf..d552960 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Dalamud.Configuration; +using Dalamud.Configuration; using Dalamud.Game.ClientState.Keys; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index c38669b..b15b535 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Internal.Notifications; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 3ed2524..e4ea085 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -5,10 +5,6 @@ using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Numerics; using Penumbra.GameData.DataContainers; namespace Glamourer.Designs; diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index d4352e6..2d85924 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.Services; +using Glamourer.Services; using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index fa257c6..4026a98 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Utility.Raii; diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 7f8e1f2..effd78f 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using Glamourer.GameData; +using Glamourer.GameData; using Glamourer.Services; using Glamourer.State; using Glamourer.Utility; diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index f29d42e..edc35e1 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -1,6 +1,4 @@ -using System; -using System.Runtime.CompilerServices; -using Glamourer.GameData; +using Glamourer.GameData; using Glamourer.Services; using OtterGui.Classes; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index db727e8..2f346a1 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Internal.Notifications; using Glamourer.Events; using Glamourer.Services; using Newtonsoft.Json; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index a869896..d41f0fe 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -1,14 +1,4 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Numerics; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; -using Dalamud.Utility; +using Dalamud.Utility; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop.Penumbra; @@ -20,7 +10,6 @@ using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using static Glamourer.State.ActorState; namespace Glamourer.Designs; diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/EphemeralConfig.cs index 4bd57ed..d106783 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/EphemeralConfig.cs @@ -1,6 +1,4 @@ -using System; -using System.IO; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Internal.Notifications; using Glamourer.Gui; using Glamourer.Services; using Newtonsoft.Json; diff --git a/Glamourer/Events/GPoseService.cs b/Glamourer/Events/GPoseService.cs index a87914e..7b162ae 100644 --- a/Glamourer/Events/GPoseService.cs +++ b/Glamourer/Events/GPoseService.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Concurrent; -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using OtterGui.Classes; namespace Glamourer.Events; diff --git a/Glamourer/Events/ObjectUnlocked.cs b/Glamourer/Events/ObjectUnlocked.cs index b31fa3e..b15acd2 100644 --- a/Glamourer/Events/ObjectUnlocked.cs +++ b/Glamourer/Events/ObjectUnlocked.cs @@ -1,4 +1,3 @@ -using System; using OtterGui.Classes; namespace Glamourer.Events; diff --git a/Glamourer/Events/SlotUpdating.cs b/Glamourer/Events/SlotUpdating.cs index ab6e2f9..97f9be9 100644 --- a/Glamourer/Events/SlotUpdating.cs +++ b/Glamourer/Events/SlotUpdating.cs @@ -1,4 +1,3 @@ -using System; using Glamourer.Interop.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; diff --git a/Glamourer/GameData/ColorParameters.cs b/Glamourer/GameData/ColorParameters.cs index 975e003..1942804 100644 --- a/Glamourer/GameData/ColorParameters.cs +++ b/Glamourer/GameData/ColorParameters.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using Penumbra.String.Functions; namespace Glamourer.GameData; diff --git a/Glamourer/GameData/CustomizeData.cs b/Glamourer/GameData/CustomizeData.cs index 4d4e2fd..62828ae 100644 --- a/Glamourer/GameData/CustomizeData.cs +++ b/Glamourer/GameData/CustomizeData.cs @@ -1,6 +1,4 @@ -using System; -using System.Runtime.InteropServices; -using Penumbra.GameData.Enums; +using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.GameData; diff --git a/Glamourer/GameData/CustomizeManager.cs b/Glamourer/GameData/CustomizeManager.cs index a78cdc0..57df104 100644 --- a/Glamourer/GameData/CustomizeManager.cs +++ b/Glamourer/GameData/CustomizeManager.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Dalamud.Interface.Internal; +using Dalamud.Interface.Internal; using Dalamud.Plugin.Services; using OtterGui.Classes; using OtterGui.Services; diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs index 16a6dad..2d2b650 100644 --- a/Glamourer/GameData/CustomizeParameterData.cs +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -1,7 +1,4 @@ -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using FFXIVClientStructs.FFXIV.Shader; +using FFXIVClientStructs.FFXIV.Shader; namespace Glamourer.GameData; diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index d874538..d2b2ff4 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; - -namespace Glamourer.GameData; +namespace Glamourer.GameData; [Flags] public enum CustomizeParameterFlag : ushort diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index 7c98903..0c80e13 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using OtterGui; +using OtterGui; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 90cae8d..ba892ec 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Dalamud; +using Dalamud; using Dalamud.Plugin.Services; using Dalamud.Utility; using Lumina.Excel; diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index d364c1e..302f1ce 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -1,9 +1,4 @@ -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Lumina.Excel.GeneratedSheets; diff --git a/Glamourer/GameData/NpcData.cs b/Glamourer/GameData/NpcData.cs index 2db8fb5..860f80a 100644 --- a/Glamourer/GameData/NpcData.cs +++ b/Glamourer/GameData/NpcData.cs @@ -1,6 +1,4 @@ -using System; -using System.Text; -using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.Object; using Penumbra.GameData.Structs; namespace Glamourer.GameData; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index c9d76ef..8f88216 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using Dalamud.Plugin; +using Dalamud.Plugin; using Glamourer.Api; using Glamourer.Gui; using Glamourer.Interop; diff --git a/Glamourer/GlobalUsings.cs b/Glamourer/GlobalUsings.cs new file mode 100644 index 0000000..03d2082 --- /dev/null +++ b/Glamourer/GlobalUsings.cs @@ -0,0 +1,19 @@ +global using System; +global using System.Collections; +global using System.Collections.Concurrent; +global using System.Collections.Generic; +global using System.Diagnostics; +global using System.Diagnostics.CodeAnalysis; +global using System.Globalization; +global using System.IO; +global using System.IO.Compression; +global using System.Linq; +global using System.Numerics; +global using System.Reflection; +global using System.Runtime.CompilerServices; +global using System.Runtime.InteropServices; +global using System.Security.Cryptography; +global using System.Text; +global using System.Text.RegularExpressions; +global using System.Threading; +global using System.Threading.Tasks; diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index 7addc45..b7f9737 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using ImGuiNET; namespace Glamourer.Gui; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index 5fa28a6..f3753ae 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -1,5 +1,4 @@ -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.GameData; using ImGuiNET; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index a50424c..ae64075 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using ImGuiNET; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index 9b35b92..f3eabbd 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Glamourer.GameData; +using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index e970fb3..02d4def 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using ImGuiNET; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index b741f39..4062d69 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -1,7 +1,4 @@ -using System; -using System.Numerics; -using System.Reflection; -using Dalamud.Interface.Internal; +using Dalamud.Interface.Internal; using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.GameData; diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs index 09426e2..becaef5 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Events; using Glamourer.GameData; using Glamourer.State; diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 41760ed..a97fbe7 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -1,7 +1,6 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.State; -using System.Numerics; using Dalamud.Interface.Utility.Raii; using ImGuiNET; using OtterGui.Services; diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index d52be90..c63fa3c 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Glamourer.Automation; using Glamourer.GameData; diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index d808cbe..0e361a1 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Dalamud.Game.ClientState.Keys; +using Dalamud.Game.ClientState.Keys; using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index f94d997..2a902c8 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Events; using Glamourer.Services; using Glamourer.State; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index e8840bc..5910929 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Numerics; -using Dalamud.Interface.Components; +using Dalamud.Interface.Components; using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; using Glamourer.Events; diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index 2a3dcec..39d63dc 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Glamourer.Unlocks; diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 9e1790a..d9fd12a 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index 027a211..32f383b 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Glamourer.Services; +using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index ba16ab9..77257b5 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.ClientState.Conditions; using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 95a1042..59b237c 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Windowing; using Glamourer.Gui.Tabs.UnlocksTab; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 565ef84..0ce4147 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Glamourer.Designs; diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index b499bc1..d030abb 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Glamourer.Events; +using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index aad9e52..8edc8fc 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.ClientState.Conditions; using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 934a19d..8bace46 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.Services; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs index 76b0a55..0d4584d 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility; using ImGuiNET; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs index e5d4b21..145531f 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility; using ImGuiNET; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs index 0eeda59..49feae9 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Utility; using ImGuiNET; using OtterGui; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 054ee0a..a50445e 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Text; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Automation; using Glamourer.Interop; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index c1544b2..4e63361 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Automation; using Glamourer.Events; diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 53cffd6..5408a7c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Glamourer.GameData; using Glamourer.Designs; using Glamourer.Events; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index 7ab089a..17e180e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.GameData; +using Glamourer.GameData; using Glamourer.Services; using ImGuiNET; using OtterGui; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs index 8703d8c..a53a677 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Glamourer.Unlocks; +using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs index b06e807..35bd136 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs @@ -1,6 +1,4 @@ -using System.IO; -using System.Numerics; -using Glamourer.Interop; +using Glamourer.Interop; using ImGuiNET; using OtterGui; using Penumbra.GameData.Files; diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs index ffe6945..cd89aec 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs @@ -1,5 +1,4 @@ -using System; -using ImGuiNET; +using ImGuiNET; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index b61b4a6..0c4d617 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using ImGuiNET; +using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs index 0de63ed..042dfd3 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Text; -using Dalamud.Interface; +using Dalamud.Interface; using Glamourer.Designs; using Glamourer.Utility; using ImGuiNET; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index 8f40388..d4070ef 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Dalamud.Interface; +using Dalamud.Interface; using Glamourer.Designs; using ImGuiNET; using OtterGui; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index a4e17ed..7bc83f9 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Dalamud.Interface; +using Dalamud.Interface; using Glamourer.Designs; using Glamourer.Services; using ImGuiNET; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index 24e839e..02348a2 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using Glamourer.Api; using Glamourer.Interop; diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs index efcc664..afbb86b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index 1514647..f751ef0 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Shader; using Glamourer.GameData; using Glamourer.Interop; diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 2ebf215..1ff78cb 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs index e90cbda..e4ea44b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -1,8 +1,4 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Numerics; -using Glamourer.Interop; +using Glamourer.Interop; using ImGuiNET; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 9c7ccc1..e53a1f7 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using ImGuiNET; diff --git a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs index 027c316..2abc1db 100644 --- a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs @@ -1,5 +1,4 @@ -using System.Linq; -using Glamourer.Interop; +using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs index ecd8d83..99c79ed 100644 --- a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs index 0db26b7..d00fea8 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -1,5 +1,4 @@ -using System.Linq; -using Glamourer.Designs; +using Glamourer.Designs; using ImGuiNET; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index ca58393..88c2bdb 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -1,7 +1,4 @@ -using System; -using System.Diagnostics; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Services; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index b288303..a4f71c0 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; using Glamourer.Designs; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 74ff84f..b3c1b4a 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Internal.Notifications; using FFXIVClientStructs.FFXIV.Client.System.Framework; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index 1c5a0b9..7fca8c2 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 29c8ab7..7624c0b 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -1,5 +1,4 @@ -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Utility; using Dalamud.Utility; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs index 0b2aa4d..4cd899f 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility; using Glamourer.Interop.Penumbra; using ImGuiNET; using OtterGui.Classes; diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 4b1e128..a5d57a2 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Designs; using ImGuiNET; diff --git a/Glamourer/Gui/Tabs/HeaderDrawer.cs b/Glamourer/Gui/Tabs/HeaderDrawer.cs index 6f62e08..0690f97 100644 --- a/Glamourer/Gui/Tabs/HeaderDrawer.cs +++ b/Glamourer/Gui/Tabs/HeaderDrawer.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Utility; using ImGuiNET; using OtterGui; diff --git a/Glamourer/Gui/Tabs/MessageTab.cs b/Glamourer/Gui/Tabs/MessageTab.cs index 69a2a2c..b3ce428 100644 --- a/Glamourer/Gui/Tabs/MessageTab.cs +++ b/Glamourer/Gui/Tabs/MessageTab.cs @@ -1,5 +1,4 @@ -using System; -using OtterGui.Classes; +using OtterGui.Classes; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs; diff --git a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs index bb22a85..7941c13 100644 --- a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs +++ b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Services; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs b/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs index f57985b..1b698f9 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.GameData; using OtterGui.Classes; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 235b866..f815e3d 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Internal.Notifications; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs index 10d2264..b3f0cef 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Glamourer.GameData; +using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs index fb64227..4efa4c3 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility; using ImGuiNET; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index ac3dc1b..ea1fd24 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -1,8 +1,4 @@ -using System; -using System.Linq; -using System.Numerics; -using System.Runtime.CompilerServices; -using Dalamud.Game.ClientState.Keys; +using Dalamud.Game.ClientState.Keys; using Dalamud.Interface; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 65203b9..ea89e56 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.Utility; using Glamourer.GameData; using Glamourer.Interop; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index d7e1ce3..d4fd4b0 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.Utility; using Glamourer.Events; using Glamourer.Interop; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs index d7b8162..1c82add 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs @@ -1,6 +1,4 @@ -using System; -using System.Numerics; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Windowing; using ImGuiNET; using OtterGui.Raii; diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index 3991893..ed435c4 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Events; using Glamourer.State; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index d55fde0..7e22ff1 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -1,4 +1,3 @@ -using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.GameData; diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 060021e..d055adc 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Hooking; +using Dalamud.Hooking; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Events; diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs index 9ddcc03..8e25d8e 100644 --- a/Glamourer/Interop/CharaFile/CharaFile.cs +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Services; using Newtonsoft.Json.Linq; using Penumbra.GameData.Enums; diff --git a/Glamourer/Interop/CharaFile/CmaFile.cs b/Glamourer/Interop/CharaFile/CmaFile.cs index e525e7e..bf67a59 100644 --- a/Glamourer/Interop/CharaFile/CmaFile.cs +++ b/Glamourer/Interop/CharaFile/CmaFile.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Services; using Newtonsoft.Json.Linq; using Penumbra.GameData.Enums; diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 96dc107..228eda7 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.ContextMenu; +using Dalamud.ContextMenu; using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin; diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 4ec59e9..b31a1b0 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -1,8 +1,4 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using Dalamud.Interface.DragDrop; +using Dalamud.Interface.DragDrop; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Interop.CharaFile; diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 6e72e91..63abc9d 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using Dalamud.Hooking; +using Dalamud.Hooking; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Misc; diff --git a/Glamourer/Interop/JobService.cs b/Glamourer/Interop/JobService.cs index 5ee9cf6..bff849e 100644 --- a/Glamourer/Interop/JobService.cs +++ b/Glamourer/Interop/JobService.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs index 30378a1..b6bd511 100644 --- a/Glamourer/Interop/MetaService.cs +++ b/Glamourer/Interop/MetaService.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Hooking; +using Dalamud.Hooking; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index 5db7400..c44fcd0 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using Dalamud.Game.ClientState.Objects; +using Dalamud.Game.ClientState.Objects; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Control; using Glamourer.Interop.Structs; diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index ce4f99e..f702e84 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.State; +using Glamourer.State; using Penumbra.Api.Enums; namespace Glamourer.Interop.Penumbra; diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 23ca919..7f26e4f 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using Dalamud.Interface.Internal.Notifications; -using Dalamud.Logging; +using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin; using Glamourer.Events; using Glamourer.Interop.Structs; diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index ea71cad..585b13b 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -1,6 +1,4 @@ -using System; -using System.Runtime.CompilerServices; -using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 78dd9fa..a5164d7 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -1,5 +1,4 @@ using Penumbra.GameData.Actors; -using System; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Penumbra.GameData.Enums; diff --git a/Glamourer/Interop/Structs/ActorData.cs b/Glamourer/Interop/Structs/ActorData.cs index d93df6b..c1a0ea4 100644 --- a/Glamourer/Interop/Structs/ActorData.cs +++ b/Glamourer/Interop/Structs/ActorData.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using OtterGui.Log; +using OtterGui.Log; namespace Glamourer.Interop.Structs; diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index 9705248..d91ca58 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -1,5 +1,4 @@ -using System; -using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 31472ff..0891945 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -1,5 +1,4 @@ -using System; -using Dalamud.Hooking; +using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using Glamourer.Events; diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 75c5e36..082f6b8 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -1,6 +1,4 @@ -using System; -using System.Runtime.CompilerServices; -using Dalamud.Hooking; +using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 4c87430..8780c2a 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -1,6 +1,4 @@ -using System; -using System.Threading; -using Dalamud.Hooking; +using Dalamud.Hooking; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs index ea71319..3abf13a 100644 --- a/Glamourer/Services/BackupService.cs +++ b/Glamourer/Services/BackupService.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.IO; using OtterGui.Classes; using OtterGui.Log; diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 7bc5f04..77fdcaa 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -1,8 +1,3 @@ -using System; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; using Penumbra.GameData.Enums; namespace Glamourer.Services; diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 47074f7..fa08425 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -1,7 +1,4 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Dalamud.Game.Command; +using Dalamud.Game.Command; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Services; using Glamourer.Automation; diff --git a/Glamourer/Services/ConfigMigrationService.cs b/Glamourer/Services/ConfigMigrationService.cs index 3be7c29..55b0664 100644 --- a/Glamourer/Services/ConfigMigrationService.cs +++ b/Glamourer/Services/ConfigMigrationService.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; -using System.Linq; -using Glamourer.Automation; +using Glamourer.Automation; using Glamourer.Gui; using Newtonsoft.Json.Linq; diff --git a/Glamourer/Services/CustomizeService.cs b/Glamourer/Services/CustomizeService.cs index 0b094f3..bb9737d 100644 --- a/Glamourer/Services/CustomizeService.cs +++ b/Glamourer/Services/CustomizeService.cs @@ -1,6 +1,3 @@ -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; using Glamourer.GameData; using OtterGui.Services; using Penumbra.GameData.DataContainers; diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index 7f54f19..244c972 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.IO; using Dalamud.Plugin; using Glamourer.Designs; diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 6e73945..e382573 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; -using System.Runtime.CompilerServices; using Dalamud.Plugin.Services; using Lumina.Excel; using Penumbra.GameData.Data; diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index e740677..6539c7b 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -1,5 +1,3 @@ -using System; -using System.Numerics; using Dalamud.Interface; using Dalamud.Interface.Internal; using Dalamud.Plugin.Services; diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index cd5a23a..ef00f38 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -2,7 +2,6 @@ using Glamourer.Events; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; -using System.Linq; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; using Glamourer.GameData; diff --git a/Glamourer/State/FunEquipSet.cs b/Glamourer/State/FunEquipSet.cs index 4df0d7d..bb56fcb 100644 --- a/Glamourer/State/FunEquipSet.cs +++ b/Glamourer/State/FunEquipSet.cs @@ -1,5 +1,4 @@ -using System; -using Glamourer.Interop.Structs; +using Glamourer.Interop.Structs; using Penumbra.GameData.Structs; namespace Glamourer.State; diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 43f8f07..f152528 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Gui; diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 5278f72..fbc7b6b 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,5 +1,4 @@ -using System.Linq; -using FFXIVClientStructs.FFXIV.Shader; +using FFXIVClientStructs.FFXIV.Shader; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 28e631c..55ea1c1 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Common.Math; using Glamourer.Events; using Glamourer.GameData; @@ -8,6 +6,7 @@ using Glamourer.Services; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Vector3 = FFXIVClientStructs.FFXIV.Common.Math.Vector3; namespace Glamourer.State; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 5d1b57c..f9684d8 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -7,7 +7,6 @@ using Glamourer.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Shader; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 4039f00..e39f30b 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Numerics; -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Shader; using Glamourer.Designs; using Glamourer.Events; diff --git a/Glamourer/State/WorldSets.cs b/Glamourer/State/WorldSets.cs index 4590eec..5a26b77 100644 --- a/Glamourer/State/WorldSets.cs +++ b/Glamourer/State/WorldSets.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using Glamourer.Interop.Structs; +using Glamourer.Interop.Structs; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index 2bdbb78..b63a98e 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Dalamud; +using Dalamud; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility; diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index a96ae35..83ff472 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Internal.Notifications; using Glamourer.Services; using Newtonsoft.Json; using OtterGui.Classes; diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index fa7d74a..de35335 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.UI; using Glamourer.Events; diff --git a/Glamourer/Unlocks/UnlockDictionaryHelpers.cs b/Glamourer/Unlocks/UnlockDictionaryHelpers.cs index a1523d0..28f5793 100644 --- a/Glamourer/Unlocks/UnlockDictionaryHelpers.cs +++ b/Glamourer/Unlocks/UnlockDictionaryHelpers.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Internal.Notifications; using OtterGui.Classes; namespace Glamourer.Unlocks; diff --git a/Glamourer/Utility/CompressExtensions.cs b/Glamourer/Utility/CompressExtensions.cs index b3ea9c2..9971456 100644 --- a/Glamourer/Utility/CompressExtensions.cs +++ b/Glamourer/Utility/CompressExtensions.cs @@ -1,8 +1,4 @@ -using System; -using System.IO; -using System.IO.Compression; -using System.Text; -using Penumbra.String.Functions; +using Penumbra.String.Functions; namespace Glamourer.Utility; From 31bff511b8b578d0cc03740cf4a9117b8280c2de Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 8 Jan 2024 23:36:57 +0100 Subject: [PATCH 140/786] Update OtterGui. --- Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs | 2 +- OtterGui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index a5d57a2..474c2f4 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -52,7 +52,7 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager ImGui.TableNextColumn(); var icon = (path is DesignFileSystem.Leaf ? FontAwesomeIcon.FileCircleMinus : FontAwesomeIcon.FolderMinus).ToIconString(); if (ImGuiUtil.DrawDisabledButton(icon, new Vector2(sizeType), "Remove from selection.", false, true)) - _selector.RemovePathFromMultiselection(path); + _selector.RemovePathFromMultiSelection(path); ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); diff --git a/OtterGui b/OtterGui index e4a8261..82df166 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit e4a82619332aade2748a381956a1ab8934de6211 +Subproject commit 82df166f1fa0e8f1cf9ca8f677cbeac71fb549ab From 6158bcb2f9d73d96803f208adcff15f62fc407de Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 8 Jan 2024 22:40:43 +0000 Subject: [PATCH 141/786] [CI] Updating repo.json for testing_1.0.7.4 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index e030efe..0a74434 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.7.0", - "TestingAssemblyVersion": "1.0.7.3", + "TestingAssemblyVersion": "1.0.7.4", "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.0.7.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 5ea779a34cd3f60f00490b3702ab85484975c70d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 9 Jan 2024 16:05:18 +0100 Subject: [PATCH 142/786] Add decal color, fix some bugs and improve logic and handling somewhat. --- Glamourer/Designs/DesignBase.cs | 36 ++- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/GameData/CustomizeParameterData.cs | 223 ++++++++++-------- Glamourer/GameData/CustomizeParameterFlag.cs | 18 +- Glamourer/GameData/CustomizeParameterValue.cs | 47 ++++ Glamourer/GameData/DecalParameters.cs | 8 + .../CustomizeParameterDrawData.cs | 12 +- .../Customization/CustomizeParameterDrawer.cs | 58 ++++- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 10 +- Glamourer/Interop/Structs/Model.cs | 61 +++++ Glamourer/State/StateApplier.cs | 19 +- Glamourer/State/StateEditor.cs | 2 +- Glamourer/State/StateListener.cs | 19 +- Glamourer/State/StateManager.cs | 10 +- 14 files changed, 351 insertions(+), 174 deletions(-) create mode 100644 Glamourer/GameData/CustomizeParameterValue.cs create mode 100644 Glamourer/GameData/DecalParameters.cs diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index e4ea085..cc037fb 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -371,7 +371,7 @@ public class DesignBase }; } - foreach (var flag in CustomizeParameterExtensions.TripleFlags) + foreach (var flag in CustomizeParameterExtensions.RgbFlags) { ret[flag.ToString()] = new JObject() { @@ -382,6 +382,18 @@ public class DesignBase }; } + foreach (var flag in CustomizeParameterExtensions.RgbaFlags) + { + ret[flag.ToString()] = new JObject() + { + ["Red"] = DesignData.Parameters[flag][0], + ["Green"] = DesignData.Parameters[flag][1], + ["Blue"] = DesignData.Parameters[flag][2], + ["Alpha"] = DesignData.Parameters[flag][3], + ["Apply"] = DoApplyParameter(flag), + }; + } + return ret; } @@ -424,7 +436,7 @@ public class DesignBase continue; var value = token["Value"]?.ToObject() ?? 0f; - design.GetDesignDataRef().Parameters[flag] = new Vector3(value); + design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(value); } foreach (var flag in CustomizeParameterExtensions.PercentageFlags) @@ -433,10 +445,10 @@ public class DesignBase continue; var value = Math.Clamp(token["Percentage"]?.ToObject() ?? 0f, 0f, 1f); - design.GetDesignDataRef().Parameters[flag] = new Vector3(value); + design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(value); } - foreach (var flag in CustomizeParameterExtensions.TripleFlags) + foreach (var flag in CustomizeParameterExtensions.RgbFlags) { if (!TryGetToken(flag, out var token)) continue; @@ -444,7 +456,19 @@ public class DesignBase var r = Math.Clamp(token["Red"]?.ToObject() ?? 0f, 0, 1); var g = Math.Clamp(token["Green"]?.ToObject() ?? 0f, 0, 1); var b = Math.Clamp(token["Blue"]?.ToObject() ?? 0f, 0, 1); - design.GetDesignDataRef().Parameters[flag] = new Vector3(r, g, b); + design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(r, g, b); + } + + foreach (var flag in CustomizeParameterExtensions.RgbaFlags) + { + if (!TryGetToken(flag, out var token)) + continue; + + var r = Math.Clamp(token["Red"]?.ToObject() ?? 0f, 0, 1); + var g = Math.Clamp(token["Green"]?.ToObject() ?? 0f, 0, 1); + var b = Math.Clamp(token["Blue"]?.ToObject() ?? 0f, 0, 1); + var a = Math.Clamp(token["Alpha"]?.ToObject() ?? 0f, 0, 1); + design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(r, g, b, a); } return; @@ -461,7 +485,7 @@ public class DesignBase } design.ApplyParameters &= ~flag; - design.GetDesignDataRef().Parameters[flag] = Vector3.Zero; + design.GetDesignDataRef().Parameters[flag] = CustomizeParameterValue.Zero; return false; } } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index d41f0fe..f16281a 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -418,7 +418,7 @@ public class DesignManager } /// Change a customize parameter. - public void ChangeCustomizeParameter(Design design, CustomizeParameterFlag flag, Vector3 value) + public void ChangeCustomizeParameter(Design design, CustomizeParameterFlag flag, CustomizeParameterValue value) { var old = design.DesignData.Parameters[flag]; if (!design.GetDesignDataRef().Parameters.Set(flag, value)) diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs index 2d2b650..0ffbad6 100644 --- a/Glamourer/GameData/CustomizeParameterData.cs +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -13,55 +13,58 @@ public struct CustomizeParameterData public Vector3 LeftEye; public Vector3 RightEye; public Vector3 FeatureColor; + public Vector4 DecalColor; public float FacePaintUvMultiplier; public float FacePaintUvOffset; public float MuscleTone; public float LipOpacity; - public Vector3 this[CustomizeParameterFlag flag] + public CustomizeParameterValue this[CustomizeParameterFlag flag] { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] readonly get { return flag switch { - CustomizeParameterFlag.SkinDiffuse => SkinDiffuse, - CustomizeParameterFlag.MuscleTone => new Vector3(MuscleTone, 0, 0), - CustomizeParameterFlag.SkinSpecular => SkinSpecular, - CustomizeParameterFlag.LipDiffuse => LipDiffuse, - CustomizeParameterFlag.LipOpacity => new Vector3(LipOpacity, 0, 0), - CustomizeParameterFlag.HairDiffuse => HairDiffuse, - CustomizeParameterFlag.HairSpecular => HairSpecular, - CustomizeParameterFlag.HairHighlight => HairHighlight, - CustomizeParameterFlag.LeftEye => LeftEye, - CustomizeParameterFlag.RightEye => RightEye, - CustomizeParameterFlag.FeatureColor => FeatureColor, - CustomizeParameterFlag.FacePaintUvMultiplier => new Vector3(FacePaintUvMultiplier, 0, 0), - CustomizeParameterFlag.FacePaintUvOffset => new Vector3(FacePaintUvOffset, 0, 0), - _ => Vector3.Zero, + CustomizeParameterFlag.SkinDiffuse => new CustomizeParameterValue(SkinDiffuse), + CustomizeParameterFlag.MuscleTone => new CustomizeParameterValue(MuscleTone), + CustomizeParameterFlag.SkinSpecular => new CustomizeParameterValue(SkinSpecular), + CustomizeParameterFlag.LipDiffuse => new CustomizeParameterValue(LipDiffuse), + CustomizeParameterFlag.LipOpacity => new CustomizeParameterValue(LipOpacity), + CustomizeParameterFlag.HairDiffuse => new CustomizeParameterValue(HairDiffuse), + CustomizeParameterFlag.HairSpecular => new CustomizeParameterValue(HairSpecular), + CustomizeParameterFlag.HairHighlight => new CustomizeParameterValue(HairHighlight), + CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye), + CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye), + CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(FeatureColor), + CustomizeParameterFlag.DecalColor => new CustomizeParameterValue(DecalColor), + CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(FacePaintUvMultiplier), + CustomizeParameterFlag.FacePaintUvOffset => new CustomizeParameterValue(FacePaintUvOffset), + _ => CustomizeParameterValue.Zero, }; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] set => Set(flag, value); } - public bool Set(CustomizeParameterFlag flag, Vector3 value) + public bool Set(CustomizeParameterFlag flag, CustomizeParameterValue value) { return flag switch { - CustomizeParameterFlag.SkinDiffuse => SetIfDifferent(ref SkinDiffuse, value), - CustomizeParameterFlag.MuscleTone => SetIfDifferent(ref MuscleTone, Math.Clamp(value[0], -100, 100)), - CustomizeParameterFlag.SkinSpecular => SetIfDifferent(ref SkinSpecular, value), - CustomizeParameterFlag.LipDiffuse => SetIfDifferent(ref LipDiffuse, value), - CustomizeParameterFlag.LipOpacity => SetIfDifferent(ref LipOpacity, Math.Clamp(value[0], -100, 100)), - CustomizeParameterFlag.HairDiffuse => SetIfDifferent(ref HairDiffuse, value), - CustomizeParameterFlag.HairSpecular => SetIfDifferent(ref HairSpecular, value), - CustomizeParameterFlag.HairHighlight => SetIfDifferent(ref HairHighlight, value), - CustomizeParameterFlag.LeftEye => SetIfDifferent(ref LeftEye, value), - CustomizeParameterFlag.RightEye => SetIfDifferent(ref RightEye, value), - CustomizeParameterFlag.FeatureColor => SetIfDifferent(ref FeatureColor, value), - CustomizeParameterFlag.FacePaintUvMultiplier => SetIfDifferent(ref FacePaintUvMultiplier, value[0]), - CustomizeParameterFlag.FacePaintUvOffset => SetIfDifferent(ref FacePaintUvOffset, value[0]), + CustomizeParameterFlag.SkinDiffuse => SetIfDifferent(ref SkinDiffuse, value.InternalTriple), + CustomizeParameterFlag.MuscleTone => SetIfDifferent(ref MuscleTone, value.Single), + CustomizeParameterFlag.SkinSpecular => SetIfDifferent(ref SkinSpecular, value.InternalTriple), + CustomizeParameterFlag.LipDiffuse => SetIfDifferent(ref LipDiffuse, value.InternalTriple), + CustomizeParameterFlag.LipOpacity => SetIfDifferent(ref LipOpacity, value.Single), + CustomizeParameterFlag.HairDiffuse => SetIfDifferent(ref HairDiffuse, value.InternalTriple), + CustomizeParameterFlag.HairSpecular => SetIfDifferent(ref HairSpecular, value.InternalTriple), + CustomizeParameterFlag.HairHighlight => SetIfDifferent(ref HairHighlight, value.InternalTriple), + CustomizeParameterFlag.LeftEye => SetIfDifferent(ref LeftEye, value.InternalTriple), + CustomizeParameterFlag.RightEye => SetIfDifferent(ref RightEye, value.InternalTriple), + CustomizeParameterFlag.FeatureColor => SetIfDifferent(ref FeatureColor, value.InternalTriple), + CustomizeParameterFlag.DecalColor => SetIfDifferent(ref DecalColor, value.InternalQuadruple), + CustomizeParameterFlag.FacePaintUvMultiplier => SetIfDifferent(ref FacePaintUvMultiplier, value.Single), + CustomizeParameterFlag.FacePaintUvOffset => SetIfDifferent(ref FacePaintUvOffset, value.Single), _ => false, }; } @@ -69,32 +72,55 @@ public struct CustomizeParameterData [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public readonly void Apply(ref CustomizeParameter parameters, CustomizeParameterFlag flags = CustomizeParameterExtensions.All) { - if (flags.HasFlag(CustomizeParameterFlag.SkinDiffuse)) - parameters.SkinColor = Convert(SkinDiffuse, parameters.SkinColor.W); - if (flags.HasFlag(CustomizeParameterFlag.MuscleTone)) - parameters.SkinColor.W = MuscleTone; + parameters.SkinColor = (flags & (CustomizeParameterFlag.SkinDiffuse | CustomizeParameterFlag.MuscleTone)) switch + { + 0 => parameters.SkinColor, + CustomizeParameterFlag.SkinDiffuse => new CustomizeParameterValue(SkinDiffuse, parameters.SkinColor.W).XivQuadruple, + CustomizeParameterFlag.MuscleTone => parameters.SkinColor with { W = MuscleTone }, + _ => new CustomizeParameterValue(SkinDiffuse, MuscleTone).XivQuadruple, + }; + + parameters.LipColor = (flags & (CustomizeParameterFlag.LipDiffuse | CustomizeParameterFlag.LipOpacity)) switch + { + 0 => parameters.LipColor, + CustomizeParameterFlag.LipDiffuse => new CustomizeParameterValue(LipDiffuse, parameters.LipColor.W).XivQuadruple, + CustomizeParameterFlag.LipOpacity => parameters.LipColor with { W = LipOpacity }, + _ => new CustomizeParameterValue(LipDiffuse, LipOpacity).XivQuadruple, + }; + + parameters.LeftColor = (flags & (CustomizeParameterFlag.LeftEye | CustomizeParameterFlag.FacePaintUvMultiplier)) switch + { + 0 => parameters.LeftColor, + CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple, + CustomizeParameterFlag.FacePaintUvMultiplier => parameters.LeftColor with { W = FacePaintUvMultiplier }, + _ => new CustomizeParameterValue(LeftEye, FacePaintUvMultiplier).XivQuadruple, + }; + + parameters.RightColor = (flags & (CustomizeParameterFlag.RightEye | CustomizeParameterFlag.FacePaintUvOffset)) switch + { + 0 => parameters.RightColor, + CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye, parameters.RightColor.W).XivQuadruple, + CustomizeParameterFlag.FacePaintUvOffset => parameters.RightColor with { W = FacePaintUvOffset }, + _ => new CustomizeParameterValue(RightEye, FacePaintUvOffset).XivQuadruple, + }; + if (flags.HasFlag(CustomizeParameterFlag.SkinSpecular)) - parameters.SkinFresnelValue0 = Convert(SkinSpecular, 0); - if (flags.HasFlag(CustomizeParameterFlag.LipDiffuse)) - parameters.LipColor = Convert(LipDiffuse, parameters.LipColor.W); - if (flags.HasFlag(CustomizeParameterFlag.LipOpacity)) - parameters.LipColor.W = LipOpacity; + parameters.SkinFresnelValue0 = new CustomizeParameterValue(SkinDiffuse).XivQuadruple; if (flags.HasFlag(CustomizeParameterFlag.HairDiffuse)) - parameters.MainColor = Convert(HairDiffuse); + parameters.MainColor = new CustomizeParameterValue(HairDiffuse).XivTriple; if (flags.HasFlag(CustomizeParameterFlag.HairSpecular)) - parameters.HairFresnelValue0 = Convert(HairSpecular); + parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple; if (flags.HasFlag(CustomizeParameterFlag.HairHighlight)) - parameters.MeshColor = Convert(HairHighlight); - if (flags.HasFlag(CustomizeParameterFlag.LeftEye)) - parameters.LeftColor = Convert(LeftEye, parameters.LeftColor.W); - if (flags.HasFlag(CustomizeParameterFlag.RightEye)) - parameters.RightColor = Convert(RightEye, parameters.RightColor.W); + parameters.MeshColor = new CustomizeParameterValue(HairHighlight).XivTriple; if (flags.HasFlag(CustomizeParameterFlag.FeatureColor)) - parameters.OptionColor = Convert(FeatureColor); - if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvMultiplier)) - parameters.LeftColor.W = FacePaintUvMultiplier; - if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvOffset)) - parameters.RightColor.W = FacePaintUvOffset; + parameters.OptionColor = new CustomizeParameterValue(FeatureColor).XivTriple; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public readonly void Apply(ref DecalParameters parameters, CustomizeParameterFlag flags = CustomizeParameterExtensions.All) + { + if (flags.HasFlag(CustomizeParameterFlag.DecalColor)) + parameters.Color = new CustomizeParameterValue(DecalColor).XivQuadruple; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -103,37 +129,37 @@ public struct CustomizeParameterData switch (flag) { case CustomizeParameterFlag.SkinDiffuse: - parameters.SkinColor = Convert(SkinDiffuse, parameters.SkinColor.W); + parameters.SkinColor = new CustomizeParameterValue(SkinDiffuse, parameters.SkinColor.W).XivQuadruple; break; case CustomizeParameterFlag.MuscleTone: parameters.SkinColor.W = MuscleTone; break; case CustomizeParameterFlag.SkinSpecular: - parameters.SkinFresnelValue0 = Convert(SkinSpecular, 0); + parameters.SkinFresnelValue0 = new CustomizeParameterValue(SkinSpecular).XivQuadruple; break; case CustomizeParameterFlag.LipDiffuse: - parameters.LipColor = Convert(LipDiffuse, parameters.LipColor.W); + parameters.LipColor = new CustomizeParameterValue(LipDiffuse, parameters.LipColor.W).XivQuadruple; break; case CustomizeParameterFlag.LipOpacity: parameters.LipColor.W = LipOpacity; break; case CustomizeParameterFlag.HairDiffuse: - parameters.MainColor = Convert(HairDiffuse); + parameters.MainColor = new CustomizeParameterValue(HairDiffuse).XivTriple; break; case CustomizeParameterFlag.HairSpecular: - parameters.HairFresnelValue0 = Convert(HairSpecular); + parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple; break; case CustomizeParameterFlag.HairHighlight: - parameters.MeshColor = Convert(HairHighlight); + parameters.MeshColor = new CustomizeParameterValue(HairHighlight).XivTriple; break; case CustomizeParameterFlag.LeftEye: - parameters.LeftColor = Convert(LeftEye, parameters.LeftColor.W); + parameters.LeftColor = new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple; break; case CustomizeParameterFlag.RightEye: - parameters.RightColor = Convert(RightEye, parameters.RightColor.W); + parameters.RightColor = new CustomizeParameterValue(RightEye, parameters.RightColor.W).XivQuadruple; break; case CustomizeParameterFlag.FeatureColor: - parameters.OptionColor = Convert(FeatureColor); + parameters.OptionColor = new CustomizeParameterValue(FeatureColor).XivTriple; break; case CustomizeParameterFlag.FacePaintUvMultiplier: parameters.LeftColor.W = FacePaintUvMultiplier; @@ -144,61 +170,49 @@ public struct CustomizeParameterData } } - public static CustomizeParameterData FromParameters(in CustomizeParameter parameter) + public static CustomizeParameterData FromParameters(in CustomizeParameter parameter, in DecalParameters decal) => new() { FacePaintUvOffset = parameter.RightColor.W, FacePaintUvMultiplier = parameter.LeftColor.W, MuscleTone = parameter.SkinColor.W, LipOpacity = parameter.LipColor.W, - SkinDiffuse = Convert(parameter.SkinColor), - SkinSpecular = Convert(parameter.SkinFresnelValue0), - LipDiffuse = Convert(parameter.LipColor), - HairDiffuse = Convert(parameter.MainColor), - HairSpecular = Convert(parameter.HairFresnelValue0), - HairHighlight = Convert(parameter.MeshColor), - LeftEye = Convert(parameter.LeftColor), - RightEye = Convert(parameter.RightColor), - FeatureColor = Convert(parameter.OptionColor), + SkinDiffuse = new CustomizeParameterValue(parameter.SkinColor).InternalTriple, + SkinSpecular = new CustomizeParameterValue(parameter.SkinFresnelValue0).InternalTriple, + LipDiffuse = new CustomizeParameterValue(parameter.LipColor).InternalTriple, + HairDiffuse = new CustomizeParameterValue(parameter.MainColor).InternalTriple, + HairSpecular = new CustomizeParameterValue(parameter.HairFresnelValue0).InternalTriple, + HairHighlight = new CustomizeParameterValue(parameter.MeshColor).InternalTriple, + LeftEye = new CustomizeParameterValue(parameter.LeftColor).InternalTriple, + RightEye = new CustomizeParameterValue(parameter.RightColor).InternalTriple, + FeatureColor = new CustomizeParameterValue(parameter.OptionColor).InternalTriple, + DecalColor = FromParameter(decal), }; - public static Vector3 FromParameter(in CustomizeParameter parameter, CustomizeParameterFlag flag) + public static CustomizeParameterValue FromParameter(in CustomizeParameter parameter, CustomizeParameterFlag flag) => flag switch { - CustomizeParameterFlag.SkinDiffuse => Convert(parameter.SkinColor), - CustomizeParameterFlag.MuscleTone => new Vector3(parameter.SkinColor.W), - CustomizeParameterFlag.SkinSpecular => Convert(parameter.SkinFresnelValue0), - CustomizeParameterFlag.LipDiffuse => Convert(parameter.LipColor), - CustomizeParameterFlag.LipOpacity => new Vector3(parameter.LipColor.W), - CustomizeParameterFlag.HairDiffuse => Convert(parameter.MainColor), - CustomizeParameterFlag.HairSpecular => Convert(parameter.HairFresnelValue0), - CustomizeParameterFlag.HairHighlight => Convert(parameter.MeshColor), - CustomizeParameterFlag.LeftEye => Convert(parameter.LeftColor), - CustomizeParameterFlag.RightEye => Convert(parameter.RightColor), - CustomizeParameterFlag.FeatureColor => Convert(parameter.OptionColor), - CustomizeParameterFlag.FacePaintUvMultiplier => new Vector3(parameter.LeftColor.W), - CustomizeParameterFlag.FacePaintUvOffset => new Vector3(parameter.RightColor.W), - _ => Vector3.Zero, + CustomizeParameterFlag.SkinDiffuse => new CustomizeParameterValue(parameter.SkinColor), + CustomizeParameterFlag.MuscleTone => new CustomizeParameterValue(parameter.SkinColor.W), + CustomizeParameterFlag.SkinSpecular => new CustomizeParameterValue(parameter.SkinFresnelValue0), + CustomizeParameterFlag.LipDiffuse => new CustomizeParameterValue(parameter.LipColor), + CustomizeParameterFlag.LipOpacity => new CustomizeParameterValue(parameter.LipColor.W), + CustomizeParameterFlag.HairDiffuse => new CustomizeParameterValue(parameter.MainColor), + CustomizeParameterFlag.HairSpecular => new CustomizeParameterValue(parameter.HairFresnelValue0), + CustomizeParameterFlag.HairHighlight => new CustomizeParameterValue(parameter.MeshColor), + CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(parameter.LeftColor), + CustomizeParameterFlag.RightEye => new CustomizeParameterValue(parameter.RightColor), + CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(parameter.OptionColor), + CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(parameter.LeftColor.W), + CustomizeParameterFlag.FacePaintUvOffset => new CustomizeParameterValue(parameter.RightColor.W), + _ => CustomizeParameterValue.Zero, }; - private static FFXIVClientStructs.FFXIV.Common.Math.Vector4 Convert(Vector3 value, float w) - => new(value.X * value.X, value.Y * value.Y, value.Z * value.Z, w); - - private static Vector3 Convert(FFXIVClientStructs.FFXIV.Common.Math.Vector3 value) - => new((float)Math.Sqrt(value.X), (float)Math.Sqrt(value.Y), (float)Math.Sqrt(value.Z)); - - private static Vector3 Convert(FFXIVClientStructs.FFXIV.Common.Math.Vector4 value) - => new((float)Math.Sqrt(value.X), (float)Math.Sqrt(value.Y), (float)Math.Sqrt(value.Z)); - - private static FFXIVClientStructs.FFXIV.Common.Math.Vector3 Convert(Vector3 value) - => new(value.X * value.X, value.Y * value.Y, value.Z * value.Z); + public static Vector4 FromParameter(in DecalParameters parameter) + => new CustomizeParameterValue(parameter.Color).InternalQuadruple; private static bool SetIfDifferent(ref Vector3 val, Vector3 @new) { - @new.X = Math.Clamp(@new.X, 0, 1); - @new.Y = Math.Clamp(@new.Y, 0, 1); - @new.Z = Math.Clamp(@new.Z, 0, 1); - if (@new == val) return false; @@ -206,7 +220,16 @@ public struct CustomizeParameterData return true; } - private static bool SetIfDifferent(ref T val, T @new) where T : IEqualityOperators + private static bool SetIfDifferent(ref float val, float @new) + { + if (@new == val) + return false; + + val = @new; + return true; + } + + private static bool SetIfDifferent(ref Vector4 val, Vector4 @new) { if (@new == val) return false; diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index d2b2ff4..d52e6c9 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -16,28 +16,28 @@ public enum CustomizeParameterFlag : ushort FeatureColor = 0x0400, FacePaintUvMultiplier = 0x0800, FacePaintUvOffset = 0x1000, + DecalColor = 0x2000, } public static class CustomizeParameterExtensions { - public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x1FFF; + public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x3FFF; - public const CustomizeParameterFlag Triples = All - & ~(CustomizeParameterFlag.MuscleTone - | CustomizeParameterFlag.LipOpacity - | CustomizeParameterFlag.FacePaintUvOffset - | CustomizeParameterFlag.FacePaintUvMultiplier); + public const CustomizeParameterFlag RgbTriples = All + & ~(RgbaQuadruples | Percentages | Values); + public const CustomizeParameterFlag RgbaQuadruples = CustomizeParameterFlag.DecalColor; public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone | CustomizeParameterFlag.LipOpacity; - public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier; + public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier; public static readonly IReadOnlyList AllFlags = [.. Enum.GetValues()]; - public static readonly IReadOnlyList TripleFlags = AllFlags.Where(f => Triples.HasFlag(f)).ToArray(); + public static readonly IReadOnlyList RgbaFlags = AllFlags.Where(f => RgbaQuadruples.HasFlag(f)).ToArray(); + public static readonly IReadOnlyList RgbFlags = AllFlags.Where(f => RgbTriples.HasFlag(f)).ToArray(); public static readonly IReadOnlyList PercentageFlags = AllFlags.Where(f => Percentages.HasFlag(f)).ToArray(); public static readonly IReadOnlyList ValueFlags = AllFlags.Where(f => Values.HasFlag(f)).ToArray(); public static int Count(this CustomizeParameterFlag flag) - => Triples.HasFlag(flag) ? 3 : 1; + => RgbaQuadruples.HasFlag(flag) ? 4 : RgbTriples.HasFlag(flag) ? 3 : 1; public static IEnumerable Iterate(this CustomizeParameterFlag flags) => AllFlags.Where(f => flags.HasFlag(f)); diff --git a/Glamourer/GameData/CustomizeParameterValue.cs b/Glamourer/GameData/CustomizeParameterValue.cs new file mode 100644 index 0000000..74cf666 --- /dev/null +++ b/Glamourer/GameData/CustomizeParameterValue.cs @@ -0,0 +1,47 @@ +namespace Glamourer.GameData; + +public readonly struct CustomizeParameterValue +{ + public static readonly CustomizeParameterValue Zero = default; + + private readonly Vector4 _data; + + public CustomizeParameterValue(Vector4 data) + => _data = data; + + public CustomizeParameterValue(Vector3 data, float w = 0) + => _data = new Vector4(data, w); + + public CustomizeParameterValue(FFXIVClientStructs.FFXIV.Common.Math.Vector4 data) + => _data = new Vector4(Root(data.X), Root(data.Y), Root(data.Z), data.W); + + public CustomizeParameterValue(FFXIVClientStructs.FFXIV.Common.Math.Vector3 data) + => _data = new Vector4(Root(data.X), Root(data.Y), Root(data.Z), 0); + + public CustomizeParameterValue(float value, float y = 0, float z = 0, float w = 0) + => _data = new Vector4(value, y, z, w); + + public Vector3 InternalTriple + => new(_data.X, _data.Y, _data.Z); + + public float Single + => _data.X; + + public Vector4 InternalQuadruple + => _data; + + public FFXIVClientStructs.FFXIV.Common.Math.Vector4 XivQuadruple + => new(Square(_data.X), Square(_data.Y), Square(_data.Z), _data.W); + + public FFXIVClientStructs.FFXIV.Common.Math.Vector3 XivTriple + => new(Square(_data.X), Square(_data.Y), Square(_data.Z)); + + private static float Square(float x) + => x < 0 ? -x * x : x * x; + + private static float Root(float x) + => x < 0 ? -(float)Math.Sqrt(-x) : x; + + public float this[int idx] + => _data[idx]; +} diff --git a/Glamourer/GameData/DecalParameters.cs b/Glamourer/GameData/DecalParameters.cs new file mode 100644 index 0000000..de5231f --- /dev/null +++ b/Glamourer/GameData/DecalParameters.cs @@ -0,0 +1,8 @@ +using Vector4 = FFXIVClientStructs.FFXIV.Common.Math.Vector4; + +namespace Glamourer.GameData; + +public struct DecalParameters +{ + public Vector4 Color; +} diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs index becaef5..0bae022 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs @@ -10,11 +10,13 @@ public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in Des public readonly CustomizeParameterFlag Flag = flag; public bool Locked; public bool DisplayApplication; + public bool AllowRevert; - public Action ValueSetter = null!; - public Action ApplySetter = null!; - public Vector3 CurrentValue = data.Parameters[flag]; - public bool CurrentApply; + public Action ValueSetter = null!; + public Action ApplySetter = null!; + public CustomizeParameterValue CurrentValue = data.Parameters[flag]; + public CustomizeParameterValue GameValue; + public bool CurrentApply; public static CustomizeParameterDrawData FromDesign(DesignManager manager, Design design, CustomizeParameterFlag flag) => new(flag, design.DesignData) @@ -32,5 +34,7 @@ public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in Des Locked = state.IsLocked, DisplayApplication = false, ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, StateChanged.Source.Manual), + GameValue = state.BaseData.Parameters[flag], + AllowRevert = true, }; } diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index a97fbe7..cf1753c 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -3,6 +3,7 @@ using Glamourer.GameData; using Glamourer.State; using Dalamud.Interface.Utility.Raii; using ImGuiNET; +using OtterGui; using OtterGui.Services; namespace Glamourer.Gui.Customization; @@ -11,8 +12,11 @@ public class CustomizeParameterDrawer(Configuration config) : IService { public void Draw(DesignManager designManager, Design design) { - foreach (var flag in CustomizeParameterExtensions.TripleFlags) - DrawColorInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); + foreach (var flag in CustomizeParameterExtensions.RgbFlags) + DrawColorInput3(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); + + foreach (var flag in CustomizeParameterExtensions.RgbaFlags) + DrawColorInput4(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); foreach (var flag in CustomizeParameterExtensions.PercentageFlags) DrawPercentageInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); @@ -23,8 +27,11 @@ public class CustomizeParameterDrawer(Configuration config) : IService public void Draw(StateManager stateManager, ActorState state) { - foreach (var flag in CustomizeParameterExtensions.TripleFlags) - DrawColorInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + foreach (var flag in CustomizeParameterExtensions.RgbFlags) + DrawColorInput3(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + + foreach (var flag in CustomizeParameterExtensions.RgbaFlags) + DrawColorInput4(CustomizeParameterDrawData.FromState(stateManager, state, flag)); foreach (var flag in CustomizeParameterExtensions.PercentageFlags) DrawPercentageInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); @@ -33,15 +40,30 @@ public class CustomizeParameterDrawer(Configuration config) : IService DrawValueInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); } - private void DrawColorInput(in CustomizeParameterDrawData data) + private void DrawColorInput3(in CustomizeParameterDrawData data) { using var id = ImRaii.PushId((int)data.Flag); - var value = data.CurrentValue; + var value = data.CurrentValue.InternalTriple; using (_ = ImRaii.Disabled(data.Locked)) { - if (ImGui.ColorEdit3("##value", ref value, ImGuiColorEditFlags.Float)) - data.ValueSetter(value); + if (ImGui.ColorEdit3("##value", ref value, ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions)) + data.ValueSetter(new CustomizeParameterValue(value)); } + DrawRevert(data); + + DrawApplyAndLabel(data); + } + + private void DrawColorInput4(in CustomizeParameterDrawData data) + { + using var id = ImRaii.PushId((int)data.Flag); + var value = data.CurrentValue.InternalQuadruple; + using (_ = ImRaii.Disabled(data.Locked)) + { + if (ImGui.ColorEdit4("##value", ref value, ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions)) + data.ValueSetter(new CustomizeParameterValue(value)); + } + DrawRevert(data); DrawApplyAndLabel(data); } @@ -54,8 +76,9 @@ public class CustomizeParameterDrawer(Configuration config) : IService using (_ = ImRaii.Disabled(data.Locked)) { if (ImGui.InputFloat("##value", ref value, 0.1f, 0.5f)) - data.ValueSetter(new Vector3(value)); + data.ValueSetter(new CustomizeParameterValue(value)); } + DrawRevert(data); DrawApplyAndLabel(data); } @@ -67,13 +90,26 @@ public class CustomizeParameterDrawer(Configuration config) : IService using (_ = ImRaii.Disabled(data.Locked)) { - if (ImGui.SliderFloat("##value", ref value, 0, 100, "%.2f", ImGuiSliderFlags.AlwaysClamp)) - data.ValueSetter(new Vector3(value / 100f)); + if (ImGui.SliderFloat("##value", ref value, -1000f, 1000f, "%.2f")) + data.ValueSetter(new CustomizeParameterValue(value / 100f)); } + DrawRevert(data); + DrawApplyAndLabel(data); } + private static void DrawRevert(in CustomizeParameterDrawData data) + { + if (data.Locked || !data.AllowRevert) + return; + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) + data.ValueSetter(data.GameValue); + + ImGuiUtil.HoverTooltip("Hold Control and Right-click to revert to game values."); + } + private static void DrawApply(in CustomizeParameterDrawData data) { if (UiHelpers.DrawCheckbox("##apply", "Apply this custom parameter when applying the Design.", data.CurrentApply, out var enabled, diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index f751ef0..8e93877 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -85,19 +85,13 @@ public unsafe class ModelEvaluationPanel( { if (!model.IsHuman) return; - if (model.AsHuman->CustomizeParameterCBuffer == null) - return; - var ptr = (CustomizeParameter*)model.AsHuman->CustomizeParameterCBuffer->UnsafeSourcePointer; - if (ptr == null) - return; - - var convert = CustomizeParameterData.FromParameters(*ptr); + var convert = model.GetParameterData(); foreach (var flag in CustomizeParameterExtensions.AllFlags) { ImGuiUtil.DrawTableColumn(flag.ToString()); ImGuiUtil.DrawTableColumn(string.Empty); - ImGuiUtil.DrawTableColumn(convert[flag].ToString()); + ImGuiUtil.DrawTableColumn(convert[flag].InternalQuadruple.ToString()); ImGui.TableNextColumn(); } } diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index d91ca58..fc7b880 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -1,5 +1,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Shader; +using Glamourer.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object; @@ -159,6 +161,65 @@ public readonly unsafe struct Model : IEquatable return (main, off, mainData, offData); } + public CustomizeParameterData GetParameterData() + { + if (!IsHuman) + return default; + + var cBuffer1 = AsHuman->CustomizeParameterCBuffer; + var cBuffer2 = AsHuman->DecalColorCBuffer; + var ptr1 = (CustomizeParameter*)(cBuffer1 == null ? null : cBuffer1->UnsafeSourcePointer); + var ptr2 = (DecalParameters*)(cBuffer2 == null ? null : cBuffer2->UnsafeSourcePointer); + return CustomizeParameterData.FromParameters(ptr1 != null ? *ptr1 : default, ptr2 != null ? *ptr2 : default); + } + + public void ApplyParameterData(CustomizeParameterFlag flags, in CustomizeParameterData data) + { + if (!IsHuman) + return; + + if (flags.HasFlag(CustomizeParameterFlag.DecalColor)) + { + var cBufferDecal = AsHuman->DecalColorCBuffer; + var ptrDecal = (DecalParameters*)(cBufferDecal == null ? null : cBufferDecal->UnsafeSourcePointer); + if (ptrDecal != null) + data.Apply(ref *ptrDecal); + } + + flags &= ~CustomizeParameterFlag.DecalColor; + var cBuffer = AsHuman->CustomizeParameterCBuffer; + var ptr = (CustomizeParameter*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); + if (ptr != null) + data.Apply(ref *ptr, flags); + } + + public bool ApplySingleParameterData(CustomizeParameterFlag flag, in CustomizeParameterData data) + { + if (!IsHuman) + return false; + + if (flag is CustomizeParameterFlag.DecalColor) + { + var cBuffer = AsHuman->DecalColorCBuffer; + var ptr = (DecalParameters*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); + if (ptr == null) + return false; + + data.Apply(ref *ptr); + return true; + } + else + { + var cBuffer = AsHuman->CustomizeParameterCBuffer; + var ptr = (CustomizeParameter*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); + if (ptr == null) + return false; + + data.ApplySingle(ref *ptr, flag); + return true; + } + } + private (Model, Model, int) GetChildrenWeapons() { Span weapons = stackalloc Model[2]; diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index fbc7b6b..b737736 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,5 +1,4 @@ -using FFXIVClientStructs.FFXIV.Shader; -using Glamourer.Events; +using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -282,23 +281,13 @@ public class StateApplier( } /// Change the customize parameters on models. Can change multiple at once. - public unsafe void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values) + public void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values) { - if (!_config.UseAdvancedParameters) + if (!_config.UseAdvancedParameters || flags == 0) return; foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) - { - var buffer = actor.Model.AsHuman->CustomizeParameterCBuffer; - if (buffer == null) - continue; - - var ptr = (CustomizeParameter*)buffer->UnsafeSourcePointer; - if (ptr == null) - continue; - - values.Apply(ref *ptr, flags); - } + actor.Model.ApplyParameterData(flags, values); } /// diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 55ea1c1..1d5048e 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -210,7 +210,7 @@ public class StateEditor } /// Change the customize flags of a character. - public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, Vector3 value, StateChanged.Source source, out Vector3 oldValue, + public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateChanged.Source source, out CustomizeParameterValue oldValue, uint key = 0) { oldValue = state.ModelData.Parameters[flag]; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index f9684d8..3851871 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -729,30 +729,27 @@ public class StateListener : IDisposable if (!model.IsHuman) return; - var cBuffer = model.AsHuman->CustomizeParameterCBuffer; - if (cBuffer == null) - return; - - var ptr = (CustomizeParameter*)cBuffer->UnsafeSourcePointer; - if (ptr == null) - return; - + var data = model.GetParameterData(); foreach (var flag in CustomizeParameterExtensions.AllFlags) { - var newValue = CustomizeParameterData.FromParameter(*ptr, flag); - + var newValue = data[flag]; switch (state[flag]) { case StateChanged.Source.Game: + if (state.BaseData.Parameters.Set(flag, newValue)) + _manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game); + break; case StateChanged.Source.Manual: if (state.BaseData.Parameters.Set(flag, newValue)) _manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game); + else if (_config.UseAdvancedParameters) + model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; case StateChanged.Source.Fixed: case StateChanged.Source.Ipc: state.BaseData.Parameters.Set(flag, newValue); if (_config.UseAdvancedParameters) - state.ModelData.Parameters.ApplySingle(ref *ptr, flag); + model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index e39f30b..4d5098d 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -186,13 +186,7 @@ public class StateManager( // Weapon visibility could technically be inferred from the weapon draw objects, // but since we use hat visibility from the game object we can also use weapon visibility from it. ret.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden); - - if (model.IsHuman && model.AsHuman->CustomizeParameterCBuffer != null) - { - var ptr = model.AsHuman->CustomizeParameterCBuffer->UnsafeSourcePointer; - if (ptr != null) - ret.Parameters = CustomizeParameterData.FromParameters(*(CustomizeParameter*)ptr); - } + ret.Parameters = model.GetParameterData(); return ret; } @@ -315,7 +309,7 @@ public class StateManager( } /// Change the crest of an equipment piece. - public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, Vector3 value, StateChanged.Source source, uint key = 0) + public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateChanged.Source source, uint key = 0) { if (!_editor.ChangeParameter(state, flag, value, source, out var old, key)) return; From bb671e8dd23ec761ef8f96062feb11abcbdd4d4f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 9 Jan 2024 16:54:43 +0100 Subject: [PATCH 143/786] Add revert options to equip. --- Glamourer/GameData/CustomizeParameterValue.cs | 3 + Glamourer/Gui/Equipment/EquipDrawData.cs | 14 +++- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 73 ++++++++++++------- Penumbra.GameData | 2 +- 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/Glamourer/GameData/CustomizeParameterValue.cs b/Glamourer/GameData/CustomizeParameterValue.cs index 74cf666..fc3e28d 100644 --- a/Glamourer/GameData/CustomizeParameterValue.cs +++ b/Glamourer/GameData/CustomizeParameterValue.cs @@ -44,4 +44,7 @@ public readonly struct CustomizeParameterValue public float this[int idx] => _data[idx]; + + public override string ToString() + => _data.ToString(); } diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 2a902c8..57da890 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -1,6 +1,6 @@ -using Glamourer.Designs; +using Dalamud.Game.Inventory; +using Glamourer.Designs; using Glamourer.Events; -using Glamourer.Services; using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -12,6 +12,7 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public readonly EquipSlot Slot = slot; public bool Locked; public bool DisplayApplication; + public bool AllowRevert; public Action ItemSetter = null!; public Action StainSetter = null!; @@ -19,6 +20,8 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public Action ApplyStainSetter = null!; public EquipItem CurrentItem = designData.Item(slot); public StainId CurrentStain = designData.Stain(slot); + public EquipItem GameItem = default; + public StainId GameStain = default; public bool CurrentApply; public bool CurrentApplyStain; @@ -28,8 +31,8 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot) => new(slot, design.DesignData) { - ItemSetter = slot.IsEquipment() || slot.IsAccessory() - ? i => manager.ChangeEquip(design, slot, i) + ItemSetter = slot.IsEquipment() || slot.IsAccessory() + ? i => manager.ChangeEquip(design, slot, i) : i => manager.ChangeWeapon(design, slot, i), StainSetter = i => manager.ChangeStain(design, slot, i), ApplySetter = b => manager.ChangeApplyEquip(design, slot, b), @@ -47,5 +50,8 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) StainSetter = i => manager.ChangeStain(state, slot, i, StateChanged.Source.Manual), Locked = state.IsLocked, DisplayApplication = false, + GameItem = state.BaseData.Item(slot), + GameStain = state.BaseData.Stain(slot), + AllowRevert = true, }; } diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 5910929..22f40d7 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -19,7 +19,7 @@ public class EquipmentDrawer private readonly ItemManager _items; private readonly GlamourerColorCombo _stainCombo; - private readonly DictStain _stainData; + private readonly DictStain _stainData; private readonly ItemCombo[] _itemCombo; private readonly Dictionary _weaponCombo; private readonly CodeService _codes; @@ -424,13 +424,8 @@ public class EquipmentDrawer else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) data.StainSetter(Stain.None.RowIndex); - if (!data.Locked && data.CurrentStain != Stain.None.RowIndex) - { - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - data.StainSetter(Stain.None.RowIndex); - - ImGuiUtil.HoverTooltip("Right-click to clear."); - } + if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out var id)) + data.StainSetter(Stain.None.RowIndex); } private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open) @@ -450,13 +445,35 @@ public class EquipmentDrawer else if (combo.CustomVariant.Id > 0) data.ItemSetter(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); - if (!data.Locked && data.CurrentItem.PrimaryId.Id != 0) - { - if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) - data.ItemSetter(ItemManager.NothingItem(data.Slot)); + if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, ItemManager.NothingItem(data.Slot), + out var item)) + data.ItemSetter(item); + } - ImGuiUtil.HoverTooltip("Right-click to clear."); + private static bool ResetOrClear(bool locked, bool clicked, bool allowRevert, bool allowClear, + in T currentItem, in T revertItem, in T clearItem, out T? item) where T : IEquatable + { + if (locked) + { + item = default; + return false; } + + clicked = clicked || ImGui.IsItemClicked(ImGuiMouseButton.Right); + + (var tt, item, var valid) = (allowRevert && !revertItem.Equals(currentItem), allowClear && !clearItem.Equals(currentItem), + ImGui.GetIO().KeyCtrl) switch + { + (true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.", revertItem, true), + (true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.", clearItem, true), + (true, false, true) => ("Control and Right-Click to revert to game.", revertItem, true), + (true, false, false) => ("Control and Right-Click to revert to game.", (T?)default, false), + (false, true, _) => ("Right-click to clear.", clearItem, true), + (false, false, _) => (string.Empty, (T?)default, false), + }; + ImGuiUtil.HoverTooltip(tt); + + return clicked && valid; } private void DrawMainhand(ref EquipDrawData mainhand, ref EquipDrawData offhand, out string label, bool drawAll, bool small, @@ -469,23 +486,30 @@ public class EquipmentDrawer } label = combo.Label; - var unknown = !_gPose.InGPose && mainhand.CurrentItem.Type is FullEquipType.Unknown; - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); + var unknown = !_gPose.InGPose && mainhand.CurrentItem.Type is FullEquipType.Unknown; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); + EquipItem? changedItem = null; using (var _ = ImRaii.Disabled(mainhand.Locked | unknown)) { if (!mainhand.Locked && open) UiHelpers.OpenCombo($"##{label}"); if (combo.Draw(mainhand.CurrentItem.Name, mainhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth)) + changedItem = combo.CurrentSelection; + else if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem, + default, out var c)) + changedItem = c; + + if (changedItem != null) { - mainhand.ItemSetter(combo.CurrentSelection); - if (combo.CurrentSelection.Type.ValidOffhand() != mainhand.CurrentItem.Type.ValidOffhand()) + mainhand.ItemSetter(changedItem.Value); + if (changedItem.Value.Type.ValidOffhand() != mainhand.CurrentItem.Type.ValidOffhand()) { - offhand.CurrentItem = _items.GetDefaultOffhand(combo.CurrentSelection); + offhand.CurrentItem = _items.GetDefaultOffhand(changedItem.Value); offhand.ItemSetter(offhand.CurrentItem); } - mainhand.CurrentItem = combo.CurrentSelection; + mainhand.CurrentItem = changedItem.Value; } } @@ -511,16 +535,9 @@ public class EquipmentDrawer _requiredComboWidth)) offhand.ItemSetter(combo.CurrentSelection); - if (locked) - return; - var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem); - if (defaultOffhand.Id == offhand.CurrentItem.Id) - return; - - ImGuiUtil.HoverTooltip("Right-click to set to Default."); - if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) - offhand.ItemSetter(defaultOffhand); + if (ResetOrClear(locked, open, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item)) + offhand.ItemSetter(item); } private static void DrawApply(in EquipDrawData data) diff --git a/Penumbra.GameData b/Penumbra.GameData index 55535b4..f68b6ee 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 55535b445f51f09ada75d2803484025bb51cca01 +Subproject commit f68b6eee157bc2016e1cb7d5ffac491b287b13a8 From a2731b4010c033de5e1a6bfc837e3ad6230f2b6f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 9 Jan 2024 16:54:54 +0100 Subject: [PATCH 144/786] Fix after-gpose bug. --- Glamourer/Events/GPoseService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Events/GPoseService.cs b/Glamourer/Events/GPoseService.cs index 7b162ae..a84f1d6 100644 --- a/Glamourer/Events/GPoseService.cs +++ b/Glamourer/Events/GPoseService.cs @@ -56,7 +56,7 @@ public sealed class GPoseService : EventWrapper InGPose = inGPose; Invoke(InGPose); var actions = InGPose ? _onEnter : _onLeave; - foreach (var action in actions) + while (actions.TryDequeue(out var action)) { try { From 7b6e037e5f18e5a0e2163aea019eb6e690cea6e2 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 9 Jan 2024 16:02:32 +0000 Subject: [PATCH 145/786] [CI] Updating repo.json for testing_1.0.7.5 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 0a74434..fbea42f 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.7.0", - "TestingAssemblyVersion": "1.0.7.4", + "TestingAssemblyVersion": "1.0.7.5", "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.0.7.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.4/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c06f617e04abc2e2e3688ba5dd225f63e5d2f64c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 9 Jan 2024 17:32:46 +0100 Subject: [PATCH 146/786] Some more changes. --- Glamourer/Designs/DesignBase.cs | 2 +- Glamourer/Designs/DesignConverter.cs | 7 ++++--- Glamourer/GameData/CustomizeParameterFlag.cs | 20 +++++++++++++++++++ .../Customization/CustomizeParameterDrawer.cs | 16 +++++++++++++-- OtterGui | 2 +- 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index cc037fb..867ee9d 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -69,7 +69,7 @@ public class DesignBase private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; public CustomizeSet CustomizeSet { get; private set; } - public CustomizeParameterFlag ApplyParameters { get; private set; } + public CustomizeParameterFlag ApplyParameters { get; internal set; } internal CustomizeFlag ApplyCustomize { diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index effd78f..949fa06 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -50,9 +50,10 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi public DesignBase Convert(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags) { var design = _designs.CreateTemporary(); - design.ApplyEquip = equipFlags & EquipFlagExtensions.All; - design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; - design.ApplyCrest = crestFlags & CrestExtensions.All; + design.ApplyEquip = equipFlags & EquipFlagExtensions.All; + design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; + design.ApplyCrest = crestFlags & CrestExtensions.All; + design.ApplyParameters = parameterFlags & CustomizeParameterExtensions.All; design.SetApplyHatVisible(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand)); diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index d52e6c9..b4bfdbd 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -44,4 +44,24 @@ public static class CustomizeParameterExtensions public static int ToInternalIndex(this CustomizeParameterFlag flag) => BitOperations.TrailingZeroCount((uint)flag); + + public static string ToName(this CustomizeParameterFlag flag) + => flag switch + { + CustomizeParameterFlag.SkinDiffuse => "Skin Color", + CustomizeParameterFlag.MuscleTone => "Muscle Tone", + CustomizeParameterFlag.SkinSpecular => "Skin Shine", + CustomizeParameterFlag.LipDiffuse => "Lip Color", + CustomizeParameterFlag.LipOpacity => "Lip Opacity", + CustomizeParameterFlag.HairDiffuse => "Hair Color", + CustomizeParameterFlag.HairSpecular => "Hair Shine", + CustomizeParameterFlag.HairHighlight => "Hair Highlights", + CustomizeParameterFlag.LeftEye => "Left Eye Color", + CustomizeParameterFlag.RightEye => "Right Eye Color", + CustomizeParameterFlag.FeatureColor => "Tattoo Color", + CustomizeParameterFlag.FacePaintUvMultiplier => "Face Paint Orientation", + CustomizeParameterFlag.FacePaintUvOffset => "Face Paint Offset", + CustomizeParameterFlag.DecalColor => "Face Paint Color", + _ => string.Empty, + }; } diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index cf1753c..5f5c5cb 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -1,9 +1,9 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.State; -using Dalamud.Interface.Utility.Raii; using ImGuiNET; using OtterGui; +using OtterGui.Raii; using OtterGui.Services; namespace Glamourer.Gui.Customization; @@ -12,6 +12,7 @@ public class CustomizeParameterDrawer(Configuration config) : IService { public void Draw(DesignManager designManager, Design design) { + using var _ = EnsureSize(); foreach (var flag in CustomizeParameterExtensions.RgbFlags) DrawColorInput3(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); @@ -25,8 +26,16 @@ public class CustomizeParameterDrawer(Configuration config) : IService DrawValueInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); } + private ImRaii.IEndObject EnsureSize() + { + var iconSize = ImGui.GetTextLineHeight() * 2 + ImGui.GetStyle().ItemSpacing.Y + 4 * ImGui.GetStyle().FramePadding.Y; + var width = 6 * iconSize + 4 * ImGui.GetStyle().ItemInnerSpacing.X; + return ImRaii.ItemWidth(width); + } + public void Draw(StateManager stateManager, ActorState state) { + using var _ = EnsureSize(); foreach (var flag in CustomizeParameterExtensions.RgbFlags) DrawColorInput3(CustomizeParameterDrawData.FromState(stateManager, state, flag)); @@ -49,6 +58,7 @@ public class CustomizeParameterDrawer(Configuration config) : IService if (ImGui.ColorEdit3("##value", ref value, ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions)) data.ValueSetter(new CustomizeParameterValue(value)); } + DrawRevert(data); DrawApplyAndLabel(data); @@ -63,6 +73,7 @@ public class CustomizeParameterDrawer(Configuration config) : IService if (ImGui.ColorEdit4("##value", ref value, ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions)) data.ValueSetter(new CustomizeParameterValue(value)); } + DrawRevert(data); DrawApplyAndLabel(data); @@ -78,6 +89,7 @@ public class CustomizeParameterDrawer(Configuration config) : IService if (ImGui.InputFloat("##value", ref value, 0.1f, 0.5f)) data.ValueSetter(new CustomizeParameterValue(value)); } + DrawRevert(data); DrawApplyAndLabel(data); @@ -126,6 +138,6 @@ public class CustomizeParameterDrawer(Configuration config) : IService } ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - ImGui.TextUnformatted(data.Flag.ToString()); + ImGui.TextUnformatted(data.Flag.ToName()); } } diff --git a/OtterGui b/OtterGui index 82df166..f8f3e0b 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 82df166f1fa0e8f1cf9ca8f677cbeac71fb549ab +Subproject commit f8f3e0b9bd39ed58f1233affc40df187b0c2b70e From 5d0993a9ce8174f74657e89bd1db5fcc35018577 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 9 Jan 2024 16:34:53 +0000 Subject: [PATCH 147/786] [CI] Updating repo.json for testing_1.0.7.6 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index fbea42f..baf6706 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.7.0", - "TestingAssemblyVersion": "1.0.7.5", + "TestingAssemblyVersion": "1.0.7.6", "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.0.7.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.6/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From ed27b1dff41ef4ee69556ce859fb2b24b9540621 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 9 Jan 2024 18:29:55 +0100 Subject: [PATCH 148/786] Use advanced parameters from IPC regardless of setting. --- Glamourer/State/StateApplier.cs | 6 +++--- Glamourer/State/StateListener.cs | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index b737736..8b69e0a 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -281,9 +281,9 @@ public class StateApplier( } /// Change the customize parameters on models. Can change multiple at once. - public void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values) + public void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values, bool force) { - if (!_config.UseAdvancedParameters || flags == 0) + if (!force && !_config.UseAdvancedParameters || flags == 0) return; foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) @@ -295,7 +295,7 @@ public class StateApplier( { var data = GetData(state); if (apply) - ChangeParameters(data, flags, state.ModelData.Parameters); + ChangeParameters(data, flags, state.ModelData.Parameters, state.IsLocked); return data; } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 3851871..6cf873d 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -746,11 +746,14 @@ public class StateListener : IDisposable model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; case StateChanged.Source.Fixed: - case StateChanged.Source.Ipc: state.BaseData.Parameters.Set(flag, newValue); if (_config.UseAdvancedParameters) model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; + case StateChanged.Source.Ipc: + state.BaseData.Parameters.Set(flag, newValue); + model.ApplySingleParameterData(flag, state.ModelData.Parameters); + break; } } } From 630647b54484d7643836bb0e45244e8084c4fcfe Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 10 Jan 2024 15:44:19 +0100 Subject: [PATCH 149/786] Fix some issues with parameters. --- Glamourer/Designs/DesignBase.cs | 16 ++++++++-------- Glamourer/Events/DesignChanged.cs | 2 +- Glamourer/Events/StateChanged.cs | 2 +- Glamourer/GameData/CustomizeParameterData.cs | 2 +- Glamourer/GameData/CustomizeParameterValue.cs | 2 +- .../Customization/CustomizeParameterDrawer.cs | 9 +++++++-- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 12 +++++++++--- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 3 ++- Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs | 3 ++- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 14 ++++++++++---- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 4 ++-- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 9 ++++++--- Glamourer/State/StateManager.cs | 2 +- OtterGui | 2 +- 14 files changed, 52 insertions(+), 30 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 867ee9d..4e3f5b6 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -444,7 +444,7 @@ public class DesignBase if (!TryGetToken(flag, out var token)) continue; - var value = Math.Clamp(token["Percentage"]?.ToObject() ?? 0f, 0f, 1f); + var value = token["Percentage"]?.ToObject() ?? 0f; design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(value); } @@ -453,9 +453,9 @@ public class DesignBase if (!TryGetToken(flag, out var token)) continue; - var r = Math.Clamp(token["Red"]?.ToObject() ?? 0f, 0, 1); - var g = Math.Clamp(token["Green"]?.ToObject() ?? 0f, 0, 1); - var b = Math.Clamp(token["Blue"]?.ToObject() ?? 0f, 0, 1); + var r = token["Red"]?.ToObject() ?? 0f; + var g = token["Green"]?.ToObject() ?? 0f; + var b = token["Blue"]?.ToObject() ?? 0f; design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(r, g, b); } @@ -464,10 +464,10 @@ public class DesignBase if (!TryGetToken(flag, out var token)) continue; - var r = Math.Clamp(token["Red"]?.ToObject() ?? 0f, 0, 1); - var g = Math.Clamp(token["Green"]?.ToObject() ?? 0f, 0, 1); - var b = Math.Clamp(token["Blue"]?.ToObject() ?? 0f, 0, 1); - var a = Math.Clamp(token["Alpha"]?.ToObject() ?? 0f, 0, 1); + var r = token["Red"]?.ToObject() ?? 0f; + var g = token["Green"]?.ToObject() ?? 0f; + var b = token["Blue"]?.ToObject() ?? 0f; + var a = token["Alpha"]?.ToObject() ?? 0f; design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(r, g, b, a); } diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 4bdb1df..2217c34 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -65,7 +65,7 @@ public sealed class DesignChanged() /// An existing design had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. Crest, - /// An existing design had a customize parameter changed. Data is the old value, the new value and the flag [(Vector3, Vector3, CustomizeParameterFlag)]. + /// An existing design had a customize parameter changed. Data is the old value, the new value and the flag [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. Parameter, /// An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 50c3d2e..58dd49c 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -39,7 +39,7 @@ public sealed class StateChanged() /// A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. Crest, - /// A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(Vector3, Vector3, CustomizeParameterFlag)]. + /// A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. Parameter, /// A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs index 0ffbad6..ee1d5ae 100644 --- a/Glamourer/GameData/CustomizeParameterData.cs +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -105,7 +105,7 @@ public struct CustomizeParameterData }; if (flags.HasFlag(CustomizeParameterFlag.SkinSpecular)) - parameters.SkinFresnelValue0 = new CustomizeParameterValue(SkinDiffuse).XivQuadruple; + parameters.SkinFresnelValue0 = new CustomizeParameterValue(SkinSpecular).XivQuadruple; if (flags.HasFlag(CustomizeParameterFlag.HairDiffuse)) parameters.MainColor = new CustomizeParameterValue(HairDiffuse).XivTriple; if (flags.HasFlag(CustomizeParameterFlag.HairSpecular)) diff --git a/Glamourer/GameData/CustomizeParameterValue.cs b/Glamourer/GameData/CustomizeParameterValue.cs index fc3e28d..0e22d18 100644 --- a/Glamourer/GameData/CustomizeParameterValue.cs +++ b/Glamourer/GameData/CustomizeParameterValue.cs @@ -40,7 +40,7 @@ public readonly struct CustomizeParameterValue => x < 0 ? -x * x : x * x; private static float Root(float x) - => x < 0 ? -(float)Math.Sqrt(-x) : x; + => x < 0 ? -(float)Math.Sqrt(-x) : (float)Math.Sqrt(x); public float this[int idx] => _data[idx]; diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 5f5c5cb..3434011 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -55,7 +55,7 @@ public class CustomizeParameterDrawer(Configuration config) : IService var value = data.CurrentValue.InternalTriple; using (_ = ImRaii.Disabled(data.Locked)) { - if (ImGui.ColorEdit3("##value", ref value, ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions)) + if (ImGui.ColorEdit3("##value", ref value, GetFlags())) data.ValueSetter(new CustomizeParameterValue(value)); } @@ -70,7 +70,7 @@ public class CustomizeParameterDrawer(Configuration config) : IService var value = data.CurrentValue.InternalQuadruple; using (_ = ImRaii.Disabled(data.Locked)) { - if (ImGui.ColorEdit4("##value", ref value, ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions)) + if (ImGui.ColorEdit4("##value", ref value, GetFlags())) data.ValueSetter(new CustomizeParameterValue(value)); } @@ -140,4 +140,9 @@ public class CustomizeParameterDrawer(Configuration config) : IService ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); ImGui.TextUnformatted(data.Flag.ToName()); } + + private static ImGuiColorEditFlags GetFlags() + => ImGui.GetIO().KeyCtrl + ? ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions + : ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR; } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 8edc8fc..431eed0 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -134,7 +134,8 @@ public class ActorPanel( var header = _state!.ModelData.ModelId == 0 ? "Customization" : $"Customization (Model Id #{_state.ModelData.ModelId})###Customization"; - if (!ImGui.CollapsingHeader(header)) + using var h = ImRaii.CollapsingHeader(header); + if (!h) return; if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw)) @@ -146,7 +147,8 @@ public class ActorPanel( private void DrawEquipmentHeader() { - if (!ImGui.CollapsingHeader("Equipment")) + using var h = ImRaii.CollapsingHeader("Equipment"); + if (!h) return; _equipmentDrawer.Prepare(); @@ -171,7 +173,11 @@ public class ActorPanel( private void DrawParameterHeader() { - if (!_config.UseAdvancedParameters || !ImGui.CollapsingHeader("Advanced Customizations")) + if (!_config.UseAdvancedParameters) + return; + + using var h = ImRaii.CollapsingHeader("Advanced Customizations"); + if (!h) return; _parameterDrawer.Draw(_stateManager, _state!); diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index 0c4d617..b2b7bdb 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -12,7 +12,8 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) public void Draw() { - if (!ImGui.CollapsingHeader(Label)) + using var h = ImRaii.CollapsingHeader(Label); + if (!h) return; foreach (var subTree in SubTrees) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 88c2bdb..00a23cc 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -41,7 +41,8 @@ public class DesignDetailTab public void Draw() { - if (!ImGui.CollapsingHeader("Design Details")) + using var h = ImRaii.CollapsingHeader("Design Details"); + if (!h) return; DrawDesignInfoTable(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index b3c1b4a..6cefc63 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -92,7 +92,8 @@ public class DesignPanel( private void DrawEquipment() { - if (!ImGui.CollapsingHeader("Equipment")) + using var h = ImRaii.CollapsingHeader("Equipment"); + if (!h) return; _equipmentDrawer.Prepare(); @@ -142,7 +143,8 @@ public class DesignPanel( var header = _selector.Selected!.DesignData.ModelId == 0 ? "Customization" : $"Customization (Model Id #{_selector.Selected!.DesignData.ModelId})###Customization"; - if (!ImGui.CollapsingHeader(header)) + using var h = ImRaii.CollapsingHeader(header); + if (!h) return; if (_customizationDrawer.Draw(_selector.Selected!.DesignData.Customize, _selector.Selected.ApplyCustomizeRaw, @@ -162,7 +164,10 @@ public class DesignPanel( private void DrawCustomizeParameters() { - if (!_config.UseAdvancedParameters || !ImGui.CollapsingHeader("Advanced Customization")) + if (!_config.UseAdvancedParameters) + return; + using var h = ImRaii.CollapsingHeader("Advanced Customizations"); + if (!h) return; _parameterDrawer.Draw(_manager, _selector.Selected!); @@ -214,7 +219,8 @@ public class DesignPanel( private void DrawApplicationRules() { - if (!ImGui.CollapsingHeader("Application Rules")) + using var h = ImRaii.CollapsingHeader("Application Rules"); + if (!h) return; using (var _ = ImRaii.Group()) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 7624c0b..e70cbec 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -28,13 +28,13 @@ public class ModAssociationsTab public void Draw() { - var headerOpen = ImGui.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" + "You can also use it to quickly open the associated mod page in Penumbra.\n\n" + "It is not feasible to apply those changes automatically in general cases, since there would be no way to revert those changes, handle multiple designs applying at once, etc."); - if (!headerOpen) + if (!h) return; DrawApplyAllButton(); diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index f815e3d..c6d5be4 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -141,7 +141,8 @@ public class NpcPanel( private void DrawCustomization() { - if (!ImGui.CollapsingHeader("Customization")) + using var h = ImRaii.CollapsingHeader("Customization"); + if (!h) return; _customizeDrawer.Draw(_selector.Selection.Customize, true, true); @@ -150,7 +151,8 @@ public class NpcPanel( private void DrawEquipment() { - if (!ImGui.CollapsingHeader("Equipment")) + using var h = ImRaii.CollapsingHeader("Equipment"); + if (!h) return; _equipDrawer.Prepare(); @@ -223,7 +225,8 @@ public class NpcPanel( private void DrawAppearanceInfo() { - if (!ImGui.CollapsingHeader("Appearance Details")) + using var h = ImRaii.CollapsingHeader("Appearance Details"); + if (!h) return; using var table = ImRaii.Table("Details", 2); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 4d5098d..0eace8b 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -456,7 +456,7 @@ public class StateManager( _applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible()); _applier.ChangeVisor(actors, state.ModelData.IsVisorToggled()); _applier.ChangeCrests(actors, state.ModelData.CrestVisibility); - _applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters); + _applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); } return actors; diff --git a/OtterGui b/OtterGui index f8f3e0b..9f9705f 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit f8f3e0b9bd39ed58f1233affc40df187b0c2b70e +Subproject commit 9f9705f417114d006c7b1f043637083f0782bb6b From 8a9fa987069c3f78509616c71583eafba04b3530 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 10 Jan 2024 16:41:45 +0100 Subject: [PATCH 150/786] Make LipDiffuse a Vec4 instead of two values. --- Glamourer/GameData/CustomizeParameterData.cs | 28 +++++--------------- Glamourer/GameData/CustomizeParameterFlag.cs | 26 +++++++++--------- 2 files changed, 19 insertions(+), 35 deletions(-) diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs index ee1d5ae..f10289f 100644 --- a/Glamourer/GameData/CustomizeParameterData.cs +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -4,20 +4,19 @@ namespace Glamourer.GameData; public struct CustomizeParameterData { + public Vector4 DecalColor; + public Vector4 LipDiffuse; public Vector3 SkinDiffuse; public Vector3 SkinSpecular; - public Vector3 LipDiffuse; public Vector3 HairDiffuse; public Vector3 HairSpecular; public Vector3 HairHighlight; public Vector3 LeftEye; public Vector3 RightEye; public Vector3 FeatureColor; - public Vector4 DecalColor; public float FacePaintUvMultiplier; public float FacePaintUvOffset; public float MuscleTone; - public float LipOpacity; public CustomizeParameterValue this[CustomizeParameterFlag flag] { @@ -30,7 +29,6 @@ public struct CustomizeParameterData CustomizeParameterFlag.MuscleTone => new CustomizeParameterValue(MuscleTone), CustomizeParameterFlag.SkinSpecular => new CustomizeParameterValue(SkinSpecular), CustomizeParameterFlag.LipDiffuse => new CustomizeParameterValue(LipDiffuse), - CustomizeParameterFlag.LipOpacity => new CustomizeParameterValue(LipOpacity), CustomizeParameterFlag.HairDiffuse => new CustomizeParameterValue(HairDiffuse), CustomizeParameterFlag.HairSpecular => new CustomizeParameterValue(HairSpecular), CustomizeParameterFlag.HairHighlight => new CustomizeParameterValue(HairHighlight), @@ -54,8 +52,7 @@ public struct CustomizeParameterData CustomizeParameterFlag.SkinDiffuse => SetIfDifferent(ref SkinDiffuse, value.InternalTriple), CustomizeParameterFlag.MuscleTone => SetIfDifferent(ref MuscleTone, value.Single), CustomizeParameterFlag.SkinSpecular => SetIfDifferent(ref SkinSpecular, value.InternalTriple), - CustomizeParameterFlag.LipDiffuse => SetIfDifferent(ref LipDiffuse, value.InternalTriple), - CustomizeParameterFlag.LipOpacity => SetIfDifferent(ref LipOpacity, value.Single), + CustomizeParameterFlag.LipDiffuse => SetIfDifferent(ref LipDiffuse, value.InternalQuadruple), CustomizeParameterFlag.HairDiffuse => SetIfDifferent(ref HairDiffuse, value.InternalTriple), CustomizeParameterFlag.HairSpecular => SetIfDifferent(ref HairSpecular, value.InternalTriple), CustomizeParameterFlag.HairHighlight => SetIfDifferent(ref HairHighlight, value.InternalTriple), @@ -80,14 +77,6 @@ public struct CustomizeParameterData _ => new CustomizeParameterValue(SkinDiffuse, MuscleTone).XivQuadruple, }; - parameters.LipColor = (flags & (CustomizeParameterFlag.LipDiffuse | CustomizeParameterFlag.LipOpacity)) switch - { - 0 => parameters.LipColor, - CustomizeParameterFlag.LipDiffuse => new CustomizeParameterValue(LipDiffuse, parameters.LipColor.W).XivQuadruple, - CustomizeParameterFlag.LipOpacity => parameters.LipColor with { W = LipOpacity }, - _ => new CustomizeParameterValue(LipDiffuse, LipOpacity).XivQuadruple, - }; - parameters.LeftColor = (flags & (CustomizeParameterFlag.LeftEye | CustomizeParameterFlag.FacePaintUvMultiplier)) switch { 0 => parameters.LeftColor, @@ -112,6 +101,8 @@ public struct CustomizeParameterData parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple; if (flags.HasFlag(CustomizeParameterFlag.HairHighlight)) parameters.MeshColor = new CustomizeParameterValue(HairHighlight).XivTriple; + if (flags.HasFlag(CustomizeParameterFlag.LipDiffuse)) + parameters.LipColor = new CustomizeParameterValue(LipDiffuse).XivQuadruple; if (flags.HasFlag(CustomizeParameterFlag.FeatureColor)) parameters.OptionColor = new CustomizeParameterValue(FeatureColor).XivTriple; } @@ -138,10 +129,7 @@ public struct CustomizeParameterData parameters.SkinFresnelValue0 = new CustomizeParameterValue(SkinSpecular).XivQuadruple; break; case CustomizeParameterFlag.LipDiffuse: - parameters.LipColor = new CustomizeParameterValue(LipDiffuse, parameters.LipColor.W).XivQuadruple; - break; - case CustomizeParameterFlag.LipOpacity: - parameters.LipColor.W = LipOpacity; + parameters.LipColor = new CustomizeParameterValue(LipDiffuse).XivQuadruple; break; case CustomizeParameterFlag.HairDiffuse: parameters.MainColor = new CustomizeParameterValue(HairDiffuse).XivTriple; @@ -176,10 +164,9 @@ public struct CustomizeParameterData FacePaintUvOffset = parameter.RightColor.W, FacePaintUvMultiplier = parameter.LeftColor.W, MuscleTone = parameter.SkinColor.W, - LipOpacity = parameter.LipColor.W, SkinDiffuse = new CustomizeParameterValue(parameter.SkinColor).InternalTriple, SkinSpecular = new CustomizeParameterValue(parameter.SkinFresnelValue0).InternalTriple, - LipDiffuse = new CustomizeParameterValue(parameter.LipColor).InternalTriple, + LipDiffuse = new CustomizeParameterValue(parameter.LipColor).InternalQuadruple, HairDiffuse = new CustomizeParameterValue(parameter.MainColor).InternalTriple, HairSpecular = new CustomizeParameterValue(parameter.HairFresnelValue0).InternalTriple, HairHighlight = new CustomizeParameterValue(parameter.MeshColor).InternalTriple, @@ -196,7 +183,6 @@ public struct CustomizeParameterData CustomizeParameterFlag.MuscleTone => new CustomizeParameterValue(parameter.SkinColor.W), CustomizeParameterFlag.SkinSpecular => new CustomizeParameterValue(parameter.SkinFresnelValue0), CustomizeParameterFlag.LipDiffuse => new CustomizeParameterValue(parameter.LipColor), - CustomizeParameterFlag.LipOpacity => new CustomizeParameterValue(parameter.LipColor.W), CustomizeParameterFlag.HairDiffuse => new CustomizeParameterValue(parameter.MainColor), CustomizeParameterFlag.HairSpecular => new CustomizeParameterValue(parameter.HairFresnelValue0), CustomizeParameterFlag.HairHighlight => new CustomizeParameterValue(parameter.MeshColor), diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index b4bfdbd..a0c814a 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -7,27 +7,26 @@ public enum CustomizeParameterFlag : ushort MuscleTone = 0x0002, SkinSpecular = 0x0004, LipDiffuse = 0x0008, - LipOpacity = 0x0010, - HairDiffuse = 0x0020, - HairSpecular = 0x0040, - HairHighlight = 0x0080, - LeftEye = 0x0100, - RightEye = 0x0200, - FeatureColor = 0x0400, - FacePaintUvMultiplier = 0x0800, - FacePaintUvOffset = 0x1000, - DecalColor = 0x2000, + HairDiffuse = 0x0010, + HairSpecular = 0x0020, + HairHighlight = 0x0040, + LeftEye = 0x0080, + RightEye = 0x0100, + FeatureColor = 0x0200, + FacePaintUvMultiplier = 0x0400, + FacePaintUvOffset = 0x0800, + DecalColor = 0x1000, } public static class CustomizeParameterExtensions { - public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x3FFF; + public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x1FFF; public const CustomizeParameterFlag RgbTriples = All & ~(RgbaQuadruples | Percentages | Values); - public const CustomizeParameterFlag RgbaQuadruples = CustomizeParameterFlag.DecalColor; - public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone | CustomizeParameterFlag.LipOpacity; + public const CustomizeParameterFlag RgbaQuadruples = CustomizeParameterFlag.DecalColor | CustomizeParameterFlag.LipDiffuse; + public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone; public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier; public static readonly IReadOnlyList AllFlags = [.. Enum.GetValues()]; @@ -52,7 +51,6 @@ public static class CustomizeParameterExtensions CustomizeParameterFlag.MuscleTone => "Muscle Tone", CustomizeParameterFlag.SkinSpecular => "Skin Shine", CustomizeParameterFlag.LipDiffuse => "Lip Color", - CustomizeParameterFlag.LipOpacity => "Lip Opacity", CustomizeParameterFlag.HairDiffuse => "Hair Color", CustomizeParameterFlag.HairSpecular => "Hair Shine", CustomizeParameterFlag.HairHighlight => "Hair Highlights", From 50b1b641415d62f7427ee86fd30feb5d8c407909 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 11 Jan 2024 12:41:03 +0100 Subject: [PATCH 151/786] Add lip opacity migration for Nova. --- Glamourer/Designs/DesignBase.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 4e3f5b6..e766f3a 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -471,6 +471,7 @@ public class DesignBase design.GetDesignDataRef().Parameters[flag] = new CustomizeParameterValue(r, g, b, a); } + MigrateLipOpacity(); return; // Load the token and set application. @@ -488,6 +489,14 @@ public class DesignBase design.GetDesignDataRef().Parameters[flag] = CustomizeParameterValue.Zero; return false; } + + void MigrateLipOpacity() + { + var token = parameters!["LipOpacity"]?["Percentage"]?.ToObject(); + var actualToken = parameters![CustomizeParameterFlag.LipDiffuse]?["Alpha"]; + if (token != null && actualToken == null) + design.GetDesignDataRef().Parameters.LipDiffuse.W = token.Value; + } } protected static void LoadEquip(ItemManager items, JToken? equip, DesignBase design, string name, bool allowUnknown) From bc69e6d0ade18458027bf0029a71e776fe6d8fab Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 11 Jan 2024 11:42:48 +0000 Subject: [PATCH 152/786] [CI] Updating repo.json for testing_1.0.7.7 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index baf6706..73e0af9 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.7.0", - "TestingAssemblyVersion": "1.0.7.6", + "TestingAssemblyVersion": "1.0.7.7", "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.0.7.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.6/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.7/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From d45e47378bd31a57525d7526bd8c2421e87170f8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 11 Jan 2024 19:19:33 +0100 Subject: [PATCH 153/786] Fix dumb. --- Glamourer/Designs/DesignBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index e766f3a..da923b9 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -493,7 +493,7 @@ public class DesignBase void MigrateLipOpacity() { var token = parameters!["LipOpacity"]?["Percentage"]?.ToObject(); - var actualToken = parameters![CustomizeParameterFlag.LipDiffuse]?["Alpha"]; + var actualToken = parameters![CustomizeParameterFlag.LipDiffuse.ToString()]?["Alpha"]; if (token != null && actualToken == null) design.GetDesignDataRef().Parameters.LipDiffuse.W = token.Value; } From 47e222a9a9b23b2d6997cdfb536dc4a397276c5a Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 11 Jan 2024 18:22:22 +0000 Subject: [PATCH 154/786] [CI] Updating repo.json for testing_1.0.7.8 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 73e0af9..ec1448f 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.0.7.0", - "TestingAssemblyVersion": "1.0.7.7", + "TestingAssemblyVersion": "1.0.7.8", "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.0.7.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.7/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.8/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From ada0c6f479c2a06f27f5b6e09802c01082e28d11 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 12 Jan 2024 00:02:27 +0100 Subject: [PATCH 155/786] Add customization options to favorites, currently only hairstyles and facepaints. --- .../Customization/CustomizationDrawer.Icon.cs | 15 +++- .../Gui/Customization/CustomizationDrawer.cs | 3 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 5 ++ Glamourer/Unlocks/FavoriteManager.cs | 71 +++++++++++++++---- OtterGui | 2 +- 5 files changed, 80 insertions(+), 16 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index f3eabbd..4e0f3de 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,4 +1,5 @@ using Glamourer.GameData; +using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -70,7 +71,10 @@ public partial class CustomizationDrawer var icon = _service.Manager.GetIcon(custom.IconId); using (var _ = ImRaii.Group()) { - using var frameColor = ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed, current == i); + var isFavorite = _favorites.Contains(_set.Gender, _set.Clan, _currentIndex, custom.Value); + using var frameColor = current == i + ? ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed) + : ImRaii.PushColor(ImGuiCol.Button, ColorId.FavoriteStarOn.Value(), isFavorite); if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) { @@ -78,7 +82,14 @@ public partial class CustomizationDrawer ImGui.CloseCurrentPopup(); } - ImGuiUtil.HoverIconTooltip(icon, _iconSize); + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + if (isFavorite) + _favorites.Remove(_set.Gender, _set.Clan, _currentIndex, custom.Value); + else + _favorites.TryAdd(_set.Gender, _set.Clan, _currentIndex, custom.Value); + + ImGuiUtil.HoverIconTooltip(icon, _iconSize, + FavoriteManager.TypeAllowed(_currentIndex) ? "Right-Click to toggle favorite." : string.Empty); var text = custom.Value.ToString(); var textWidth = ImGui.CalcTextSize(text).X; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 4062d69..8cb7f91 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -3,6 +3,7 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.GameData; using Glamourer.Services; +using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -11,7 +12,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; -public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeService _service, CodeService _codes, Configuration _config) +public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeService _service, CodeService _codes, Configuration _config, FavoriteManager _favorites) : IDisposable { private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index ea89e56..aa67fb4 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -119,6 +119,11 @@ public class UnlockOverview ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); + + if (_favorites.Contains(_selected3, _selected2, customize.Index, customize.Value)) + ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), + 12 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 6 * ImGuiHelpers.GlobalScale); + if (ImGui.IsItemHovered()) { using var tt = ImRaii.Tooltip(); diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index 83ff472..f5f80a1 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -2,16 +2,28 @@ using Glamourer.Services; using Newtonsoft.Json; using OtterGui.Classes; +using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Unlocks; public class FavoriteManager : ISavable { - private const int CurrentVersion = 1; - private readonly SaveService _saveService; - private readonly HashSet _favorites = new(); - private readonly HashSet _favoriteColors = new(); + private readonly record struct FavoriteHairStyle(Gender Gender, SubRace Race, CustomizeIndex Type, CustomizeValue Id) + { + public uint ToValue() + => (uint)Id.Value | ((uint)Type << 8) | ((uint)Race << 16) | ((uint)Gender << 24); + + public FavoriteHairStyle(uint value) + : this((Gender)((value >> 24) & 0xFF), (SubRace)((value >> 16) & 0xFF), (CustomizeIndex)((value >> 8) & 0xFF), (CustomizeValue)(value & 0xFF)) + { } + } + + private const int CurrentVersion = 1; + private readonly SaveService _saveService; + private readonly HashSet _favorites = []; + private readonly HashSet _favoriteColors = []; + private readonly HashSet _favoriteHairStyles = []; public FavoriteManager(SaveService saveService) { @@ -19,6 +31,14 @@ public class FavoriteManager : ISavable Load(); } + public static bool TypeAllowed(CustomizeIndex type) + => type switch + { + CustomizeIndex.Hairstyle => true, + CustomizeIndex.FacePaint => true, + _ => false, + }; + private void Load() { var file = _saveService.FileNames.FavoriteFile; @@ -40,7 +60,9 @@ public class FavoriteManager : ISavable case 1: _favorites.UnionWith(load.FavoriteItems.Select(i => (ItemId)i)); _favoriteColors.UnionWith(load.FavoriteColors.Select(i => (StainId)i)); + _favoriteHairStyles.UnionWith(load.FavoriteHairStyles.Select(t => new FavoriteHairStyle(t))); break; + default: throw new Exception($"Unknown Version {load.Version}"); } } @@ -53,7 +75,7 @@ public class FavoriteManager : ISavable private void LoadV0(string text) { - var array = JsonConvert.DeserializeObject(text) ?? Array.Empty(); + var array = JsonConvert.DeserializeObject(text) ?? []; _favorites.UnionWith(array.Select(i => (ItemId)i)); Save(); } @@ -66,10 +88,8 @@ public class FavoriteManager : ISavable public void Save(StreamWriter writer) { - using var j = new JsonTextWriter(writer) - { - Formatting = Formatting.Indented, - }; + using var j = new JsonTextWriter(writer); + j.Formatting = Formatting.Indented; j.WriteStartObject(); j.WritePropertyName(nameof(LoadStruct.Version)); j.WriteValue(CurrentVersion); @@ -83,6 +103,11 @@ public class FavoriteManager : ISavable foreach (var stain in _favoriteColors) j.WriteValue(stain.Id); j.WriteEndArray(); + j.WritePropertyName(nameof(LoadStruct.FavoriteHairStyles)); + j.WriteStartArray(); + foreach (var hairStyle in _favoriteHairStyles) + j.WriteValue(hairStyle.ToValue()); + j.WriteEndArray(); j.WriteEndObject(); } @@ -110,6 +135,15 @@ public class FavoriteManager : ISavable return true; } + public bool TryAdd(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value) + { + if (!TypeAllowed(type) || !_favoriteHairStyles.Add(new FavoriteHairStyle(gender, race, type, value))) + return false; + + Save(); + return true; + } + public bool Remove(EquipItem item) => Remove(item.ItemId); @@ -134,6 +168,15 @@ public class FavoriteManager : ISavable return true; } + public bool Remove(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value) + { + if (!_favoriteHairStyles.Remove(new FavoriteHairStyle(gender, race, type, value))) + return false; + + Save(); + return true; + } + public bool Contains(EquipItem item) => _favorites.Contains(item.ItemId); @@ -146,11 +189,15 @@ public class FavoriteManager : ISavable public bool Contains(StainId stain) => _favoriteColors.Contains(stain); + public bool Contains(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value) + => _favoriteHairStyles.Contains(new FavoriteHairStyle(gender, race, type, value)); + private struct LoadStruct { - public int Version = CurrentVersion; - public uint[] FavoriteItems = Array.Empty(); - public byte[] FavoriteColors = Array.Empty(); + public int Version = CurrentVersion; + public uint[] FavoriteItems = []; + public byte[] FavoriteColors = []; + public uint[] FavoriteHairStyles = []; public LoadStruct() { } diff --git a/OtterGui b/OtterGui index 9f9705f..6d6e7b3 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 9f9705f417114d006c7b1f043637083f0782bb6b +Subproject commit 6d6e7b37c31bc82b8b2811c85a09f67fb0434f8a From a50b63f67edcba7cb886f7694ef9aa653617ab44 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 12 Jan 2024 13:46:45 +0100 Subject: [PATCH 156/786] Add Palette+ Import. --- Glamourer/Configuration.cs | 1 + .../Customization/CustomizeParameterDrawer.cs | 70 +++++++++++++- Glamourer/Gui/Tabs/SettingsTab.cs | 7 +- .../Interop/PalettePlus/PaletteImport.cs | 94 +++++++++++++++++++ 4 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 Glamourer/Interop/PalettePlus/PaletteImport.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index d552960..fb99bf5 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -35,6 +35,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool ShowQuickBarInTabs { get; set; } = true; public bool OpenWindowAtStart { get; set; } = false; public bool UseAdvancedParameters { get; set; } = false; + public bool ShowPalettePlusImport { get; set; } = true; 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/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 3434011..23be6c4 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -1,5 +1,6 @@ using Glamourer.Designs; using Glamourer.GameData; +using Glamourer.Interop.PalettePlus; using Glamourer.State; using ImGuiNET; using OtterGui; @@ -8,11 +9,19 @@ using OtterGui.Services; namespace Glamourer.Gui.Customization; -public class CustomizeParameterDrawer(Configuration config) : IService +public class CustomizeParameterDrawer(Configuration config, PaletteImport import) : IService { + private readonly Dictionary _lastData = []; + private string _paletteName = string.Empty; + private CustomizeParameterData _data; + private float _width; + private bool _foundPalette; + + public void Draw(DesignManager designManager, Design design) { using var _ = EnsureSize(); + DrawPaletteImport(designManager, design); foreach (var flag in CustomizeParameterExtensions.RgbFlags) DrawColorInput3(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); @@ -26,11 +35,54 @@ public class CustomizeParameterDrawer(Configuration config) : IService DrawValueInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); } - private ImRaii.IEndObject EnsureSize() + private void DrawPaletteImport(DesignManager manager, Design design) { - var iconSize = ImGui.GetTextLineHeight() * 2 + ImGui.GetStyle().ItemSpacing.Y + 4 * ImGui.GetStyle().FramePadding.Y; - var width = 6 * iconSize + 4 * ImGui.GetStyle().ItemInnerSpacing.X; - return ImRaii.ItemWidth(width); + if (!config.ShowPalettePlusImport) + return; + + var spacing = ImGui.GetStyle().ItemInnerSpacing.X; + + if (ImGui.InputTextWithHint("##import", "Palette Name...", ref _paletteName, 256)) + { + _data = design.DesignData.Parameters; + _foundPalette = import.TryRead(_paletteName, ref _data); + } + + ImGui.SameLine(0, spacing); + var value = true; + if (ImGui.Checkbox("Show Import", ref value)) + { + config.ShowPalettePlusImport = false; + config.Save(); + } + + ImGuiUtil.HoverTooltip("Hide the Palette+ Import bar from all designs. You can re-enable it in Glamourers interface settings."); + + var buttonWidth = new Vector2((_width - spacing) / 2, 0); + var tt = _foundPalette + ? $"Apply the imported data from the Palette+ palette [{_paletteName}] to this design." + : $"The palette [{_paletteName}] could not be imported from your Palette+ configuration."; + if (ImGuiUtil.DrawDisabledButton("Apply Import", buttonWidth, tt, !_foundPalette || design.WriteProtected())) + { + // Reload Data in case anything changed since entering text. + _data = design.DesignData.Parameters; + _foundPalette = import.TryRead(_paletteName, ref _data); + _lastData[design] = design.DesignData.Parameters; + foreach (var parameter in CustomizeParameterExtensions.AllFlags) + manager.ChangeCustomizeParameter(design, parameter, _data[parameter]); + } + + ImGui.SameLine(0, spacing); + var enabled = _lastData.TryGetValue(design, out var oldData); + tt = enabled + ? $"Revert to the last set of advanced customization parameters of [{design.Name}] before importing." + : $"You have not imported any data that could be reverted for [{design.Name}]."; + if (ImGuiUtil.DrawDisabledButton("Revert Import", buttonWidth, tt, !enabled || design.WriteProtected())) + { + _lastData.Remove(design); + foreach (var parameter in CustomizeParameterExtensions.AllFlags) + manager.ChangeCustomizeParameter(design, parameter, oldData[parameter]); + } } public void Draw(StateManager stateManager, ActorState state) @@ -145,4 +197,12 @@ public class CustomizeParameterDrawer(Configuration config) : IService => ImGui.GetIO().KeyCtrl ? ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions : ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR; + + + private ImRaii.IEndObject EnsureSize() + { + var iconSize = ImGui.GetTextLineHeight() * 2 + ImGui.GetStyle().ItemSpacing.Y + 4 * ImGui.GetStyle().FramePadding.Y; + _width = 6 * iconSize + 4 * ImGui.GetStyle().ItemInnerSpacing.X; + return ImRaii.ItemWidth(_width); + } } diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index ea1fd24..ce80cde 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -94,7 +94,8 @@ public class SettingsTab : ITab Checkbox("Revert Manual Changes on Zone Change", "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone.", _config.RevertManualChangesOnZoneChange, v => _config.RevertManualChangesOnZoneChange = v); - Checkbox("Enable Advanced Customization Options", "Enable the display and editing of advanced customization options like arbitrary colors.", + Checkbox("Enable Advanced Customization Options", + "Enable the display and editing of advanced customization options like arbitrary colors.", _config.UseAdvancedParameters, v => _config.UseAdvancedParameters = v); ImGui.NewLine(); } @@ -171,6 +172,10 @@ public class SettingsTab : ITab Checkbox("Show Unobtained Item Warnings", "Show information whether you have unlocked all items and customizations in your automated design or not.", _config.ShowUnlockedItemWarnings, v => _config.ShowUnlockedItemWarnings = v); + if (_config.UseAdvancedParameters) + Checkbox("Show Palette+ Import Button", + "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs.", + _config.ShowPalettePlusImport, v => _config.ShowPalettePlusImport = v); Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general.", _config.DebugMode, v => _config.DebugMode = v); ImGui.NewLine(); diff --git a/Glamourer/Interop/PalettePlus/PaletteImport.cs b/Glamourer/Interop/PalettePlus/PaletteImport.cs new file mode 100644 index 0000000..2bd305f --- /dev/null +++ b/Glamourer/Interop/PalettePlus/PaletteImport.cs @@ -0,0 +1,94 @@ +using Dalamud.Plugin; +using Glamourer.GameData; +using Newtonsoft.Json.Linq; +using OtterGui.Services; + +namespace Glamourer.Interop.PalettePlus; + +public class PaletteImport(DalamudPluginInterface pluginInterface) : IService +{ + private string ConfigFile + => Path.Combine(Path.GetDirectoryName(pluginInterface.GetPluginConfigDirectory())!, "PalettePlus.json"); + + public bool TryRead(string name, ref CustomizeParameterData data) + { + if (name.Length == 0) + return false; + + + var path = ConfigFile; + if (!File.Exists(path)) + return false; + + try + { + var text = File.ReadAllText(path); + var obj = JObject.Parse(text); + var palettes = obj["SavedPalettes"]; + + var target = palettes?.Children().FirstOrDefault(c => c["Name"]?.ToObject() == name); + if (target == null) + return false; + + var conditions = target["Conditions"]?.ToObject() ?? 0; + var parameters = target["ShaderParams"]; + if (parameters == null) + return false; + + var discard = 0f; + + Parse("SkinTone", ref data.SkinDiffuse.X, ref data.SkinDiffuse.Y, ref data.SkinDiffuse.Z, ref discard); + Parse("SkinGloss", ref data.SkinSpecular.X, ref data.SkinSpecular.Y, ref data.SkinSpecular.Z, ref discard); + Parse("LipColor", ref data.LipDiffuse.X, ref data.LipDiffuse.Y, ref data.LipDiffuse.Z, ref data.LipDiffuse.W); + Parse("HairColor", ref data.HairDiffuse.X, ref data.HairDiffuse.Y, ref data.HairDiffuse.Z, ref discard); + Parse("HairShine", ref data.HairSpecular.X, ref data.HairSpecular.Y, ref data.HairSpecular.Z, ref discard); + Parse("LeftEyeColor", ref data.LeftEye.X, ref data.LeftEye.Y, ref data.LeftEye.Z, ref discard); + Parse("RaceFeatureColor", ref data.FeatureColor.X, ref data.FeatureColor.Y, ref data.FeatureColor.Z, ref discard); + Parse("FacePaintColor", ref data.DecalColor.X, ref data.DecalColor.Y, ref data.DecalColor.Z, ref data.DecalColor.W); + // Highlights is flag 2. + if ((conditions & 2) == 2) + Parse("HighlightsColor", ref data.HairHighlight.X, ref data.HairHighlight.Y, ref data.HairHighlight.Z, ref discard); + // Heterochromia is flag 1 + if ((conditions & 1) == 1) + Parse("RightEyeColor", ref data.RightEye.X, ref data.RightEye.Y, ref data.RightEye.Z, ref discard); + + ParseSingle("FacePaintOffset", ref data.FacePaintUvOffset); + ParseSingle("FacePaintWidth", ref data.FacePaintUvMultiplier); + ParseSingle("MuscleTone", ref data.MuscleTone); + + return true; + + void Parse(string attribute, ref float x, ref float y, ref float z, ref float w) + { + var node = parameters![attribute]; + if (node == null) + return; + + var xVal = node["X"]?.ToObject(); + var yVal = node["Y"]?.ToObject(); + var zVal = node["Z"]?.ToObject(); + var wVal = node["W"]?.ToObject(); + if (xVal.HasValue) + x = xVal.Value > 0 ? MathF.Sqrt(xVal.Value) : -MathF.Sqrt(-xVal.Value); + if (yVal.HasValue) + y = yVal.Value > 0 ? MathF.Sqrt(yVal.Value) : -MathF.Sqrt(-yVal.Value); + if (zVal.HasValue) + z = zVal.Value > 0 ? MathF.Sqrt(zVal.Value) : -MathF.Sqrt(-zVal.Value); + if (wVal.HasValue) + w = wVal.Value; + } + + void ParseSingle(string attribute, ref float value) + { + var node = parameters![attribute]?.ToObject(); + if (node.HasValue) + value = node.Value; + } + } + catch (Exception ex) + { + Glamourer.Log.Error($"Could not read Palette+ configuration:\n{ex}"); + return false; + } + } +} From c87885bd3ba703144f9dd34bb7f937c9f4aa6f14 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 12 Jan 2024 23:35:41 +0100 Subject: [PATCH 157/786] Store Model ID for NPCs, fix issue with applying NPC data setting extended colors. --- Glamourer/GameData/NpcCustomizeSet.cs | 2 ++ Glamourer/GameData/NpcData.cs | 6 ++++++ Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 7 ++++++- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 9 ++++++--- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index 302f1ce..d9d8f27 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -72,6 +72,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList { Name = name, Customize = customize, + ModelId = row.ModelChara.Row, Id = id, Kind = ObjectKind.EventNpc, }; @@ -132,6 +133,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList var ret = new NpcData { Customize = customize, + ModelId = baseRow.ModelChara.Row, Id = baseRow.RowId, Kind = ObjectKind.BattleNpc, }; diff --git a/Glamourer/GameData/NpcData.cs b/Glamourer/GameData/NpcData.cs index 860f80a..6b5f2bd 100644 --- a/Glamourer/GameData/NpcData.cs +++ b/Glamourer/GameData/NpcData.cs @@ -24,6 +24,9 @@ public unsafe struct NpcData /// The data ID of the NPC, either event NPC or battle NPC name. public NpcId Id; + /// The Model ID of the NPC. + public uint ModelId; + /// Whether the NPCs visor is toggled. public bool VisorToggled; @@ -87,6 +90,9 @@ public unsafe struct NpcData /// Check if the appearance data, excluding ID and Name, of two NpcData is equal. public bool DataEquals(in NpcData other) { + if (ModelId != other.ModelId) + return false; + if (VisorToggled != other.VisorToggled) return false; diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 1ff78cb..505ed62 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -33,7 +33,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); var resetScroll = ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64); - using var table = ImRaii.Table("npcs", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, + using var table = ImRaii.Table("npcs", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, new Vector2(-1, 400 * ImGuiHelpers.GlobalScale)); if (!table) return; @@ -45,6 +45,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300); ImGui.TableSetupColumn("Kind", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Model", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Visor", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Compare", ImGuiTableColumnFlags.WidthStretch); @@ -83,6 +84,10 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(data.Id.Id.ToString()); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(data.ModelId.ToString()); + using (_ = ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TableNextColumn(); diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index c6d5be4..2852128 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -141,7 +141,10 @@ public class NpcPanel( private void DrawCustomization() { - using var h = ImRaii.CollapsingHeader("Customization"); + var header = _selector.Selection.ModelId == 0 + ? "Customization" + : $"Customization (Model Id #{_selector.Selection.ModelId})###Customization"; + using var h = ImRaii.CollapsingHeader(header); if (!h) return; @@ -197,8 +200,8 @@ public class NpcPanel( if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); - var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest, applyParameters); + var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags(); + var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0); _state.ApplyDesign(design, state, StateChanged.Source.Manual); } } From 917e80d4678970ec374c0726ee56c2ed97e08129 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 15 Jan 2024 21:51:45 +0100 Subject: [PATCH 158/786] Prepare changelog. --- Glamourer/Configuration.cs | 2 +- Glamourer/Gui/GlamourerChangelog.cs | 19 +++++++++++++++++++ Glamourer/State/StateListener.cs | 1 - 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index fb99bf5..2f925a3 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -34,7 +34,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool RevertManualChangesOnZoneChange { get; set; } = false; public bool ShowQuickBarInTabs { get; set; } = true; public bool OpenWindowAtStart { get; set; } = false; - public bool UseAdvancedParameters { get; set; } = false; + public bool UseAdvancedParameters { get; set; } = true; public bool ShowPalettePlusImport { get; set; } = true; public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index ae9227f..505279e 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -25,6 +25,7 @@ public class GlamourerChangelog Add1_0_5_0(Changelog); Add1_0_6_0(Changelog); Add1_0_7_0(Changelog); + Add1_1_0_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -45,6 +46,24 @@ public class GlamourerChangelog } } + private static void Add1_1_0_0(Changelog log) + => log.NextVersion("Version 1.1.0.0") + .RegisterHighlight("Added a new tab to browse, apply or copy (human) NPC appearances.") + .RegisterHighlight("A characters body type can now be changed when copying state or saving designs from certain NPCs.") + .RegisterHighlight("Added support for picking advanced colors for your characters customizations.") + .RegisterEntry("The display and application of those can be toggled off in Glamourers behaviour settings.", 1) + .RegisterEntry("This provides the same functionality as Palette+, and Palette+ will probably be discontinued soonish (in accordance with Chirp).", 1) + .RegisterEntry("An option to import existing palettes from Palette+ by name is provided for designs, and can be toggled off in the settings.", 1) + .RegisterHighlight("Advanced colors, equipment and dyes can now be reset to their game state separately by Control-Rightclicking them.") + .RegisterHighlight("Hairstyles and face paints can now be made favourites.") + .RegisterEntry("Added a new command '/glamour delete' to delete saved designs by name or identifier.") + .RegisterEntry("Added an optional parameter to the '/glamour apply' command that makes it apply the associated mod settings for a design to the collection associated with the identified character.") + .RegisterEntry("Fixed changing weapons in Designs not working correctly.") + .RegisterEntry("Fixed restricted gear protection breaking outfits for Mare pairs.") + .RegisterEntry("Improved the handling of some cheat codes and added new ones.") + .RegisterEntry("Added IPC to set single items or stains on characters.") + .RegisterEntry("Added IPC to apply designs by GUID, and obtain a list of designs."); + private static void Add1_0_7_0(Changelog log) => log.NextVersion("Version 1.0.7.0") .RegisterHighlight("Glamourer now can set the free company crests on body slots, head slots and shields.") diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 6cf873d..7b40d69 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -9,7 +9,6 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Shader; using Glamourer.GameData; using Penumbra.GameData.DataContainers; From 6a3fb7f5992d718f476b55bd1d36f3ef6ef16960 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 16 Jan 2024 15:40:07 +0000 Subject: [PATCH 159/786] [CI] Updating repo.json for 1.1.0.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index ec1448f..9e4ed47 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.7.0", - "TestingAssemblyVersion": "1.0.7.8", + "AssemblyVersion": "1.1.0.0", + "TestingAssemblyVersion": "1.1.0.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.0.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.0.7.8/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 34fa1e37c8a1bb69783929aac4b5689a84efee7f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jan 2024 17:54:30 +0100 Subject: [PATCH 160/786] Fix issue with null-favorites. --- Glamourer/Unlocks/FavoriteManager.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index f5f80a1..6d0a3ab 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -15,7 +15,8 @@ public class FavoriteManager : ISavable => (uint)Id.Value | ((uint)Type << 8) | ((uint)Race << 16) | ((uint)Gender << 24); public FavoriteHairStyle(uint value) - : this((Gender)((value >> 24) & 0xFF), (SubRace)((value >> 16) & 0xFF), (CustomizeIndex)((value >> 8) & 0xFF), (CustomizeValue)(value & 0xFF)) + : this((Gender)((value >> 24) & 0xFF), (SubRace)((value >> 16) & 0xFF), (CustomizeIndex)((value >> 8) & 0xFF), + (CustomizeValue)(value & 0xFF)) { } } @@ -54,7 +55,7 @@ public class FavoriteManager : ISavable } else { - var load = JsonConvert.DeserializeObject(text); + var load = JsonConvert.DeserializeObject(text); switch (load.Version) { case 1: @@ -91,19 +92,19 @@ public class FavoriteManager : ISavable using var j = new JsonTextWriter(writer); j.Formatting = Formatting.Indented; j.WriteStartObject(); - j.WritePropertyName(nameof(LoadStruct.Version)); + j.WritePropertyName(nameof(LoadIntermediary.Version)); j.WriteValue(CurrentVersion); - j.WritePropertyName(nameof(LoadStruct.FavoriteItems)); + j.WritePropertyName(nameof(LoadIntermediary.FavoriteItems)); j.WriteStartArray(); foreach (var item in _favorites) j.WriteValue(item.Id); j.WriteEndArray(); - j.WritePropertyName(nameof(LoadStruct.FavoriteColors)); + j.WritePropertyName(nameof(LoadIntermediary.FavoriteColors)); j.WriteStartArray(); foreach (var stain in _favoriteColors) j.WriteValue(stain.Id); j.WriteEndArray(); - j.WritePropertyName(nameof(LoadStruct.FavoriteHairStyles)); + j.WritePropertyName(nameof(LoadIntermediary.FavoriteHairStyles)); j.WriteStartArray(); foreach (var hairStyle in _favoriteHairStyles) j.WriteValue(hairStyle.ToValue()); @@ -192,14 +193,11 @@ public class FavoriteManager : ISavable public bool Contains(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value) => _favoriteHairStyles.Contains(new FavoriteHairStyle(gender, race, type, value)); - private struct LoadStruct + private class LoadIntermediary { public int Version = CurrentVersion; public uint[] FavoriteItems = []; public byte[] FavoriteColors = []; public uint[] FavoriteHairStyles = []; - - public LoadStruct() - { } } } From cfa35b237982f5c9791faa6800b71cf3ba0c4b97 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jan 2024 18:19:49 +0100 Subject: [PATCH 161/786] Update Gamedata. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index f68b6ee..119ffaa 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit f68b6eee157bc2016e1cb7d5ffac491b287b13a8 +Subproject commit 119ffaa240f0446bdd05c5254c4e0dd8539a2d7d From 8d4f71122ce23a8b3140b28d20b1f1fb1559d07c Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 16 Jan 2024 17:24:06 +0000 Subject: [PATCH 162/786] [CI] Updating repo.json for 1.1.0.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 9e4ed47..a31e74d 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.1.0.0", - "TestingAssemblyVersion": "1.1.0.0", + "AssemblyVersion": "1.1.0.1", + "TestingAssemblyVersion": "1.1.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From d57d40bd597d1af70b3459143fcc305bea80f3f2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jan 2024 21:31:57 +0100 Subject: [PATCH 163/786] Fix name of customize parameters. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 6cefc63..f609bce 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -313,7 +313,7 @@ public class DesignPanel( foreach (var flag in CustomizeParameterExtensions.AllFlags) { var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : _selector.Selected!.DoApplyParameter(flag); - if (ImGui.Checkbox($"Apply {flag}", ref apply) || bigChange) + if (ImGui.Checkbox($"Apply {flag.ToName()}", ref apply) || bigChange) _manager.ChangeApplyParameter(_selector.Selected!, flag, apply); } } From c245b30eaaa52598b2c062f43b76aeadb88ce5e6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jan 2024 21:32:10 +0100 Subject: [PATCH 164/786] Fix clone not copying parameter rules. --- Glamourer/Designs/Design.cs | 2 +- Glamourer/Designs/DesignBase.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index b15b535..4ff6f2d 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -22,7 +22,7 @@ public sealed class Design : DesignBase, ISavable internal Design(Design other) : base(other) { - Tags = other.Tags.ToArray(); + Tags = [.. other.Tags]; Description = other.Description; AssociatedMods = new SortedList(other.AssociatedMods); } diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index da923b9..023f984 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -40,11 +40,12 @@ public class DesignBase internal DesignBase(DesignBase clone) { - _designData = clone._designData; - CustomizeSet = clone.CustomizeSet; - ApplyCustomize = clone.ApplyCustomizeRaw; - ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; - _designFlags = clone._designFlags & (DesignFlags)0x0F; + _designData = clone._designData; + CustomizeSet = clone.CustomizeSet; + ApplyCustomize = clone.ApplyCustomizeRaw; + ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; + ApplyParameters = clone.ApplyParameters & CustomizeParameterExtensions.All; + _designFlags = clone._designFlags & (DesignFlags)0x0F; } /// Ensure that the customization set is updated when the design data changes. From 13bf83b2b7798b7e2fe2ef70b0d5075793addba1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jan 2024 21:32:23 +0100 Subject: [PATCH 165/786] Add design color to preview in combos. --- Glamourer/Gui/DesignCombo.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index c63fa3c..e255c17 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -81,9 +81,13 @@ public abstract class DesignComboBase : FilterComboCache>, { _currentDesign = currentDesign; InnerWidth = 400 * ImGuiHelpers.GlobalScale; - var name = label ?? "Select Design Here..."; - var ret = Draw("##design", name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()) - && CurrentSelection != null; + var name = label ?? "Select Design Here..."; + bool ret; + using (_ = currentDesign != null ? ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(currentDesign)) : null) + { + ret = Draw("##design", name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()) + && CurrentSelection != null; + } if (currentDesign != null) { @@ -195,7 +199,6 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable _autoDesignManager = autoDesignManager; } - public void Draw(AutoDesignSet set, AutoDesign? design, int autoDesignIndex) { if (!Draw(design?.Design, design?.Name(Incognito), ImGui.GetContentRegionAvail().X)) From ed0ee6439ad2044517ab792959c647f03d16566d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jan 2024 21:33:18 +0100 Subject: [PATCH 166/786] Improve Palette+ Import. --- .../Customization/CustomizeParameterDrawer.cs | 37 ++-- Glamourer/Gui/Tabs/SettingsTab.cs | 170 ++++++++------- .../Interop/PalettePlus/PaletteImport.cs | 203 ++++++++++++------ OtterGui | 2 +- 4 files changed, 256 insertions(+), 156 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 23be6c4..12de304 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -14,8 +14,8 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import private readonly Dictionary _lastData = []; private string _paletteName = string.Empty; private CustomizeParameterData _data; + private CustomizeParameterFlag _flags; private float _width; - private bool _foundPalette; public void Draw(DesignManager designManager, Design design) @@ -35,6 +35,24 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import DrawValueInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); } + private void DrawPaletteCombo() + { + using var id = ImRaii.PushId("Palettes"); + using var combo = ImRaii.Combo("##import", _paletteName.Length > 0 ? _paletteName : "Select Palette..."); + if (!combo) + return; + + foreach (var (name, (palette, flags)) in import.Data) + { + if (!ImGui.Selectable(name, _paletteName == name)) + continue; + + _paletteName = name; + _data = palette; + _flags = flags; + } + } + private void DrawPaletteImport(DesignManager manager, Design design) { if (!config.ShowPalettePlusImport) @@ -42,11 +60,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import var spacing = ImGui.GetStyle().ItemInnerSpacing.X; - if (ImGui.InputTextWithHint("##import", "Palette Name...", ref _paletteName, 256)) - { - _data = design.DesignData.Parameters; - _foundPalette = import.TryRead(_paletteName, ref _data); - } + DrawPaletteCombo(); ImGui.SameLine(0, spacing); var value = true; @@ -59,16 +73,13 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import ImGuiUtil.HoverTooltip("Hide the Palette+ Import bar from all designs. You can re-enable it in Glamourers interface settings."); var buttonWidth = new Vector2((_width - spacing) / 2, 0); - var tt = _foundPalette + var tt = _paletteName.Length > 0 ? $"Apply the imported data from the Palette+ palette [{_paletteName}] to this design." - : $"The palette [{_paletteName}] could not be imported from your Palette+ configuration."; - if (ImGuiUtil.DrawDisabledButton("Apply Import", buttonWidth, tt, !_foundPalette || design.WriteProtected())) + : "Please select a palette first."; + if (ImGuiUtil.DrawDisabledButton("Apply Import", buttonWidth, tt, _paletteName.Length == 0 || design.WriteProtected())) { - // Reload Data in case anything changed since entering text. - _data = design.DesignData.Parameters; - _foundPalette = import.TryRead(_paletteName, ref _data); _lastData[design] = design.DesignData.Parameters; - foreach (var parameter in CustomizeParameterExtensions.AllFlags) + foreach (var parameter in _flags.Iterate()) manager.ChangeCustomizeParameter(design, parameter, _data[parameter]); } diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index ce80cde..13e6412 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -6,6 +6,7 @@ using Dalamud.Plugin.Services; using Glamourer.Designs; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; +using Glamourer.Interop.PalettePlus; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; @@ -16,34 +17,21 @@ using OtterGui.Widgets; namespace Glamourer.Gui.Tabs; -public class SettingsTab : ITab +public class SettingsTab( + Configuration config, + DesignFileSystemSelector selector, + CodeService codeService, + PenumbraAutoRedraw autoRedraw, + ContextMenuService contextMenuService, + UiBuilder uiBuilder, + GlamourerChangelog changelog, + FunModule funModule, + IKeyState keys, + DesignColorUi designColorUi, + PaletteImport paletteImport) + : ITab { - private readonly VirtualKey[] _validKeys; - private readonly Configuration _config; - private readonly DesignFileSystemSelector _selector; - private readonly CodeService _codeService; - private readonly PenumbraAutoRedraw _autoRedraw; - private readonly ContextMenuService _contextMenuService; - private readonly UiBuilder _uiBuilder; - private readonly GlamourerChangelog _changelog; - private readonly FunModule _funModule; - private readonly DesignColorUi _designColorUi; - - public SettingsTab(Configuration config, DesignFileSystemSelector selector, CodeService codeService, PenumbraAutoRedraw autoRedraw, - ContextMenuService contextMenuService, UiBuilder uiBuilder, GlamourerChangelog changelog, FunModule funModule, IKeyState keys, - DesignColorUi designColorUi) - { - _config = config; - _selector = selector; - _codeService = codeService; - _autoRedraw = autoRedraw; - _contextMenuService = contextMenuService; - _uiBuilder = uiBuilder; - _changelog = changelog; - _funModule = funModule; - _designColorUi = designColorUi; - _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); - } + private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); public ReadOnlySpan Label => "Settings"u8; @@ -58,7 +46,7 @@ public class SettingsTab : ITab Checkbox("Enable Auto Designs", "Enable the application of designs associated to characters in the Automation tab to be applied automatically.", - _config.EnableAutoDesigns, v => _config.EnableAutoDesigns = v); + config.EnableAutoDesigns, v => config.EnableAutoDesigns = v); ImGui.NewLine(); ImGui.NewLine(); ImGui.NewLine(); @@ -71,7 +59,7 @@ public class SettingsTab : ITab DrawCodes(); } - MainWindow.DrawSupportButtons(_changelog.Changelog); + MainWindow.DrawSupportButtons(changelog.Changelog); } private void DrawBehaviorSettings() @@ -81,22 +69,23 @@ public class SettingsTab : ITab Checkbox("Use Replacement Gear for Gear Unavailable to Your Race or Gender", "Use different gender- and race-appropriate models as a substitute when detecting certain items not available for a characters current gender and race.", - _config.UseRestrictedGearProtection, v => _config.UseRestrictedGearProtection = v); + config.UseRestrictedGearProtection, v => config.UseRestrictedGearProtection = v); Checkbox("Do Not Apply Unobtained Items in Automation", "Enable this if you want automatically applied designs to only consider items and customizations you have actually unlocked once, and skip those you have not.", - _config.UnlockedItemMode, v => _config.UnlockedItemMode = v); + config.UnlockedItemMode, v => config.UnlockedItemMode = v); Checkbox("Enable Festival Easter-Eggs", "Glamourer may do some fun things on specific dates. Disable this if you do not want your experience disrupted by this.", - _config.DisableFestivals == 0, v => _config.DisableFestivals = v ? (byte)0 : (byte)2); + config.DisableFestivals == 0, v => config.DisableFestivals = v ? (byte)0 : (byte)2); Checkbox("Auto-Reload Gear", "Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection.", - _config.AutoRedrawEquipOnChanges, _autoRedraw.SetState); + config.AutoRedrawEquipOnChanges, autoRedraw.SetState); Checkbox("Revert Manual Changes on Zone Change", "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone.", - _config.RevertManualChangesOnZoneChange, v => _config.RevertManualChangesOnZoneChange = v); + config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); Checkbox("Enable Advanced Customization Options", "Enable the display and editing of advanced customization options like arbitrary colors.", - _config.UseAdvancedParameters, v => _config.UseAdvancedParameters = v); + config.UseAdvancedParameters, v => config.UseAdvancedParameters = v); + PaletteImportButton(); ImGui.NewLine(); } @@ -107,59 +96,59 @@ public class SettingsTab : ITab EphemeralCheckbox("Show Quick Design Bar", "Show a bar separate from the main window that allows you to quickly apply designs or revert your character and target.", - _config.Ephemeral.ShowDesignQuickBar, v => _config.Ephemeral.ShowDesignQuickBar = v); + config.Ephemeral.ShowDesignQuickBar, v => config.Ephemeral.ShowDesignQuickBar = v); EphemeralCheckbox("Lock Quick Design Bar", "Prevent the quick design bar from being moved and lock it in place.", - _config.Ephemeral.LockDesignQuickBar, - v => _config.Ephemeral.LockDesignQuickBar = v); + config.Ephemeral.LockDesignQuickBar, + v => config.Ephemeral.LockDesignQuickBar = v); if (Widget.ModifiableKeySelector("Hotkey to Toggle Quick Design Bar", "Set a hotkey that opens or closes the quick design bar.", 100 * ImGuiHelpers.GlobalScale, - _config.ToggleQuickDesignBar, v => _config.ToggleQuickDesignBar = v, _validKeys)) - _config.Save(); + config.ToggleQuickDesignBar, v => config.ToggleQuickDesignBar = v, _validKeys)) + config.Save(); Checkbox("Show Quick Design Bar in Main Window", "Show the quick design bar in the tab selection part of the main window, too.", - _config.ShowQuickBarInTabs, v => _config.ShowQuickBarInTabs = v); + config.ShowQuickBarInTabs, v => config.ShowQuickBarInTabs = v); ImGui.Dummy(Vector2.Zero); ImGui.Separator(); ImGui.Dummy(Vector2.Zero); Checkbox("Enable Game Context Menus", "Whether to show a Try On via Glamourer button on context menus for equippable items.", - _config.EnableGameContextMenu, v => + config.EnableGameContextMenu, v => { - _config.EnableGameContextMenu = v; + config.EnableGameContextMenu = v; if (v) - _contextMenuService.Enable(); + contextMenuService.Enable(); else - _contextMenuService.Disable(); + contextMenuService.Disable(); }); Checkbox("Hide Window in Cutscenes", "Whether the main Glamourer window should automatically be hidden when entering cutscenes or not.", - _config.HideWindowInCutscene, + config.HideWindowInCutscene, v => { - _config.HideWindowInCutscene = v; - _uiBuilder.DisableCutsceneUiHide = !v; + config.HideWindowInCutscene = v; + uiBuilder.DisableCutsceneUiHide = !v; }); EphemeralCheckbox("Lock Main Window", "Prevent the main window from being moved and lock it in place.", - _config.Ephemeral.LockMainWindow, - v => _config.Ephemeral.LockMainWindow = v); + config.Ephemeral.LockMainWindow, + v => config.Ephemeral.LockMainWindow = v); Checkbox("Open Main Window at Game Start", "Whether the main Glamourer window should be open or closed after launching the game.", - _config.OpenWindowAtStart, v => _config.OpenWindowAtStart = v); + config.OpenWindowAtStart, v => config.OpenWindowAtStart = v); ImGui.Dummy(Vector2.Zero); ImGui.Separator(); ImGui.Dummy(Vector2.Zero); Checkbox("Smaller Equip Display", "Use single-line display without icons and small dye buttons instead of double-line display.", - _config.SmallEquip, v => _config.SmallEquip = v); + config.SmallEquip, v => config.SmallEquip = v); 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); + !config.HideApplyCheckmarks, v => config.HideApplyCheckmarks = !v); if (Widget.DoubleModifierSelector("Design Deletion Modifier", "A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale, - _config.DeleteDesignModifier, v => _config.DeleteDesignModifier = v)) - _config.Save(); + config.DeleteDesignModifier, v => config.DeleteDesignModifier = v)) + config.Save(); Checkbox("Auto-Open Design Folders", - "Have design folders open or closed as their default state after launching.", _config.OpenFoldersByDefault, - v => _config.OpenFoldersByDefault = v); + "Have design folders open or closed as their default state after launching.", config.OpenFoldersByDefault, + v => config.OpenFoldersByDefault = v); DrawFolderSortType(); ImGui.Dummy(Vector2.Zero); @@ -168,19 +157,36 @@ public class SettingsTab : ITab Checkbox("Show all Application Rule Checkboxes for Automation", "Show multiple separate application rule checkboxes for automated designs, instead of a single box for enabling or disabling.", - _config.ShowAllAutomatedApplicationRules, v => _config.ShowAllAutomatedApplicationRules = v); + config.ShowAllAutomatedApplicationRules, v => config.ShowAllAutomatedApplicationRules = v); Checkbox("Show Unobtained Item Warnings", "Show information whether you have unlocked all items and customizations in your automated design or not.", - _config.ShowUnlockedItemWarnings, v => _config.ShowUnlockedItemWarnings = v); - if (_config.UseAdvancedParameters) + config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v); + if (config.UseAdvancedParameters) + { Checkbox("Show Palette+ Import Button", "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs.", - _config.ShowPalettePlusImport, v => _config.ShowPalettePlusImport = v); - Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general.", _config.DebugMode, - v => _config.DebugMode = v); + config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v); + using var id = ImRaii.PushId(1); + PaletteImportButton(); + } + + Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general.", config.DebugMode, + v => config.DebugMode = v); ImGui.NewLine(); } + private void PaletteImportButton() + { + if (!config.UseAdvancedParameters || !config.ShowPalettePlusImport) + return; + + ImGui.SameLine(); + if (ImGui.Button("Import Palette+ to Designs")) + paletteImport.ImportDesigns(); + ImGuiUtil.HoverTooltip( + $"Import all existing Palettes from your Palette+ Config into Designs at PalettePlus/[Name] if these do not exist. Existing Palettes are:\n\n\t - {string.Join("\n\t - ", paletteImport.Data.Keys)}"); + } + /// Draw the entire Color subsection. private void DrawColorSettings() { @@ -190,7 +196,7 @@ public class SettingsTab : ITab using (var tree = ImRaii.TreeNode("Custom Design Colors")) { if (tree) - _designColorUi.Draw(); + designColorUi.Draw(); } using (var tree = ImRaii.TreeNode("Color Settings")) @@ -199,9 +205,9 @@ public class SettingsTab : ITab foreach (var color in Enum.GetValues()) { var (defaultColor, name, description) = color.Data(); - var currentColor = _config.Colors.TryGetValue(color, out var current) ? current : defaultColor; - if (Widget.ColorPicker(name, description, currentColor, c => _config.Colors[color] = c, defaultColor)) - _config.Save(); + var currentColor = config.Colors.TryGetValue(color, out var current) ? current : defaultColor; + if (Widget.ColorPicker(name, description, currentColor, c => config.Colors[color] = c, defaultColor)) + config.Save(); } } @@ -228,10 +234,10 @@ public class SettingsTab : ITab using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale, _currentCode.Length > 0)) { - var color = _codeService.CheckCode(_currentCode) != null ? ColorId.ActorAvailable : ColorId.ActorUnavailable; + 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)) + if (codeService.AddCode(_currentCode)) _currentCode = string.Empty; } @@ -240,30 +246,30 @@ public class SettingsTab : ITab DrawCodeHints(); - if (_config.Codes.Count <= 0) + if (config.Codes.Count <= 0) return; - for (var i = 0; i < _config.Codes.Count; ++i) + for (var i = 0; i < config.Codes.Count; ++i) { - var (code, state) = _config.Codes[i]; - var action = _codeService.CheckCode(code); + 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(); + codeService.SaveState(); } } if (ImGui.Button("Who am I?!?")) - _funModule.WhoAmI(); + funModule.WhoAmI(); ImGui.SameLine(); if (ImGui.Button("Who is that!?!")) - _funModule.WhoIsThat(); + funModule.WhoIsThat(); } private void DrawCodeHints() @@ -279,7 +285,7 @@ public class SettingsTab : ITab if (ImGui.Checkbox(string.Empty, ref tmp) && tmp != current) { setter(tmp); - _config.Save(); + config.Save(); } ImGui.SameLine(); @@ -294,7 +300,7 @@ public class SettingsTab : ITab if (ImGui.Checkbox(string.Empty, ref tmp) && tmp != current) { setter(tmp); - _config.Ephemeral.Save(); + config.Ephemeral.Save(); } ImGui.SameLine(); @@ -304,7 +310,7 @@ public class SettingsTab : ITab /// Different supported sort modes as a combo. private void DrawFolderSortType() { - var sortMode = _config.SortMode; + var sortMode = config.SortMode; ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); using (var combo = ImRaii.Combo("##sortMode", sortMode.Name)) { @@ -313,9 +319,9 @@ public class SettingsTab : ITab { if (ImGui.Selectable(val.Name, val.GetType() == sortMode.GetType()) && val.GetType() != sortMode.GetType()) { - _config.SortMode = val; - _selector.SetFilterDirty(); - _config.Save(); + config.SortMode = val; + selector.SetFilterDirty(); + config.Save(); } ImGuiUtil.HoverTooltip(val.Description); diff --git a/Glamourer/Interop/PalettePlus/PaletteImport.cs b/Glamourer/Interop/PalettePlus/PaletteImport.cs index 2bd305f..5253c05 100644 --- a/Glamourer/Interop/PalettePlus/PaletteImport.cs +++ b/Glamourer/Interop/PalettePlus/PaletteImport.cs @@ -1,94 +1,177 @@ using Dalamud.Plugin; +using Glamourer.Designs; using Glamourer.GameData; +using Glamourer.State; using Newtonsoft.Json.Linq; using OtterGui.Services; namespace Glamourer.Interop.PalettePlus; -public class PaletteImport(DalamudPluginInterface pluginInterface) : IService +public class PaletteImport(DalamudPluginInterface pluginInterface, DesignManager designManager, DesignFileSystem designFileSystem) : IService { private string ConfigFile => Path.Combine(Path.GetDirectoryName(pluginInterface.GetPluginConfigDirectory())!, "PalettePlus.json"); - public bool TryRead(string name, ref CustomizeParameterData data) + private readonly Dictionary _data = []; + + public IReadOnlyDictionary Data { - if (name.Length == 0) - return false; + get + { + if (_data.Count > 0) + return _data; + PopulateDict(); + return _data; + } + } + public void ImportDesigns() + { + foreach (var (name, (palette, flags)) in Data) + { + var fullPath = $"PalettePlus/{name}"; + if (designFileSystem.Find(fullPath, out _)) + { + Glamourer.Log.Information($"Skipped adding palette {name} because {fullPath} already exists."); + continue; + } + + var design = designManager.CreateEmpty(fullPath, true); + design.ApplyCustomize = 0; + design.ApplyEquip = 0; + design.ApplyCrest = 0; + designManager.ChangeApplyMeta(design, ActorState.MetaIndex.VisorState, false); + designManager.ChangeApplyMeta(design, ActorState.MetaIndex.HatState, false); + designManager.ChangeApplyMeta(design, ActorState.MetaIndex.WeaponState, false); + foreach (var flag in flags.Iterate()) + { + designManager.ChangeApplyParameter(design, flag, true); + designManager.ChangeCustomizeParameter(design, flag, palette[flag]); + } + Glamourer.Log.Information($"Added design for palette {name} at {fullPath}."); + } + } + + private void PopulateDict() + { var path = ConfigFile; if (!File.Exists(path)) - return false; + return; try { var text = File.ReadAllText(path); var obj = JObject.Parse(text); var palettes = obj["SavedPalettes"]; + if (palettes == null) + return; - var target = palettes?.Children().FirstOrDefault(c => c["Name"]?.ToObject() == name); - if (target == null) - return false; - - var conditions = target["Conditions"]?.ToObject() ?? 0; - var parameters = target["ShaderParams"]; - if (parameters == null) - return false; - - var discard = 0f; - - Parse("SkinTone", ref data.SkinDiffuse.X, ref data.SkinDiffuse.Y, ref data.SkinDiffuse.Z, ref discard); - Parse("SkinGloss", ref data.SkinSpecular.X, ref data.SkinSpecular.Y, ref data.SkinSpecular.Z, ref discard); - Parse("LipColor", ref data.LipDiffuse.X, ref data.LipDiffuse.Y, ref data.LipDiffuse.Z, ref data.LipDiffuse.W); - Parse("HairColor", ref data.HairDiffuse.X, ref data.HairDiffuse.Y, ref data.HairDiffuse.Z, ref discard); - Parse("HairShine", ref data.HairSpecular.X, ref data.HairSpecular.Y, ref data.HairSpecular.Z, ref discard); - Parse("LeftEyeColor", ref data.LeftEye.X, ref data.LeftEye.Y, ref data.LeftEye.Z, ref discard); - Parse("RaceFeatureColor", ref data.FeatureColor.X, ref data.FeatureColor.Y, ref data.FeatureColor.Z, ref discard); - Parse("FacePaintColor", ref data.DecalColor.X, ref data.DecalColor.Y, ref data.DecalColor.Z, ref data.DecalColor.W); - // Highlights is flag 2. - if ((conditions & 2) == 2) - Parse("HighlightsColor", ref data.HairHighlight.X, ref data.HairHighlight.Y, ref data.HairHighlight.Z, ref discard); - // Heterochromia is flag 1 - if ((conditions & 1) == 1) - Parse("RightEyeColor", ref data.RightEye.X, ref data.RightEye.Y, ref data.RightEye.Z, ref discard); - - ParseSingle("FacePaintOffset", ref data.FacePaintUvOffset); - ParseSingle("FacePaintWidth", ref data.FacePaintUvMultiplier); - ParseSingle("MuscleTone", ref data.MuscleTone); - - return true; - - void Parse(string attribute, ref float x, ref float y, ref float z, ref float w) + foreach (var child in palettes.Children()) { - var node = parameters![attribute]; - if (node == null) - return; + var name = child["Name"]?.ToObject() ?? string.Empty; + if (name.Length == 0) + continue; - var xVal = node["X"]?.ToObject(); - var yVal = node["Y"]?.ToObject(); - var zVal = node["Z"]?.ToObject(); - var wVal = node["W"]?.ToObject(); - if (xVal.HasValue) - x = xVal.Value > 0 ? MathF.Sqrt(xVal.Value) : -MathF.Sqrt(-xVal.Value); - if (yVal.HasValue) - y = yVal.Value > 0 ? MathF.Sqrt(yVal.Value) : -MathF.Sqrt(-yVal.Value); - if (zVal.HasValue) - z = zVal.Value > 0 ? MathF.Sqrt(zVal.Value) : -MathF.Sqrt(-zVal.Value); - if (wVal.HasValue) - w = wVal.Value; - } + var conditions = child["Conditions"]?.ToObject() ?? 0; + var parameters = child["ShaderParams"]; + if (parameters == null) + continue; - void ParseSingle(string attribute, ref float value) - { - var node = parameters![attribute]?.ToObject(); - if (node.HasValue) - value = node.Value; + var orig = name; + var counter = 1; + while (_data.ContainsKey(name)) + name = $"{orig} #{++counter}"; + + var data = new CustomizeParameterData(); + CustomizeParameterFlag flags = 0; + var discard = 0f; + Parse("SkinTone", CustomizeParameterFlag.SkinDiffuse, + ref data.SkinDiffuse.X, ref data.SkinDiffuse.Y, ref data.SkinDiffuse.Z, ref discard); + Parse("SkinGloss", CustomizeParameterFlag.SkinSpecular, + ref data.SkinSpecular.X, ref data.SkinSpecular.Y, ref data.SkinSpecular.Z, ref discard); + Parse("LipColor", CustomizeParameterFlag.LipDiffuse, + ref data.LipDiffuse.X, ref data.LipDiffuse.Y, ref data.LipDiffuse.Z, ref data.LipDiffuse.W); + Parse("HairColor", CustomizeParameterFlag.HairDiffuse, + ref data.HairDiffuse.X, ref data.HairDiffuse.Y, ref data.HairDiffuse.Z, ref discard); + Parse("HairShine", CustomizeParameterFlag.HairSpecular, + ref data.HairSpecular.X, ref data.HairSpecular.Y, ref data.HairSpecular.Z, ref discard); + Parse("LeftEyeColor", CustomizeParameterFlag.LeftEye, + ref data.LeftEye.X, ref data.LeftEye.Y, ref data.LeftEye.Z, ref discard); + Parse("RaceFeatureColor", CustomizeParameterFlag.FeatureColor, + ref data.FeatureColor.X, ref data.FeatureColor.Y, ref data.FeatureColor.Z, ref discard); + Parse("FacePaintColor", CustomizeParameterFlag.DecalColor, + ref data.DecalColor.X, ref data.DecalColor.Y, ref data.DecalColor.Z, ref data.DecalColor.W); + // Highlights is flag 2. + if ((conditions & 2) == 2) + Parse("HighlightsColor", CustomizeParameterFlag.HairHighlight, + ref data.HairHighlight.X, ref data.HairHighlight.Y, ref data.HairHighlight.Z, ref discard); + // Heterochromia is flag 1 + if ((conditions & 1) == 1) + { + Parse("RightEyeColor", CustomizeParameterFlag.RightEye, + ref data.RightEye.X, ref data.RightEye.Y, ref data.RightEye.Z, ref discard); + } + else if (flags.HasFlag(CustomizeParameterFlag.LeftEye)) + { + data.RightEye = data.LeftEye; + flags |= CustomizeParameterFlag.RightEye; + } + + ParseSingle("FacePaintOffset", CustomizeParameterFlag.FacePaintUvOffset, ref data.FacePaintUvOffset); + ParseSingle("FacePaintWidth", CustomizeParameterFlag.FacePaintUvMultiplier, ref data.FacePaintUvMultiplier); + ParseSingle("MuscleTone", CustomizeParameterFlag.MuscleTone, ref data.MuscleTone); + + while (!_data.TryAdd(name, (data, flags))) + name = $"{orig} ({++counter})"; + continue; + + + void Parse(string attribute, CustomizeParameterFlag flag, ref float x, ref float y, ref float z, ref float w) + { + var node = parameters![attribute]; + if (node == null) + return; + + flags |= flag; + var xVal = node["X"]?.ToObject(); + var yVal = node["Y"]?.ToObject(); + var zVal = node["Z"]?.ToObject(); + var wVal = node["W"]?.ToObject(); + if (xVal.HasValue) + x = xVal.Value > 0 ? MathF.Sqrt(xVal.Value) : -MathF.Sqrt(-xVal.Value); + if (yVal.HasValue) + y = yVal.Value > 0 ? MathF.Sqrt(yVal.Value) : -MathF.Sqrt(-yVal.Value); + if (zVal.HasValue) + z = zVal.Value > 0 ? MathF.Sqrt(zVal.Value) : -MathF.Sqrt(-zVal.Value); + if (wVal.HasValue) + w = wVal.Value; + } + + void ParseSingle(string attribute, CustomizeParameterFlag flag, ref float value) + { + var node = parameters![attribute]?.ToObject(); + if (!node.HasValue) + return; + + value = node.Value; + flags |= flag; + } } } catch (Exception ex) { Glamourer.Log.Error($"Could not read Palette+ configuration:\n{ex}"); - return false; } } + + public bool TryRead(string name, ref CustomizeParameterData data) + { + if (!Data.TryGetValue(name, out var t)) + return false; + + foreach (var flag in t.Item2.Iterate()) + data[flag] = t.Item1[flag]; + return true; + } } diff --git a/OtterGui b/OtterGui index 6d6e7b3..9259090 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 6d6e7b37c31bc82b8b2811c85a09f67fb0434f8a +Subproject commit 9259090121b26f097948e7bbd83b32708ea0410d From 831908475ce5c9b88558becf508f376bf1e33cab Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jan 2024 21:50:46 +0100 Subject: [PATCH 167/786] Add tooltip to percentage selector and improve value drag range. --- Glamourer/Gui/Customization/CustomizeParameterDrawer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 12de304..cef22f6 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -165,10 +165,10 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import using (_ = ImRaii.Disabled(data.Locked)) { - if (ImGui.SliderFloat("##value", ref value, -1000f, 1000f, "%.2f")) + if (ImGui.SliderFloat("##value", ref value, -100f, 200f, "%.2f")) data.ValueSetter(new CustomizeParameterValue(value / 100f)); + ImGuiUtil.HoverTooltip("You can control-click this to enter arbitrary values by hand instead of dragging."); } - DrawRevert(data); DrawApplyAndLabel(data); From 78ec0f950f123b74fe7991760b7c989a7fc8cdc7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jan 2024 22:07:33 +0100 Subject: [PATCH 168/786] Fix gamedata. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 119ffaa..08cd678 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 119ffaa240f0446bdd05c5254c4e0dd8539a2d7d +Subproject commit 08cd678b4fe8d2fd34a326fe9d0904508f07b598 From d2fd8f839bd3087593a970c31e9a4f48cafd6a0a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jan 2024 22:11:46 +0100 Subject: [PATCH 169/786] Fix some stuff. --- Glamourer/Gui/GlamourerChangelog.cs | 11 +++++++++++ Glamourer/Unlocks/FavoriteManager.cs | 10 +++++----- Penumbra.GameData | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 505279e..ab51af6 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -26,6 +26,7 @@ public class GlamourerChangelog Add1_0_6_0(Changelog); Add1_0_7_0(Changelog); Add1_1_0_0(Changelog); + Add1_1_0_2(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -46,6 +47,16 @@ public class GlamourerChangelog } } + private static void Add1_1_0_2(Changelog log) + => log.NextVersion("Version 1.1.0.2") + .RegisterEntry("Added design colors in the preview of combos (in the quick bar and the automation panel).") + .RegisterHighlight("Improved Palette+ import options: Instead of entering a name, you can now select from available palettes.") + .RegisterHighlight("In the settings tab, there is also a button to import ALL palettes from Palette+ as separate designs.", 1) + .RegisterEntry("Fixed an issue with the favourites file not loading.") + .RegisterEntry("Fixed the name of the advanced parameters in the application panel.") + .RegisterEntry("Fixed design clones not respecting advanced parameter application rules."); + + private static void Add1_1_0_0(Changelog log) => log.NextVersion("Version 1.1.0.0") .RegisterHighlight("Added a new tab to browse, apply or copy (human) NPC appearances.") diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index 6d0a3ab..33242c9 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -56,15 +56,15 @@ public class FavoriteManager : ISavable else { var load = JsonConvert.DeserializeObject(text); - switch (load.Version) + switch (load?.Version ?? 0) { case 1: - _favorites.UnionWith(load.FavoriteItems.Select(i => (ItemId)i)); - _favoriteColors.UnionWith(load.FavoriteColors.Select(i => (StainId)i)); - _favoriteHairStyles.UnionWith(load.FavoriteHairStyles.Select(t => new FavoriteHairStyle(t))); + _favorites.UnionWith(load!.FavoriteItems.Select(i => (ItemId)i)); + _favoriteColors.UnionWith(load!.FavoriteColors.Select(i => (StainId)i)); + _favoriteHairStyles.UnionWith(load!.FavoriteHairStyles.Select(t => new FavoriteHairStyle(t))); break; - default: throw new Exception($"Unknown Version {load.Version}"); + default: throw new Exception($"Unknown Version {load?.Version ?? 0}"); } } } diff --git a/Penumbra.GameData b/Penumbra.GameData index 08cd678..d732a0f 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 08cd678b4fe8d2fd34a326fe9d0904508f07b598 +Subproject commit d732a0fccbed724878dd361ed60635695f10aebd From 68655cf3098a4b0796ce46af150b75652f6dbad4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jan 2024 22:15:00 +0100 Subject: [PATCH 170/786] 1.1.0.2 --- Glamourer/Gui/GlamourerChangelog.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index ab51af6..6166f1b 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -52,6 +52,8 @@ public class GlamourerChangelog .RegisterEntry("Added design colors in the preview of combos (in the quick bar and the automation panel).") .RegisterHighlight("Improved Palette+ import options: Instead of entering a name, you can now select from available palettes.") .RegisterHighlight("In the settings tab, there is also a button to import ALL palettes from Palette+ as separate designs.", 1) + .RegisterEntry("Added a tooltip that you can enter numeric values to drag sliders by control-clicking for the muscle slider, also used slightly more useful caps.") + .RegisterEntry("Fixed issues with monk weapons, again.") .RegisterEntry("Fixed an issue with the favourites file not loading.") .RegisterEntry("Fixed the name of the advanced parameters in the application panel.") .RegisterEntry("Fixed design clones not respecting advanced parameter application rules."); From 0430d994f7ea94b4bc5de02d0ff28fcda72fdf6e Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 16 Jan 2024 21:16:45 +0000 Subject: [PATCH 171/786] [CI] Updating repo.json for 1.1.0.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index a31e74d..ff1e97b 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.1.0.1", - "TestingAssemblyVersion": "1.1.0.1", + "AssemblyVersion": "1.1.0.2", + "TestingAssemblyVersion": "1.1.0.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 57f4ccaaa5a33cca913ec7708cfdca724f0bf503 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 00:27:27 +0100 Subject: [PATCH 172/786] Update restricted items. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index d732a0f..d9e465a 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d732a0fccbed724878dd361ed60635695f10aebd +Subproject commit d9e465a31993f0782c3f428743f5a851729eed90 From 547c40fe52cf3281ce6c3c6be18453574bcd06d8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 00:38:39 +0100 Subject: [PATCH 173/786] Fix advanced parameters not applying after race change. --- Glamourer/Events/StateChanged.cs | 2 ++ Glamourer/State/StateListener.cs | 9 +++++++++ Glamourer/State/StateManager.cs | 6 +++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 58dd49c..8555838 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -58,6 +58,8 @@ public sealed class StateChanged() Manual, Fixed, Ipc, + // Only used for CustomizeParameters. + Outstanding, } public enum Priority diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 7b40d69..54b6ed0 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -752,6 +752,15 @@ public class StateListener : IDisposable case StateChanged.Source.Ipc: state.BaseData.Parameters.Set(flag, newValue); model.ApplySingleParameterData(flag, state.ModelData.Parameters); + break; + case StateChanged.Source.Outstanding: + state.BaseData.Parameters.Set(flag, newValue); + if (_config.UseAdvancedParameters) + { + model.ApplySingleParameterData(flag, state.ModelData.Parameters); + state[flag] = StateChanged.Source.Manual; + } + break; } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 0eace8b..a8fc7b9 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -403,8 +403,12 @@ public class StateManager( foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest)) _editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key); + var paramSource = source is StateChanged.Source.Manual && redraw + ? StateChanged.Source.Outstanding + : source; + foreach (var flag in CustomizeParameterExtensions.AllFlags.Where(design.DoApplyParameter)) - _editor.ChangeParameter(state, flag, design.DesignData.Parameters[flag], source, out _, key); + _editor.ChangeParameter(state, flag, design.DesignData.Parameters[flag], paramSource, out _, key); } var actors = ApplyAll(state, redraw, false); From 78a77eb6c4f2a9fae91ced229a5511a89b30f510 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 00:51:39 +0100 Subject: [PATCH 174/786] Fix fistweapon hack. --- Glamourer/State/StateListener.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 54b6ed0..6b33d71 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -371,7 +371,7 @@ public class StateListener : IDisposable return false; var offhand = actor.GetOffhand(); - return offhand.Variant == 0 && offhand.Weapon.Id != 0 && armor.Set.Id == offhand.Weapon.Id; + return offhand.Variant == 0 && offhand.Weapon.Id != 0 && armor.Set.Id == offhand.Skeleton.Id; } } From 5a30107d788d5a7732af70fe29c865a15039fbb0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 01:35:27 +0100 Subject: [PATCH 175/786] Add check for Palette+. --- Glamourer/Gui/Tabs/SettingsTab.cs | 5 +- .../Interop/PalettePlus/PaletteImport.cs | 10 ---- .../Interop/PalettePlus/PalettePlusChecker.cs | 49 +++++++++++++++++++ 3 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 Glamourer/Interop/PalettePlus/PalettePlusChecker.cs diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index 13e6412..75ee642 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -28,7 +28,8 @@ public class SettingsTab( FunModule funModule, IKeyState keys, DesignColorUi designColorUi, - PaletteImport paletteImport) + PaletteImport paletteImport, + PalettePlusChecker paletteChecker) : ITab { private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); @@ -84,7 +85,7 @@ public class SettingsTab( config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); Checkbox("Enable Advanced Customization Options", "Enable the display and editing of advanced customization options like arbitrary colors.", - config.UseAdvancedParameters, v => config.UseAdvancedParameters = v); + config.UseAdvancedParameters, paletteChecker.SetAdvancedParameters); PaletteImportButton(); ImGui.NewLine(); } diff --git a/Glamourer/Interop/PalettePlus/PaletteImport.cs b/Glamourer/Interop/PalettePlus/PaletteImport.cs index 5253c05..474527b 100644 --- a/Glamourer/Interop/PalettePlus/PaletteImport.cs +++ b/Glamourer/Interop/PalettePlus/PaletteImport.cs @@ -164,14 +164,4 @@ public class PaletteImport(DalamudPluginInterface pluginInterface, DesignManager Glamourer.Log.Error($"Could not read Palette+ configuration:\n{ex}"); } } - - public bool TryRead(string name, ref CustomizeParameterData data) - { - if (!Data.TryGetValue(name, out var t)) - return false; - - foreach (var flag in t.Item2.Iterate()) - data[flag] = t.Item1[flag]; - return true; - } } diff --git a/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs b/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs new file mode 100644 index 0000000..6a23e90 --- /dev/null +++ b/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs @@ -0,0 +1,49 @@ +using Dalamud.Interface.Internal.Notifications; +using Dalamud.Plugin; +using OtterGui.Classes; +using OtterGui.Services; + +namespace Glamourer.Interop.PalettePlus; + +public sealed class PalettePlusChecker : IRequiredService, IDisposable +{ + private readonly Timer _paletteTimer; + private readonly Configuration _config; + private readonly DalamudPluginInterface _pluginInterface; + + public PalettePlusChecker(Configuration config, DalamudPluginInterface pluginInterface) + { + _config = config; + _pluginInterface = pluginInterface; + _paletteTimer = new Timer(_ => PalettePlusCheck(), null, TimeSpan.FromSeconds(30), Timeout.InfiniteTimeSpan); + } + + public void Dispose() + => _paletteTimer.Dispose(); + + public void SetAdvancedParameters(bool value) + { + _config.UseAdvancedParameters = value; + PalettePlusCheck(); + } + + private void PalettePlusCheck() + { + if (!_config.UseAdvancedParameters) + return; + + try + { + var subscriber = _pluginInterface.GetIpcSubscriber("PalettePlus.ApiVersion"); + subscriber.InvokeFunc(); + Glamourer.Messager.AddMessage(new Notification( + "You currently have Palette+ installed. This conflicts with Glamourers advanced options and will cause invalid state.\n\n" + + "Please uninstall Palette+ and restart your game. Palette+ is deprecated and no longer supported by Mare Synchronos.", + NotificationType.Warning, 10000)); + } + catch + { + // ignored + } + } +} From 0cb2933a0ee79e6768f1e11e4231ffa3abcc2b1d Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 17 Jan 2024 01:12:36 +0000 Subject: [PATCH 176/786] [CI] Updating repo.json for 1.1.0.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index ff1e97b..02c11b4 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.1.0.2", - "TestingAssemblyVersion": "1.1.0.2", + "AssemblyVersion": "1.1.0.3", + "TestingAssemblyVersion": "1.1.0.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From ee0bdace3003d98f5961a810759f073fdb02d12c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 11:58:52 +0100 Subject: [PATCH 177/786] Fix application rule labels. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 22 ++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index f609bce..447fc5d 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -175,8 +175,9 @@ public class DesignPanel( private void DrawCustomizeApplication() { - var set = _selector.Selected!.CustomizeSet; - var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; + using var id = ImRaii.PushId("Customizations"); + var set = _selector.Selected!.CustomizeSet; + var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) @@ -207,8 +208,9 @@ public class DesignPanel( private void DrawCrestApplication() { - var flags = (uint)_selector.Selected!.ApplyCrest; - var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); + using var id = ImRaii.PushId("Crests"); + var flags = (uint)_selector.Selected!.ApplyCrest; + var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); foreach (var flag in CrestExtensions.AllRelevantSet) { var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(flag); @@ -238,9 +240,9 @@ public class DesignPanel( { void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable slots) { - var flags = (uint)(allFlags & _selector.Selected!.ApplyEquip); - - var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); + var flags = (uint)(allFlags & _selector.Selected!.ApplyEquip); + using var id = ImRaii.PushId(label); + var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); if (stain) foreach (var slot in slots) { @@ -283,6 +285,7 @@ public class DesignPanel( private void DrawMetaApplication() { + using var id = ImRaii.PushId("Meta"); const uint all = 0x0Fu; var flags = (_selector.Selected!.DoApplyHatVisible() ? 0x01u : 0x00) | (_selector.Selected!.DoApplyVisorToggle() ? 0x02u : 0x00) @@ -308,8 +311,9 @@ public class DesignPanel( private void DrawParameterApplication() { - var flags = (uint)_selector.Selected!.ApplyParameters; - var bigChange = ImGui.CheckboxFlags("Apply All Customize Parameters", ref flags, (uint)CustomizeParameterExtensions.All); + using var id = ImRaii.PushId("Parameter"); + var flags = (uint)_selector.Selected!.ApplyParameters; + var bigChange = ImGui.CheckboxFlags("Apply All Customize Parameters", ref flags, (uint)CustomizeParameterExtensions.All); foreach (var flag in CustomizeParameterExtensions.AllFlags) { var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : _selector.Selected!.DoApplyParameter(flag); From 51a9de3108253d8a07bc17809f6397413beb24b8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 12:15:34 +0100 Subject: [PATCH 178/786] Update BNPCs. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index d9e465a..afc0345 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d9e465a31993f0782c3f428743f5a851729eed90 +Subproject commit afc0345819ca64ec08432e0011b65ff9880c2967 From 1ab9d5a2a7b3a89ca5d6a9fd0bdea27e9fde7fe0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 13:01:58 +0100 Subject: [PATCH 179/786] Fix bodytype not transmitting through Mare? --- Glamourer/Designs/DesignBase.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 023f984..2363060 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -634,13 +634,16 @@ public class DesignBase PrintWarning(customizations.ValidateClan(clan, race, out race, out clan)); var gender = (Gender)((json[CustomizeIndex.Gender.ToString()]?["Value"]?.ToObject() ?? 0) + 1); PrintWarning(customizations.ValidateGender(race, gender, out gender)); - design._designData.Customize.Race = race; - design._designData.Customize.Clan = clan; - design._designData.Customize.Gender = gender; - design.CustomizeSet = design.SetCustomizationSet(customizations); - design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject() ?? false); - design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject() ?? false); - design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject() ?? false); + var bodyType = (CustomizeValue)(json[CustomizeIndex.BodyType.ToString()]?["Value"]?.ToObject() ?? 1); + design._designData.Customize.Race = race; + design._designData.Customize.Clan = clan; + design._designData.Customize.Gender = gender; + design._designData.Customize.BodyType = bodyType; + design.CustomizeSet = design.SetCustomizationSet(customizations); + design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject() ?? false); + design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject() ?? false); + design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject() ?? false); + design.SetApplyCustomize(CustomizeIndex.BodyType, bodyType != 0); var set = design.CustomizeSet; foreach (var idx in CustomizationExtensions.AllBasic) From 3b5f89e6a1b6ead9f6089abdfe9ffe5cfe6db821 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 13:31:22 +0100 Subject: [PATCH 180/786] Fix palettes not resetting on Reapply Automation with Use Game State as Base. --- Glamourer/Automation/AutoDesignApplier.cs | 2 +- Glamourer/State/StateManager.cs | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index aa808d1..52df3e9 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -274,7 +274,7 @@ public class AutoDesignApplier : IDisposable CustomizeParameterFlag totalParameterFlags = 0; byte totalMetaFlags = 0; if (set.BaseState == AutoDesignSet.Base.Game) - _state.ResetStateFixed(state); + _state.ResetStateFixed(state, respectManual); else if (!respectManual) state.RemoveFixedDesignSources(); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index a8fc7b9..807f5c8 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -504,7 +504,7 @@ public class StateManager( _event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors, null); } - public void ResetStateFixed(ActorState state, uint key = 0) + public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) { if (!state.Unlock(key)) return; @@ -541,10 +541,13 @@ public class StateManager( foreach (var flag in CustomizeParameterExtensions.AllFlags) { - if (state[flag] is StateChanged.Source.Fixed) + switch (state[flag]) { - state[flag] = StateChanged.Source.Game; - state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag]; + case StateChanged.Source.Fixed: + case StateChanged.Source.Manual when !respectManualPalettes: + state[flag] = StateChanged.Source.Game; + state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag]; + break; } } From 8b8f85dd85b0c2b98ddf079afd290a7ab35164ff Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 14:29:30 +0100 Subject: [PATCH 181/786] Fixed handling of Pending in automation. --- Glamourer/Automation/AutoDesignApplier.cs | 2 +- Glamourer/Events/StateChanged.cs | 2 +- Glamourer/State/StateListener.cs | 2 +- Glamourer/State/StateManager.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 52df3e9..07bf41f 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -360,7 +360,7 @@ public class AutoDesignApplier : IDisposable if (!parameterFlags.HasFlag(flag)) continue; - if (!respectManual || state[flag] is not StateChanged.Source.Manual) + if (!respectManual || state[flag] is not StateChanged.Source.Manual and not StateChanged.Source.Pending) _state.ChangeCustomizeParameter(state, flag, design.Parameters[flag], source); totalParameterFlags |= flag; } diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 8555838..01e2758 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -59,7 +59,7 @@ public sealed class StateChanged() Fixed, Ipc, // Only used for CustomizeParameters. - Outstanding, + Pending, } public enum Priority diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 6b33d71..dda3cd1 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -753,7 +753,7 @@ public class StateListener : IDisposable state.BaseData.Parameters.Set(flag, newValue); model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; - case StateChanged.Source.Outstanding: + case StateChanged.Source.Pending: state.BaseData.Parameters.Set(flag, newValue); if (_config.UseAdvancedParameters) { diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 807f5c8..bb99396 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -404,7 +404,7 @@ public class StateManager( _editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key); var paramSource = source is StateChanged.Source.Manual && redraw - ? StateChanged.Source.Outstanding + ? StateChanged.Source.Pending : source; foreach (var flag in CustomizeParameterExtensions.AllFlags.Where(design.DoApplyParameter)) From 42aa4fb4a267277d73ce02847b22789bef819eaf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 15:21:29 +0100 Subject: [PATCH 182/786] Add a button to revert advanced customizations only to game state. --- Glamourer/Configuration.cs | 43 +++++----- Glamourer/Gui/DesignQuickBar.cs | 130 ++++++++++++++++++++---------- Glamourer/Gui/MainWindow.cs | 5 +- Glamourer/Gui/Tabs/SettingsTab.cs | 3 + Glamourer/State/StateManager.cs | 23 +++++- 5 files changed, 134 insertions(+), 70 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 2f925a3..0f8d75a 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -18,27 +18,28 @@ 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 UseAdvancedParameters { get; set; } = true; - public bool ShowPalettePlusImport { get; set; } = true; - 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 UseAdvancedParameters { get; set; } = true; + public bool ShowRevertAdvancedParametersButton { get; set; } = true; + public bool ShowPalettePlusImport { get; set; } = true; + 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; [JsonConverter(typeof(SortModeConverter))] [JsonProperty(Order = int.MaxValue)] diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 0e361a1..f6e73f9 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -16,7 +16,7 @@ using Penumbra.GameData.Actors; namespace Glamourer.Gui; -public class DesignQuickBar : Window, IDisposable +public sealed class DesignQuickBar : Window, IDisposable { private ImGuiWindowFlags GetFlags => _config.Ephemeral.LockDesignQuickBar @@ -32,6 +32,7 @@ public class DesignQuickBar : Window, IDisposable private readonly ImRaii.Style _windowPadding = new(); private readonly ImRaii.Color _windowColor = new(); private DateTime _keyboardToggle = DateTime.UnixEpoch; + private int _numButtons = 0; public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState, ObjectManager objects, AutoDesignApplier autoDesignApplier) @@ -60,7 +61,7 @@ public class DesignQuickBar : Window, IDisposable public override void PreDraw() { Flags = GetFlags; - Size = new Vector2(12 * ImGui.GetFrameHeight(), ImGui.GetFrameHeight()); + UpdateWidth(); _windowPadding.Push(ImGuiStyleVar.WindowPadding, new Vector2(ImGuiHelpers.GlobalScale * 4)) .Push(ImGuiStyleVar.WindowBorderSize, 0); @@ -75,6 +76,12 @@ public class DesignQuickBar : Window, IDisposable _windowColor.Dispose(); } + public void DrawAtEnd(float yPos) + { + var width = UpdateWidth(); + ImGui.SetCursorPos(new Vector2(ImGui.GetWindowContentRegionMax().X - width, yPos - ImGuiHelpers.GlobalScale)); + Draw(); + } public override void Draw() => Draw(ImGui.GetContentRegionAvail().X); @@ -82,20 +89,19 @@ public 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); - var contentRegion = width; - var buttonSize = new Vector2(ImGui.GetFrameHeight()); - var comboSize = contentRegion - 3 * buttonSize.X - 3 * spacing.X; + using var group = ImRaii.Group(); + var spacing = ImGui.GetStyle().ItemInnerSpacing; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + var comboSize = width - _numButtons * (buttonSize.X + spacing.X); _designCombo.Draw(comboSize); PrepareButtons(); ImGui.SameLine(); DrawApplyButton(buttonSize); ImGui.SameLine(); DrawRevertButton(buttonSize); - ImGui.SameLine(); DrawRevertAutomationButton(buttonSize); + DrawRevertAdvancedCustomization(buttonSize); } private ActorIdentifier _playerIdentifier; @@ -111,10 +117,8 @@ public class DesignQuickBar : Window, IDisposable _objects.Update(); (_playerIdentifier, _playerData) = _objects.PlayerData; (_targetIdentifier, _targetData) = _objects.TargetData; - if (!_stateManager.TryGetValue(_playerIdentifier, out _playerState)) - _playerState = null; - if (!_stateManager.TryGetValue(_targetIdentifier, out _targetState)) - _targetState = null; + _playerState = _stateManager.GetValueOrDefault(_playerIdentifier); + _targetState = _stateManager.GetValueOrDefault(_targetIdentifier); } private void DrawApplyButton(Vector2 size) @@ -183,52 +187,79 @@ public class DesignQuickBar : Window, IDisposable if (available == 0) tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; - var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); if (clicked) _stateManager.ResetState(state!, StateChanged.Source.Manual); } public void DrawRevertAutomationButton(Vector2 buttonSize) { + if (!_config.EnableAutoDesigns) + return; + var available = 0; var tooltip = string.Empty; - if (!_config.EnableAutoDesigns) - { - tooltip = "Automation is not enabled, you can not reset to automation state."; - } - else - { - if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) - { - available |= 1; - tooltip = "Left-Click: Revert the player character to their automation state."; - } - if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) - { - if (available != 0) - tooltip += '\n'; - available |= 2; - tooltip += $"Right-Click: Revert {_targetIdentifier} to their automation state."; - } - - if (available == 0) - tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) + { + available |= 1; + tooltip = "Left-Click: Revert the player character to their automation state."; } + if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Revert {_targetIdentifier} to their automation state."; + } + + if (available == 0) + tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + + ImGui.SameLine(); var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, tooltip, available); if (!clicked) - { } - else + return; + + foreach (var actor in data.Objects) { - foreach (var actor in data.Objects) - { - _autoDesignApplier.ReapplyAutomation(actor, id, state!); - _stateManager.ReapplyState(actor); - } + _autoDesignApplier.ReapplyAutomation(actor, id, state!); + _stateManager.ReapplyState(actor); } } + public void DrawRevertAdvancedCustomization(Vector2 buttonSize) + { + if (!_config.ShowRevertAdvancedParametersButton || !_config.UseAdvancedParameters) + return; + + var available = 0; + var tooltip = string.Empty; + + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) + { + available |= 1; + tooltip = "Left-Click: Revert the advanced customizations of the player character to their game state."; + } + + if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Revert the advanced customizations of {_targetIdentifier} to their game state."; + } + + if (available == 0) + tooltip = "Neither player character nor target are available or their state is locked."; + + ImGui.SameLine(); + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, tooltip, available); + if (clicked) + _stateManager.ResetAdvancedState(state!, StateChanged.Source.Manual); + } + private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip, int available) { @@ -251,11 +282,24 @@ public class DesignQuickBar : Window, IDisposable _config.Ephemeral.Save(); } - public bool CheckKeyState(ModifiableHotkey key, bool noKey) + private bool CheckKeyState(ModifiableHotkey key, bool noKey) { if (key.Hotkey == VirtualKey.NO_KEY) return noKey; return _keyState[key.Hotkey] && key.Modifier1.IsActive() && key.Modifier2.IsActive(); } + + private float UpdateWidth() + { + _numButtons = (_config.EnableAutoDesigns, _config is { ShowRevertAdvancedParametersButton: true, UseAdvancedParameters: true }) switch + { + (true, true) => 4, + (false, true) => 3, + (true, false) => 3, + (false, false) => 2, + }; + Size = new Vector2((7 + _numButtons) * ImGui.GetFrameHeight() + _numButtons * ImGui.GetStyle().ItemInnerSpacing.X, ImGui.GetFrameHeight()); + return Size.Value.X; + } } diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 0ce4147..6f09d04 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -105,10 +105,7 @@ public class MainWindow : Window, IDisposable } if (_config.ShowQuickBarInTabs) - { - ImGui.SetCursorPos(new Vector2(ImGui.GetWindowContentRegionMax().X - 10 * ImGui.GetFrameHeight(), yPos - ImGuiHelpers.GlobalScale)); - _quickBar.Draw(); - } + _quickBar.DrawAtEnd(yPos); } private ReadOnlySpan ToLabel(TabType type) diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index 75ee642..edca6b9 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -164,6 +164,9 @@ public class SettingsTab( config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v); if (config.UseAdvancedParameters) { + Checkbox("Show Revert Advanced Customizations Button in Quick Design Bar", + "Show a button to revert only advanced customizations on your character or a target in the quick design bar.", + config.ShowRevertAdvancedParametersButton, v => config.ShowRevertAdvancedParametersButton = v); Checkbox("Show Palette+ Import Button", "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs.", config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index bb99396..c506389 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -309,7 +309,8 @@ public class StateManager( } /// Change the crest of an equipment piece. - public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateChanged.Source source, uint key = 0) + public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, + StateChanged.Source source, uint key = 0) { if (!_editor.ChangeParameter(state, flag, value, source, out var old, key)) return; @@ -501,7 +502,25 @@ public class StateManager( Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors, null); + _event.Invoke(StateChanged.Type.Reset, source, state, actors, null); + } + + public void ResetAdvancedState(ActorState state, StateChanged.Source source, uint key = 0) + { + if (!state.Unlock(key) || !state.ModelData.IsHuman) + return; + + state.ModelData.Parameters = state.BaseData.Parameters; + + foreach (var flag in CustomizeParameterExtensions.AllFlags) + state[flag] = StateChanged.Source.Game; + + var actors = ActorData.Invalid; + if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) + actors = _applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); + Glamourer.Log.Verbose( + $"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + _event.Invoke(StateChanged.Type.Reset, source, state, actors, null); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) From 4abae59974cf80d9727c9f0a0e20ca84974767d2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 16:27:51 +0100 Subject: [PATCH 183/786] Fix another color thing. --- Glamourer/State/StateListener.cs | 5 +---- Glamourer/State/StateManager.cs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index dda3cd1..b90d835 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -755,12 +755,9 @@ public class StateListener : IDisposable break; case StateChanged.Source.Pending: state.BaseData.Parameters.Set(flag, newValue); + state[flag] = StateChanged.Source.Manual; if (_config.UseAdvancedParameters) - { model.ApplySingleParameterData(flag, state.ModelData.Parameters); - state[flag] = StateChanged.Source.Manual; - } - break; } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index c506389..4f683b8 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -404,7 +404,7 @@ public class StateManager( foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest)) _editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key); - var paramSource = source is StateChanged.Source.Manual && redraw + var paramSource = source is StateChanged.Source.Manual ? StateChanged.Source.Pending : source; From 53c4dfeee5f313123a41fd1436142d2abf643823 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 17:22:32 +0100 Subject: [PATCH 184/786] Add color display options. --- Glamourer/Configuration.cs | 6 +- .../Customization/CustomizeParameterDrawer.cs | 76 ++++++++++++++++++- Glamourer/Gui/Tabs/SettingsTab.cs | 2 + 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 0f8d75a..9e01d18 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.Customization; using Glamourer.Services; using Newtonsoft.Json; using OtterGui; @@ -37,6 +38,9 @@ public class Configuration : IPluginConfiguration, ISavable public bool UseAdvancedParameters { get; set; } = true; public bool ShowRevertAdvancedParametersButton { 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 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; @@ -45,7 +49,7 @@ public class Configuration : IPluginConfiguration, ISavable [JsonProperty(Order = int.MaxValue)] public ISortMode SortMode { get; set; } = ISortMode.FoldersFirst; - public List<(string Code, bool Enabled)> Codes { get; set; } = []; + public List<(string Code, bool Enabled)> Codes { get; set; } = []; #if DEBUG public bool DebugMode { get; set; } = true; diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index cef22f6..72763c8 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -22,6 +22,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import { using var _ = EnsureSize(); DrawPaletteImport(designManager, design); + DrawConfig(true); foreach (var flag in CustomizeParameterExtensions.RgbFlags) DrawColorInput3(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); @@ -99,6 +100,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import public void Draw(StateManager stateManager, ActorState state) { using var _ = EnsureSize(); + DrawConfig(false); foreach (var flag in CustomizeParameterExtensions.RgbFlags) DrawColorInput3(CustomizeParameterDrawData.FromState(stateManager, state, flag)); @@ -112,6 +114,68 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import DrawValueInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); } + public void DrawConfig(bool withApply) + { + if (!config.ShowColorConfig) + return; + + DrawColorDisplayOptions(); + DrawColorFormatOptions(withApply); + var value = config.ShowColorConfig; + ImGui.SameLine(); + if (ImGui.Checkbox("Show Config", ref value)) + { + config.ShowColorConfig = value; + config.Save(); + } + + ImGuiUtil.HoverTooltip( + "Hide the color configuration options from the Advanced Customization panel. You can re-enable it in Glamourers interface settings."); + } + + public void DrawColorDisplayOptions() + { + using var group = ImRaii.Group(); + if (ImGui.RadioButton("RGB", config.UseRgbForColors) && !config.UseRgbForColors) + { + config.UseRgbForColors = true; + config.Save(); + } + + ImGui.SameLine(); + if (ImGui.RadioButton("HSV", !config.UseRgbForColors) && config.UseRgbForColors) + { + config.UseRgbForColors = false; + config.Save(); + } + } + + public void DrawColorFormatOptions(bool withApply) + { + var width = _width + - (ImGui.CalcTextSize("Float").X + + ImGui.CalcTextSize("Integer").X + + 2 * (ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.X) + + ImGui.GetStyle().ItemInnerSpacing.X + + ImGui.GetItemRectSize().X); + if (!withApply) + width -= ImGui.GetFrameHeight() + ImGui.GetStyle().ItemInnerSpacing.X; + + ImGui.SameLine(0, width); + if (ImGui.RadioButton("Float", config.UseFloatForColors) && !config.UseFloatForColors) + { + config.UseFloatForColors = true; + config.Save(); + } + + ImGui.SameLine(); + if (ImGui.RadioButton("Integer", !config.UseFloatForColors) && config.UseFloatForColors) + { + config.UseFloatForColors = false; + config.Save(); + } + } + private void DrawColorInput3(in CustomizeParameterDrawData data) { using var id = ImRaii.PushId((int)data.Flag); @@ -169,6 +233,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import data.ValueSetter(new CustomizeParameterValue(value / 100f)); ImGuiUtil.HoverTooltip("You can control-click this to enter arbitrary values by hand instead of dragging."); } + DrawRevert(data); DrawApplyAndLabel(data); @@ -204,11 +269,14 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import ImGui.TextUnformatted(data.Flag.ToName()); } - private static ImGuiColorEditFlags GetFlags() - => ImGui.GetIO().KeyCtrl - ? ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions - : ImGuiColorEditFlags.Float | ImGuiColorEditFlags.HDR; + private ImGuiColorEditFlags GetFlags() + => Format | Display | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions; + private ImGuiColorEditFlags Format + => config.UseFloatForColors ? ImGuiColorEditFlags.Float : ImGuiColorEditFlags.Uint8; + + private ImGuiColorEditFlags Display + => config.UseRgbForColors ? ImGuiColorEditFlags.DisplayRGB : ImGuiColorEditFlags.DisplayHSV; private ImRaii.IEndObject EnsureSize() { diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index edca6b9..b883dd9 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -167,6 +167,8 @@ public class SettingsTab( Checkbox("Show Revert Advanced Customizations Button in Quick Design Bar", "Show a button to revert only advanced customizations on your character or a target in the quick design bar.", config.ShowRevertAdvancedParametersButton, v => config.ShowRevertAdvancedParametersButton = v); + Checkbox("Show Color Display Config", "Show the Color Display configuration options in the Advanced Customization panels.", + config.ShowColorConfig, v => config.ShowColorConfig = v); Checkbox("Show Palette+ Import Button", "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs.", config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v); From cd50950dae30f57e4c92cb53391925591870c37c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 17:27:48 +0100 Subject: [PATCH 185/786] 1.1.0.4 --- Glamourer/Gui/GlamourerChangelog.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 6166f1b..fcbebd6 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -27,6 +27,7 @@ public class GlamourerChangelog Add1_0_7_0(Changelog); Add1_1_0_0(Changelog); Add1_1_0_2(Changelog); + Add1_1_0_4(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -47,6 +48,19 @@ public class GlamourerChangelog } } + private static void Add1_1_0_4(Changelog log) + => log.NextVersion("Version 1.1.0.4") + .RegisterEntry("Added a check and warning for a lingering Palette+ installation.") + .RegisterHighlight("Added a button to only revert advanced customizations to game state to the quick design bar. This can be toggled off in the interface settings.") + .RegisterEntry("Added visible configuration options for color display for the advanced customizations.") + .RegisterEntry("Updated Battle NPC data from Gubal for 6.55.") + .RegisterEntry("Fixed issues with advanced customizations not resetting correctly with Use Game State as Base.") + .RegisterEntry("Fixed an issues with non-standard body type customizations not transmitting through Mare.") + .RegisterEntry("Fixed issues with application rule checkboxes not working for advanced parameters.") + .RegisterEntry("Fixed an issue with fist weapons, again again.") + .RegisterEntry("Fixed multiple issues with advanced parameters not applying after certain other changes.") + .RegisterEntry("Fixed another wrong restricted item."); + private static void Add1_1_0_2(Changelog log) => log.NextVersion("Version 1.1.0.2") .RegisterEntry("Added design colors in the preview of combos (in the quick bar and the automation panel).") From 4c9f362f215782b715cf60986d46d8be010f11c5 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 17 Jan 2024 16:29:43 +0000 Subject: [PATCH 186/786] [CI] Updating repo.json for 1.1.0.4 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 02c11b4..35510d7 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.1.0.3", - "TestingAssemblyVersion": "1.1.0.3", + "AssemblyVersion": "1.1.0.4", + "TestingAssemblyVersion": "1.1.0.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 805e192d63c74fc53cdfdef91188d82e6b37289b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jan 2024 17:47:23 +0100 Subject: [PATCH 187/786] Add alpha preview. --- Glamourer/Gui/Customization/CustomizeParameterDrawer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 72763c8..28f3d3c 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -197,7 +197,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import var value = data.CurrentValue.InternalQuadruple; using (_ = ImRaii.Disabled(data.Locked)) { - if (ImGui.ColorEdit4("##value", ref value, GetFlags())) + if (ImGui.ColorEdit4("##value", ref value, GetFlags() | ImGuiColorEditFlags.AlphaPreviewHalf)) data.ValueSetter(new CustomizeParameterValue(value)); } From d13e3ccbd7f503d5c6547dd265b46ac3984c27da Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 18 Jan 2024 00:50:06 +0100 Subject: [PATCH 188/786] Add some better handling for Highlights, add copy/paste buttons for colors. --- Glamourer/GameData/CustomizeParameterValue.cs | 30 +++++- .../Customization/CustomizeParameterDrawer.cs | 102 ++++++++++++------ Glamourer/State/StateEditor.cs | 5 +- Glamourer/State/StateManager.cs | 10 ++ Penumbra.GameData | 2 +- 5 files changed, 113 insertions(+), 36 deletions(-) diff --git a/Glamourer/GameData/CustomizeParameterValue.cs b/Glamourer/GameData/CustomizeParameterValue.cs index 0e22d18..0e2df8e 100644 --- a/Glamourer/GameData/CustomizeParameterValue.cs +++ b/Glamourer/GameData/CustomizeParameterValue.cs @@ -1,4 +1,6 @@ -namespace Glamourer.GameData; +using Newtonsoft.Json; + +namespace Glamourer.GameData; public readonly struct CustomizeParameterValue { @@ -47,4 +49,30 @@ public readonly struct CustomizeParameterValue public override string ToString() => _data.ToString(); + + public string ToJson() + { + try + { + return JsonConvert.SerializeObject(_data, Formatting.None); + } + catch + { + return string.Empty; + } + } + + public static bool FromJson(string input, out CustomizeParameterValue value) + { + try + { + value = new CustomizeParameterValue(JsonConvert.DeserializeObject(input)); + return true; + } + catch + { + value = default; + return false; + } + } } diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 28f3d3c..62f46a2 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Dalamud.Interface; +using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop.PalettePlus; using Glamourer.State; @@ -17,17 +18,20 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import private CustomizeParameterFlag _flags; private float _width; - public void Draw(DesignManager designManager, Design design) { - using var _ = EnsureSize(); + using var generalSize = EnsureSize(); DrawPaletteImport(designManager, design); DrawConfig(true); - foreach (var flag in CustomizeParameterExtensions.RgbFlags) - DrawColorInput3(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); - foreach (var flag in CustomizeParameterExtensions.RgbaFlags) - DrawColorInput4(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); + using (_ = ImRaii.ItemWidth(_width - 2 * ImGui.GetFrameHeight() - 2 * ImGui.GetStyle().ItemInnerSpacing.X)) + { + foreach (var flag in CustomizeParameterExtensions.RgbFlags) + DrawColorInput3(CustomizeParameterDrawData.FromDesign(designManager, design, flag), true); + + foreach (var flag in CustomizeParameterExtensions.RgbaFlags) + DrawColorInput4(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); + } foreach (var flag in CustomizeParameterExtensions.PercentageFlags) DrawPercentageInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); @@ -36,6 +40,26 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import DrawValueInput(CustomizeParameterDrawData.FromDesign(designManager, design, flag)); } + public void Draw(StateManager stateManager, ActorState state) + { + using var _ = EnsureSize(); + DrawConfig(false); + using (_ = ImRaii.ItemWidth(_width - 2 * ImGui.GetFrameHeight() - 2 * ImGui.GetStyle().ItemInnerSpacing.X)) + { + foreach (var flag in CustomizeParameterExtensions.RgbFlags) + DrawColorInput3(CustomizeParameterDrawData.FromState(stateManager, state, flag), state.ModelData.Customize.Highlights); + + foreach (var flag in CustomizeParameterExtensions.RgbaFlags) + DrawColorInput4(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + } + + foreach (var flag in CustomizeParameterExtensions.PercentageFlags) + DrawPercentageInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + + foreach (var flag in CustomizeParameterExtensions.ValueFlags) + DrawValueInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); + } + private void DrawPaletteCombo() { using var id = ImRaii.PushId("Palettes"); @@ -97,24 +121,8 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import } } - public void Draw(StateManager stateManager, ActorState state) - { - using var _ = EnsureSize(); - DrawConfig(false); - foreach (var flag in CustomizeParameterExtensions.RgbFlags) - DrawColorInput3(CustomizeParameterDrawData.FromState(stateManager, state, flag)); - foreach (var flag in CustomizeParameterExtensions.RgbaFlags) - DrawColorInput4(CustomizeParameterDrawData.FromState(stateManager, state, flag)); - - foreach (var flag in CustomizeParameterExtensions.PercentageFlags) - DrawPercentageInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); - - foreach (var flag in CustomizeParameterExtensions.ValueFlags) - DrawValueInput(CustomizeParameterDrawData.FromState(stateManager, state, flag)); - } - - public void DrawConfig(bool withApply) + private void DrawConfig(bool withApply) { if (!config.ShowColorConfig) return; @@ -133,7 +141,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import "Hide the color configuration options from the Advanced Customization panel. You can re-enable it in Glamourers interface settings."); } - public void DrawColorDisplayOptions() + private void DrawColorDisplayOptions() { using var group = ImRaii.Group(); if (ImGui.RadioButton("RGB", config.UseRgbForColors) && !config.UseRgbForColors) @@ -150,7 +158,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import } } - public void DrawColorFormatOptions(bool withApply) + private void DrawColorFormatOptions(bool withApply) { var width = _width - (ImGui.CalcTextSize("Float").X @@ -176,16 +184,22 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import } } - private void DrawColorInput3(in CustomizeParameterDrawData data) + private void DrawColorInput3(in CustomizeParameterDrawData data, bool allowHighlights) { - using var id = ImRaii.PushId((int)data.Flag); - var value = data.CurrentValue.InternalTriple; - using (_ = ImRaii.Disabled(data.Locked)) + using var id = ImRaii.PushId((int)data.Flag); + var value = data.CurrentValue.InternalTriple; + var noHighlights = !allowHighlights && data.Flag is CustomizeParameterFlag.HairHighlight; + DrawCopyPasteButtons(data, data.Locked || noHighlights); + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + using (_ = ImRaii.Disabled(data.Locked || noHighlights)) { if (ImGui.ColorEdit3("##value", ref value, GetFlags())) data.ValueSetter(new CustomizeParameterValue(value)); } + if (noHighlights) + ImGuiUtil.HoverTooltip("Highlights are disabled in your regular customizations.", ImGuiHoveredFlags.AllowWhenDisabled); + DrawRevert(data); DrawApplyAndLabel(data); @@ -195,6 +209,8 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import { using var id = ImRaii.PushId((int)data.Flag); var value = data.CurrentValue.InternalQuadruple; + DrawCopyPasteButtons(data, data.Locked); + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); using (_ = ImRaii.Disabled(data.Locked)) { if (ImGui.ColorEdit4("##value", ref value, GetFlags() | ImGuiColorEditFlags.AlphaPreviewHalf)) @@ -229,7 +245,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import using (_ = ImRaii.Disabled(data.Locked)) { - if (ImGui.SliderFloat("##value", ref value, -100f, 200f, "%.2f")) + if (ImGui.SliderFloat("##value", ref value, -100f, 300, "%.2f")) data.ValueSetter(new CustomizeParameterValue(value / 100f)); ImGuiUtil.HoverTooltip("You can control-click this to enter arbitrary values by hand instead of dragging."); } @@ -281,7 +297,29 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import private ImRaii.IEndObject EnsureSize() { var iconSize = ImGui.GetTextLineHeight() * 2 + ImGui.GetStyle().ItemSpacing.Y + 4 * ImGui.GetStyle().FramePadding.Y; - _width = 6 * iconSize + 4 * ImGui.GetStyle().ItemInnerSpacing.X; + _width = 7 * iconSize + 4 * ImGui.GetStyle().ItemInnerSpacing.X; return ImRaii.ItemWidth(_width); } + + private static void DrawCopyPasteButtons(in CustomizeParameterDrawData data, bool locked) + { + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Copy.ToIconString(), new Vector2(ImGui.GetFrameHeight()), + "Copy this color to clipboard.", false, true)) + ImGui.SetClipboardText(data.CurrentValue.ToJson()); + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), new Vector2(ImGui.GetFrameHeight()), + "Try to paste this color from clipboard", locked, true)) + return; + + try + { + var text = ImGui.GetClipboardText(); + if (CustomizeParameterValue.FromJson(text, out var v)) + data.ValueSetter(v); + } + catch + { + // ignored + } + } } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 1d5048e..9dc8418 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -210,8 +210,8 @@ public class StateEditor } /// Change the customize flags of a character. - public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateChanged.Source source, out CustomizeParameterValue oldValue, - uint key = 0) + public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateChanged.Source source, + out CustomizeParameterValue oldValue, uint key = 0) { oldValue = state.ModelData.Parameters[flag]; if (!state.CanUnlock(key)) @@ -219,6 +219,7 @@ public class StateEditor state.ModelData.Parameters.Set(flag, value); state[flag] = source; + return true; } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 4f683b8..0286e73 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -312,6 +312,10 @@ public class StateManager( public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateChanged.Source source, uint key = 0) { + // Also apply main color to highlights when highlights is off. + if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse) + ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, source, key); + if (!_editor.ChangeParameter(state, flag, value, source, out var old, key)) return; @@ -410,6 +414,12 @@ public class StateManager( foreach (var flag in CustomizeParameterExtensions.AllFlags.Where(design.DoApplyParameter)) _editor.ChangeParameter(state, flag, design.DesignData.Parameters[flag], paramSource, out _, key); + + // Do not apply highlights from a design if highlights is unchecked. + if (!state.ModelData.Customize.Highlights) + _editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight, + state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse], + state[CustomizeParameterFlag.HairDiffuse], out _, key); } var actors = ApplyAll(state, redraw, false); diff --git a/Penumbra.GameData b/Penumbra.GameData index afc0345..1ebaf1d 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit afc0345819ca64ec08432e0011b65ff9880c2967 +Subproject commit 1ebaf1d209b7edc783896b3a6af4907c91bb302e From 593bb4724185f4b42785b209c1afa6a4d6e5ddf9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 18 Jan 2024 00:55:47 +0100 Subject: [PATCH 189/786] Reorder names. --- Glamourer/GameData/CustomizeParameterFlag.cs | 4 ++-- Glamourer/Gui/Customization/CustomizeParameterDrawer.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index a0c814a..59a3511 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -57,8 +57,8 @@ public static class CustomizeParameterExtensions CustomizeParameterFlag.LeftEye => "Left Eye Color", CustomizeParameterFlag.RightEye => "Right Eye Color", CustomizeParameterFlag.FeatureColor => "Tattoo Color", - CustomizeParameterFlag.FacePaintUvMultiplier => "Face Paint Orientation", - CustomizeParameterFlag.FacePaintUvOffset => "Face Paint Offset", + CustomizeParameterFlag.FacePaintUvMultiplier => "Multiplier for Face Paint", + CustomizeParameterFlag.FacePaintUvOffset => "Offset of Face Paint", CustomizeParameterFlag.DecalColor => "Face Paint Color", _ => string.Empty, }; diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 62f46a2..387780b 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -42,7 +42,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import public void Draw(StateManager stateManager, ActorState state) { - using var _ = EnsureSize(); + using var generalSize = EnsureSize(); DrawConfig(false); using (_ = ImRaii.ItemWidth(_width - 2 * ImGui.GetFrameHeight() - 2 * ImGui.GetStyle().ItemInnerSpacing.X)) { From 092e0ee30e8d7d2fdca163f74f046fbfbf244c9e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 18 Jan 2024 00:59:53 +0100 Subject: [PATCH 190/786] Make it not use clipboard because duh. --- Glamourer/GameData/CustomizeParameterValue.cs | 26 ------------------- .../Customization/CustomizeParameterDrawer.cs | 24 +++++------------ 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/Glamourer/GameData/CustomizeParameterValue.cs b/Glamourer/GameData/CustomizeParameterValue.cs index 0e2df8e..e1e0943 100644 --- a/Glamourer/GameData/CustomizeParameterValue.cs +++ b/Glamourer/GameData/CustomizeParameterValue.cs @@ -49,30 +49,4 @@ public readonly struct CustomizeParameterValue public override string ToString() => _data.ToString(); - - public string ToJson() - { - try - { - return JsonConvert.SerializeObject(_data, Formatting.None); - } - catch - { - return string.Empty; - } - } - - public static bool FromJson(string input, out CustomizeParameterValue value) - { - try - { - value = new CustomizeParameterValue(JsonConvert.DeserializeObject(input)); - return true; - } - catch - { - value = default; - return false; - } - } } diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 387780b..414398e 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -17,6 +17,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import private CustomizeParameterData _data; private CustomizeParameterFlag _flags; private float _width; + private CustomizeParameterValue? _copy; public void Draw(DesignManager designManager, Design design) { @@ -301,25 +302,14 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import return ImRaii.ItemWidth(_width); } - private static void DrawCopyPasteButtons(in CustomizeParameterDrawData data, bool locked) + private void DrawCopyPasteButtons(in CustomizeParameterDrawData data, bool locked) { if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Copy.ToIconString(), new Vector2(ImGui.GetFrameHeight()), - "Copy this color to clipboard.", false, true)) - ImGui.SetClipboardText(data.CurrentValue.ToJson()); + "Copy this color for later use.", false, true)) + _copy = data.CurrentValue; ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), new Vector2(ImGui.GetFrameHeight()), - "Try to paste this color from clipboard", locked, true)) - return; - - try - { - var text = ImGui.GetClipboardText(); - if (CustomizeParameterValue.FromJson(text, out var v)) - data.ValueSetter(v); - } - catch - { - // ignored - } + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), new Vector2(ImGui.GetFrameHeight()), + _copy.HasValue ? "Paste the currently copied value." : "No value copied yet.", locked || !_copy.HasValue, true)) + data.ValueSetter(_copy!.Value); } } From 59131ec1910b570b6e3a01f1993d5ee08869fb78 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 18 Jan 2024 22:44:55 +0100 Subject: [PATCH 191/786] Fix load order dependency on weapon load. --- Glamourer/Interop/WeaponService.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 8780c2a..da7d9dd 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -42,7 +42,6 @@ public unsafe class WeaponService : IDisposable private readonly Hook _loadWeaponHook; - private void LoadWeaponDetour(DrawDataContainer* drawData, uint slot, ulong weaponValue, byte redrawOnEquality, byte unk2, byte skipGameObject, byte unk4) { @@ -80,7 +79,7 @@ public unsafe class WeaponService : IDisposable } else { - _original(drawData, slot, weaponValue, redrawOnEquality, unk2, skipGameObject, unk4); + _loadWeaponHook.Original(drawData, slot, weaponValue, redrawOnEquality, unk2, skipGameObject, unk4); } } @@ -91,18 +90,18 @@ public unsafe class WeaponService : IDisposable { case EquipSlot.MainHand: _inUpdate.Value = true; - _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); + _original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); _inUpdate.Value = false; return; case EquipSlot.OffHand: _inUpdate.Value = true; - _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0); + _original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0); _inUpdate.Value = false; return; case EquipSlot.BothHand: _inUpdate.Value = true; - _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); - _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0); + _original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); + _original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0); _inUpdate.Value = false; return; } From 1a409d475aa248b74b9b2ef28bce5110d99e10b0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 19 Jan 2024 13:24:24 +0100 Subject: [PATCH 192/786] Increase version. --- Glamourer/Glamourer.csproj | 4 ++-- Glamourer/Glamourer.json | 2 +- OtterGui | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 0d2bb36..2ee5538 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -5,8 +5,8 @@ x64 Glamourer Glamourer - 1.0.0.2 - 1.0.0.2 + 9.0.0.1 + 9.0.0.1 SoftOtter Glamourer Copyright © 2023 diff --git a/Glamourer/Glamourer.json b/Glamourer/Glamourer.json index 148951b..cdf2cba 100644 --- a/Glamourer/Glamourer.json +++ b/Glamourer/Glamourer.json @@ -5,7 +5,7 @@ "Description": "Adds functionality to change and store appearance of players, customization and equip. Requires Penumbra to be installed and activated to work. Can also add preview options to the Changed Items tab for Penumbra.", "Tags": [ "Appearance", "Glamour", "Race", "Outfit", "Armor", "Clothes", "Skins", "Customization", "Design", "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.0.1.0", + "AssemblyVersion": "9.0.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, diff --git a/OtterGui b/OtterGui index 9259090..d734d5d 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 9259090121b26f097948e7bbd83b32708ea0410d +Subproject commit d734d5d2b0686db0f5f4270dc379360d31f72e59 From c7430e59b3e18b7bac87f8a5d75cdbbd3d29ea7f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 20 Jan 2024 15:13:23 +0100 Subject: [PATCH 193/786] Start --- Glamourer/Automation/ApplicationType.cs | 62 ++++ Glamourer/Automation/AutoDesign.cs | 73 +---- Glamourer/Automation/AutoDesignApplier.cs | 46 +-- Glamourer/Automation/AutoDesignManager.cs | 59 ++-- Glamourer/Automation/AutoDesignSet.cs | 15 +- Glamourer/Automation/FixedDesignMigrator.cs | 2 +- Glamourer/Designs/Design.cs | 50 +++- Glamourer/Designs/DesignConverter.cs | 13 +- Glamourer/Designs/DesignManager.cs | 44 +-- Glamourer/Designs/DesignStorage.cs | 6 + Glamourer/Designs/Links/DesignLink.cs | 18 ++ Glamourer/Designs/Links/DesignLinkLoader.cs | 27 ++ Glamourer/Designs/Links/DesignLinkManager.cs | 74 +++++ Glamourer/Designs/Links/DesignMerger.cs | 265 ++++++++++++++++++ Glamourer/Designs/Links/LinkContainer.cs | 177 ++++++++++++ Glamourer/Events/DesignChanged.cs | 8 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 8 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 20 +- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 20 +- .../Gui/Tabs/DebugTab/AutoDesignPanel.cs | 2 +- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 120 ++++++++ Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 29 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 2 +- Glamourer/Gui/ToggleDrawData.cs | 30 +- Glamourer/Interop/CharaFile/CharaFile.cs | 30 +- Glamourer/Interop/ImportService.cs | 2 - .../Interop/PalettePlus/PaletteImport.cs | 6 +- Glamourer/Services/CommandService.cs | 12 +- Glamourer/State/ActorState.cs | 50 +--- Glamourer/State/StateApplier.cs | 2 +- Glamourer/State/StateEditor.cs | 52 ++-- Glamourer/State/StateListener.cs | 28 +- Glamourer/State/StateManager.cs | 82 +++--- Glamourer/State/StateSource.cs | 58 ++++ OtterGui | 2 +- 35 files changed, 1118 insertions(+), 376 deletions(-) create mode 100644 Glamourer/Automation/ApplicationType.cs create mode 100644 Glamourer/Designs/DesignStorage.cs create mode 100644 Glamourer/Designs/Links/DesignLink.cs create mode 100644 Glamourer/Designs/Links/DesignLinkLoader.cs create mode 100644 Glamourer/Designs/Links/DesignLinkManager.cs create mode 100644 Glamourer/Designs/Links/DesignMerger.cs create mode 100644 Glamourer/Designs/Links/LinkContainer.cs create mode 100644 Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs create mode 100644 Glamourer/State/StateSource.cs diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs new file mode 100644 index 0000000..e4866de --- /dev/null +++ b/Glamourer/Automation/ApplicationType.cs @@ -0,0 +1,62 @@ +using Glamourer.Designs; +using Glamourer.GameData; +using Penumbra.GameData.Enums; + +namespace Glamourer.Automation; + +[Flags] +public enum ApplicationType : byte +{ + Armor = 0x01, + Customizations = 0x02, + Weapons = 0x04, + GearCustomization = 0x08, + Accessories = 0x10, + + All = Armor | Accessories | Customizations | Weapons | GearCustomization, +} + +public static class ApplicationTypeExtensions +{ + public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor, + bool + ApplyWeapon, bool ApplyWet) ApplyWhat(this ApplicationType type, DesignBase? design) + { + var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0) + | (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0) + | (type.HasFlag(ApplicationType.Accessories) ? AccessoryFlags : 0) + | (type.HasFlag(ApplicationType.GearCustomization) ? StainFlags : 0); + var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0; + var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0; + var crestFlag = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0; + + if (design == null) + return (equipFlags, customizeFlags, crestFlag, parameterFlags, type.HasFlag(ApplicationType.Armor), + type.HasFlag(ApplicationType.Armor), + type.HasFlag(ApplicationType.Weapons), type.HasFlag(ApplicationType.Customizations)); + + return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest, + parameterFlags & design.ApplyParameters, + type.HasFlag(ApplicationType.Armor) && design.DoApplyHatVisible(), + type.HasFlag(ApplicationType.Armor) && design.DoApplyVisorToggle(), + type.HasFlag(ApplicationType.Weapons) && design.DoApplyWeaponVisible(), + type.HasFlag(ApplicationType.Customizations) && design.DoApplyWetness()); + } + + public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand; + public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet; + public const EquipFlag AccessoryFlags = EquipFlag.Ears | EquipFlag.Neck | EquipFlag.Wrist | EquipFlag.RFinger | EquipFlag.LFinger; + + public const EquipFlag StainFlags = EquipFlag.MainhandStain + | EquipFlag.OffhandStain + | EquipFlag.HeadStain + | EquipFlag.BodyStain + | EquipFlag.HandsStain + | EquipFlag.LegsStain + | EquipFlag.FeetStain + | EquipFlag.EarsStain + | EquipFlag.NeckStain + | EquipFlag.WristStain + | EquipFlag.RFingerStain + | EquipFlag.LFingerStain; +} \ No newline at end of file diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 7ab3021..9d709ab 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -12,22 +12,10 @@ public class AutoDesign { public const string RevertName = "Revert"; - [Flags] - public enum Type : byte - { - Armor = 0x01, - Customizations = 0x02, - Weapons = 0x04, - GearCustomization = 0x08, - Accessories = 0x10, - - All = Armor | Accessories | Customizations | Weapons | GearCustomization, - } - - public Design? Design; - public JobGroup Jobs; - public Type ApplicationType; - public short GearsetIndex = -1; + public Design? Design; + public JobGroup Jobs; + public ApplicationType Type; + public short GearsetIndex = -1; public string Name(bool incognito) => Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text; @@ -41,10 +29,10 @@ public class AutoDesign public AutoDesign Clone() => new() { - Design = Design, - ApplicationType = ApplicationType, - Jobs = Jobs, - GearsetIndex = GearsetIndex, + Design = Design, + Type = Type, + Jobs = Jobs, + GearsetIndex = GearsetIndex, }; public unsafe bool IsActive(Actor actor) @@ -64,9 +52,9 @@ public class AutoDesign public JObject Serialize() => new() { - ["Design"] = Design?.Identifier.ToString(), - ["ApplicationType"] = (uint)ApplicationType, - ["Conditions"] = CreateConditionObject(), + ["Design"] = Design?.Identifier.ToString(), + ["Type"] = (uint)Type, + ["Conditions"] = CreateConditionObject(), }; private JObject CreateConditionObject() @@ -82,42 +70,5 @@ public class AutoDesign public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat() - { - var equipFlags = (ApplicationType.HasFlag(Type.Weapons) ? WeaponFlags : 0) - | (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0) - | (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0) - | (ApplicationType.HasFlag(Type.GearCustomization) ? StainFlags : 0); - var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0; - var parameterFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeParameterExtensions.All : 0; - var crestFlag = ApplicationType.HasFlag(Type.GearCustomization) ? CrestExtensions.AllRelevant : 0; - - if (Revert) - return (equipFlags, customizeFlags, crestFlag, parameterFlags, ApplicationType.HasFlag(Type.Armor), - ApplicationType.HasFlag(Type.Armor), - ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations)); - - return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, crestFlag & Design.ApplyCrest, - parameterFlags & Design.ApplyParameters, - ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(), - ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(), - ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(), - ApplicationType.HasFlag(Type.Customizations) && Design.DoApplyWetness()); - } - - public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand; - public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet; - public const EquipFlag AccessoryFlags = EquipFlag.Ears | EquipFlag.Neck | EquipFlag.Wrist | EquipFlag.RFinger | EquipFlag.LFinger; - - public const EquipFlag StainFlags = EquipFlag.MainhandStain - | EquipFlag.OffhandStain - | EquipFlag.HeadStain - | EquipFlag.BodyStain - | EquipFlag.HandsStain - | EquipFlag.LegsStain - | EquipFlag.FeetStain - | EquipFlag.EarsStain - | EquipFlag.NeckStain - | EquipFlag.WristStain - | EquipFlag.RFingerStain - | EquipFlag.LFingerStain; + => Type.ApplyWhat(Design); } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 07bf41f..a92ebe9 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -1,6 +1,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Designs; +using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; @@ -15,7 +16,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Automation; -public class AutoDesignApplier : IDisposable +public sealed class AutoDesignApplier : IDisposable { private readonly Configuration _config; private readonly AutoDesignManager _manager; @@ -30,6 +31,7 @@ public class AutoDesignApplier : IDisposable private readonly ObjectManager _objects; private readonly WeaponLoading _weapons; private readonly HumanModelList _humans; + private readonly DesignMerger _designMerger; private readonly IClientState _clientState; private ActorState? _jobChangeState; @@ -182,7 +184,7 @@ public class AutoDesignApplier : IDisposable } else if (_state.TryGetValue(id, out var state)) { - state.RemoveFixedDesignSources(); + state.Source.RemoveFixedDesignSources(); } } } @@ -196,9 +198,9 @@ public class AutoDesignApplier : IDisposable { if (id.Type is IdentifierType.Player && id.HomeWorld == WorldId.AnyWorld) foreach (var state in _state.Where(kvp => kvp.Key.PlayerName == id.PlayerName).Select(kvp => kvp.Value)) - state.RemoveFixedDesignSources(); + state.Source.RemoveFixedDesignSources(); else if (_state.TryGetValue(id, out var state)) - state.RemoveFixedDesignSources(); + state.Source.RemoveFixedDesignSources(); } } } @@ -276,7 +278,7 @@ public class AutoDesignApplier : IDisposable if (set.BaseState == AutoDesignSet.Base.Game) _state.ResetStateFixed(state, respectManual); else if (!respectManual) - state.RemoveFixedDesignSources(); + state.Source.RemoveFixedDesignSources(); if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) return; @@ -286,7 +288,7 @@ public class AutoDesignApplier : IDisposable if (!design.IsActive(actor)) continue; - if (design.ApplicationType is 0) + if (design.Type is 0) continue; ref readonly var data = ref design.GetDesignData(state); @@ -342,7 +344,7 @@ public class AutoDesignApplier : IDisposable if (!crestFlags.HasFlag(slot)) continue; - if (!respectManual || state[slot] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[slot] is not StateChanged.Source.Manual) _state.ChangeCrest(state, slot, design.Crest(slot), source); totalCrestFlags |= slot; } @@ -360,7 +362,7 @@ public class AutoDesignApplier : IDisposable if (!parameterFlags.HasFlag(flag)) continue; - if (!respectManual || state[flag] is not StateChanged.Source.Manual and not StateChanged.Source.Pending) + if (!respectManual || state.Source[flag] is not StateChanged.Source.Manual and not StateChanged.Source.Pending) _state.ChangeCustomizeParameter(state, flag, design.Parameters[flag], source); totalParameterFlags |= flag; } @@ -381,7 +383,7 @@ public class AutoDesignApplier : IDisposable var item = design.Item(slot); if (!_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _)) { - if (!respectManual || state[slot, false] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[slot, false] is not StateChanged.Source.Manual) _state.ChangeItem(state, slot, item, source); totalEquipFlags |= flag; } @@ -390,7 +392,7 @@ public class AutoDesignApplier : IDisposable var stainFlag = slot.ToStainFlag(); if (equipFlags.HasFlag(stainFlag)) { - if (!respectManual || state[slot, true] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[slot, true] is not StateChanged.Source.Manual) _state.ChangeStain(state, slot, design.Stain(slot), source); totalEquipFlags |= stainFlag; } @@ -400,7 +402,7 @@ public class AutoDesignApplier : IDisposable { var item = design.Item(EquipSlot.MainHand); var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); - var checkState = !respectManual || state[EquipSlot.MainHand, false] is not StateChanged.Source.Manual; + var checkState = !respectManual || state.Source[EquipSlot.MainHand, false] is not StateChanged.Source.Manual; if (checkUnlock && checkState) { if (fromJobChange) @@ -420,7 +422,7 @@ public class AutoDesignApplier : IDisposable { var item = design.Item(EquipSlot.OffHand); var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); - var checkState = !respectManual || state[EquipSlot.OffHand, false] is not StateChanged.Source.Manual; + var checkState = !respectManual || state.Source[EquipSlot.OffHand, false] is not StateChanged.Source.Manual; if (checkUnlock && checkState) { if (fromJobChange) @@ -438,14 +440,14 @@ public class AutoDesignApplier : IDisposable if (equipFlags.HasFlag(EquipFlag.MainhandStain)) { - if (!respectManual || state[EquipSlot.MainHand, true] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[EquipSlot.MainHand, true] is not StateChanged.Source.Manual) _state.ChangeStain(state, EquipSlot.MainHand, design.Stain(EquipSlot.MainHand), source); totalEquipFlags |= EquipFlag.MainhandStain; } if (equipFlags.HasFlag(EquipFlag.OffhandStain)) { - if (!respectManual || state[EquipSlot.OffHand, true] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[EquipSlot.OffHand, true] is not StateChanged.Source.Manual) _state.ChangeStain(state, EquipSlot.OffHand, design.Stain(EquipSlot.OffHand), source); totalEquipFlags |= EquipFlag.OffhandStain; } @@ -467,7 +469,7 @@ public class AutoDesignApplier : IDisposable if (customizeFlags.HasFlag(CustomizeFlag.Clan)) { - if (!respectManual || state[CustomizeIndex.Clan] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[CustomizeIndex.Clan] is not StateChanged.Source.Manual) fixFlags |= _customizations.ChangeClan(ref customize, design.Customize.Clan); customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race); totalCustomizeFlags |= CustomizeFlag.Clan | CustomizeFlag.Race; @@ -475,7 +477,7 @@ public class AutoDesignApplier : IDisposable if (customizeFlags.HasFlag(CustomizeFlag.Gender)) { - if (!respectManual || state[CustomizeIndex.Gender] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[CustomizeIndex.Gender] is not StateChanged.Source.Manual) fixFlags |= _customizations.ChangeGender(ref customize, design.Customize.Gender); customizeFlags &= ~CustomizeFlag.Gender; totalCustomizeFlags |= CustomizeFlag.Gender; @@ -486,7 +488,7 @@ public class AutoDesignApplier : IDisposable if (customizeFlags.HasFlag(CustomizeFlag.Face)) { - if (!respectManual || state[CustomizeIndex.Face] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[CustomizeIndex.Face] is not StateChanged.Source.Manual) _state.ChangeCustomize(state, CustomizeIndex.Face, design.Customize.Face, source); customizeFlags &= ~CustomizeFlag.Face; totalCustomizeFlags |= CustomizeFlag.Face; @@ -506,7 +508,7 @@ public class AutoDesignApplier : IDisposable if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _)) continue; - if (!respectManual || state[index] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[index] is not StateChanged.Source.Manual) _state.ChangeCustomize(state, index, value, source); totalCustomizeFlags |= flag; } @@ -518,28 +520,28 @@ public class AutoDesignApplier : IDisposable { if (applyHat && (totalMetaFlags & 0x01) == 0) { - if (!respectManual || state[ActorState.MetaIndex.HatState] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[MetaIndex.HatState] is not StateChanged.Source.Manual) _state.ChangeHatState(state, design.IsHatVisible(), source); totalMetaFlags |= 0x01; } if (applyVisor && (totalMetaFlags & 0x02) == 0) { - if (!respectManual || state[ActorState.MetaIndex.VisorState] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[MetaIndex.VisorState] is not StateChanged.Source.Manual) _state.ChangeVisorState(state, design.IsVisorToggled(), source); totalMetaFlags |= 0x02; } if (applyWeapon && (totalMetaFlags & 0x04) == 0) { - if (!respectManual || state[ActorState.MetaIndex.WeaponState] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[MetaIndex.WeaponState] is not StateChanged.Source.Manual) _state.ChangeWeaponState(state, design.IsWeaponVisible(), source); totalMetaFlags |= 0x04; } if (applyWet && (totalMetaFlags & 0x08) == 0) { - if (!respectManual || state[ActorState.MetaIndex.Wetness] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[MetaIndex.Wetness] is not StateChanged.Source.Manual) _state.ChangeWetness(state, design.IsWet(), source); totalMetaFlags |= 0x08; } diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 38e4479..72b8daf 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -232,7 +232,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos var newDesign = new AutoDesign() { Design = design, - ApplicationType = AutoDesign.Type.All, + Type = ApplicationType.All, Jobs = _jobs.JobGroups[1], }; set.Designs.Add(newDesign); @@ -328,21 +328,21 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, index)); } - public void ChangeApplicationType(AutoDesignSet set, int which, AutoDesign.Type type) + public void ChangeApplicationType(AutoDesignSet set, int which, ApplicationType applicationType) { if (which >= set.Designs.Count || which < 0) return; - type &= AutoDesign.Type.All; + applicationType &= ApplicationType.All; var design = set.Designs[which]; - if (design.ApplicationType == type) + if (design.Type == applicationType) return; - var old = design.ApplicationType; - design.ApplicationType = type; + var old = design.Type; + design.Type = applicationType; Save(); - Glamourer.Log.Debug($"Changed application type from {old} to {type} for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, type)); + Glamourer.Log.Debug($"Changed application type from {old} to {applicationType} for associated design {which + 1} in design set."); + _event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, applicationType)); } public string ToFilename(FilenameService fileNames) @@ -490,12 +490,13 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos } } - var applicationType = (AutoDesign.Type)(jObj["ApplicationType"]?.ToObject() ?? 0); + // ApplicationType is a migration from an older property name. + var applicationType = (ApplicationType)(jObj["Type"]?.ToObject() ?? jObj["ApplicationType"]?.ToObject() ?? 0); - var ret = new AutoDesign() + var ret = new AutoDesign { Design = design, - ApplicationType = applicationType & AutoDesign.Type.All, + Type = applicationType & ApplicationType.All, }; return ParseConditions(setName, jObj, ret) ? ret : null; } @@ -550,7 +551,24 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos private ActorIdentifier[] GetGroup(ActorIdentifier identifier) { if (!identifier.IsValid) - return Array.Empty(); + return []; + + return identifier.Type switch + { + IdentifierType.Player => + [ + identifier.CreatePermanent(), + ], + IdentifierType.Retainer => + [ + _actors.CreateRetainer(identifier.PlayerName, + identifier.Retainer == ActorIdentifier.RetainerType.Mannequin + ? ActorIdentifier.RetainerType.Mannequin + : ActorIdentifier.RetainerType.Bell).CreatePermanent(), + ], + IdentifierType.Npc => CreateNpcs(_actors, identifier), + _ => [], + }; static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier) { @@ -566,23 +584,6 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos identifier.Kind, kvp.Key)).ToArray(); } - - return identifier.Type switch - { - IdentifierType.Player => new[] - { - identifier.CreatePermanent(), - }, - IdentifierType.Retainer => new[] - { - _actors.CreateRetainer(identifier.PlayerName, - identifier.Retainer == ActorIdentifier.RetainerType.Mannequin - ? ActorIdentifier.RetainerType.Mannequin - : ActorIdentifier.RetainerType.Bell).CreatePermanent(), - }, - IdentifierType.Npc => CreateNpcs(_actors, identifier), - _ => Array.Empty(), - }; } private void OnDesignChange(DesignChanged.Type type, Design design, object? data) diff --git a/Glamourer/Automation/AutoDesignSet.cs b/Glamourer/Automation/AutoDesignSet.cs index 3e1d713..29a0e2e 100644 --- a/Glamourer/Automation/AutoDesignSet.cs +++ b/Glamourer/Automation/AutoDesignSet.cs @@ -3,12 +3,12 @@ using Penumbra.GameData.Actors; namespace Glamourer.Automation; -public class AutoDesignSet +public class AutoDesignSet(string name, ActorIdentifier[] identifiers, List designs) { - public readonly List Designs; + public readonly List Designs = designs; - public string Name; - public ActorIdentifier[] Identifiers; + public string Name = name; + public ActorIdentifier[] Identifiers = identifiers; public bool Enabled; public Base BaseState = Base.Current; @@ -32,13 +32,6 @@ public class AutoDesignSet : this(name, identifiers, new List()) { } - public AutoDesignSet(string name, ActorIdentifier[] identifiers, List designs) - { - Name = name; - Identifiers = identifiers; - Designs = designs; - } - public enum Base : byte { Current, diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index 9809fd8..856561a 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -56,7 +56,7 @@ public class FixedDesignMigrator(JobService jobs) autoManager.AddDesign(set, leaf.Value); autoManager.ChangeJobCondition(set, set.Designs.Count - 1, design.Item2); - autoManager.ChangeApplicationType(set, set.Designs.Count - 1, design.Item3 ? AutoDesign.Type.All : 0); + autoManager.ChangeApplicationType(set, set.Designs.Count - 1, design.Item3 ? ApplicationType.All : 0); } } } diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 4ff6f2d..9f74af0 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -1,4 +1,6 @@ using Dalamud.Interface.Internal.Notifications; +using Glamourer.Automation; +using Glamourer.Designs.Links; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; @@ -35,10 +37,11 @@ public sealed class Design : DesignBase, ISavable public DateTimeOffset LastEdit { get; internal set; } public LowerString Name { get; internal set; } = LowerString.Empty; public string Description { get; internal set; } = string.Empty; - public string[] Tags { get; internal set; } = Array.Empty(); + public string[] Tags { get; internal set; } = []; public int Index { get; internal set; } public string Color { get; internal set; } = string.Empty; - public SortedList AssociatedMods { get; private set; } = new(); + public SortedList AssociatedMods { get; private set; } = []; + public LinkContainer Links { get; private set; } = []; public string Incognito => Identifier.ToString()[..8]; @@ -64,6 +67,7 @@ public sealed class Design : DesignBase, ISavable ["Customize"] = SerializeCustomize(), ["Parameters"] = SerializeParameters(), ["Mods"] = SerializeMods(), + ["Links"] = Links.Serialize(), }; return ret; } @@ -95,24 +99,18 @@ public sealed class Design : DesignBase, ISavable #region Deserialization - public static Design LoadDesign(CustomizeService customizations, ItemManager items, JObject json) + public static Design LoadDesign(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) { var version = json["FileVersion"]?.ToObject() ?? 0; return version switch { - FileVersion => LoadDesignV1(customizations, items, json), + FileVersion => LoadDesignV1(customizations, items, linkLoader, json), _ => throw new Exception("The design to be loaded has no valid Version."), }; } - private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, JObject json) + private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) { - static string[] ParseTags(JObject json) - { - var tags = json["Tags"]?.ToObject() ?? Array.Empty(); - return tags.OrderBy(t => t).Distinct().ToArray(); - } - var creationDate = json["CreationDate"]?.ToObject() ?? throw new ArgumentNullException("CreationDate"); var design = new Design(customizations, items) @@ -131,8 +129,15 @@ public sealed class Design : DesignBase, ISavable LoadEquip(items, json["Equipment"], design, design.Name, true); LoadMods(json["Mods"], design); LoadParameters(json["Parameters"], design, design.Name); + LoadLinks(linkLoader, json["Links"], design); design.Color = json["Color"]?.ToObject() ?? string.Empty; return design; + + static string[] ParseTags(JObject json) + { + var tags = json["Tags"]?.ToObject() ?? Array.Empty(); + return tags.OrderBy(t => t).Distinct().ToArray(); + } } private static void LoadMods(JToken? mods, Design design) @@ -161,6 +166,29 @@ public sealed class Design : DesignBase, ISavable } } + private static void LoadLinks(DesignLinkLoader linkLoader, JToken? links, Design design) + { + if (links is not JObject obj) + return; + + Parse(obj["Before"] as JArray, LinkOrder.Before); + Parse(obj["After"] as JArray, LinkOrder.After); + return; + + void Parse(JArray? array, LinkOrder order) + { + if (array == null) + return; + + foreach (var obj in array.OfType()) + { + var identifier = obj["Design"]?.ToObject() ?? throw new ArgumentNullException("Design"); + var type = (ApplicationType)(obj["Type"]?.ToObject() ?? 0); + linkLoader.AddObject(design, new LinkData(identifier, type, order)); + } + } + } + #endregion #region ISavable diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 949fa06..596fbc3 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -1,4 +1,5 @@ -using Glamourer.GameData; +using Glamourer.Designs.Links; +using Glamourer.GameData; using Glamourer.Services; using Glamourer.State; using Glamourer.Utility; @@ -10,7 +11,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Designs; -public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans) +public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans, DesignLinkLoader _linkLoader) { public const byte Version = 6; @@ -75,7 +76,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi case (byte)'{': var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes)); ret = jObj1["Identifier"] != null - ? Design.LoadDesign(_customize, _items, jObj1) + ? Design.LoadDesign(_customize, _items, _linkLoader, jObj1) : DesignBase.LoadDesignBase(_customize, _items, jObj1); break; case 1: @@ -90,7 +91,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 3); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, jObj2) + ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } @@ -101,7 +102,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 5); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, jObj2) + ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } @@ -111,7 +112,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 6); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, jObj2) + ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index f16281a..2df7d8d 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -1,4 +1,5 @@ using Dalamud.Utility; +using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop.Penumbra; @@ -20,30 +21,33 @@ public class DesignManager private readonly HumanModelList _humans; private readonly SaveService _saveService; private readonly DesignChanged _event; - private readonly List _designs = []; + private readonly DesignStorage _designs; private readonly Dictionary _undoStore = []; public IReadOnlyList Designs => _designs; public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, - DesignChanged @event, HumanModelList humans) + DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader) { - _saveService = saveService; - _items = items; - _customizations = customizations; - _event = @event; - _humans = humans; + _designs = storage; + _saveService = saveService; + _items = items; + _customizations = customizations; + _event = @event; + _humans = humans; + + LoadDesigns(designLinkLoader); CreateDesignFolder(saveService); - LoadDesigns(); MigrateOldDesigns(); + designLinkLoader.SetAllObjects(); } /// /// Clear currently loaded designs and load all designs anew from file. /// Invalid data is fixed, but changes are not saved until manual changes. /// - public void LoadDesigns() + public void LoadDesigns(DesignLinkLoader linkLoader) { _humans.Awaiter.Wait(); _customizations.Awaiter.Wait(); @@ -59,7 +63,7 @@ public class DesignManager { var text = File.ReadAllText(f.FullName); var data = JObject.Parse(text); - var design = Design.LoadDesign(_customizations, _items, data); + var design = Design.LoadDesign(_customizations, _items, linkLoader, data); designs.Value!.Add((design, f.FullName)); } catch (Exception ex) @@ -497,14 +501,14 @@ public class DesignManager } /// Change the bool value of one of the meta flags. - public void ChangeMeta(Design design, ActorState.MetaIndex metaIndex, bool value) + public void ChangeMeta(Design design, MetaIndex metaIndex, bool value) { var change = metaIndex switch { - ActorState.MetaIndex.Wetness => design.GetDesignDataRef().SetIsWet(value), - ActorState.MetaIndex.HatState => design.GetDesignDataRef().SetHatVisible(value), - ActorState.MetaIndex.VisorState => design.GetDesignDataRef().SetVisor(value), - ActorState.MetaIndex.WeaponState => design.GetDesignDataRef().SetWeaponVisible(value), + MetaIndex.Wetness => design.GetDesignDataRef().SetIsWet(value), + MetaIndex.HatState => design.GetDesignDataRef().SetHatVisible(value), + MetaIndex.VisorState => design.GetDesignDataRef().SetVisor(value), + MetaIndex.WeaponState => design.GetDesignDataRef().SetWeaponVisible(value), _ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null), }; if (!change) @@ -517,14 +521,14 @@ public class DesignManager } /// Change the application value of one of the meta flags. - public void ChangeApplyMeta(Design design, ActorState.MetaIndex metaIndex, bool value) + public void ChangeApplyMeta(Design design, MetaIndex metaIndex, bool value) { var change = metaIndex switch { - ActorState.MetaIndex.Wetness => design.SetApplyWetness(value), - ActorState.MetaIndex.HatState => design.SetApplyHatVisible(value), - ActorState.MetaIndex.VisorState => design.SetApplyVisorToggle(value), - ActorState.MetaIndex.WeaponState => design.SetApplyWeaponVisible(value), + MetaIndex.Wetness => design.SetApplyWetness(value), + MetaIndex.HatState => design.SetApplyHatVisible(value), + MetaIndex.VisorState => design.SetApplyVisorToggle(value), + MetaIndex.WeaponState => design.SetApplyWeaponVisible(value), _ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null), }; if (!change) diff --git a/Glamourer/Designs/DesignStorage.cs b/Glamourer/Designs/DesignStorage.cs new file mode 100644 index 0000000..297f3be --- /dev/null +++ b/Glamourer/Designs/DesignStorage.cs @@ -0,0 +1,6 @@ +using OtterGui.Services; + +namespace Glamourer.Designs; + +public class DesignStorage : List, IService +{} diff --git a/Glamourer/Designs/Links/DesignLink.cs b/Glamourer/Designs/Links/DesignLink.cs new file mode 100644 index 0000000..2adc055 --- /dev/null +++ b/Glamourer/Designs/Links/DesignLink.cs @@ -0,0 +1,18 @@ +using Glamourer.Automation; + +namespace Glamourer.Designs.Links; + +public record struct DesignLink(Design Link, ApplicationType Type); + +public readonly record struct LinkData(Guid Identity, ApplicationType Type, LinkOrder Order) +{ + public override string ToString() + => Identity.ToString(); +} + +public enum LinkOrder : byte +{ + Self, + After, + Before, +}; diff --git a/Glamourer/Designs/Links/DesignLinkLoader.cs b/Glamourer/Designs/Links/DesignLinkLoader.cs new file mode 100644 index 0000000..4d438bd --- /dev/null +++ b/Glamourer/Designs/Links/DesignLinkLoader.cs @@ -0,0 +1,27 @@ +using Dalamud.Interface.Internal.Notifications; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Services; + +namespace Glamourer.Designs.Links; + +public sealed class DesignLinkLoader(DesignStorage designStorage, MessageService messager) + : DelayedReferenceLoader(messager), IService +{ + protected override bool TryGetObject(LinkData data, [NotNullWhen(true)] out Design? obj) + => designStorage.FindFirst(d => d.Identifier == data.Identity, out obj); + + protected override bool SetObject(Design parent, Design child, LinkData data, out string error) + => LinkContainer.AddLink(parent, child, data.Type, data.Order, out error); + + protected override void HandleChildNotFound(Design parent, LinkData data) + { + Messager.AddMessage(new Notification( + $"Could not find the design {data.Identity}. If this design was deleted, please re-save {parent.Identifier}.", + NotificationType.Warning)); + } + + protected override void HandleChildNotSet(Design parent, Design child, string error) + => Messager.AddMessage(new Notification($"Could not link {child.Identifier} to {parent.Identifier}: {error}", + NotificationType.Warning)); +} diff --git a/Glamourer/Designs/Links/DesignLinkManager.cs b/Glamourer/Designs/Links/DesignLinkManager.cs new file mode 100644 index 0000000..e3fe094 --- /dev/null +++ b/Glamourer/Designs/Links/DesignLinkManager.cs @@ -0,0 +1,74 @@ +using Glamourer.Automation; +using Glamourer.Events; +using Glamourer.Services; +using OtterGui.Services; + +namespace Glamourer.Designs.Links; + +public sealed class DesignLinkManager : IService, IDisposable +{ + private readonly DesignStorage _storage; + private readonly DesignChanged _event; + private readonly SaveService _saveService; + + public DesignLinkManager(DesignStorage storage, DesignChanged @event, SaveService saveService) + { + _storage = storage; + _event = @event; + _saveService = saveService; + + _event.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignLinkManager); + } + + public void Dispose() + => _event.Unsubscribe(OnDesignChanged); + + public void MoveDesignLink(Design parent, int idxFrom, LinkOrder orderFrom, int idxTo, LinkOrder orderTo) + { + if (!parent.Links.Reorder(idxFrom, orderFrom, idxTo, orderTo)) + return; + + parent.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(parent); + Glamourer.Log.Debug($"Moved link from {orderFrom} {idxFrom} to {idxTo} {orderTo}."); + _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + } + + public void AddDesignLink(Design parent, Design child, LinkOrder order) + { + if (!LinkContainer.AddLink(parent, child, ApplicationType.All, order, out _)) + return; + + parent.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(parent); + Glamourer.Log.Debug($"Added new {order} link to {child.Identifier} for {parent.Identifier}."); + _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + } + + public void RemoveDesignLink(Design parent, int idx, LinkOrder order) + { + if (!parent.Links.Remove(idx, order)) + return; + + parent.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(parent); + Glamourer.Log.Debug($"Removed the {order} link at {idx} for {parent.Identifier}."); + _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + } + + private void OnDesignChanged(DesignChanged.Type type, Design deletedDesign, object? _) + { + if (type is not DesignChanged.Type.Deleted) + return; + + foreach (var design in _storage) + { + if (design.Links.Remove(deletedDesign)) + { + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug($"Removed {deletedDesign.Identifier} from {design.Identifier} links due to deletion."); + _saveService.QueueSave(design); + } + } + } +} diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs new file mode 100644 index 0000000..94832f9 --- /dev/null +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -0,0 +1,265 @@ +using Glamourer.Automation; +using Glamourer.Events; +using Glamourer.GameData; +using Glamourer.Services; +using Glamourer.State; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Designs.Links; + +using WeaponDict = Dictionary; + +public sealed class MergedDesign +{ + public MergedDesign(DesignManager designManager) + { + Design = designManager.CreateTemporary(); + Design.ApplyEquip = 0; + Design.ApplyCustomize = 0; + Design.ApplyCrest = 0; + Design.ApplyParameters = 0; + Design.SetApplyWetness(false); + Design.SetApplyVisorToggle(false); + Design.SetApplyWeaponVisible(false); + Design.SetApplyHatVisible(false); + } + + public readonly DesignBase Design; + public readonly WeaponDict Weapons = new(4); + public readonly StateSource Source = new(); +} + +public class DesignMerger(DesignManager designManager, CustomizeService _customize) +{ + public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef) + { + var ret = new MergedDesign(designManager); + CustomizeFlag fixFlags = 0; + foreach (var (design, type) in designs) + { + if (type is 0) + continue; + + ref readonly var data = ref design == null ? ref baseRef : ref design.GetDesignDataRef(); + var source = design == null ? StateChanged.Source.Game : StateChanged.Source.Manual; + + if (!data.IsHuman) + continue; + + var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyHat, applyVisor, applyWeapon, applyWet) = type.ApplyWhat(design); + ReduceMeta(data, applyHat, applyVisor, applyWeapon, applyWet, ret, source); + ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source); + ReduceEquip(data, equipFlags, ret, source); + ReduceMainhands(data, equipFlags, ret, source); + ReduceOffhands(data, equipFlags, ret, source); + ReduceCrests(data, crestFlags, ret, source); + ReduceParameters(data, parameterFlags, ret, source); + } + + ApplyFixFlags(ret, fixFlags); + return ret; + } + + + private static void ReduceMeta(in DesignData design, bool applyHat, bool applyVisor, bool applyWeapon, bool applyWet, MergedDesign ret, + StateChanged.Source source) + { + if (applyHat && !ret.Design.DoApplyHatVisible()) + { + ret.Design.SetApplyHatVisible(true); + ret.Design.GetDesignDataRef().SetHatVisible(design.IsHatVisible()); + ret.Source[MetaIndex.HatState] = source; + } + + if (applyVisor && !ret.Design.DoApplyVisorToggle()) + { + ret.Design.SetApplyVisorToggle(true); + ret.Design.GetDesignDataRef().SetVisor(design.IsVisorToggled()); + ret.Source[MetaIndex.VisorState] = source; + } + + if (applyWeapon && !ret.Design.DoApplyWeaponVisible()) + { + ret.Design.SetApplyWeaponVisible(true); + ret.Design.GetDesignDataRef().SetWeaponVisible(design.IsWeaponVisible()); + ret.Source[MetaIndex.WeaponState] = source; + } + + if (applyWet && !ret.Design.DoApplyWetness()) + { + ret.Design.SetApplyWetness(true); + ret.Design.GetDesignDataRef().SetIsWet(design.IsWet()); + ret.Source[MetaIndex.Wetness] = source; + } + } + + private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateChanged.Source source) + { + crestFlags &= ~ret.Design.ApplyCrest; + if (crestFlags == 0) + return; + + foreach (var slot in CrestExtensions.AllRelevantSet) + { + if (!crestFlags.HasFlag(slot)) + continue; + + ret.Design.GetDesignDataRef().SetCrest(slot, design.Crest(slot)); + ret.Design.SetApplyCrest(slot, true); + ret.Source[slot] = source; + } + } + + private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret, + StateChanged.Source source) + { + parameterFlags &= ~ret.Design.ApplyParameters; + if (parameterFlags == 0) + return; + + foreach (var flag in CustomizeParameterExtensions.AllFlags) + { + if (!parameterFlags.HasFlag(flag)) + continue; + + ret.Design.GetDesignDataRef().Parameters.Set(flag, design.Parameters[flag]); + ret.Design.SetApplyParameter(flag, true); + ret.Source[flag] = source; + } + } + + private static void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source) + { + equipFlags &= ~ret.Design.ApplyEquip; + if (equipFlags == 0) + return; + + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var flag = slot.ToFlag(); + if (equipFlags.HasFlag(flag)) + { + ret.Design.GetDesignDataRef().SetItem(slot, design.Item(slot)); + ret.Design.SetApplyEquip(slot, true); + ret.Source[slot, false] = source; + } + + var stainFlag = slot.ToStainFlag(); + if (equipFlags.HasFlag(stainFlag)) + { + ret.Design.GetDesignDataRef().SetStain(slot, design.Stain(slot)); + ret.Design.SetApplyStain(slot, true); + ret.Source[slot, true] = source; + } + } + + foreach (var slot in EquipSlotExtensions.WeaponSlots) + { + var stainFlag = slot.ToStainFlag(); + if (equipFlags.HasFlag(stainFlag)) + { + ret.Design.GetDesignDataRef().SetStain(slot, design.Stain(slot)); + ret.Design.SetApplyStain(slot, true); + ret.Source[slot, true] = source; + } + } + } + + private static void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source) + { + if (!equipFlags.HasFlag(EquipFlag.Mainhand)) + return; + + ret.Design.SetApplyEquip(EquipSlot.MainHand, true); + var weapon = design.Item(EquipSlot.MainHand); + ret.Weapons.TryAdd(weapon.Type, (weapon, source)); + } + + private static void ReduceOffhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source) + { + if (!equipFlags.HasFlag(EquipFlag.Offhand)) + return; + + ret.Design.SetApplyEquip(EquipSlot.OffHand, true); + var weapon = design.Item(EquipSlot.OffHand); + if (weapon.Valid) + ret.Weapons.TryAdd(weapon.Type, (weapon, source)); + } + + private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret, + StateChanged.Source source) + { + customizeFlags &= ~ret.Design.ApplyCustomizeRaw; + if (customizeFlags == 0) + return; + + // Skip anything not human. + if (!ret.Design.DesignData.IsHuman || !design.IsHuman) + return; + + var customize = ret.Design.DesignData.Customize; + if (customizeFlags.HasFlag(CustomizeFlag.Clan)) + { + fixFlags |= _customize.ChangeClan(ref customize, design.Customize.Clan); + ret.Design.SetApplyCustomize(CustomizeIndex.Clan, true); + ret.Design.SetApplyCustomize(CustomizeIndex.Race, true); + customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race); + ret.Source[CustomizeIndex.Clan] = source; + ret.Source[CustomizeIndex.Race] = source; + } + + if (customizeFlags.HasFlag(CustomizeFlag.Gender)) + { + fixFlags |= _customize.ChangeGender(ref customize, design.Customize.Gender); + ret.Design.SetApplyCustomize(CustomizeIndex.Gender, true); + customizeFlags &= ~CustomizeFlag.Gender; + ret.Source[CustomizeIndex.Gender] = source; + } + + if (customizeFlags.HasFlag(CustomizeFlag.Face)) + { + customize[CustomizeIndex.Face] = design.Customize.Face; + ret.Design.SetApplyCustomize(CustomizeIndex.Face, true); + customizeFlags &= ~CustomizeFlag.Face; + ret.Source[CustomizeIndex.Face] = source; + } + + var set = _customize.Manager.GetSet(customize.Clan, customize.Gender); + var face = customize.Face; + foreach (var index in Enum.GetValues()) + { + var flag = index.ToFlag(); + if (!customizeFlags.HasFlag(flag)) + continue; + + var value = design.Customize[index]; + if (!CustomizeService.IsCustomizationValid(set, face, index, value, out var data)) + continue; + + customize[index] = data?.Value ?? value; + ret.Design.SetApplyCustomize(index, true); + ret.Source[index] = source; + fixFlags &= ~flag; + } + } + + private static void ApplyFixFlags(MergedDesign ret, CustomizeFlag fixFlags) + { + if (fixFlags == 0) + return; + + var source = ret.Design.DoApplyCustomize(CustomizeIndex.Clan) + ? ret.Source[CustomizeIndex.Clan] + : ret.Source[CustomizeIndex.Gender]; + foreach (var index in Enum.GetValues()) + { + var flag = index.ToFlag(); + if (!fixFlags.HasFlag(flag)) + continue; + + ret.Source[index] = source; + ret.Design.SetApplyCustomize(index, true); + } + } +} diff --git a/Glamourer/Designs/Links/LinkContainer.cs b/Glamourer/Designs/Links/LinkContainer.cs new file mode 100644 index 0000000..08a1b6d --- /dev/null +++ b/Glamourer/Designs/Links/LinkContainer.cs @@ -0,0 +1,177 @@ +using Glamourer.Automation; +using Newtonsoft.Json.Linq; +using OtterGui.Filesystem; + +namespace Glamourer.Designs.Links; + +public sealed class LinkContainer : List +{ + public List Before + => this; + + public readonly List After = []; + + public new int Count + => base.Count + After.Count; + + public bool Reorder(int fromIndex, LinkOrder fromOrder, int toIndex, LinkOrder toOrder) + { + var fromList = fromOrder switch + { + LinkOrder.Before => Before, + LinkOrder.After => After, + _ => throw new ArgumentException("Invalid link order."), + }; + + var toList = toOrder switch + { + LinkOrder.Before => Before, + LinkOrder.After => After, + _ => throw new ArgumentException("Invalid link order."), + }; + + if (fromList == toList) + return fromList.Move(fromIndex, toIndex); + + if (fromIndex < 0 || fromIndex >= fromList.Count) + return false; + + toIndex = Math.Clamp(toIndex, 0, toList.Count); + toList.Insert(toIndex, fromList[fromIndex]); + fromList.RemoveAt(fromIndex); + return true; + } + + public bool Remove(int idx, LinkOrder order) + { + var list = order switch + { + LinkOrder.Before => Before, + LinkOrder.After => After, + _ => throw new ArgumentException("Invalid link order."), + }; + if (idx < 0 || idx >= list.Count) + return false; + + list.RemoveAt(idx); + return true; + } + + public static bool CanAddLink(Design parent, Design child, LinkOrder order, out string error) + { + if (parent == child) + { + error = $"Can not link {parent.Identifier} with itself."; + return false; + } + + if (parent.Links.Contains(child)) + { + error = $"Design {parent.Identifier} already contains a direct link to {child.Identifier}."; + return false; + } + + if (GetAllLinks(parent).Any(l => l.Link.Link == child && l.Order != order)) + { + error = $"Adding {child.Identifier} to {parent.Identifier}s links would create a circle, the parent already links to the child in the opposite direction."; + return false; + } + + if (GetAllLinks(child).Any(l => l.Link.Link == parent && l.Order == order)) + { + error = $"Adding {child.Identifier} to {parent.Identifier}s links would create a circle, the child already links to the parent in the opposite direction."; + return false; + } + + error = string.Empty; + return true; + } + + public static bool AddLink(Design parent, Design child, ApplicationType type, LinkOrder order, out string error) + { + if (!CanAddLink(parent, child, order, out error)) + return false; + + var list = order switch + { + LinkOrder.Before => parent.Links.Before, + LinkOrder.After => parent.Links.After, + _ => null, + }; + + if (list == null) + { + error = $"Order {order} is invalid."; + return false; + } + + type &= ApplicationType.All; + list.Add(new DesignLink(child, type)); + error = string.Empty; + return true; + } + + public bool Contains(Design child) + => Before.Any(l => l.Link == child) || After.Any(l => l.Link == child); + + public bool Remove(Design child) + => Before.RemoveAll(l => l.Link == child) + After.RemoveAll(l => l.Link == child) > 0; + + public static IEnumerable<(DesignLink Link, LinkOrder Order)> GetAllLinks(Design design) + { + var set = new HashSet(design.Links.Count * 4); + return GetAllLinks(new DesignLink(design, ApplicationType.All), LinkOrder.Self, set); + } + + private static IEnumerable<(DesignLink Link, LinkOrder Order)> GetAllLinks(DesignLink design, LinkOrder currentOrder, ISet visited) + { + if (design.Link.Links.Count == 0) + { + if (visited.Add(design.Link)) + yield return (design, currentOrder); + + yield break; + } + + foreach (var link in design.Link.Links.Before + .Where(l => !visited.Contains(l.Link)) + .SelectMany(l => GetAllLinks(l, currentOrder == LinkOrder.After ? LinkOrder.After : LinkOrder.Before, visited))) + yield return link; + + if (visited.Add(design.Link)) + yield return (design, currentOrder); + + foreach (var link in design.Link.Links.After.Where(l => !visited.Contains(l.Link)) + .SelectMany(l => GetAllLinks(l, currentOrder == LinkOrder.Before ? LinkOrder.Before : LinkOrder.After, visited))) + yield return link; + } + + public JObject Serialize() + { + var before = new JArray(); + foreach (var link in Before) + { + before.Add(new JObject + { + ["Design"] = link.Link.Identifier, + ["Type"] = (uint)link.Type, + }); + } + + var after = new JArray(); + foreach (var link in After) + { + before.Add(new JObject + { + ["Design"] = link.Link.Identifier, + ["Type"] = (uint)link.Type, + }); + } + + return new JObject + { + [nameof(Before)] = before, + [nameof(After)] = after, + }; + } +} diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 2217c34..9b75d5a 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -12,7 +12,7 @@ namespace Glamourer.Events; /// Parameter is any additional data depending on the type of change. /// /// -public sealed class DesignChanged() +public sealed class DesignChanged() : EventWrapper(nameof(DesignChanged)) { public enum Type @@ -50,6 +50,9 @@ public sealed class DesignChanged() /// An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. RemovedMod, + /// An existing design had a link to a different design added, removed or moved. Data is null. + ChangedLink, + /// An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. Customize, @@ -92,6 +95,9 @@ public sealed class DesignChanged() public enum Priority { + /// + DesignLinkManager = 1, + /// AutoDesignManager = 1, diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 431eed0..e85057d 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -141,7 +141,7 @@ public class ActorPanel( if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw)) _stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.Wetness, _stateManager, _state)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.Wetness, _stateManager, _state)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } @@ -187,21 +187,21 @@ public class ActorPanel( { using (_ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.HatState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!)); } ImGui.SameLine(); using (_ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.VisorState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!)); } ImGui.SameLine(); using (_ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.WeaponState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!)); } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index a50445e..bb9e135 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -369,13 +369,13 @@ public class SetPanel( private void DrawApplicationTypeBoxes(AutoDesignSet set, AutoDesign design, int autoDesignIndex, bool singleLine) { using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale)); - var newType = design.ApplicationType; + var newType = design.Type; var newTypeInt = (uint)newType; style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); using (_ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value())) { - if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)AutoDesign.Type.All)) - newType = (AutoDesign.Type)newTypeInt; + if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)ApplicationType.All)) + newType = (ApplicationType)newTypeInt; } style.Pop(); @@ -385,7 +385,7 @@ public class SetPanel( void Box(int idx) { var (type, description) = Types[idx]; - var value = design.ApplicationType.HasFlag(type); + var value = design.Type.HasFlag(type); if (ImGui.Checkbox($"##{(byte)type}", ref value)) newType = value ? newType | type : newType & ~type; ImGuiUtil.HoverTooltip(description); @@ -429,14 +429,14 @@ public class SetPanel( } - private static readonly IReadOnlyList<(AutoDesign.Type, string)> Types = new[] + private static readonly IReadOnlyList<(ApplicationType, string)> Types = new[] { - (AutoDesign.Type.Customizations, + (ApplicationType.Customizations, "Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."), - (AutoDesign.Type.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."), - (AutoDesign.Type.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."), - (AutoDesign.Type.GearCustomization, "Apply all dye and crest changes that are enabled in this design."), - (AutoDesign.Type.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), + (ApplicationType.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."), + (ApplicationType.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."), + (ApplicationType.GearCustomization, "Apply all dye and crest changes that are enabled in this design."), + (ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), }; private sealed class JobGroupCombo : FilterComboCache diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 5408a7c..6eac9ad 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -70,44 +70,44 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM return $"{item.Name} ({item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; } - PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]); + PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state.Source[MetaIndex.ModelId]); ImGui.TableNextRow(); - PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state[ActorState.MetaIndex.Wetness]); + PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state.Source[MetaIndex.Wetness]); ImGui.TableNextRow(); if (state.BaseData.IsHuman && state.ModelData.IsHuman) { - PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state[ActorState.MetaIndex.HatState]); + PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state.Source[MetaIndex.HatState]); ImGui.TableNextRow(); PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(), - state[ActorState.MetaIndex.VisorState]); + state.Source[MetaIndex.VisorState]); ImGui.TableNextRow(); PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(), - state[ActorState.MetaIndex.WeaponState]); + state.Source[MetaIndex.WeaponState]); ImGui.TableNextRow(); foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) { - PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state[slot, false]); + PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state.Source[slot, false]); ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString()); ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString()); - ImGuiUtil.DrawTableColumn(state[slot, true].ToString()); + ImGuiUtil.DrawTableColumn(state.Source[slot, true].ToString()); } foreach (var type in Enum.GetValues()) { - PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state[type]); + PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state.Source[type]); ImGui.TableNextRow(); } foreach (var crest in CrestExtensions.AllRelevantSet) { - PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state[crest]); + PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state.Source[crest]); ImGui.TableNextRow(); } foreach (var flag in CustomizeParameterExtensions.AllFlags) { - PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state[flag]); + PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state.Source[flag]); ImGui.TableNextRow(); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs index cbc0e40..6dd7f6e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs @@ -42,7 +42,7 @@ public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IGameDataDr foreach (var (design, designIdx) in set.Designs.WithIndex()) { ImGuiUtil.DrawTableColumn($"{design.Name(false)} ({designIdx})"); - ImGuiUtil.DrawTableColumn($"{design.ApplicationType} {design.Jobs.Name}"); + ImGuiUtil.DrawTableColumn($"{design.Type} {design.Jobs.Name}"); } } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs new file mode 100644 index 0000000..774ee3c --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -0,0 +1,120 @@ +using Dalamud.Interface; +using Glamourer.Designs; +using Glamourer.Designs.Links; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, DesignCombo _combo) : IUiService +{ + private int _dragDropIndex = -1; + private LinkOrder _dragDropOrder = LinkOrder.Self; + private int _dragDropTargetIndex = -1; + private LinkOrder _dragDropTargetOrder = LinkOrder.Self; + + public void Draw() + { + using var header = ImRaii.CollapsingHeader("Design Links"); + if (!header) + return; + + var width = ImGui.GetContentRegionAvail().X / 2; + DrawList(_selector.Selected!.Links.Before, LinkOrder.Before, width); + ImGui.SameLine(); + DrawList(_selector.Selected!.Links.After, LinkOrder.After, width); + + if (_dragDropTargetIndex < 0 + || _dragDropIndex < 0) + return; + + _linkManager.MoveDesignLink(_selector.Selected!, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder); + _dragDropIndex = -1; + _dragDropTargetIndex = -1; + _dragDropOrder = LinkOrder.Self; + _dragDropTargetOrder = LinkOrder.Self; + } + + private void DrawList(IReadOnlyList list, LinkOrder order, float width) + { + using var id = ImRaii.PushId((int)order); + using var table = ImRaii.Table("table", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter, + new Vector2(width, list.Count * ImGui.GetFrameHeightWithSpacing())); + if (!table) + return; + + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + for (var i = 0; i < list.Count; ++i) + { + id.Push(i); + + ImGui.TableNextColumn(); + var delete = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this link.", false, true); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted($"#{i:D2}"); + + var (design, flags) = list[i]; + ImGui.TableNextColumn(); + + ImGui.AlignTextToFramePadding(); + ImGui.Selectable(_selector.IncognitoMode ? design.Incognito : design.Name.Text); + DrawDragDrop(design, order, i); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(flags.ToString()); + + if (delete) + _linkManager.RemoveDesignLink(_selector.Selected!, i--, order); + } + + ImGui.TableNextColumn(); + string tt; + bool canAdd; + if (_combo.Design == null) + { + tt = "Select a design first."; + canAdd = false; + } + else + { + canAdd = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, order, out var error); + tt = canAdd ? $"Add a link to {_combo.Design.Name}." : $"Can not add a link to {_combo.Design.Name}: {error}"; + } + + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, !canAdd, true)) + _linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, order); + + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + _combo.Draw(200); + } + + private void DrawDragDrop(Design design, LinkOrder order, int index) + { + using (var source = ImRaii.DragDropSource()) + { + if (source) + { + ImGui.SetDragDropPayload("DraggingLink", IntPtr.Zero, 0); + ImGui.TextUnformatted($"Reordering {design.Name}..."); + _dragDropIndex = index; + _dragDropOrder = order; + } + } + + using var target = ImRaii.DragDropTarget(); + if (!target) + return; + + if (!ImGuiUtil.IsDropping("DraggingLink")) + return; + + _dragDropTargetIndex = index; + _dragDropTargetOrder = order; + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 447fc5d..f942439 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -31,7 +31,8 @@ public class DesignPanel( DesignConverter _converter, ImportService _importService, MultiDesignPanel _multiDesignPanel, - CustomizeParameterDrawer _parameterDrawer) + CustomizeParameterDrawer _parameterDrawer, + DesignLinkDrawer _designLinkDrawer) { private readonly FileDialogManager _fileDialog = new(); @@ -119,21 +120,21 @@ public class DesignPanel( { using (var _ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selector.Selected!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selector.Selected!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!)); } } @@ -158,7 +159,7 @@ public class DesignPanel( _manager.ChangeCustomize(_selector.Selected, idx, _customizationDrawer.Customize[idx]); } - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.Wetness, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, _selector.Selected!)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } @@ -166,6 +167,7 @@ public class DesignPanel( { if (!_config.UseAdvancedParameters) return; + using var h = ImRaii.CollapsingHeader("Advanced Customizations"); if (!h) return; @@ -259,20 +261,20 @@ public class DesignPanel( } } - ApplyEquip("Weapons", AutoDesign.WeaponFlags, false, new[] + ApplyEquip("Weapons", ApplicationTypeExtensions.WeaponFlags, false, new[] { EquipSlot.MainHand, EquipSlot.OffHand, }); ImGui.NewLine(); - ApplyEquip("Armor", AutoDesign.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots); + ApplyEquip("Armor", ApplicationTypeExtensions.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots); ImGui.NewLine(); - ApplyEquip("Accessories", AutoDesign.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots); + ApplyEquip("Accessories", ApplicationTypeExtensions.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots); ImGui.NewLine(); - ApplyEquip("Dyes", AutoDesign.StainFlags, true, + ApplyEquip("Dyes", ApplicationTypeExtensions.StainFlags, true, EquipSlotExtensions.FullSlots); ImGui.NewLine(); @@ -294,19 +296,19 @@ public class DesignPanel( var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); var apply = bigChange ? (flags & 0x01) == 0x01 : _selector.Selected!.DoApplyHatVisible(); if (ImGui.Checkbox("Apply Hat Visibility", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.HatState, apply); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, apply); apply = bigChange ? (flags & 0x02) == 0x02 : _selector.Selected!.DoApplyVisorToggle(); if (ImGui.Checkbox("Apply Visor State", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.VisorState, apply); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.VisorState, apply); apply = bigChange ? (flags & 0x04) == 0x04 : _selector.Selected!.DoApplyWeaponVisible(); if (ImGui.Checkbox("Apply Weapon Visibility", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.WeaponState, apply); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.WeaponState, apply); apply = bigChange ? (flags & 0x08) == 0x08 : _selector.Selected!.DoApplyWetness(); if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.Wetness, apply); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, apply); } private void DrawParameterApplication() @@ -370,6 +372,7 @@ public class DesignPanel( _designDetails.Draw(); DrawApplicationRules(); _modAssociations.Draw(); + _designLinkDrawer.Draw(); } private void DrawButtonRow() diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 2852128..a406a7b 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -172,7 +172,7 @@ public class NpcPanel( _equipDrawer.DrawWeapons(mainhandData, offhandData, false); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(ActorState.MetaIndex.VisorState, _selector.Selection.VisorToggled)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(MetaIndex.VisorState, _selector.Selection.VisorToggled)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index ed435c4..bb834a4 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -22,17 +22,17 @@ public ref struct ToggleDrawData public ToggleDrawData() { } - public static ToggleDrawData FromDesign(ActorState.MetaIndex index, DesignManager manager, Design design) + public static ToggleDrawData FromDesign(MetaIndex index, DesignManager manager, Design design) { var (label, value, apply, setValue, setApply) = index switch { - ActorState.MetaIndex.HatState => ("Hat Visible", design.DesignData.IsHatVisible(), design.DoApplyHatVisible(), + MetaIndex.HatState => ("Hat Visible", design.DesignData.IsHatVisible(), design.DoApplyHatVisible(), (Action)(b => manager.ChangeMeta(design, index, b)), (Action)(b => manager.ChangeApplyMeta(design, index, b))), - ActorState.MetaIndex.VisorState => ("Visor Toggled", design.DesignData.IsVisorToggled(), design.DoApplyVisorToggle(), + MetaIndex.VisorState => ("Visor Toggled", design.DesignData.IsVisorToggled(), design.DoApplyVisorToggle(), b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), - ActorState.MetaIndex.WeaponState => ("Weapon Visible", design.DesignData.IsWeaponVisible(), design.DoApplyWeaponVisible(), + MetaIndex.WeaponState => ("Weapon Visible", design.DesignData.IsWeaponVisible(), design.DoApplyWeaponVisible(), b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), - ActorState.MetaIndex.Wetness => ("Force Wetness", design.DesignData.IsWet(), design.DoApplyWetness(), + MetaIndex.Wetness => ("Force Wetness", design.DesignData.IsWet(), design.DoApplyWetness(), b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), _ => throw new Exception("Unsupported meta index."), }; @@ -73,19 +73,19 @@ public ref struct ToggleDrawData SetValue = v => manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual), }; - public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state) + public static ToggleDrawData FromState(MetaIndex index, StateManager manager, ActorState state) { var (label, tooltip, value, setValue) = index switch { - ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear.", state.ModelData.IsHatVisible(), + MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear.", state.ModelData.IsHatVisible(), (Action)(b => manager.ChangeHatState(state, b, StateChanged.Source.Manual))), - ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear.", + MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear.", state.ModelData.IsVisorToggled(), b => manager.ChangeVisorState(state, b, StateChanged.Source.Manual)), - ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn.", + MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn.", state.ModelData.IsWeaponVisible(), b => manager.ChangeWeaponState(state, b, StateChanged.Source.Manual)), - ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not.", state.ModelData.IsWet(), + MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not.", state.ModelData.IsWet(), b => manager.ChangeWetness(state, b, StateChanged.Source.Manual)), _ => throw new Exception("Unsupported meta index."), }; @@ -100,14 +100,14 @@ public ref struct ToggleDrawData }; } - public static ToggleDrawData FromValue(ActorState.MetaIndex index, bool value) + public static ToggleDrawData FromValue(MetaIndex index, bool value) { var (label, tooltip) = index switch { - ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear."), - ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear."), - ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn."), - ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not."), + MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear."), + MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear."), + MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn."), + MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not."), _ => throw new Exception("Unsupported meta index."), }; return new ToggleDrawData diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs index 8e25d8e..0613fb3 100644 --- a/Glamourer/Interop/CharaFile/CharaFile.cs +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -14,24 +14,17 @@ public sealed class CharaFile public CustomizeFlag ApplyCustomize; public EquipFlag ApplyEquip; - public static CharaFile? ParseData(ItemManager items, string data, string? name = null) + public static CharaFile ParseData(ItemManager items, string data, string? name = null) { - try - { - var jObj = JObject.Parse(data); - SanityCheck(jObj); - var ret = new CharaFile(); - ret.Data.SetDefaultEquipment(items); - ret.Data.ModelId = ParseModelId(jObj); - ret.Name = jObj["Nickname"]?.ToObject() ?? name ?? "New Design"; - ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize); - ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data); - return ret; - } - catch - { - return null; - } + var jObj = JObject.Parse(data); + SanityCheck(jObj); + var ret = new CharaFile(); + ret.Data.SetDefaultEquipment(items); + ret.Data.ModelId = ParseModelId(jObj); + ret.Name = jObj["Nickname"]?.ToObject() ?? name ?? "New Design"; + ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize); + ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data); + return ret; } private static EquipFlag ParseEquipment(ItemManager items, JObject jObj, ref DesignData data) @@ -282,9 +275,6 @@ public sealed class CharaFile private static void SanityCheck(JObject jObj) { - if (jObj["TypeName"]?.ToObject() is not "Anamnesis Character File") - throw new Exception("Wrong TypeName property set."); - var type = jObj["ObjectKind"]?.ToObject(); if (type is not "Player") throw new Exception($"ObjectKind {type} != Player is not supported."); diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index b31a1b0..9587feb 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -63,8 +63,6 @@ public class ImportService(CustomizeService _customizations, IDragDropManager _d { var text = File.ReadAllText(path); var file = CharaFile.CharaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path)); - if (file == null) - throw new Exception(); name = file.Name; design = new DesignBase(_customizations, file.Data, file.ApplyEquip, file.ApplyCustomize); diff --git a/Glamourer/Interop/PalettePlus/PaletteImport.cs b/Glamourer/Interop/PalettePlus/PaletteImport.cs index 474527b..b26f8f9 100644 --- a/Glamourer/Interop/PalettePlus/PaletteImport.cs +++ b/Glamourer/Interop/PalettePlus/PaletteImport.cs @@ -41,9 +41,9 @@ public class PaletteImport(DalamudPluginInterface pluginInterface, DesignManager design.ApplyCustomize = 0; design.ApplyEquip = 0; design.ApplyCrest = 0; - designManager.ChangeApplyMeta(design, ActorState.MetaIndex.VisorState, false); - designManager.ChangeApplyMeta(design, ActorState.MetaIndex.HatState, false); - designManager.ChangeApplyMeta(design, ActorState.MetaIndex.WeaponState, false); + designManager.ChangeApplyMeta(design, MetaIndex.VisorState, false); + designManager.ChangeApplyMeta(design, MetaIndex.HatState, false); + designManager.ChangeApplyMeta(design, MetaIndex.WeaponState, false); foreach (var flag in flags.Iterate()) { designManager.ChangeApplyParameter(design, flag, true); diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index fa08425..f659847 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -255,26 +255,26 @@ public class CommandService : IDisposable } --designIdx; - AutoDesign.Type applicationFlags = 0; + ApplicationType applicationFlags = 0; if (split2.Length == 2) foreach (var character in split2[1]) { switch (char.ToLowerInvariant(character)) { case 'c': - applicationFlags |= AutoDesign.Type.Customizations; + applicationFlags |= ApplicationType.Customizations; break; case 'e': - applicationFlags |= AutoDesign.Type.Armor; + applicationFlags |= ApplicationType.Armor; break; case 'a': - applicationFlags |= AutoDesign.Type.Accessories; + applicationFlags |= ApplicationType.Accessories; break; case 'd': - applicationFlags |= AutoDesign.Type.GearCustomization; + applicationFlags |= ApplicationType.GearCustomization; break; case 'w': - applicationFlags |= AutoDesign.Type.Weapons; + applicationFlags |= ApplicationType.Weapons; break; default: _chat.Print(new SeStringBuilder().AddText("The value ").AddPurple(split2[1], true) diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index ef00f38..83b22ac 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -11,15 +11,6 @@ namespace Glamourer.State; public class ActorState { - public enum MetaIndex - { - Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices, - HatState, - VisorState, - WeaponState, - ModelId, - } - public readonly ActorIdentifier Identifier; public bool AllowsRedraw(ICondition condition) @@ -77,47 +68,14 @@ public class ActorState => Unlock(1337); /// This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. - private readonly StateChanged.Source[] _sources = Enumerable - .Repeat(StateChanged.Source.Game, - EquipFlagExtensions.NumEquipFlags - + CustomizationExtensions.NumIndices - + 5 - + CrestExtensions.AllRelevantSet.Count - + CustomizeParameterExtensions.AllFlags.Count).ToArray(); + public readonly StateSource Source = new(); internal ActorState(ActorIdentifier identifier) => Identifier = identifier.CreatePermanent(); - public ref StateChanged.Source this[EquipSlot slot, bool stain] - => ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)]; - - public ref StateChanged.Source this[CrestFlag slot] - => ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()]; - - public ref StateChanged.Source this[CustomizeIndex type] - => ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type]; - - public ref StateChanged.Source this[MetaIndex index] - => ref _sources[(int)index]; - - public ref StateChanged.Source this[CustomizeParameterFlag flag] - => ref _sources[ - EquipFlagExtensions.NumEquipFlags - + CustomizationExtensions.NumIndices + 5 - + CrestExtensions.AllRelevantSet.Count - + flag.ToInternalIndex()]; - - public void RemoveFixedDesignSources() - { - for (var i = 0; i < _sources.Length; ++i) - { - if (_sources[i] is StateChanged.Source.Fixed) - _sources[i] = StateChanged.Source.Manual; - } - } - public CustomizeParameterFlag OnlyChangedParameters() - => CustomizeParameterExtensions.AllFlags.Where(f => this[f] is not StateChanged.Source.Game).Aggregate((CustomizeParameterFlag) 0, (a, b) => a | b); + => CustomizeParameterExtensions.AllFlags.Where(f => Source[f] is not StateChanged.Source.Game) + .Aggregate((CustomizeParameterFlag)0, (a, b) => a | b); public bool UpdateTerritory(ushort territory) { @@ -127,4 +85,4 @@ public class ActorState LastTerritory = territory; return true; } -} +} \ No newline at end of file diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 8b69e0a..d497006 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -118,7 +118,7 @@ public class StateApplier( // If the source is not IPC we do not want to apply restrictions. var data = GetData(state); if (apply) - ChangeArmor(data, slot, state.ModelData.Armor(slot), state[slot, false] is not StateChanged.Source.Ipc, + ChangeArmor(data, slot, state.ModelData.Armor(slot), state.Source[slot, false] is not StateChanged.Source.Ipc, state.ModelData.IsHatVisible()); return data; diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 9dc8418..c9f1d58 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -61,21 +61,21 @@ public class StateEditor state.ModelData.SetHatVisible(true); state.ModelData.SetWeaponVisible(true); state.ModelData.SetVisor(false); - state[ActorState.MetaIndex.ModelId] = source; - state[ActorState.MetaIndex.HatState] = source; - state[ActorState.MetaIndex.WeaponState] = source; - state[ActorState.MetaIndex.VisorState] = source; + state.Source[MetaIndex.ModelId] = source; + state.Source[MetaIndex.HatState] = source; + state.Source[MetaIndex.WeaponState] = source; + state.Source[MetaIndex.VisorState] = source; foreach (var slot in EquipSlotExtensions.FullSlots) { - state[slot, true] = source; - state[slot, false] = source; + state.Source[slot, true] = source; + state.Source[slot, false] = source; } - state[CustomizeIndex.Clan] = source; - state[CustomizeIndex.Gender] = source; + state.Source[CustomizeIndex.Clan] = source; + state.Source[CustomizeIndex.Gender] = source; var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); foreach (var index in Enum.GetValues().Where(set.IsAvailable)) - state[index] = source; + state.Source[index] = source; } else { @@ -83,7 +83,7 @@ public class StateEditor return false; state.ModelData.LoadNonHuman(modelId, customize, equipData); - state[ActorState.MetaIndex.ModelId] = source; + state.Source[MetaIndex.ModelId] = source; } return true; @@ -98,7 +98,7 @@ public class StateEditor return false; state.ModelData.Customize[idx] = value; - state[idx] = source; + state.Source[idx] = source; return true; } @@ -120,7 +120,7 @@ public class StateEditor foreach (var type in Enum.GetValues()) { if (applied.HasFlag(type.ToFlag())) - state[type] = source; + state.Source[type] = source; } return true; @@ -144,12 +144,12 @@ public class StateEditor _gPose.AddActionOnLeave(() => { if (old.Type == state.BaseData.Item(slot).Type) - ChangeItem(state, slot, old, state[slot, false], out _, key); + ChangeItem(state, slot, old, state.Source[slot, false], out _, key); }); } state.ModelData.SetItem(slot, item); - state[slot, false] = source; + state.Source[slot, false] = source; return true; } @@ -174,14 +174,14 @@ public class StateEditor _gPose.AddActionOnLeave(() => { if (old.Type == state.BaseData.Item(slot).Type) - ChangeEquip(state, slot, old, oldS, state[slot, false], out _, out _, key); + ChangeEquip(state, slot, old, oldS, state.Source[slot, false], out _, out _, key); }); } state.ModelData.SetItem(slot, item); state.ModelData.SetStain(slot, stain); - state[slot, false] = source; - state[slot, true] = source; + state.Source[slot, false] = source; + state.Source[slot, true] = source; return true; } @@ -193,7 +193,7 @@ public class StateEditor return false; state.ModelData.SetStain(slot, stain); - state[slot, true] = source; + state.Source[slot, true] = source; return true; } @@ -205,7 +205,7 @@ public class StateEditor return false; state.ModelData.SetCrest(slot, crest); - state[slot] = source; + state.Source[slot] = source; return true; } @@ -218,20 +218,20 @@ public class StateEditor return false; state.ModelData.Parameters.Set(flag, value); - state[flag] = source; + state.Source[flag] = source; return true; } - public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue, + public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateChanged.Source source, out bool oldValue, uint key = 0) { (var setter, oldValue) = index switch { - ActorState.MetaIndex.Wetness => ((Func)(v => state.ModelData.SetIsWet(v)), state.ModelData.IsWet()), - ActorState.MetaIndex.HatState => ((Func)(v => state.ModelData.SetHatVisible(v)), state.ModelData.IsHatVisible()), - ActorState.MetaIndex.VisorState => ((Func)(v => state.ModelData.SetVisor(v)), state.ModelData.IsVisorToggled()), - ActorState.MetaIndex.WeaponState => ((Func)(v => state.ModelData.SetWeaponVisible(v)), + MetaIndex.Wetness => ((Func)(v => state.ModelData.SetIsWet(v)), state.ModelData.IsWet()), + MetaIndex.HatState => ((Func)(v => state.ModelData.SetHatVisible(v)), state.ModelData.IsHatVisible()), + MetaIndex.VisorState => ((Func)(v => state.ModelData.SetVisor(v)), state.ModelData.IsVisorToggled()), + MetaIndex.WeaponState => ((Func)(v => state.ModelData.SetWeaponVisible(v)), state.ModelData.IsWeaponVisible()), _ => throw new Exception("Invalid MetaIndex."), }; @@ -240,7 +240,7 @@ public class StateEditor return false; setter(value); - state[index] = source; + state.Source[index] = source; return true; } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index b90d835..d529206 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -170,7 +170,7 @@ public class StateListener : IDisposable var set = _customizations.Manager.GetSet(model.Clan, model.Gender); foreach (var index in CustomizationExtensions.AllBasic) { - if (state[index] is not StateChanged.Source.Fixed) + if (state.Source[index] is not StateChanged.Source.Fixed) { var newValue = customize[index]; var oldValue = model[index]; @@ -213,7 +213,7 @@ public class StateListener : IDisposable && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor); - locked = state[slot, false] is StateChanged.Source.Ipc; + locked = state.Source[slot, false] is StateChanged.Source.Ipc; } _funModule.ApplyFunToSlot(actor, ref armor, slot); @@ -240,7 +240,7 @@ public class StateListener : IDisposable continue; var changed = changedItem.Weapon(stain); - if (current.Value == changed.Value && state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (current.Value == changed.Value && state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) { _manager.ChangeItem(state, slot, currentItem, StateChanged.Source.Game); _manager.ChangeStain(state, slot, current.Stain, StateChanged.Source.Game); @@ -251,7 +251,7 @@ public class StateListener : IDisposable _applier.ChangeWeapon(objects, slot, currentItem, stain); break; default: - _applier.ChangeArmor(objects, slot, current.ToArmor(), state[slot, false] is not StateChanged.Source.Ipc, + _applier.ChangeArmor(objects, slot, current.ToArmor(), state.Source[slot, false] is not StateChanged.Source.Ipc, state.ModelData.IsHatVisible()); break; } @@ -285,12 +285,12 @@ public class StateListener : IDisposable // Do nothing. But this usually can not happen because the hooked function also writes to game objects later. case UpdateState.Transformed: break; case UpdateState.Change: - if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game); else apply = true; - if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (state.Source[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); else apply = true; @@ -384,12 +384,12 @@ public class StateListener : IDisposable // Update model state if not on fixed design. case UpdateState.Change: var apply = false; - if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game); else apply = true; - if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (state.Source[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); else apply = true; @@ -418,7 +418,7 @@ public class StateListener : IDisposable switch (UpdateBaseCrest(actor, state, slot, value)) { case UpdateState.Change: - if (state[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (state.Source[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game); else value = state.ModelData.Crest(slot); @@ -564,7 +564,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) + if (state.Source[MetaIndex.VisorState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) value = state.ModelData.IsVisorToggled(); else _manager.ChangeVisorState(state, value, StateChanged.Source.Game); @@ -597,7 +597,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) + if (state.Source[MetaIndex.HatState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) value = state.ModelData.IsHatVisible(); else _manager.ChangeHatState(state, value, StateChanged.Source.Game); @@ -630,7 +630,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) + if (state.Source[MetaIndex.WeaponState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) value = state.ModelData.IsWeaponVisible(); else _manager.ChangeWeaponState(state, value, StateChanged.Source.Game); @@ -732,7 +732,7 @@ public class StateListener : IDisposable foreach (var flag in CustomizeParameterExtensions.AllFlags) { var newValue = data[flag]; - switch (state[flag]) + switch (state.Source[flag]) { case StateChanged.Source.Game: if (state.BaseData.Parameters.Set(flag, newValue)) @@ -755,7 +755,7 @@ public class StateListener : IDisposable break; case StateChanged.Source.Pending: state.BaseData.Parameters.Set(flag, newValue); - state[flag] = StateChanged.Source.Manual; + state.Source[flag] = StateChanged.Source.Manual; if (_config.UseAdvancedParameters) model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 0286e73..4bbc595 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -329,49 +329,49 @@ public class StateManager( /// Change hat visibility. public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0) { - if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.HatState, value, source, out var old, key)) + if (!_editor.ChangeMetaState(state, MetaIndex.HatState, value, source, out var old, key)) return; var actors = _applier.ChangeHatState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); Glamourer.Log.Verbose( $"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.HatState)); + _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.HatState)); } /// Change weapon visibility. public void ChangeWeaponState(ActorState state, bool value, StateChanged.Source source, uint key = 0) { - if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.WeaponState, value, source, out var old, key)) + if (!_editor.ChangeMetaState(state, MetaIndex.WeaponState, value, source, out var old, key)) return; var actors = _applier.ChangeWeaponState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); Glamourer.Log.Verbose( $"Set Weapon Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.WeaponState)); + _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.WeaponState)); } /// Change visor state. public void ChangeVisorState(ActorState state, bool value, StateChanged.Source source, uint key = 0) { - if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.VisorState, value, source, out var old, key)) + if (!_editor.ChangeMetaState(state, MetaIndex.VisorState, value, source, out var old, key)) return; var actors = _applier.ChangeVisor(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); Glamourer.Log.Verbose( $"Set Visor State in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.VisorState)); + _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.VisorState)); } /// Set GPose Wetness. public void ChangeWetness(ActorState state, bool value, StateChanged.Source source, uint key = 0) { - if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.Wetness, value, source, out var old, key)) + if (!_editor.ChangeMetaState(state, MetaIndex.Wetness, value, source, out var old, key)) return; var actors = _applier.ChangeWetness(state, true); Glamourer.Log.Verbose( $"Set Wetness in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, state[ActorState.MetaIndex.Wetness], state, actors, (old, value, ActorState.MetaIndex.Wetness)); + _event.Invoke(StateChanged.Type.Other, state.Source[MetaIndex.Wetness], state, actors, (old, value, MetaIndex.Wetness)); } #endregion @@ -385,16 +385,16 @@ public class StateManager( var redraw = oldModelId != design.DesignData.ModelId || !design.DesignData.IsHuman; if (design.DoApplyWetness()) - _editor.ChangeMetaState(state, ActorState.MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key); + _editor.ChangeMetaState(state, MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key); if (state.ModelData.IsHuman) { if (design.DoApplyHatVisible()) - _editor.ChangeMetaState(state, ActorState.MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key); + _editor.ChangeMetaState(state, MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key); if (design.DoApplyWeaponVisible()) - _editor.ChangeMetaState(state, ActorState.MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key); + _editor.ChangeMetaState(state, MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key); if (design.DoApplyVisorToggle()) - _editor.ChangeMetaState(state, ActorState.MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key); + _editor.ChangeMetaState(state, MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key); var flags = state.AllowsRedraw(_condition) ? design.ApplyCustomize @@ -419,13 +419,13 @@ public class StateManager( if (!state.ModelData.Customize.Highlights) _editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight, state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse], - state[CustomizeParameterFlag.HairDiffuse], out _, key); + state.Source[CustomizeParameterFlag.HairDiffuse], out _, key); } var actors = ApplyAll(state, redraw, false); Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Design, state[ActorState.MetaIndex.Wetness], state, actors, design); + _event.Invoke(StateChanged.Type.Design, state.Source[MetaIndex.Wetness], state, actors, design); return; void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain) @@ -455,7 +455,7 @@ public class StateManager( _applier.ChangeCustomize(actors, state.ModelData.Customize); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - _applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state[slot, false] is not StateChanged.Source.Ipc, + _applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Source[slot, false] is not StateChanged.Source.Ipc, state.ModelData.IsHatVisible()); } @@ -489,22 +489,22 @@ public class StateManager( state.ModelData = state.BaseData; state.ModelData.SetIsWet(false); foreach (var index in Enum.GetValues()) - state[index] = StateChanged.Source.Game; + state.Source[index] = StateChanged.Source.Game; foreach (var slot in EquipSlotExtensions.FullSlots) { - state[slot, true] = StateChanged.Source.Game; - state[slot, false] = StateChanged.Source.Game; + state.Source[slot, true] = StateChanged.Source.Game; + state.Source[slot, false] = StateChanged.Source.Game; } - foreach (var type in Enum.GetValues()) - state[type] = StateChanged.Source.Game; + foreach (var type in Enum.GetValues()) + state.Source[type] = StateChanged.Source.Game; foreach (var slot in CrestExtensions.AllRelevantSet) - state[slot] = StateChanged.Source.Game; + state.Source[slot] = StateChanged.Source.Game; foreach (var flag in CustomizeParameterExtensions.AllFlags) - state[flag] = StateChanged.Source.Game; + state.Source[flag] = StateChanged.Source.Game; var actors = ActorData.Invalid; if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) @@ -523,7 +523,7 @@ public class StateManager( state.ModelData.Parameters = state.BaseData.Parameters; foreach (var flag in CustomizeParameterExtensions.AllFlags) - state[flag] = StateChanged.Source.Game; + state.Source[flag] = StateChanged.Source.Game; var actors = ActorData.Invalid; if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) @@ -538,69 +538,69 @@ public class StateManager( if (!state.Unlock(key)) return; - foreach (var index in Enum.GetValues().Where(i => state[i] is StateChanged.Source.Fixed)) + foreach (var index in Enum.GetValues().Where(i => state.Source[i] is StateChanged.Source.Fixed)) { - state[index] = StateChanged.Source.Game; + state.Source[index] = StateChanged.Source.Game; state.ModelData.Customize[index] = state.BaseData.Customize[index]; } foreach (var slot in EquipSlotExtensions.FullSlots) { - if (state[slot, true] is StateChanged.Source.Fixed) + if (state.Source[slot, true] is StateChanged.Source.Fixed) { - state[slot, true] = StateChanged.Source.Game; + state.Source[slot, true] = StateChanged.Source.Game; state.ModelData.SetStain(slot, state.BaseData.Stain(slot)); } - if (state[slot, false] is StateChanged.Source.Fixed) + if (state.Source[slot, false] is StateChanged.Source.Fixed) { - state[slot, false] = StateChanged.Source.Game; + state.Source[slot, false] = StateChanged.Source.Game; state.ModelData.SetItem(slot, state.BaseData.Item(slot)); } } foreach (var slot in CrestExtensions.AllRelevantSet) { - if (state[slot] is StateChanged.Source.Fixed) + if (state.Source[slot] is StateChanged.Source.Fixed) { - state[slot] = StateChanged.Source.Game; + state.Source[slot] = StateChanged.Source.Game; state.ModelData.SetCrest(slot, state.BaseData.Crest(slot)); } } foreach (var flag in CustomizeParameterExtensions.AllFlags) { - switch (state[flag]) + switch (state.Source[flag]) { case StateChanged.Source.Fixed: case StateChanged.Source.Manual when !respectManualPalettes: - state[flag] = StateChanged.Source.Game; + state.Source[flag] = StateChanged.Source.Game; state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag]; break; } } - if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed) + if (state.Source[MetaIndex.HatState] is StateChanged.Source.Fixed) { - state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game; + state.Source[MetaIndex.HatState] = StateChanged.Source.Game; state.ModelData.SetHatVisible(state.BaseData.IsHatVisible()); } - if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed) + if (state.Source[MetaIndex.VisorState] is StateChanged.Source.Fixed) { - state[ActorState.MetaIndex.VisorState] = StateChanged.Source.Game; + state.Source[MetaIndex.VisorState] = StateChanged.Source.Game; state.ModelData.SetVisor(state.BaseData.IsVisorToggled()); } - if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed) + if (state.Source[MetaIndex.WeaponState] is StateChanged.Source.Fixed) { - state[ActorState.MetaIndex.WeaponState] = StateChanged.Source.Game; + state.Source[MetaIndex.WeaponState] = StateChanged.Source.Game; state.ModelData.SetWeaponVisible(state.BaseData.IsWeaponVisible()); } - if (state[ActorState.MetaIndex.Wetness] is StateChanged.Source.Fixed) + if (state.Source[MetaIndex.Wetness] is StateChanged.Source.Fixed) { - state[ActorState.MetaIndex.Wetness] = StateChanged.Source.Game; + state.Source[MetaIndex.Wetness] = StateChanged.Source.Game; state.ModelData.SetIsWet(state.BaseData.IsWet()); } } diff --git a/Glamourer/State/StateSource.cs b/Glamourer/State/StateSource.cs new file mode 100644 index 0000000..04c934d --- /dev/null +++ b/Glamourer/State/StateSource.cs @@ -0,0 +1,58 @@ +using Glamourer.GameData; +using Penumbra.GameData.Enums; +using static Glamourer.Events.StateChanged; + +namespace Glamourer.State; + +public enum MetaIndex +{ + Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices, + HatState, + VisorState, + WeaponState, + ModelId, +} + +public readonly struct StateSource +{ + public static readonly int Size = EquipFlagExtensions.NumEquipFlags + + CustomizationExtensions.NumIndices + + 5 + + CrestExtensions.AllRelevantSet.Count + + CustomizeParameterExtensions.AllFlags.Count; + + + private readonly Source[] _data = Enumerable.Repeat(Source.Game, Size).ToArray(); + + public StateSource() + { } + + public ref Source this[EquipSlot slot, bool stain] + => ref _data[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)]; + + public ref Source this[CrestFlag slot] + => ref _data[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()]; + + public ref Source this[CustomizeIndex type] + => ref _data[EquipFlagExtensions.NumEquipFlags + (int)type]; + + public ref Source this[MetaIndex index] + => ref _data[(int)index]; + + public ref Source this[CustomizeParameterFlag flag] + => ref _data[ + EquipFlagExtensions.NumEquipFlags + + CustomizationExtensions.NumIndices + + 5 + + CrestExtensions.AllRelevantSet.Count + + flag.ToInternalIndex()]; + + public void RemoveFixedDesignSources() + { + for (var i = 0; i < _data.Length; ++i) + { + if (_data[i] is Source.Fixed) + _data[i] = Source.Manual; + } + } +} diff --git a/OtterGui b/OtterGui index d734d5d..04eb0b5 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit d734d5d2b0686db0f5f4270dc379360d31f72e59 +Subproject commit 04eb0b5ed3930e9cb87ad00dffa9c8be90b58bb3 From 18683f7d4321d30ca3e8a3aacf423d426102531d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 20 Jan 2024 17:01:57 +0100 Subject: [PATCH 194/786] Update the elephants. --- Glamourer/State/FunModule.cs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index f152528..14fc65a 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -116,8 +116,6 @@ public unsafe class FunModule : IDisposable SetRandomItem(slot, ref armor); break; case CodeService.CodeFlag.Elephants: - SetElephant(slot, ref armor); - break; case CodeService.CodeFlag.World when actor.Index != 0: KeepOldArmor(actor, slot, ref armor); break; @@ -166,8 +164,9 @@ public unsafe class FunModule : IDisposable SetRandomItem(slot, ref armor[idx]); break; case CodeService.CodeFlag.Elephants: - SetElephant(EquipSlot.Body, ref armor[1]); - SetElephant(EquipSlot.Head, ref armor[0]); + var stainId = ElephantStains[_rng.Next(0, ElephantStains.Length)]; + SetElephant(EquipSlot.Body, ref armor[1], stainId); + SetElephant(EquipSlot.Head, ref armor[0], stainId); break; case CodeService.CodeFlag.World when actor.Index != 0: _worldSets.Apply(actor, _rng, armor); @@ -216,12 +215,24 @@ public unsafe class FunModule : IDisposable armor.Variant = item.Variant; } - private static void SetElephant(EquipSlot slot, ref CharacterArmor armor) + private static ReadOnlySpan ElephantStains + => + [ + 87, 87, 87, 87, 87, // Cherry Pink + 83, 83, 83, // Colibri Pink + 80, // Iris Purple + 85, // Regal Purple + 103, // Pastel Pink + 82, 82, 82, // Lotus Pink + 7, // Rose Pink + ]; + + private void SetElephant(EquipSlot slot, ref CharacterArmor armor, StainId stainId) { armor = slot switch { - EquipSlot.Body => new CharacterArmor(6133, 1, 87), - EquipSlot.Head => new CharacterArmor(6133, 1, 87), + EquipSlot.Body => new CharacterArmor(6133, 1, stainId), + EquipSlot.Head => new CharacterArmor(6133, 1, stainId), _ => armor, }; } From 5ec112d896ccaa4f4cf07041a8f49ad8ce08db87 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 21 Jan 2024 00:47:37 +0100 Subject: [PATCH 195/786] Primary constructor. --- Glamourer/State/StateEditor.cs | 39 ++++++++++------------------------ 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index c9f1d58..1d23cff 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,32 +1,15 @@ using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Common.Math; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Services; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Vector3 = FFXIVClientStructs.FFXIV.Common.Math.Vector3; namespace Glamourer.State; -public class StateEditor +public class StateEditor(CustomizeService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition) { - private readonly ItemManager _items; - private readonly CustomizeService _customizations; - private readonly HumanModelList _humans; - private readonly GPoseService _gPose; - private readonly ICondition _condition; - - public StateEditor(CustomizeService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition) - { - _customizations = customizations; - _humans = humans; - _items = items; - _gPose = gPose; - _condition = condition; - } - /// Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. /// We currently only allow changing things to humans, not humans to monsters. public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateChanged.Source source, @@ -42,7 +25,7 @@ public class StateEditor return false; var oldIsHuman = state.ModelData.IsHuman; - state.ModelData.IsHuman = _humans.IsHuman(modelId); + state.ModelData.IsHuman = humans.IsHuman(modelId); if (state.ModelData.IsHuman) { if (oldModelId == modelId) @@ -52,12 +35,12 @@ public class StateEditor if (oldIsHuman) return true; - if (!state.AllowsRedraw(_condition)) + if (!state.AllowsRedraw(condition)) return false; // Fix up everything else to make sure the result is a valid human. state.ModelData.Customize = CustomizeArray.Default; - state.ModelData.SetDefaultEquipment(_items); + state.ModelData.SetDefaultEquipment(items); state.ModelData.SetHatVisible(true); state.ModelData.SetWeaponVisible(true); state.ModelData.SetVisor(false); @@ -73,13 +56,13 @@ public class StateEditor state.Source[CustomizeIndex.Clan] = source; state.Source[CustomizeIndex.Gender] = source; - var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); + var set = customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); foreach (var index in Enum.GetValues().Where(set.IsAvailable)) state.Source[index] = source; } else { - if (!state.AllowsRedraw(_condition)) + if (!state.AllowsRedraw(condition)) return false; state.ModelData.LoadNonHuman(modelId, customize, equipData); @@ -111,7 +94,7 @@ public class StateEditor if (!state.CanUnlock(key)) return false; - (var customize, var applied, changed) = _customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich, true); + (var customize, var applied, changed) = customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich, true); if (changed == 0) return false; @@ -137,11 +120,11 @@ public class StateEditor if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType || slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType) { - if (!_gPose.InGPose) + if (!gPose.InGPose) return false; var old = oldItem; - _gPose.AddActionOnLeave(() => + gPose.AddActionOnLeave(() => { if (old.Type == state.BaseData.Item(slot).Type) ChangeItem(state, slot, old, state.Source[slot, false], out _, key); @@ -166,12 +149,12 @@ public class StateEditor if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType || slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType) { - if (!_gPose.InGPose) + if (!gPose.InGPose) return false; var old = oldItem; var oldS = oldStain; - _gPose.AddActionOnLeave(() => + gPose.AddActionOnLeave(() => { if (old.Type == state.BaseData.Item(slot).Type) ChangeEquip(state, slot, old, oldS, state.Source[slot, false], out _, out _, key); From 2422295e67abb99218aafdbc1813cbcd3d56ebb3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 21 Jan 2024 00:48:02 +0100 Subject: [PATCH 196/786] Support states better in merged designs. --- Glamourer/Designs/Links/DesignMerger.cs | 74 ++++++++++++++++++++----- Glamourer/State/StateManager.cs | 1 - 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 94832f9..bde2f53 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -3,6 +3,7 @@ using Glamourer.Events; using Glamourer.GameData; using Glamourer.Services; using Glamourer.State; +using Glamourer.Unlocks; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -28,14 +29,38 @@ public sealed class MergedDesign public readonly DesignBase Design; public readonly WeaponDict Weapons = new(4); public readonly StateSource Source = new(); + + public StateChanged.Source GetSource(EquipSlot slot, bool stain, StateChanged.Source actualSource) + => GetSource(Source[slot, stain], actualSource); + + public StateChanged.Source GetSource(CrestFlag slot, StateChanged.Source actualSource) + => GetSource(Source[slot], actualSource); + + public StateChanged.Source GetSource(CustomizeIndex type, StateChanged.Source actualSource) + => GetSource(Source[type], actualSource); + + public StateChanged.Source GetSource(MetaIndex index, StateChanged.Source actualSource) + => GetSource(Source[index], actualSource); + + public StateChanged.Source GetSource(CustomizeParameterFlag flag, StateChanged.Source actualSource) + => GetSource(Source[flag], actualSource); + + public static StateChanged.Source GetSource(StateChanged.Source given, StateChanged.Source actualSource) + => given is StateChanged.Source.Game ? StateChanged.Source.Game : actualSource; } -public class DesignMerger(DesignManager designManager, CustomizeService _customize) +public class DesignMerger( + DesignManager designManager, + CustomizeService _customize, + Configuration _config, + ItemUnlockManager _itemUnlocks, + CustomizeUnlockManager _customizeUnlocks) { - public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef) + public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef, bool respectOwnership) { var ret = new MergedDesign(designManager); CustomizeFlag fixFlags = 0; + respectOwnership &= _config.UnlockedItemMode; foreach (var (design, type) in designs) { if (type is 0) @@ -49,10 +74,10 @@ public class DesignMerger(DesignManager designManager, CustomizeService _customi var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyHat, applyVisor, applyWeapon, applyWet) = type.ApplyWhat(design); ReduceMeta(data, applyHat, applyVisor, applyWeapon, applyWet, ret, source); - ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source); - ReduceEquip(data, equipFlags, ret, source); - ReduceMainhands(data, equipFlags, ret, source); - ReduceOffhands(data, equipFlags, ret, source); + ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source, respectOwnership); + ReduceEquip(data, equipFlags, ret, source, respectOwnership); + ReduceMainhands(data, equipFlags, ret, source, respectOwnership); + ReduceOffhands(data, equipFlags, ret, source, respectOwnership); ReduceCrests(data, crestFlags, ret, source); ReduceParameters(data, parameterFlags, ret, source); } @@ -129,7 +154,8 @@ public class DesignMerger(DesignManager designManager, CustomizeService _customi } } - private static void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source) + private void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source, + bool respectOwnership) { equipFlags &= ~ret.Design.ApplyEquip; if (equipFlags == 0) @@ -138,9 +164,12 @@ public class DesignMerger(DesignManager designManager, CustomizeService _customi foreach (var slot in EquipSlotExtensions.EqdpSlots) { var flag = slot.ToFlag(); + if (equipFlags.HasFlag(flag)) { - ret.Design.GetDesignDataRef().SetItem(slot, design.Item(slot)); + var item = design.Item(slot); + if (!respectOwnership || _itemUnlocks.IsUnlocked(item.Id, out _)) + ret.Design.GetDesignDataRef().SetItem(slot, item); ret.Design.SetApplyEquip(slot, true); ret.Source[slot, false] = source; } @@ -166,29 +195,45 @@ public class DesignMerger(DesignManager designManager, CustomizeService _customi } } - private static void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source) + private void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source, bool respectOwnership) { if (!equipFlags.HasFlag(EquipFlag.Mainhand)) return; - ret.Design.SetApplyEquip(EquipSlot.MainHand, true); var weapon = design.Item(EquipSlot.MainHand); + if (respectOwnership && !_itemUnlocks.IsUnlocked(weapon.Id, out _)) + return; + + if (!ret.Design.DoApplyEquip(EquipSlot.MainHand)) + { + ret.Design.SetApplyEquip(EquipSlot.MainHand, true); + ret.Design.GetDesignDataRef().SetItem(EquipSlot.MainHand, weapon); + } + ret.Weapons.TryAdd(weapon.Type, (weapon, source)); } - private static void ReduceOffhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source) + private void ReduceOffhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source, bool respectOwnership) { if (!equipFlags.HasFlag(EquipFlag.Offhand)) return; - ret.Design.SetApplyEquip(EquipSlot.OffHand, true); var weapon = design.Item(EquipSlot.OffHand); + if (respectOwnership && !_itemUnlocks.IsUnlocked(weapon.Id, out _)) + return; + + if (!ret.Design.DoApplyEquip(EquipSlot.OffHand)) + { + ret.Design.SetApplyEquip(EquipSlot.OffHand, true); + ret.Design.GetDesignDataRef().SetItem(EquipSlot.OffHand, weapon); + } + if (weapon.Valid) ret.Weapons.TryAdd(weapon.Type, (weapon, source)); } private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret, - StateChanged.Source source) + StateChanged.Source source, bool respectOwnership) { customizeFlags &= ~ret.Design.ApplyCustomizeRaw; if (customizeFlags == 0) @@ -237,6 +282,9 @@ public class DesignMerger(DesignManager designManager, CustomizeService _customi if (!CustomizeService.IsCustomizationValid(set, face, index, value, out var data)) continue; + if (data.HasValue && respectOwnership && !_customizeUnlocks.IsUnlocked(data.Value, out _)) + continue; + customize[index] = data?.Value ?? value; ret.Design.SetApplyCustomize(index, true); ret.Source[index] = source; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 4bbc595..89b8c24 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -1,5 +1,4 @@ using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Shader; using Glamourer.Designs; using Glamourer.Events; using Glamourer.GameData; From 96c4ae762e567a08eabacc8c28a408e046ba0596 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 21 Jan 2024 00:51:42 +0100 Subject: [PATCH 197/786] Make AutoDesignApplier use MergedDesign. --- Glamourer/Automation/AutoDesignApplier.cs | 118 ++++++++++------------ Glamourer/Designs/Links/DesignMerger.cs | 3 +- Glamourer/Designs/Links/MergedDesign.cs | 45 +++++++++ 3 files changed, 101 insertions(+), 65 deletions(-) create mode 100644 Glamourer/Designs/Links/MergedDesign.cs diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index a92ebe9..54664f1 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -35,20 +35,18 @@ public sealed class AutoDesignApplier : IDisposable private readonly IClientState _clientState; private ActorState? _jobChangeState; - private readonly Dictionary _jobChangeMainhand = []; - private readonly Dictionary _jobChangeOffhand = []; + private readonly Dictionary _jobChange = []; private void ResetJobChange() { _jobChangeState = null; - _jobChangeMainhand.Clear(); - _jobChangeOffhand.Clear(); + _jobChange.Clear(); } public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, CustomizeService customizations, ActorManager actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, - EquippedGearset equippedGearset) + EquippedGearset equippedGearset, DesignMerger designMerger) { _config = config; _manager = manager; @@ -64,6 +62,7 @@ public sealed class AutoDesignApplier : IDisposable _humans = humans; _clientState = clientState; _equippedGearset = equippedGearset; + _designMerger = designMerger; _jobs.JobChanged += OnJobChange; _event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier); _weapons.Subscribe(OnWeaponLoading, WeaponLoading.Priority.AutoDesignApplier); @@ -91,7 +90,7 @@ public sealed class AutoDesignApplier : IDisposable { case EquipSlot.MainHand: { - if (_jobChangeMainhand.TryGetValue(current.Type, out var data)) + if (_jobChange.TryGetValue(current.Type, out var data)) { Glamourer.Log.Verbose( $"Changing Mainhand from {_jobChangeState.ModelData.Weapon(EquipSlot.MainHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); @@ -103,7 +102,7 @@ public sealed class AutoDesignApplier : IDisposable } case EquipSlot.OffHand when current.Type == _jobChangeState.BaseData.MainhandType.Offhand(): { - if (_jobChangeOffhand.TryGetValue(current.Type, out var data)) + if (_jobChange.TryGetValue(current.Type, out var data)) { Glamourer.Log.Verbose( $"Changing Offhand from {_jobChangeState.ModelData.Weapon(EquipSlot.OffHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); @@ -270,11 +269,6 @@ public sealed class AutoDesignApplier : IDisposable private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange) { - EquipFlag totalEquipFlags = 0; - CustomizeFlag totalCustomizeFlags = 0; - CrestFlag totalCrestFlags = 0; - CustomizeParameterFlag totalParameterFlags = 0; - byte totalMetaFlags = 0; if (set.BaseState == AutoDesignSet.Base.Game) _state.ResetStateFixed(state, respectManual); else if (!respectManual) @@ -283,30 +277,8 @@ public sealed class AutoDesignApplier : IDisposable if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) return; - foreach (var design in set.Designs) - { - if (!design.IsActive(actor)) - continue; - - if (design.Type is 0) - continue; - - ref readonly var data = ref design.GetDesignData(state); - var source = design.Revert ? StateChanged.Source.Game : StateChanged.Source.Fixed; - - if (!data.IsHuman) - continue; - - var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat(); - ReduceMeta(state, data, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual, source); - ReduceCustomize(state, data, customizeFlags, ref totalCustomizeFlags, respectManual, source); - ReduceEquip(state, data, equipFlags, ref totalEquipFlags, respectManual, source, fromJobChange); - ReduceCrests(state, data, crestFlags, ref totalCrestFlags, respectManual, source); - ReduceParameters(state, data, parameterFlags, ref totalParameterFlags, respectManual, source); - } - - if (totalCustomizeFlags != 0) - state.ModelData.ModelId = 0; + var mergedDesign = _designMerger.Merge(set.Designs.Where(d => d.IsActive(actor)).Select(d => ((DesignBase?) d.Design, d.Type)), state.ModelData, true); + ApplyToState(state, mergedDesign, respectManual, fromJobChange, StateChanged.Source.Fixed); } /// Get world-specific first and all-world afterward. @@ -332,39 +304,57 @@ public sealed class AutoDesignApplier : IDisposable } } - private void ReduceCrests(ActorState state, in DesignData design, CrestFlag crestFlags, ref CrestFlag totalCrestFlags, bool respectManual, - StateChanged.Source source) + private void ApplyToState(ActorState state, MergedDesign mergedDesign, bool respectManual, bool fromJobChange, StateChanged.Source source) { - crestFlags &= ~totalCrestFlags; - if (crestFlags == 0) - return; - - foreach (var slot in CrestExtensions.AllRelevantSet) - { - if (!crestFlags.HasFlag(slot)) - continue; - + foreach (var slot in CrestExtensions.AllRelevantSet.Where(mergedDesign.Design.DoApplyCrest)) if (!respectManual || state.Source[slot] is not StateChanged.Source.Manual) - _state.ChangeCrest(state, slot, design.Crest(slot), source); - totalCrestFlags |= slot; - } - } + _state.ChangeCrest(state, slot, mergedDesign.Design.DesignData.Crest(slot), mergedDesign.GetSource(slot, source)); - private void ReduceParameters(ActorState state, in DesignData design, CustomizeParameterFlag parameterFlags, - ref CustomizeParameterFlag totalParameterFlags, bool respectManual, StateChanged.Source source) - { - parameterFlags &= ~totalParameterFlags; - if (parameterFlags == 0) - return; + foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate()) + if (!respectManual || state.Source[parameter] is not StateChanged.Source.Manual and not StateChanged.Source.Pending) + _state.ChangeCustomizeParameter(state, parameter, mergedDesign.Design.DesignData.Parameters[parameter], mergedDesign.GetSource(parameter, source)); - foreach (var flag in CustomizeParameterExtensions.AllFlags) + foreach (var slot in EquipSlotExtensions.EqdpSlots) { - if (!parameterFlags.HasFlag(flag)) + if (mergedDesign.Design.DoApplyEquip(slot)) + { + if (!respectManual || state.Source[slot, false] is not StateChanged.Source.Manual) + _state.ChangeItem(state, slot, mergedDesign.Design.DesignData.Item(slot), mergedDesign.GetSource(slot, false, source)); + } + + if (mergedDesign.Design.DoApplyStain(slot)) + { + if (!respectManual || state.Source[slot, true] is not StateChanged.Source.Manual) + _state.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot), mergedDesign.GetSource(slot, true, source)); + } + } + + foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots) + { + if (mergedDesign.Design.DoApplyStain(weaponSlot)) + { + if (!respectManual || state.Source[weaponSlot, true] is not StateChanged.Source.Manual) + _state.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot), mergedDesign.GetSource(weaponSlot, true, source)); + } + + if (!mergedDesign.Design.DoApplyEquip(weaponSlot)) continue; - if (!respectManual || state.Source[flag] is not StateChanged.Source.Manual and not StateChanged.Source.Pending) - _state.ChangeCustomizeParameter(state, flag, design.Parameters[flag], source); - totalParameterFlags |= flag; + if (respectManual && state.Source[weaponSlot, false] is StateChanged.Source.Manual) + continue; + + var currentType = state.ModelData.Item(weaponSlot).Type; + if (fromJobChange) + { + foreach (var (key, (weapon, weaponSource)) in mergedDesign.Weapons) + if (key.ToSlot() == weaponSlot) + _jobChange.TryAdd(key, (weapon, MergedDesign.GetSource(weaponSource, source))); + _jobChangeState = state; + } + else if (mergedDesign.Weapons.TryGetValue(currentType, out var weapon)) + { + _state.ChangeItem(state, weaponSlot, weapon.Item1, MergedDesign.GetSource(weapon.Item2, source)); + } } } @@ -407,7 +397,7 @@ public sealed class AutoDesignApplier : IDisposable { if (fromJobChange) { - _jobChangeMainhand.TryAdd(item.Type, (item, source)); + _jobChange.TryAdd(item.Type, (item, source)); _jobChangeState = state; } else if (state.ModelData.Item(EquipSlot.MainHand).Type == item.Type) @@ -427,7 +417,7 @@ public sealed class AutoDesignApplier : IDisposable { if (fromJobChange) { - _jobChangeOffhand.TryAdd(item.Type, (item, source)); + _jobChange.TryAdd(item.Type, (item, source)); _jobChangeState = state; } else if (state.ModelData.Item(EquipSlot.OffHand).Type == item.Type) diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index bde2f53..abf1163 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -4,6 +4,7 @@ using Glamourer.GameData; using Glamourer.Services; using Glamourer.State; using Glamourer.Unlocks; +using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -54,7 +55,7 @@ public class DesignMerger( CustomizeService _customize, Configuration _config, ItemUnlockManager _itemUnlocks, - CustomizeUnlockManager _customizeUnlocks) + CustomizeUnlockManager _customizeUnlocks) : IService { public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef, bool respectOwnership) { diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs new file mode 100644 index 0000000..eccc46f --- /dev/null +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -0,0 +1,45 @@ +using Glamourer.Events; +using Glamourer.GameData; +using Glamourer.State; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Designs.Links; + +public sealed class MergedDesign +{ + public MergedDesign(DesignManager designManager) + { + Design = designManager.CreateTemporary(); + Design.ApplyEquip = 0; + Design.ApplyCustomize = 0; + Design.ApplyCrest = 0; + Design.ApplyParameters = 0; + Design.SetApplyWetness(false); + Design.SetApplyVisorToggle(false); + Design.SetApplyWeaponVisible(false); + Design.SetApplyHatVisible(false); + } + + public readonly DesignBase Design; + public readonly Dictionary Weapons = new(4); + public readonly StateSource Source = new(); + + public StateChanged.Source GetSource(EquipSlot slot, bool stain, StateChanged.Source actualSource) + => GetSource(Source[slot, stain], actualSource); + + public StateChanged.Source GetSource(CrestFlag slot, StateChanged.Source actualSource) + => GetSource(Source[slot], actualSource); + + public StateChanged.Source GetSource(CustomizeIndex type, StateChanged.Source actualSource) + => GetSource(Source[type], actualSource); + + public StateChanged.Source GetSource(MetaIndex index, StateChanged.Source actualSource) + => GetSource(Source[index], actualSource); + + public StateChanged.Source GetSource(CustomizeParameterFlag flag, StateChanged.Source actualSource) + => GetSource(Source[flag], actualSource); + + public static StateChanged.Source GetSource(StateChanged.Source given, StateChanged.Source actualSource) + => given is StateChanged.Source.Game ? StateChanged.Source.Game : actualSource; +} From e4fc86ca38019b0058099bb3fb32a6857306bb8b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 22 Jan 2024 22:46:51 +0100 Subject: [PATCH 198/786] Protect against an exception but actual fix is in Penumbra. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 1ebaf1d..17d302a 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 1ebaf1d209b7edc783896b3a6af4907c91bb302e +Subproject commit 17d302a7d634cbb2c49c10d5b95236a40dbff370 From 4f9e2244944802f3b5b6c0106e02aeb330ed5c8e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 22 Jan 2024 23:49:56 +0100 Subject: [PATCH 199/786] Move MetaIndex to its first class enum. --- Glamourer/Designs/DesignBase.cs | 111 +++++------------- Glamourer/Designs/DesignBase64Migration.cs | 16 +-- Glamourer/Designs/DesignData.cs | 20 ++++ Glamourer/Designs/Links/DesignMerger.cs | 61 +++------- Glamourer/Designs/Links/MergedDesign.cs | 36 +++++- Glamourer/Designs/MetaIndex.cs | 39 ++++++ .../Interop/PalettePlus/PaletteImport.cs | 1 - Glamourer/State/StateEditor.cs | 1 + Glamourer/State/StateListener.cs | 1 + Glamourer/State/StateSource.cs | 9 -- 10 files changed, 147 insertions(+), 148 deletions(-) create mode 100644 Glamourer/Designs/MetaIndex.cs diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 2363060..a533bc4 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.Internal.Notifications; using Glamourer.GameData; using Glamourer.Services; +using Glamourer.State; using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.GameData.Enums; @@ -34,7 +35,7 @@ public class DesignBase _designData = designData; ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; ApplyEquip = equipFlags & EquipFlagExtensions.All; - _designFlags = 0; + ApplyMeta = 0; CustomizeSet = SetCustomizationSet(customize); } @@ -45,7 +46,7 @@ public class DesignBase ApplyCustomize = clone.ApplyCustomizeRaw; ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; ApplyParameters = clone.ApplyParameters & CustomizeParameterExtensions.All; - _designFlags = clone._designFlags & (DesignFlags)0x0F; + ApplyMeta = clone.ApplyMeta & MetaExtensions.All; } /// Ensure that the customization set is updated when the design data changes. @@ -57,16 +58,6 @@ public class DesignBase #region Application Data - [Flags] - private enum DesignFlags : byte - { - ApplyHatVisible = 0x01, - ApplyVisorState = 0x02, - ApplyWeaponVisible = 0x04, - ApplyWetness = 0x08, - WriteProtected = 0x10, - } - private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; public CustomizeSet CustomizeSet { get; private set; } @@ -84,9 +75,10 @@ public class DesignBase internal CustomizeFlag ApplyCustomizeRaw => _applyCustomize; - internal EquipFlag ApplyEquip = EquipFlagExtensions.All; - internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; - private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; + internal EquipFlag ApplyEquip = EquipFlagExtensions.All; + internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; + internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState; + private bool _writeProtected; public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize) { @@ -98,69 +90,30 @@ public class DesignBase return true; } - public bool DoApplyHatVisible() - => _designFlags.HasFlag(DesignFlags.ApplyHatVisible); - - public bool DoApplyVisorToggle() - => _designFlags.HasFlag(DesignFlags.ApplyVisorState); - - public bool DoApplyWeaponVisible() - => _designFlags.HasFlag(DesignFlags.ApplyWeaponVisible); - - public bool DoApplyWetness() - => _designFlags.HasFlag(DesignFlags.ApplyWetness); + public bool DoApplyMeta(MetaIndex index) + => ApplyMeta.HasFlag(index.ToFlag()); public bool WriteProtected() - => _designFlags.HasFlag(DesignFlags.WriteProtected); + => _writeProtected; - public bool SetApplyHatVisible(bool value) + public bool SetApplyMeta(MetaIndex index, bool value) { - var newFlag = value ? _designFlags | DesignFlags.ApplyHatVisible : _designFlags & ~DesignFlags.ApplyHatVisible; - if (newFlag == _designFlags) + var newFlag = value ? ApplyMeta | index.ToFlag() : ApplyMeta & ~index.ToFlag(); + if (newFlag == ApplyMeta) return false; - _designFlags = newFlag; - return true; - } - - public bool SetApplyVisorToggle(bool value) - { - var newFlag = value ? _designFlags | DesignFlags.ApplyVisorState : _designFlags & ~DesignFlags.ApplyVisorState; - if (newFlag == _designFlags) - return false; - - _designFlags = newFlag; - return true; - } - - public bool SetApplyWeaponVisible(bool value) - { - var newFlag = value ? _designFlags | DesignFlags.ApplyWeaponVisible : _designFlags & ~DesignFlags.ApplyWeaponVisible; - if (newFlag == _designFlags) - return false; - - _designFlags = newFlag; - return true; - } - - public bool SetApplyWetness(bool value) - { - var newFlag = value ? _designFlags | DesignFlags.ApplyWetness : _designFlags & ~DesignFlags.ApplyWetness; - if (newFlag == _designFlags) - return false; - - _designFlags = newFlag; + ApplyMeta = newFlag; return true; } public bool SetWriteProtected(bool value) { - var newFlag = value ? _designFlags | DesignFlags.WriteProtected : _designFlags & ~DesignFlags.WriteProtected; - if (newFlag == _designFlags) + if (value == _writeProtected) return false; - _designFlags = newFlag; + _writeProtected = value; return true; + } public bool DoApplyEquip(EquipSlot slot) @@ -298,9 +251,9 @@ public class DesignBase ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot)); } - ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply"); - ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply"); - ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply"); + ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply"); + ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyMeta(MetaIndex.VisorState)).ToJObject("IsToggled", "Apply"); + ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyMeta(MetaIndex.WeaponState)).ToJObject("Show", "Apply"); } else { @@ -344,7 +297,7 @@ public class DesignBase ret["Wetness"] = new JObject() { ["Value"] = _designData.IsWet(), - ["Apply"] = DoApplyWetness(), + ["Apply"] = DoApplyMeta(MetaIndex.Wetness), }; return ret; @@ -478,7 +431,7 @@ public class DesignBase // Load the token and set application. bool TryGetToken(CustomizeParameterFlag flag, [NotNullWhen(true)] out JToken? token) { - token = parameters![flag.ToString()]; + token = parameters[flag.ToString()]; if (token != null) { var apply = token["Apply"]?.ToObject() ?? false; @@ -493,8 +446,8 @@ public class DesignBase void MigrateLipOpacity() { - var token = parameters!["LipOpacity"]?["Percentage"]?.ToObject(); - var actualToken = parameters![CustomizeParameterFlag.LipDiffuse.ToString()]?["Alpha"]; + var token = parameters["LipOpacity"]?["Percentage"]?.ToObject(); + var actualToken = parameters[CustomizeParameterFlag.LipDiffuse.ToString()]?["Alpha"]; if (token != null && actualToken == null) design.GetDesignDataRef().Parameters.LipDiffuse.W = token.Value; } @@ -575,15 +528,15 @@ public class DesignBase design.SetApplyCrest(CrestFlag.OffHand, applyCrestOff); } var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse); - design.SetApplyHatVisible(metaValue.Enabled); + design.SetApplyMeta(MetaIndex.HatState, metaValue.Enabled); design._designData.SetHatVisible(metaValue.ForcedValue); metaValue = QuadBool.FromJObject(equip["Weapon"], "Show", "Apply", QuadBool.NullFalse); - design.SetApplyWeaponVisible(metaValue.Enabled); + design.SetApplyMeta(MetaIndex.WeaponState, metaValue.Enabled); design._designData.SetWeaponVisible(metaValue.ForcedValue); metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse); - design.SetApplyVisorToggle(metaValue.Enabled); + design.SetApplyMeta(MetaIndex.VisorState, metaValue.Enabled); design._designData.SetVisor(metaValue.ForcedValue); } @@ -610,7 +563,7 @@ public class DesignBase var wetness = QuadBool.FromJObject(json["Wetness"], "Value", "Apply", QuadBool.NullFalse); design._designData.SetIsWet(wetness.ForcedValue); - design.SetApplyWetness(wetness.Enabled); + design.SetApplyMeta(MetaIndex.Wetness, wetness.Enabled); design._designData.ModelId = json["ModelId"]?.ToObject() ?? 0; PrintWarning(customizations.ValidateModelId(design._designData.ModelId, out design._designData.ModelId, @@ -664,17 +617,13 @@ public class DesignBase try { _designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags, - out var writeProtected, - out var applyHat, out var applyVisor, out var applyWeapon); + out var writeProtected, out var applyMeta); ApplyEquip = equipFlags; ApplyCustomize = customizeFlags; ApplyParameters = 0; ApplyCrest = 0; + ApplyMeta = applyMeta; SetWriteProtected(writeProtected); - SetApplyHatVisible(applyHat); - SetApplyVisorToggle(applyVisor); - SetApplyWeaponVisible(applyWeapon); - SetApplyWetness(true); CustomizeSet = SetCustomizationSet(customize); } catch (Exception ex) diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index 2d85924..ec4beb1 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,4 +1,5 @@ using Glamourer.Services; +using Glamourer.State; using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -13,7 +14,7 @@ public class DesignBase64Migration public const int Base64SizeV4 = 95; public static unsafe DesignData MigrateBase64(ItemManager items, HumanModelList humans, string base64, out EquipFlag equipFlags, - out CustomizeFlag customizeFlags, out bool writeProtected, out bool applyHat, out bool applyVisor, out bool applyWeapon) + out CustomizeFlag customizeFlags, out bool writeProtected, out MetaFlag metaFlags) { static void CheckSize(int length, int requiredLength) { @@ -25,9 +26,7 @@ public class DesignBase64Migration byte applicationFlags; ushort equipFlagsS; var bytes = Convert.FromBase64String(base64); - applyHat = false; - applyVisor = false; - applyWeapon = false; + metaFlags = MetaFlag.Wetness; var data = new DesignData(); switch (bytes[0]) { @@ -77,9 +76,12 @@ public class DesignBase64Migration customizeFlags = (applicationFlags & 0x01) != 0 ? CustomizeFlagExtensions.All : 0; data.SetIsWet((applicationFlags & 0x02) != 0); - applyHat = (applicationFlags & 0x04) != 0; - applyWeapon = (applicationFlags & 0x08) != 0; - applyVisor = (applicationFlags & 0x10) != 0; + if ((applicationFlags & 0x04) != 0) + metaFlags |= MetaFlag.HatState; + if ((applicationFlags & 0x08) != 0) + metaFlags |= MetaFlag.WeaponState; + if ((applicationFlags & 0x10) != 0) + metaFlags |= MetaFlag.VisorState; writeProtected = (applicationFlags & 0x20) != 0; equipFlags = 0; diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index edc35e1..5b573d3 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -186,6 +186,26 @@ public unsafe struct DesignData return true; } + public readonly bool GetMeta(MetaIndex index) + => index switch + { + MetaIndex.Wetness => IsWet(), + MetaIndex.HatState => IsHatVisible(), + MetaIndex.VisorState => IsVisorToggled(), + MetaIndex.WeaponState => IsWeaponVisible(), + _ => false, + }; + + public bool SetMeta(MetaIndex index, bool value) + => index switch + { + MetaIndex.Wetness => SetIsWet(value), + MetaIndex.HatState => SetHatVisible(value), + MetaIndex.VisorState => SetVisor(value), + MetaIndex.WeaponState => SetWeaponVisible(value), + _ => false, + }; + public readonly bool IsWet() => (_states & 0x01) == 0x01; diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index abf1163..2aef9bc 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -6,50 +6,9 @@ using Glamourer.State; using Glamourer.Unlocks; using OtterGui.Services; using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; namespace Glamourer.Designs.Links; -using WeaponDict = Dictionary; - -public sealed class MergedDesign -{ - public MergedDesign(DesignManager designManager) - { - Design = designManager.CreateTemporary(); - Design.ApplyEquip = 0; - Design.ApplyCustomize = 0; - Design.ApplyCrest = 0; - Design.ApplyParameters = 0; - Design.SetApplyWetness(false); - Design.SetApplyVisorToggle(false); - Design.SetApplyWeaponVisible(false); - Design.SetApplyHatVisible(false); - } - - public readonly DesignBase Design; - public readonly WeaponDict Weapons = new(4); - public readonly StateSource Source = new(); - - public StateChanged.Source GetSource(EquipSlot slot, bool stain, StateChanged.Source actualSource) - => GetSource(Source[slot, stain], actualSource); - - public StateChanged.Source GetSource(CrestFlag slot, StateChanged.Source actualSource) - => GetSource(Source[slot], actualSource); - - public StateChanged.Source GetSource(CustomizeIndex type, StateChanged.Source actualSource) - => GetSource(Source[type], actualSource); - - public StateChanged.Source GetSource(MetaIndex index, StateChanged.Source actualSource) - => GetSource(Source[index], actualSource); - - public StateChanged.Source GetSource(CustomizeParameterFlag flag, StateChanged.Source actualSource) - => GetSource(Source[flag], actualSource); - - public static StateChanged.Source GetSource(StateChanged.Source given, StateChanged.Source actualSource) - => given is StateChanged.Source.Game ? StateChanged.Source.Game : actualSource; -} - public class DesignMerger( DesignManager designManager, CustomizeService _customize, @@ -57,7 +16,8 @@ public class DesignMerger( ItemUnlockManager _itemUnlocks, CustomizeUnlockManager _customizeUnlocks) : IService { - public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef, bool respectOwnership) + public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef, bool respectOwnership, + bool modAssociations) { var ret = new MergedDesign(designManager); CustomizeFlag fixFlags = 0; @@ -81,6 +41,7 @@ public class DesignMerger( ReduceOffhands(data, equipFlags, ret, source, respectOwnership); ReduceCrests(data, crestFlags, ret, source); ReduceParameters(data, parameterFlags, ret, source); + ReduceMods(design as Design, ret, modAssociations); } ApplyFixFlags(ret, fixFlags); @@ -88,6 +49,15 @@ public class DesignMerger( } + private static void ReduceMods(Design? design, MergedDesign ret, bool modAssociations) + { + if (design == null || !modAssociations) + return; + + foreach (var (mod, settings) in design.AssociatedMods) + ret.AssociatedMods.TryAdd(mod, settings); + } + private static void ReduceMeta(in DesignData design, bool applyHat, bool applyVisor, bool applyWeapon, bool applyWet, MergedDesign ret, StateChanged.Source source) { @@ -196,7 +166,8 @@ public class DesignMerger( } } - private void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source, bool respectOwnership) + private void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source, + bool respectOwnership) { if (!equipFlags.HasFlag(EquipFlag.Mainhand)) return; @@ -271,7 +242,7 @@ public class DesignMerger( ret.Source[CustomizeIndex.Face] = source; } - var set = _customize.Manager.GetSet(customize.Clan, customize.Gender); + var set = ret.Design.CustomizeSet; var face = customize.Face; foreach (var index in Enum.GetValues()) { @@ -291,6 +262,8 @@ public class DesignMerger( ret.Source[index] = source; fixFlags &= ~flag; } + + ret.Design.SetCustomize(_customize, customize); } private static void ApplyFixFlags(MergedDesign ret, CustomizeFlag fixFlags) diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index eccc46f..d4b3cab 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -1,5 +1,6 @@ using Glamourer.Events; using Glamourer.GameData; +using Glamourer.Interop.Penumbra; using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -15,15 +16,38 @@ public sealed class MergedDesign Design.ApplyCustomize = 0; Design.ApplyCrest = 0; Design.ApplyParameters = 0; - Design.SetApplyWetness(false); - Design.SetApplyVisorToggle(false); - Design.SetApplyWeaponVisible(false); - Design.SetApplyHatVisible(false); + Design.ApplyMeta = 0; + } + + public MergedDesign(DesignBase design) + { + Design = design; + if (design.DoApplyEquip(EquipSlot.MainHand)) + { + var weapon = design.DesignData.Item(EquipSlot.MainHand); + if (weapon.Valid) + Weapons.TryAdd(weapon.Type, (weapon, StateChanged.Source.Manual)); + } + + if (design.DoApplyEquip(EquipSlot.OffHand)) + { + var weapon = design.DesignData.Item(EquipSlot.OffHand); + if (weapon.Valid) + Weapons.TryAdd(weapon.Type, (weapon, StateChanged.Source.Manual)); + } + } + + public MergedDesign(Design design) + : this((DesignBase)design) + { + foreach (var (mod, settings) in design.AssociatedMods) + AssociatedMods[mod] = settings; } public readonly DesignBase Design; - public readonly Dictionary Weapons = new(4); - public readonly StateSource Source = new(); + public readonly Dictionary Weapons = new(4); + public readonly StateSource Source = new(); + public readonly SortedList AssociatedMods = []; public StateChanged.Source GetSource(EquipSlot slot, bool stain, StateChanged.Source actualSource) => GetSource(Source[slot, stain], actualSource); diff --git a/Glamourer/Designs/MetaIndex.cs b/Glamourer/Designs/MetaIndex.cs new file mode 100644 index 0000000..4fac98d --- /dev/null +++ b/Glamourer/Designs/MetaIndex.cs @@ -0,0 +1,39 @@ +using Penumbra.GameData.Enums; + +namespace Glamourer.State; + +public enum MetaIndex +{ + Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices, + HatState, + VisorState, + WeaponState, + ModelId, +} + +[Flags] +public enum MetaFlag : byte +{ + Wetness = 0x01, + HatState = 0x02, + VisorState = 0x04, + WeaponState = 0x08, +} + +public static class MetaExtensions +{ + public static readonly IReadOnlyList AllRelevant = + [MetaIndex.Wetness, MetaIndex.HatState, MetaIndex.VisorState, MetaIndex.WeaponState]; + + public const MetaFlag All = MetaFlag.Wetness | MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState; + + public static MetaFlag ToFlag(this MetaIndex index) + => index switch + { + MetaIndex.Wetness => MetaFlag.Wetness, + MetaIndex.HatState => MetaFlag.HatState, + MetaIndex.VisorState => MetaFlag.VisorState, + MetaIndex.WeaponState => MetaFlag.WeaponState, + _ => (MetaFlag) byte.MaxValue, + }; +} diff --git a/Glamourer/Interop/PalettePlus/PaletteImport.cs b/Glamourer/Interop/PalettePlus/PaletteImport.cs index b26f8f9..8513036 100644 --- a/Glamourer/Interop/PalettePlus/PaletteImport.cs +++ b/Glamourer/Interop/PalettePlus/PaletteImport.cs @@ -1,7 +1,6 @@ using Dalamud.Plugin; using Glamourer.Designs; using Glamourer.GameData; -using Glamourer.State; using Newtonsoft.Json.Linq; using OtterGui.Services; diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 1d23cff..abb3716 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,4 +1,5 @@ using Dalamud.Plugin.Services; +using Glamourer.Designs; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Services; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d529206..a8c8dd5 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -11,6 +11,7 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; using Glamourer.GameData; using Penumbra.GameData.DataContainers; +using Glamourer.Designs; namespace Glamourer.State; diff --git a/Glamourer/State/StateSource.cs b/Glamourer/State/StateSource.cs index 04c934d..80899d2 100644 --- a/Glamourer/State/StateSource.cs +++ b/Glamourer/State/StateSource.cs @@ -4,15 +4,6 @@ using static Glamourer.Events.StateChanged; namespace Glamourer.State; -public enum MetaIndex -{ - Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices, - HatState, - VisorState, - WeaponState, - ModelId, -} - public readonly struct StateSource { public static readonly int Size = EquipFlagExtensions.NumEquipFlags From 70e4833fb5355b4b5e0933bdcea775f18be2bb22 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 22 Jan 2024 23:50:19 +0100 Subject: [PATCH 200/786] Fix customize parameters for NPC target application. --- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index a406a7b..24382d5 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -219,8 +219,8 @@ public class NpcPanel( if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); - var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest, applyParameters); + var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags(); + var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0); _state.ApplyDesign(design, state, StateChanged.Source.Manual); } } From 1ad70541d3c7c7dd11f71ffcf5b10fb73ab99bba Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 23 Jan 2024 18:02:53 +0100 Subject: [PATCH 201/786] Reworked all of the meta, made StateIndex its own thing. --- Glamourer/Api/GlamourerIpc.Apply.cs | 3 +- Glamourer/Api/GlamourerIpc.Events.cs | 2 +- Glamourer/Api/GlamourerIpc.Revert.cs | 3 +- Glamourer/Api/GlamourerIpc.Set.cs | 5 +- Glamourer/Automation/ApplicationType.cs | 20 +- Glamourer/Automation/AutoDesign.cs | 3 +- Glamourer/Automation/AutoDesignApplier.cs | 89 +++---- Glamourer/Designs/DesignBase64Migration.cs | 9 +- Glamourer/Designs/DesignConverter.cs | 18 +- Glamourer/Designs/DesignManager.cs | 20 +- Glamourer/Designs/Links/DesignMerger.cs | 86 +++---- Glamourer/Designs/Links/MergedDesign.cs | 36 +-- Glamourer/Designs/MetaIndex.cs | 36 ++- Glamourer/Events/StateChanged.cs | 91 +++---- .../CustomizeParameterDrawData.cs | 2 +- Glamourer/Gui/DesignQuickBar.cs | 6 +- Glamourer/Gui/Equipment/EquipDrawData.cs | 4 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 12 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 18 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 24 +- .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 27 +- .../Gui/Tabs/DebugTab/DesignTesterPanel.cs | 6 +- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 6 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 41 ++- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 4 +- Glamourer/Gui/ToggleDrawData.cs | 69 ++--- Glamourer/Interop/ContextMenuService.cs | 12 +- Glamourer/Services/CommandService.cs | 6 +- Glamourer/State/ActorState.cs | 4 +- Glamourer/State/StateApplier.cs | 92 +++---- Glamourer/State/StateEditor.cs | 73 +++--- Glamourer/State/StateIndex.cs | 235 ++++++++++++++++++ Glamourer/State/StateListener.cs | 72 +++--- Glamourer/State/StateManager.cs | 176 ++++++------- Glamourer/State/StateSource.cs | 92 ++++--- 36 files changed, 747 insertions(+), 657 deletions(-) create mode 100644 Glamourer/State/StateIndex.cs diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs index 47c655e..08ccff4 100644 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ b/Glamourer/Api/GlamourerIpc.Apply.cs @@ -3,6 +3,7 @@ using Dalamud.Plugin; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop.Structs; +using Glamourer.State; using Penumbra.Api.Helpers; using Penumbra.GameData.Actors; @@ -130,7 +131,7 @@ public partial class GlamourerIpc if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode)) { - _stateManager.ApplyDesign(design, state, StateChanged.Source.Ipc, lockCode); + _stateManager.ApplyDesign(design, state, StateSource.Ipc, lockCode); state.Lock(lockCode); } } diff --git a/Glamourer/Api/GlamourerIpc.Events.cs b/Glamourer/Api/GlamourerIpc.Events.cs index 44f0775..8caa495 100644 --- a/Glamourer/Api/GlamourerIpc.Events.cs +++ b/Glamourer/Api/GlamourerIpc.Events.cs @@ -15,7 +15,7 @@ public partial class GlamourerIpc private readonly EventProvider> _stateChangedProvider; private readonly EventProvider _gPoseChangedProvider; - private void OnStateChanged(StateChanged.Type type, StateChanged.Source source, ActorState state, ActorData actors, object? data = null) + 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))); diff --git a/Glamourer/Api/GlamourerIpc.Revert.cs b/Glamourer/Api/GlamourerIpc.Revert.cs index 8bf47cc..44a03aa 100644 --- a/Glamourer/Api/GlamourerIpc.Revert.cs +++ b/Glamourer/Api/GlamourerIpc.Revert.cs @@ -1,6 +1,7 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using Glamourer.Events; +using Glamourer.State; using Penumbra.Api.Helpers; using Penumbra.GameData.Actors; @@ -82,7 +83,7 @@ public partial class GlamourerIpc foreach (var id in actors) { if (_stateManager.TryGetValue(id, out var state)) - _stateManager.ResetState(state, StateChanged.Source.Ipc, lockCode); + _stateManager.ResetState(state, StateSource.Ipc, lockCode); } } diff --git a/Glamourer/Api/GlamourerIpc.Set.cs b/Glamourer/Api/GlamourerIpc.Set.cs index 1a39bea..983fd71 100644 --- a/Glamourer/Api/GlamourerIpc.Set.cs +++ b/Glamourer/Api/GlamourerIpc.Set.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin; using Glamourer.Events; using Glamourer.Services; +using Glamourer.State; using Penumbra.Api.Helpers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -55,7 +56,7 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key); + _stateManager.ChangeEquip(state, slot, item, stainId, StateSource.Ipc, key); return GlamourerErrorCode.Success; } @@ -82,7 +83,7 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key); + _stateManager.ChangeEquip(state, slot, item, stainId, StateSource.Ipc, key); found = true; } diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs index e4866de..e99d948 100644 --- a/Glamourer/Automation/ApplicationType.cs +++ b/Glamourer/Automation/ApplicationType.cs @@ -18,9 +18,8 @@ public enum ApplicationType : byte public static class ApplicationTypeExtensions { - public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor, - bool - ApplyWeapon, bool ApplyWet) ApplyWhat(this ApplicationType type, DesignBase? design) + public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat( + this ApplicationType type, DesignBase? design) { var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0) | (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0) @@ -29,18 +28,15 @@ public static class ApplicationTypeExtensions var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0; var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0; var crestFlag = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0; + var metaFlag = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0) + | (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0) + | (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0); if (design == null) - return (equipFlags, customizeFlags, crestFlag, parameterFlags, type.HasFlag(ApplicationType.Armor), - type.HasFlag(ApplicationType.Armor), - type.HasFlag(ApplicationType.Weapons), type.HasFlag(ApplicationType.Customizations)); + return (equipFlags, customizeFlags, crestFlag, parameterFlags, metaFlag); return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest, - parameterFlags & design.ApplyParameters, - type.HasFlag(ApplicationType.Armor) && design.DoApplyHatVisible(), - type.HasFlag(ApplicationType.Armor) && design.DoApplyVisorToggle(), - type.HasFlag(ApplicationType.Weapons) && design.DoApplyWeaponVisible(), - type.HasFlag(ApplicationType.Customizations) && design.DoApplyWetness()); + parameterFlags & design.ApplyParameters, metaFlag & design.ApplyMeta); } public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand; @@ -59,4 +55,4 @@ public static class ApplicationTypeExtensions | EquipFlag.WristStain | EquipFlag.RFingerStain | EquipFlag.LFingerStain; -} \ No newline at end of file +} diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 9d709ab..50704f4 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -68,7 +68,6 @@ public class AutoDesign return ret; } - public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor, bool - ApplyWeapon, bool ApplyWet) ApplyWhat() + public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat() => Type.ApplyWhat(Design); } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 54664f1..db4058f 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -35,7 +35,7 @@ public sealed class AutoDesignApplier : IDisposable private readonly IClientState _clientState; private ActorState? _jobChangeState; - private readonly Dictionary _jobChange = []; + private readonly Dictionary _jobChange = []; private void ResetJobChange() { @@ -183,7 +183,7 @@ public sealed class AutoDesignApplier : IDisposable } else if (_state.TryGetValue(id, out var state)) { - state.Source.RemoveFixedDesignSources(); + state.Sources.RemoveFixedDesignSources(); } } } @@ -197,9 +197,9 @@ public sealed class AutoDesignApplier : IDisposable { if (id.Type is IdentifierType.Player && id.HomeWorld == WorldId.AnyWorld) foreach (var state in _state.Where(kvp => kvp.Key.PlayerName == id.PlayerName).Select(kvp => kvp.Value)) - state.Source.RemoveFixedDesignSources(); + state.Sources.RemoveFixedDesignSources(); else if (_state.TryGetValue(id, out var state)) - state.Source.RemoveFixedDesignSources(); + state.Sources.RemoveFixedDesignSources(); } } } @@ -256,13 +256,13 @@ public sealed class AutoDesignApplier : IDisposable else if (!GetPlayerSet(identifier, out set!)) { if (state.UpdateTerritory(_clientState.TerritoryType) && _config.RevertManualChangesOnZoneChange) - _state.ResetState(state, StateChanged.Source.Game); + _state.ResetState(state, StateSource.Game); return true; } var respectManual = !state.UpdateTerritory(_clientState.TerritoryType) || !_config.RevertManualChangesOnZoneChange; if (!respectManual) - _state.ResetState(state, StateChanged.Source.Game); + _state.ResetState(state, StateSource.Game); Reduce(actor, state, set, respectManual, false); return true; } @@ -272,13 +272,13 @@ public sealed class AutoDesignApplier : IDisposable if (set.BaseState == AutoDesignSet.Base.Game) _state.ResetStateFixed(state, respectManual); else if (!respectManual) - state.Source.RemoveFixedDesignSources(); + state.Sources.RemoveFixedDesignSources(); if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) return; - var mergedDesign = _designMerger.Merge(set.Designs.Where(d => d.IsActive(actor)).Select(d => ((DesignBase?) d.Design, d.Type)), state.ModelData, true); - ApplyToState(state, mergedDesign, respectManual, fromJobChange, StateChanged.Source.Fixed); + var mergedDesign = _designMerger.Merge(set.Designs.Where(d => d.IsActive(actor)).Select(d => ((DesignBase?) d.Design, d.Type)), state.ModelData, true, false); + ApplyToState(state, mergedDesign, respectManual, fromJobChange, StateSource.Fixed); } /// Get world-specific first and all-world afterward. @@ -304,27 +304,27 @@ public sealed class AutoDesignApplier : IDisposable } } - private void ApplyToState(ActorState state, MergedDesign mergedDesign, bool respectManual, bool fromJobChange, StateChanged.Source source) + private void ApplyToState(ActorState state, MergedDesign mergedDesign, bool respectManual, bool fromJobChange, StateSource source) { foreach (var slot in CrestExtensions.AllRelevantSet.Where(mergedDesign.Design.DoApplyCrest)) - if (!respectManual || state.Source[slot] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[slot] is not StateSource.Manual) _state.ChangeCrest(state, slot, mergedDesign.Design.DesignData.Crest(slot), mergedDesign.GetSource(slot, source)); foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate()) - if (!respectManual || state.Source[parameter] is not StateChanged.Source.Manual and not StateChanged.Source.Pending) + if (!respectManual || state.Sources[parameter] is not StateSource.Manual and not StateSource.Pending) _state.ChangeCustomizeParameter(state, parameter, mergedDesign.Design.DesignData.Parameters[parameter], mergedDesign.GetSource(parameter, source)); foreach (var slot in EquipSlotExtensions.EqdpSlots) { if (mergedDesign.Design.DoApplyEquip(slot)) { - if (!respectManual || state.Source[slot, false] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[slot, false] is not StateSource.Manual) _state.ChangeItem(state, slot, mergedDesign.Design.DesignData.Item(slot), mergedDesign.GetSource(slot, false, source)); } if (mergedDesign.Design.DoApplyStain(slot)) { - if (!respectManual || state.Source[slot, true] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[slot, true] is not StateSource.Manual) _state.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot), mergedDesign.GetSource(slot, true, source)); } } @@ -333,14 +333,14 @@ public sealed class AutoDesignApplier : IDisposable { if (mergedDesign.Design.DoApplyStain(weaponSlot)) { - if (!respectManual || state.Source[weaponSlot, true] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[weaponSlot, true] is not StateSource.Manual) _state.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot), mergedDesign.GetSource(weaponSlot, true, source)); } if (!mergedDesign.Design.DoApplyEquip(weaponSlot)) continue; - if (respectManual && state.Source[weaponSlot, false] is StateChanged.Source.Manual) + if (respectManual && state.Sources[weaponSlot, false] is StateSource.Manual) continue; var currentType = state.ModelData.Item(weaponSlot).Type; @@ -359,7 +359,7 @@ public sealed class AutoDesignApplier : IDisposable } private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual, - StateChanged.Source source, bool fromJobChange) + StateSource source, bool fromJobChange) { equipFlags &= ~totalEquipFlags; if (equipFlags == 0) @@ -373,7 +373,7 @@ public sealed class AutoDesignApplier : IDisposable var item = design.Item(slot); if (!_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _)) { - if (!respectManual || state.Source[slot, false] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[slot, false] is not StateSource.Manual) _state.ChangeItem(state, slot, item, source); totalEquipFlags |= flag; } @@ -382,7 +382,7 @@ public sealed class AutoDesignApplier : IDisposable var stainFlag = slot.ToStainFlag(); if (equipFlags.HasFlag(stainFlag)) { - if (!respectManual || state.Source[slot, true] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[slot, true] is not StateSource.Manual) _state.ChangeStain(state, slot, design.Stain(slot), source); totalEquipFlags |= stainFlag; } @@ -392,7 +392,7 @@ public sealed class AutoDesignApplier : IDisposable { var item = design.Item(EquipSlot.MainHand); var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); - var checkState = !respectManual || state.Source[EquipSlot.MainHand, false] is not StateChanged.Source.Manual; + var checkState = !respectManual || state.Sources[EquipSlot.MainHand, false] is not StateSource.Manual; if (checkUnlock && checkState) { if (fromJobChange) @@ -412,7 +412,7 @@ public sealed class AutoDesignApplier : IDisposable { var item = design.Item(EquipSlot.OffHand); var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); - var checkState = !respectManual || state.Source[EquipSlot.OffHand, false] is not StateChanged.Source.Manual; + var checkState = !respectManual || state.Sources[EquipSlot.OffHand, false] is not StateSource.Manual; if (checkUnlock && checkState) { if (fromJobChange) @@ -430,21 +430,21 @@ public sealed class AutoDesignApplier : IDisposable if (equipFlags.HasFlag(EquipFlag.MainhandStain)) { - if (!respectManual || state.Source[EquipSlot.MainHand, true] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[EquipSlot.MainHand, true] is not StateSource.Manual) _state.ChangeStain(state, EquipSlot.MainHand, design.Stain(EquipSlot.MainHand), source); totalEquipFlags |= EquipFlag.MainhandStain; } if (equipFlags.HasFlag(EquipFlag.OffhandStain)) { - if (!respectManual || state.Source[EquipSlot.OffHand, true] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[EquipSlot.OffHand, true] is not StateSource.Manual) _state.ChangeStain(state, EquipSlot.OffHand, design.Stain(EquipSlot.OffHand), source); totalEquipFlags |= EquipFlag.OffhandStain; } } private void ReduceCustomize(ActorState state, in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag totalCustomizeFlags, - bool respectManual, StateChanged.Source source) + bool respectManual, StateSource source) { customizeFlags &= ~totalCustomizeFlags; if (customizeFlags == 0) @@ -459,7 +459,7 @@ public sealed class AutoDesignApplier : IDisposable if (customizeFlags.HasFlag(CustomizeFlag.Clan)) { - if (!respectManual || state.Source[CustomizeIndex.Clan] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[CustomizeIndex.Clan] is not StateSource.Manual) fixFlags |= _customizations.ChangeClan(ref customize, design.Customize.Clan); customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race); totalCustomizeFlags |= CustomizeFlag.Clan | CustomizeFlag.Race; @@ -467,7 +467,7 @@ public sealed class AutoDesignApplier : IDisposable if (customizeFlags.HasFlag(CustomizeFlag.Gender)) { - if (!respectManual || state.Source[CustomizeIndex.Gender] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[CustomizeIndex.Gender] is not StateSource.Manual) fixFlags |= _customizations.ChangeGender(ref customize, design.Customize.Gender); customizeFlags &= ~CustomizeFlag.Gender; totalCustomizeFlags |= CustomizeFlag.Gender; @@ -478,7 +478,7 @@ public sealed class AutoDesignApplier : IDisposable if (customizeFlags.HasFlag(CustomizeFlag.Face)) { - if (!respectManual || state.Source[CustomizeIndex.Face] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[CustomizeIndex.Face] is not StateSource.Manual) _state.ChangeCustomize(state, CustomizeIndex.Face, design.Customize.Face, source); customizeFlags &= ~CustomizeFlag.Face; totalCustomizeFlags |= CustomizeFlag.Face; @@ -498,42 +498,21 @@ public sealed class AutoDesignApplier : IDisposable if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _)) continue; - if (!respectManual || state.Source[index] is not StateChanged.Source.Manual) + if (!respectManual || state.Sources[index] is not StateSource.Manual) _state.ChangeCustomize(state, index, value, source); totalCustomizeFlags |= flag; } } } - private void ReduceMeta(ActorState state, in DesignData design, bool applyHat, bool applyVisor, bool applyWeapon, bool applyWet, - ref byte totalMetaFlags, bool respectManual, StateChanged.Source source) + private void ReduceMeta(ActorState state, in DesignData design, MetaFlag apply, ref MetaFlag totalMetaFlags, bool respectManual, StateSource source) { - if (applyHat && (totalMetaFlags & 0x01) == 0) + apply &= ~totalMetaFlags; + foreach (var index in MetaExtensions.AllRelevant) { - if (!respectManual || state.Source[MetaIndex.HatState] is not StateChanged.Source.Manual) - _state.ChangeHatState(state, design.IsHatVisible(), source); - totalMetaFlags |= 0x01; - } - - if (applyVisor && (totalMetaFlags & 0x02) == 0) - { - if (!respectManual || state.Source[MetaIndex.VisorState] is not StateChanged.Source.Manual) - _state.ChangeVisorState(state, design.IsVisorToggled(), source); - totalMetaFlags |= 0x02; - } - - if (applyWeapon && (totalMetaFlags & 0x04) == 0) - { - if (!respectManual || state.Source[MetaIndex.WeaponState] is not StateChanged.Source.Manual) - _state.ChangeWeaponState(state, design.IsWeaponVisible(), source); - totalMetaFlags |= 0x04; - } - - if (applyWet && (totalMetaFlags & 0x08) == 0) - { - if (!respectManual || state.Source[MetaIndex.Wetness] is not StateChanged.Source.Manual) - _state.ChangeWetness(state, design.IsWet(), source); - totalMetaFlags |= 0x08; + if (!respectManual || state.Sources[index] is not StateSource.Manual) + _state.ChangeMeta(state, index, design.GetMeta(index), source); + totalMetaFlags |= index.ToFlag(); } } diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index ec4beb1..a8b2f7b 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -163,16 +163,15 @@ public class DesignBase64Migration } } - public static unsafe string CreateOldBase64(in DesignData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, - bool setHat, bool setVisor, bool setWeapon, bool writeProtected, float alpha = 1.0f) + public static unsafe string CreateOldBase64(in DesignData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, MetaFlag meta, bool writeProtected, float alpha = 1.0f) { var data = stackalloc byte[Base64SizeV4]; data[0] = 5; data[1] = (byte)((customizeFlags == CustomizeFlagExtensions.All ? 0x01 : 0) | (save.IsWet() ? 0x02 : 0) - | (setHat ? 0x04 : 0) - | (setWeapon ? 0x08 : 0) - | (setVisor ? 0x10 : 0) + | (meta.HasFlag(MetaFlag.HatState) ? 0x04 : 0) + | (meta.HasFlag(MetaFlag.WeaponState) ? 0x08 : 0) + | (meta.HasFlag(MetaFlag.VisorState) ? 0x10 : 0) | (writeProtected ? 0x20 : 0)); data[2] = (byte)((equipFlags.HasFlag(EquipFlag.Mainhand) ? 0x01 : 0) | (equipFlags.HasFlag(EquipFlag.Offhand) ? 0x02 : 0) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 596fbc3..14c85f6 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -55,10 +55,10 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; design.ApplyCrest = crestFlags & CrestExtensions.All; design.ApplyParameters = parameterFlags & CustomizeParameterExtensions.All; - design.SetApplyHatVisible(design.DoApplyEquip(EquipSlot.Head)); - design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head)); - design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand)); - design.SetApplyWetness(true); + design.SetApplyMeta(MetaIndex.HatState, design.DoApplyEquip(EquipSlot.Head)); + design.SetApplyMeta(MetaIndex.VisorState, design.DoApplyEquip(EquipSlot.Head)); + design.SetApplyMeta(MetaIndex.WeaponState, design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand)); + design.SetApplyMeta(MetaIndex.Wetness, true); design.SetDesignData(_customize, data); return design; } @@ -126,16 +126,14 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi return null; } - ret.SetApplyWetness(customize); + ret.SetApplyMeta(MetaIndex.Wetness, customize); ret.ApplyCustomize = customize ? CustomizeFlagExtensions.AllRelevant : 0; if (!equip) { - ret.ApplyEquip = 0; - ret.ApplyCrest = 0; - ret.SetApplyHatVisible(false); - ret.SetApplyWeaponVisible(false); - ret.SetApplyVisorToggle(false); + ret.ApplyEquip = 0; + ret.ApplyCrest = 0; + ret.ApplyMeta &= ~(MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState); } return ret; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 2df7d8d..ee4bd13 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -523,15 +523,7 @@ public class DesignManager /// Change the application value of one of the meta flags. public void ChangeApplyMeta(Design design, MetaIndex metaIndex, bool value) { - var change = metaIndex switch - { - MetaIndex.Wetness => design.SetApplyWetness(value), - MetaIndex.HatState => design.SetApplyHatVisible(value), - MetaIndex.VisorState => design.SetApplyVisorToggle(value), - MetaIndex.WeaponState => design.SetApplyWeaponVisible(value), - _ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null), - }; - if (!change) + if (!design.SetApplyMeta(metaIndex, value)) return; design.LastEdit = DateTimeOffset.UtcNow; @@ -556,14 +548,8 @@ public class DesignManager public void ApplyDesign(Design design, DesignBase other) { _undoStore[design.Identifier] = design.DesignData; - if (other.DoApplyWetness()) - design.GetDesignDataRef().SetIsWet(other.DesignData.IsWet()); - if (other.DoApplyHatVisible()) - design.GetDesignDataRef().SetHatVisible(other.DesignData.IsHatVisible()); - if (other.DoApplyVisorToggle()) - design.GetDesignDataRef().SetVisor(other.DesignData.IsVisorToggled()); - if (other.DoApplyWeaponVisible()) - design.GetDesignDataRef().SetWeaponVisible(other.DesignData.IsWeaponVisible()); + foreach (var index in MetaExtensions.AllRelevant.Where(other.DoApplyMeta)) + design.GetDesignDataRef().SetMeta(index, other.DesignData.GetMeta(index)); if (design.DesignData.IsHuman) { diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 2aef9bc..ef77226 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -1,5 +1,4 @@ using Glamourer.Automation; -using Glamourer.Events; using Glamourer.GameData; using Glamourer.Services; using Glamourer.State; @@ -28,13 +27,13 @@ public class DesignMerger( continue; ref readonly var data = ref design == null ? ref baseRef : ref design.GetDesignDataRef(); - var source = design == null ? StateChanged.Source.Game : StateChanged.Source.Manual; + var source = design == null ? StateSource.Game : StateSource.Manual; if (!data.IsHuman) continue; - var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyHat, applyVisor, applyWeapon, applyWet) = type.ApplyWhat(design); - ReduceMeta(data, applyHat, applyVisor, applyWeapon, applyWet, ret, source); + var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyMeta) = type.ApplyWhat(design); + ReduceMeta(data, applyMeta, ret, source); ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source, respectOwnership); ReduceEquip(data, equipFlags, ret, source, respectOwnership); ReduceMainhands(data, equipFlags, ret, source, respectOwnership); @@ -58,39 +57,22 @@ public class DesignMerger( ret.AssociatedMods.TryAdd(mod, settings); } - private static void ReduceMeta(in DesignData design, bool applyHat, bool applyVisor, bool applyWeapon, bool applyWet, MergedDesign ret, - StateChanged.Source source) + private static void ReduceMeta(in DesignData design, MetaFlag applyMeta, MergedDesign ret, StateSource source) { - if (applyHat && !ret.Design.DoApplyHatVisible()) - { - ret.Design.SetApplyHatVisible(true); - ret.Design.GetDesignDataRef().SetHatVisible(design.IsHatVisible()); - ret.Source[MetaIndex.HatState] = source; - } + applyMeta &= ~ret.Design.ApplyMeta; - if (applyVisor && !ret.Design.DoApplyVisorToggle()) + foreach (var index in MetaExtensions.AllRelevant) { - ret.Design.SetApplyVisorToggle(true); - ret.Design.GetDesignDataRef().SetVisor(design.IsVisorToggled()); - ret.Source[MetaIndex.VisorState] = source; - } + if (!applyMeta.HasFlag(index.ToFlag())) + continue; - if (applyWeapon && !ret.Design.DoApplyWeaponVisible()) - { - ret.Design.SetApplyWeaponVisible(true); - ret.Design.GetDesignDataRef().SetWeaponVisible(design.IsWeaponVisible()); - ret.Source[MetaIndex.WeaponState] = source; - } - - if (applyWet && !ret.Design.DoApplyWetness()) - { - ret.Design.SetApplyWetness(true); - ret.Design.GetDesignDataRef().SetIsWet(design.IsWet()); - ret.Source[MetaIndex.Wetness] = source; + ret.Design.SetApplyMeta(index, true); + ret.Design.GetDesignDataRef().SetMeta(index, design.GetMeta(index)); + ret.Sources[index] = source; } } - private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateChanged.Source source) + private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateSource source) { crestFlags &= ~ret.Design.ApplyCrest; if (crestFlags == 0) @@ -103,12 +85,12 @@ public class DesignMerger( ret.Design.GetDesignDataRef().SetCrest(slot, design.Crest(slot)); ret.Design.SetApplyCrest(slot, true); - ret.Source[slot] = source; + ret.Sources[slot] = source; } } private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret, - StateChanged.Source source) + StateSource source) { parameterFlags &= ~ret.Design.ApplyParameters; if (parameterFlags == 0) @@ -121,11 +103,11 @@ public class DesignMerger( ret.Design.GetDesignDataRef().Parameters.Set(flag, design.Parameters[flag]); ret.Design.SetApplyParameter(flag, true); - ret.Source[flag] = source; + ret.Sources[flag] = source; } } - private void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source, + private void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source, bool respectOwnership) { equipFlags &= ~ret.Design.ApplyEquip; @@ -142,7 +124,7 @@ public class DesignMerger( if (!respectOwnership || _itemUnlocks.IsUnlocked(item.Id, out _)) ret.Design.GetDesignDataRef().SetItem(slot, item); ret.Design.SetApplyEquip(slot, true); - ret.Source[slot, false] = source; + ret.Sources[slot, false] = source; } var stainFlag = slot.ToStainFlag(); @@ -150,7 +132,7 @@ public class DesignMerger( { ret.Design.GetDesignDataRef().SetStain(slot, design.Stain(slot)); ret.Design.SetApplyStain(slot, true); - ret.Source[slot, true] = source; + ret.Sources[slot, true] = source; } } @@ -161,12 +143,12 @@ public class DesignMerger( { ret.Design.GetDesignDataRef().SetStain(slot, design.Stain(slot)); ret.Design.SetApplyStain(slot, true); - ret.Source[slot, true] = source; + ret.Sources[slot, true] = source; } } } - private void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source, + private void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source, bool respectOwnership) { if (!equipFlags.HasFlag(EquipFlag.Mainhand)) @@ -185,7 +167,7 @@ public class DesignMerger( ret.Weapons.TryAdd(weapon.Type, (weapon, source)); } - private void ReduceOffhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source, bool respectOwnership) + private void ReduceOffhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source, bool respectOwnership) { if (!equipFlags.HasFlag(EquipFlag.Offhand)) return; @@ -205,7 +187,7 @@ public class DesignMerger( } private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret, - StateChanged.Source source, bool respectOwnership) + StateSource source, bool respectOwnership) { customizeFlags &= ~ret.Design.ApplyCustomizeRaw; if (customizeFlags == 0) @@ -221,25 +203,25 @@ public class DesignMerger( fixFlags |= _customize.ChangeClan(ref customize, design.Customize.Clan); ret.Design.SetApplyCustomize(CustomizeIndex.Clan, true); ret.Design.SetApplyCustomize(CustomizeIndex.Race, true); - customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race); - ret.Source[CustomizeIndex.Clan] = source; - ret.Source[CustomizeIndex.Race] = source; + customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race); + ret.Sources[CustomizeIndex.Clan] = source; + ret.Sources[CustomizeIndex.Race] = source; } if (customizeFlags.HasFlag(CustomizeFlag.Gender)) { fixFlags |= _customize.ChangeGender(ref customize, design.Customize.Gender); ret.Design.SetApplyCustomize(CustomizeIndex.Gender, true); - customizeFlags &= ~CustomizeFlag.Gender; - ret.Source[CustomizeIndex.Gender] = source; + customizeFlags &= ~CustomizeFlag.Gender; + ret.Sources[CustomizeIndex.Gender] = source; } if (customizeFlags.HasFlag(CustomizeFlag.Face)) { customize[CustomizeIndex.Face] = design.Customize.Face; ret.Design.SetApplyCustomize(CustomizeIndex.Face, true); - customizeFlags &= ~CustomizeFlag.Face; - ret.Source[CustomizeIndex.Face] = source; + customizeFlags &= ~CustomizeFlag.Face; + ret.Sources[CustomizeIndex.Face] = source; } var set = ret.Design.CustomizeSet; @@ -259,8 +241,8 @@ public class DesignMerger( customize[index] = data?.Value ?? value; ret.Design.SetApplyCustomize(index, true); - ret.Source[index] = source; - fixFlags &= ~flag; + ret.Sources[index] = source; + fixFlags &= ~flag; } ret.Design.SetCustomize(_customize, customize); @@ -272,15 +254,15 @@ public class DesignMerger( return; var source = ret.Design.DoApplyCustomize(CustomizeIndex.Clan) - ? ret.Source[CustomizeIndex.Clan] - : ret.Source[CustomizeIndex.Gender]; + ? ret.Sources[CustomizeIndex.Clan] + : ret.Sources[CustomizeIndex.Gender]; foreach (var index in Enum.GetValues()) { var flag = index.ToFlag(); if (!fixFlags.HasFlag(flag)) continue; - ret.Source[index] = source; + ret.Sources[index] = source; ret.Design.SetApplyCustomize(index, true); } } diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index d4b3cab..74e65ca 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -26,14 +26,14 @@ public sealed class MergedDesign { var weapon = design.DesignData.Item(EquipSlot.MainHand); if (weapon.Valid) - Weapons.TryAdd(weapon.Type, (weapon, StateChanged.Source.Manual)); + Weapons.TryAdd(weapon.Type, (weapon, StateSource.Manual)); } if (design.DoApplyEquip(EquipSlot.OffHand)) { var weapon = design.DesignData.Item(EquipSlot.OffHand); if (weapon.Valid) - Weapons.TryAdd(weapon.Type, (weapon, StateChanged.Source.Manual)); + Weapons.TryAdd(weapon.Type, (weapon, StateSource.Manual)); } } @@ -44,26 +44,26 @@ public sealed class MergedDesign AssociatedMods[mod] = settings; } - public readonly DesignBase Design; - public readonly Dictionary Weapons = new(4); - public readonly StateSource Source = new(); - public readonly SortedList AssociatedMods = []; + public readonly DesignBase Design; + public readonly Dictionary Weapons = new(4); + public readonly SortedList AssociatedMods = []; + public StateSources Sources = new(); - public StateChanged.Source GetSource(EquipSlot slot, bool stain, StateChanged.Source actualSource) - => GetSource(Source[slot, stain], actualSource); + public StateSource GetSource(EquipSlot slot, bool stain, StateSource actualSource) + => GetSource(Sources[slot, stain], actualSource); - public StateChanged.Source GetSource(CrestFlag slot, StateChanged.Source actualSource) - => GetSource(Source[slot], actualSource); + public StateSource GetSource(CrestFlag slot, StateSource actualSource) + => GetSource(Sources[slot], actualSource); - public StateChanged.Source GetSource(CustomizeIndex type, StateChanged.Source actualSource) - => GetSource(Source[type], actualSource); + public StateSource GetSource(CustomizeIndex type, StateSource actualSource) + => GetSource(Sources[type], actualSource); - public StateChanged.Source GetSource(MetaIndex index, StateChanged.Source actualSource) - => GetSource(Source[index], actualSource); + public StateSource GetSource(MetaIndex index, StateSource actualSource) + => GetSource(Sources[index], actualSource); - public StateChanged.Source GetSource(CustomizeParameterFlag flag, StateChanged.Source actualSource) - => GetSource(Source[flag], actualSource); + public StateSource GetSource(CustomizeParameterFlag flag, StateSource actualSource) + => GetSource(Sources[flag], actualSource); - public static StateChanged.Source GetSource(StateChanged.Source given, StateChanged.Source actualSource) - => given is StateChanged.Source.Game ? StateChanged.Source.Game : actualSource; + public static StateSource GetSource(StateSource given, StateSource actualSource) + => given is StateSource.Game ? StateSource.Game : actualSource; } diff --git a/Glamourer/Designs/MetaIndex.cs b/Glamourer/Designs/MetaIndex.cs index 4fac98d..2dd77cd 100644 --- a/Glamourer/Designs/MetaIndex.cs +++ b/Glamourer/Designs/MetaIndex.cs @@ -1,14 +1,14 @@ -using Penumbra.GameData.Enums; +using Glamourer.State; -namespace Glamourer.State; +namespace Glamourer.Designs; public enum MetaIndex { - Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices, - HatState, - VisorState, - WeaponState, - ModelId, + Wetness = StateIndex.MetaWetness, + HatState = StateIndex.MetaHatState, + VisorState = StateIndex.MetaVisorState, + WeaponState = StateIndex.MetaWeaponState, + ModelId = StateIndex.MetaModelId, } [Flags] @@ -34,6 +34,26 @@ public static class MetaExtensions MetaIndex.HatState => MetaFlag.HatState, MetaIndex.VisorState => MetaFlag.VisorState, MetaIndex.WeaponState => MetaFlag.WeaponState, - _ => (MetaFlag) byte.MaxValue, + _ => (MetaFlag)byte.MaxValue, + }; + + public static string ToName(this MetaIndex index) + => index switch + { + MetaIndex.HatState => "Hat Visible", + MetaIndex.VisorState => "Visor Toggled", + MetaIndex.WeaponState => "Weapon Visible", + MetaIndex.Wetness => "Force Wetness", + _ => "Unknown Meta", + }; + + public static string ToTooltip(this MetaIndex index) + => index switch + { + MetaIndex.HatState => "Hide or show the characters head gear.", + MetaIndex.VisorState => "Toggle the visor state of the characters head gear.", + MetaIndex.WeaponState => "Hide or show the characters weapons when not drawn.", + MetaIndex.Wetness => "Force the character to be wet or not.", + _ => string.Empty, }; } diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 01e2758..47b5d20 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -2,68 +2,59 @@ using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Classes; -namespace Glamourer.Events; - -/// -/// Triggered when a Design is edited in any way. -/// -/// Parameter is the type of the change -/// Parameter is the changed saved state. -/// Parameter is the existing actors using this saved state. -/// Parameter is any additional data depending on the type of change. -/// -/// -public sealed class StateChanged() - : EventWrapper(nameof(StateChanged)) +namespace Glamourer.Events { - public enum Type + /// + /// Triggered when a Design is edited in any way. + /// + /// Parameter is the type of the change + /// Parameter is the changed saved state. + /// Parameter is the existing actors using this saved state. + /// Parameter is any additional data depending on the type of change. + /// + /// + public sealed class StateChanged() + : EventWrapper(nameof(StateChanged)) { - /// A characters saved state had the model id changed. This means everything may have changed. Data is the old model id and the new model id. [(uint, uint)] - Model, + public enum Type + { + /// A characters saved state had the model id changed. This means everything may have changed. Data is the old model id and the new model id. [(uint, uint)] + Model, - /// A characters saved state had multiple customization values changed. TData is the old customize array and the applied changes. [(Customize, CustomizeFlag)] - EntireCustomize, + /// A characters saved state had multiple customization values changed. TData is the old customize array and the applied changes. [(Customize, CustomizeFlag)] + EntireCustomize, - /// A characters saved state had a customization value changed. Data is the old value, the new value and the type. [(CustomizeValue, CustomizeValue, CustomizeIndex)]. - Customize, + /// A characters saved state had a customization value changed. Data is the old value, the new value and the type. [(CustomizeValue, CustomizeValue, CustomizeIndex)]. + Customize, - /// A characters saved state had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. - Equip, + /// A characters saved state had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. + Equip, - /// A characters saved state had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. - Weapon, + /// A characters saved state had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. + Weapon, - /// A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. - Stain, + /// A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. + Stain, - /// A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. - Crest, + /// A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. + Crest, - /// A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. - Parameter, + /// A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. + Parameter, - /// A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] - Design, + /// A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] + Design, - /// A characters saved state had its state reset to its game values. This means everything may have changed. Data is null. - Reset, + /// A characters saved state had its state reset to its game values. This means everything may have changed. Data is null. + Reset, - /// A characters saved state had a meta toggle changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. - Other, - } + /// A characters saved state had a meta toggle changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. + Other, + } - public enum Source : byte - { - Game, - Manual, - Fixed, - Ipc, - // Only used for CustomizeParameters. - Pending, - } - - public enum Priority - { - GlamourerIpc = int.MinValue, + public enum Priority + { + GlamourerIpc = int.MinValue, + } } } diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs index 0bae022..07f3486 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs @@ -33,7 +33,7 @@ public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in Des { Locked = state.IsLocked, DisplayApplication = false, - ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, StateChanged.Source.Manual), + ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, StateSource.Manual), GameValue = state.BaseData.Parameters[flag], AllowRevert = true, }; diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index f6e73f9..37c37e7 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -163,7 +163,7 @@ public sealed class DesignQuickBar : Window, IDisposable var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); - _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); + _stateManager.ApplyDesign(design, state, StateSource.Manual); } public void DrawRevertButton(Vector2 buttonSize) @@ -189,7 +189,7 @@ public sealed class DesignQuickBar : Window, IDisposable var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); if (clicked) - _stateManager.ResetState(state!, StateChanged.Source.Manual); + _stateManager.ResetState(state!, StateSource.Manual); } public void DrawRevertAutomationButton(Vector2 buttonSize) @@ -257,7 +257,7 @@ public sealed class DesignQuickBar : Window, IDisposable ImGui.SameLine(); var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, tooltip, available); if (clicked) - _stateManager.ResetAdvancedState(state!, StateChanged.Source.Manual); + _stateManager.ResetAdvancedState(state!, StateSource.Manual); } private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip, diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 57da890..1450fb5 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -46,8 +46,8 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public static EquipDrawData FromState(StateManager manager, ActorState state, EquipSlot slot) => new(slot, state.ModelData) { - ItemSetter = i => manager.ChangeItem(state, slot, i, StateChanged.Source.Manual), - StainSetter = i => manager.ChangeStain(state, slot, i, StateChanged.Source.Manual), + ItemSetter = i => manager.ChangeItem(state, slot, i, StateSource.Manual), + StainSetter = i => manager.ChangeStain(state, slot, i, StateSource.Manual), Locked = state.IsLocked, DisplayApplication = false, GameItem = state.BaseData.Item(slot), diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index d030abb..ecea9ad 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -113,22 +113,22 @@ public class PenumbraChangedItemTooltip : IDisposable case (false, false): Glamourer.Log.Information($"Applying {item.Name} to Right Finger."); SetLastItem(EquipSlot.RFinger, item, state); - _stateManager.ChangeItem(state, EquipSlot.RFinger, item, StateChanged.Source.Manual); + _stateManager.ChangeItem(state, EquipSlot.RFinger, item, StateSource.Manual); break; case (false, true): Glamourer.Log.Information($"Applying {item.Name} to Left Finger."); SetLastItem(EquipSlot.LFinger, item, state); - _stateManager.ChangeItem(state, EquipSlot.LFinger, item, StateChanged.Source.Manual); + _stateManager.ChangeItem(state, EquipSlot.LFinger, item, StateSource.Manual); break; case (true, false) when last.Valid: Glamourer.Log.Information($"Re-Applying {last.Name} to Right Finger."); SetLastItem(EquipSlot.RFinger, default, state); - _stateManager.ChangeItem(state, EquipSlot.RFinger, last, StateChanged.Source.Manual); + _stateManager.ChangeItem(state, EquipSlot.RFinger, last, StateSource.Manual); break; case (true, true) when _lastItems[EquipSlot.LFinger.ToIndex()].Valid: Glamourer.Log.Information($"Re-Applying {last.Name} to Left Finger."); SetLastItem(EquipSlot.LFinger, default, state); - _stateManager.ChangeItem(state, EquipSlot.LFinger, last, StateChanged.Source.Manual); + _stateManager.ChangeItem(state, EquipSlot.LFinger, last, StateSource.Manual); break; } @@ -138,13 +138,13 @@ public class PenumbraChangedItemTooltip : IDisposable { Glamourer.Log.Information($"Re-Applying {last.Name} to {slot.ToName()}."); SetLastItem(slot, default, state); - _stateManager.ChangeItem(state, slot, last, StateChanged.Source.Manual); + _stateManager.ChangeItem(state, slot, last, StateSource.Manual); } else { Glamourer.Log.Information($"Applying {item.Name} to {slot.ToName()}."); SetLastItem(slot, item, state); - _stateManager.ChangeItem(state, slot, item, StateChanged.Source.Manual); + _stateManager.ChangeItem(state, slot, item, StateSource.Manual); } return; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index e85057d..e1d0eef 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -61,13 +61,13 @@ public class ActorPanel( if (_importService.CreateDatTarget(out var dat)) { - _stateManager.ChangeCustomize(_state!, dat.Customize, CustomizeApplicationFlags, StateChanged.Source.Manual); + _stateManager.ChangeCustomize(_state!, dat.Customize, CustomizeApplicationFlags, StateSource.Manual); Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_state.Identifier}.", NotificationType.Success, false); } else if (_importService.CreateCharaTarget(out var designBase, out var name)) { - _stateManager.ApplyDesign(designBase, _state!, StateChanged.Source.Manual); + _stateManager.ApplyDesign(designBase, _state!, StateSource.Manual); Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_state.Identifier}.", NotificationType.Success, false); } @@ -139,7 +139,7 @@ public class ActorPanel( return; if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw)) - _stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual); + _stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateSource.Manual); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.Wetness, _stateManager, _state)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); @@ -159,7 +159,7 @@ public class ActorPanel( var data = EquipDrawData.FromState(_stateManager, _state!, slot); _equipmentDrawer.DrawEquip(data); if (usedAllStain) - _stateManager.ChangeStain(_state, slot, newAllStain, StateChanged.Source.Manual); + _stateManager.ChangeStain(_state, slot, newAllStain, StateSource.Manual); } var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); @@ -264,7 +264,7 @@ public class ActorPanel( } if (turnHuman) - _stateManager.TurnHuman(_state, StateChanged.Source.Manual); + _stateManager.TurnHuman(_state, StateSource.Manual); } private HeaderDrawer.Button SetFromClipboardButton() @@ -340,7 +340,7 @@ public class ActorPanel( var text = ImGui.GetClipboardText(); var design = _converter.FromBase64(text, applyCustomize, applyGear, out _) ?? throw new Exception("The clipboard did not contain valid data."); - _stateManager.ApplyDesign(design, _state!, StateChanged.Source.Manual); + _stateManager.ApplyDesign(design, _state!, StateSource.Manual); } catch (Exception ex) { @@ -368,7 +368,7 @@ public class ActorPanel( { if (ImGuiUtil.DrawDisabledButton("Revert to Game", Vector2.Zero, "Revert the character to its actual state in the game.", _state!.IsLocked)) - _stateManager.ResetState(_state!, StateChanged.Source.Manual); + _stateManager.ResetState(_state!, StateSource.Manual); ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton("Reapply State", Vector2.Zero, "Try to reapply the configured state if something went wrong.", @@ -396,7 +396,7 @@ public class ActorPanel( var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state, - StateChanged.Source.Manual); + StateSource.Manual); } private void DrawApplyToTarget() @@ -414,6 +414,6 @@ public class ActorPanel( var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state, - StateChanged.Source.Manual); + StateSource.Manual); } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index bb9e135..2cb1ede 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -264,7 +264,7 @@ public class SetPanel( var size = new Vector2(ImGui.GetFrameHeight()); size.X += ImGuiHelpers.GlobalScale; - var (equipFlags, customizeFlags, _, _, _, _, _, _) = design.ApplyWhat(); + var (equipFlags, customizeFlags, _, _, _) = design.ApplyWhat(); var sb = new StringBuilder(); foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)) { diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 6eac9ad..7b3f594 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -52,11 +52,11 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM ImGuiUtil.DrawTableColumn(state.Identifier.ToString()); ImGui.TableNextColumn(); if (ImGui.Button("Reset")) - stateManager.ResetState(state, StateChanged.Source.Manual); + stateManager.ResetState(state, StateSource.Manual); ImGui.TableNextRow(); - static void PrintRow(string label, T actor, T model, StateChanged.Source source) where T : notnull + static void PrintRow(string label, T actor, T model, StateSource source) where T : notnull { ImGuiUtil.DrawTableColumn(label); ImGuiUtil.DrawTableColumn(actor.ToString()!); @@ -70,44 +70,44 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM return $"{item.Name} ({item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; } - PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state.Source[MetaIndex.ModelId]); + PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state.Sources[MetaIndex.ModelId]); ImGui.TableNextRow(); - PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state.Source[MetaIndex.Wetness]); + PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state.Sources[MetaIndex.Wetness]); ImGui.TableNextRow(); if (state.BaseData.IsHuman && state.ModelData.IsHuman) { - PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state.Source[MetaIndex.HatState]); + PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state.Sources[MetaIndex.HatState]); ImGui.TableNextRow(); PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(), - state.Source[MetaIndex.VisorState]); + state.Sources[MetaIndex.VisorState]); ImGui.TableNextRow(); PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(), - state.Source[MetaIndex.WeaponState]); + state.Sources[MetaIndex.WeaponState]); ImGui.TableNextRow(); foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) { - PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state.Source[slot, false]); + PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state.Sources[slot, false]); ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString()); ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString()); - ImGuiUtil.DrawTableColumn(state.Source[slot, true].ToString()); + ImGuiUtil.DrawTableColumn(state.Sources[slot, true].ToString()); } foreach (var type in Enum.GetValues()) { - PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state.Source[type]); + PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state.Sources[type]); ImGui.TableNextRow(); } foreach (var crest in CrestExtensions.AllRelevantSet) { - PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state.Source[crest]); + PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state.Sources[crest]); ImGui.TableNextRow(); } foreach (var flag in CustomizeParameterExtensions.AllFlags) { - PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state.Source[flag]); + PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state.Sources[flag]); ImGui.TableNextRow(); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index d4070ef..85b4010 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -25,9 +25,8 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ continue; DrawDesign(design, _designFileSystem); - var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, - design.DoApplyHatVisible(), - design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected()); + var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, design.ApplyMeta, + design.WriteProtected()); using var font = ImRaii.PushFont(UiBuilder.MonoFont); ImGuiUtil.TextWrapped(base64); if (ImGui.IsItemClicked()) @@ -85,18 +84,12 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ ImGuiUtil.DrawTableColumn(applyCrest ? "Apply" : "Keep"); } - ImGuiUtil.DrawTableColumn("Hat Visible"); - ImGuiUtil.DrawTableColumn(design.DesignData.IsHatVisible().ToString()); - ImGuiUtil.DrawTableColumn(design.DoApplyHatVisible() ? "Apply" : "Keep"); - ImGui.TableNextRow(); - ImGuiUtil.DrawTableColumn("Visor Toggled"); - ImGuiUtil.DrawTableColumn(design.DesignData.IsVisorToggled().ToString()); - ImGuiUtil.DrawTableColumn(design.DoApplyVisorToggle() ? "Apply" : "Keep"); - ImGui.TableNextRow(); - ImGuiUtil.DrawTableColumn("Weapon Visible"); - ImGuiUtil.DrawTableColumn(design.DesignData.IsWeaponVisible().ToString()); - ImGuiUtil.DrawTableColumn(design.DoApplyWeaponVisible() ? "Apply" : "Keep"); - ImGui.TableNextRow(); + foreach (var index in MetaExtensions.AllRelevant) + { + ImGuiUtil.DrawTableColumn(index.ToName()); + ImGuiUtil.DrawTableColumn(design.DesignData.GetMeta(index).ToString()); + ImGuiUtil.DrawTableColumn(design.DoApplyMeta(index) ? "Apply" : "Keep"); + } ImGuiUtil.DrawTableColumn("Model ID"); ImGuiUtil.DrawTableColumn(design.DesignData.ModelId.ToString()); @@ -111,9 +104,5 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep"); ImGui.TableNextRow(); } - - ImGuiUtil.DrawTableColumn("Is Wet"); - ImGuiUtil.DrawTableColumn(design.DesignData.IsWet().ToString()); - ImGui.TableNextRow(); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index 7bc83f9..c893f4c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -55,10 +55,8 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IGa try { - _parse64 = DesignBase64Migration.MigrateBase64(_items, _humans, _base64, out var ef, out var cf, out var wp, out var ah, - out var av, - out var aw); - _restore = DesignBase64Migration.CreateOldBase64(in _parse64, ef, cf, ah, av, aw, wp); + _parse64 = DesignBase64Migration.MigrateBase64(_items, _humans, _base64, out var ef, out var cf, out var wp, out var meta); + _restore = DesignBase64Migration.CreateOldBase64(in _parse64, ef, cf, meta, wp); _restoreBytes = Convert.FromBase64String(_restore); } catch (Exception ex) diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 505ed62..90302b8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -67,9 +67,9 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled)) { foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true)) - _state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual); - _state.ChangeVisorState(state!, data.VisorToggled, StateChanged.Source.Manual); - _state.ChangeCustomize(state!, data.Customize, CustomizeFlagExtensions.All, StateChanged.Source.Manual); + _state.ChangeEquip(state!, slot, item, stain, StateSource.Manual); + _state.ChangeMeta(state!, MetaIndex.VisorState, data.VisorToggled, StateSource.Manual); + _state.ChangeCustomize(state!, data.Customize, CustomizeFlagExtensions.All, StateSource.Manual); } ImGui.TableNextColumn(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index f942439..803a226 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -287,28 +287,25 @@ public class DesignPanel( private void DrawMetaApplication() { - using var id = ImRaii.PushId("Meta"); - const uint all = 0x0Fu; - var flags = (_selector.Selected!.DoApplyHatVisible() ? 0x01u : 0x00) - | (_selector.Selected!.DoApplyVisorToggle() ? 0x02u : 0x00) - | (_selector.Selected!.DoApplyWeaponVisible() ? 0x04u : 0x00) - | (_selector.Selected!.DoApplyWetness() ? 0x08u : 0x00); - var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); - var apply = bigChange ? (flags & 0x01) == 0x01 : _selector.Selected!.DoApplyHatVisible(); - if (ImGui.Checkbox("Apply Hat Visibility", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, apply); + using var id = ImRaii.PushId("Meta"); + const uint all = (uint)MetaExtensions.All; + var flags = (uint)_selector.Selected!.ApplyMeta; + var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); - apply = bigChange ? (flags & 0x02) == 0x02 : _selector.Selected!.DoApplyVisorToggle(); - if (ImGui.Checkbox("Apply Visor State", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.VisorState, apply); + var labels = new[] + { + "Apply Hat Visibility", + "Apply Visor State", + "Apply Weapon Visibility", + "Apply Wetness", + }; - apply = bigChange ? (flags & 0x04) == 0x04 : _selector.Selected!.DoApplyWeaponVisible(); - if (ImGui.Checkbox("Apply Weapon Visibility", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.WeaponState, apply); - - apply = bigChange ? (flags & 0x08) == 0x08 : _selector.Selected!.DoApplyWetness(); - if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, apply); + foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(labels)) + { + var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selector.Selected!.DoApplyMeta(index); + if (ImGui.Checkbox(label, ref apply) || bigChange) + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, apply); + } } private void DrawParameterApplication() @@ -442,7 +439,7 @@ public class DesignPanel( { var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); - _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); + _state.ApplyDesign(_selector.Selected!, state, StateSource.Manual); } } @@ -461,7 +458,7 @@ public class DesignPanel( { var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); - _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); + _state.ApplyDesign(_selector.Selected!, state, StateSource.Manual); } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 24382d5..829a681 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -202,7 +202,7 @@ public class NpcPanel( { var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags(); var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0); - _state.ApplyDesign(design, state, StateChanged.Source.Manual); + _state.ApplyDesign(design, state, StateSource.Manual); } } @@ -221,7 +221,7 @@ public class NpcPanel( { var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags(); var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0); - _state.ApplyDesign(design, state, StateChanged.Source.Manual); + _state.ApplyDesign(design, state, StateSource.Manual); } } diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index bb834a4..5758229 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -1,5 +1,4 @@ using Glamourer.Designs; -using Glamourer.Events; using Glamourer.State; using Penumbra.GameData.Enums; @@ -23,32 +22,17 @@ public ref struct ToggleDrawData { } public static ToggleDrawData FromDesign(MetaIndex index, DesignManager manager, Design design) - { - var (label, value, apply, setValue, setApply) = index switch + => new() { - MetaIndex.HatState => ("Hat Visible", design.DesignData.IsHatVisible(), design.DoApplyHatVisible(), - (Action)(b => manager.ChangeMeta(design, index, b)), (Action)(b => manager.ChangeApplyMeta(design, index, b))), - MetaIndex.VisorState => ("Visor Toggled", design.DesignData.IsVisorToggled(), design.DoApplyVisorToggle(), - b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), - MetaIndex.WeaponState => ("Weapon Visible", design.DesignData.IsWeaponVisible(), design.DoApplyWeaponVisible(), - b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), - MetaIndex.Wetness => ("Force Wetness", design.DesignData.IsWet(), design.DoApplyWetness(), - b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), - _ => throw new Exception("Unsupported meta index."), - }; - - return new ToggleDrawData - { - Label = label, + Label = index.ToName(), Tooltip = string.Empty, Locked = design.WriteProtected(), DisplayApplication = true, - CurrentValue = value, - CurrentApply = apply, - SetValue = setValue, - SetApply = setApply, + CurrentValue = design.DesignData.GetMeta(index), + CurrentApply = design.DoApplyMeta(index), + SetValue = b => manager.ChangeMeta(design, index, b), + SetApply = b => manager.ChangeApplyMeta(design, index, b), }; - } public static ToggleDrawData CrestFromDesign(CrestFlag slot, DesignManager manager, Design design) => new() @@ -70,52 +54,27 @@ public ref struct ToggleDrawData Tooltip = "Hide or show your free company crest on this piece of gear.", Locked = state.IsLocked, CurrentValue = state.ModelData.Crest(slot), - SetValue = v => manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual), + SetValue = v => manager.ChangeCrest(state, slot, v, StateSource.Manual), }; public static ToggleDrawData FromState(MetaIndex index, StateManager manager, ActorState state) { - var (label, tooltip, value, setValue) = index switch - { - MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear.", state.ModelData.IsHatVisible(), - (Action)(b => manager.ChangeHatState(state, b, StateChanged.Source.Manual))), - MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear.", - state.ModelData.IsVisorToggled(), - b => manager.ChangeVisorState(state, b, StateChanged.Source.Manual)), - MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn.", - state.ModelData.IsWeaponVisible(), - b => manager.ChangeWeaponState(state, b, StateChanged.Source.Manual)), - MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not.", state.ModelData.IsWet(), - b => manager.ChangeWetness(state, b, StateChanged.Source.Manual)), - _ => throw new Exception("Unsupported meta index."), - }; - return new ToggleDrawData { - Label = label, - Tooltip = tooltip, + Label = index.ToName(), + Tooltip = index.ToTooltip(), Locked = state.IsLocked, - CurrentValue = value, - SetValue = setValue, + CurrentValue = state.ModelData.GetMeta(index), + SetValue = b => manager.ChangeMeta(state, index, b, StateSource.Manual), }; } public static ToggleDrawData FromValue(MetaIndex index, bool value) - { - var (label, tooltip) = index switch + => new() { - MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear."), - MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear."), - MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn."), - MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not."), - _ => throw new Exception("Unsupported meta index."), - }; - return new ToggleDrawData - { - Label = label, - Tooltip = tooltip, + Label = index.ToName(), + Tooltip = index.ToTooltip(), Locked = true, CurrentValue = value, }; - } } diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 228eda7..6bdac74 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -118,14 +118,14 @@ public class ContextMenuService : IDisposable return; var slot = item.Type.ToSlot(); - _state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual); + _state.ChangeEquip(state, slot, item, 0, StateSource.Manual); if (item.Type.ValidOffhand().IsOffhandType()) { if (item.PrimaryId.Id is > 1600 and < 1651 && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) - _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual); + _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateSource.Manual); if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) - _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual); + _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateSource.Manual); } }; } @@ -142,14 +142,14 @@ public class ContextMenuService : IDisposable return; var slot = item.Type.ToSlot(); - _state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual); + _state.ChangeEquip(state, slot, item, 0, StateSource.Manual); if (item.Type.ValidOffhand().IsOffhandType()) { if (item.PrimaryId.Id is > 1600 and < 1651 && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) - _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual); + _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateSource.Manual); if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) - _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual); + _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateSource.Manual); } }; } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index f659847..41a2052 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -333,7 +333,7 @@ public class CommandService : IDisposable foreach (var identifier in identifiers) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ResetState(state, StateChanged.Source.Manual); + _stateManager.ResetState(state, StateSource.Manual); } @@ -419,7 +419,7 @@ public class CommandService : IDisposable if (!_objects.TryGetValue(identifier, out var actors)) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); + _stateManager.ApplyDesign(design, state, StateSource.Manual); } else { @@ -428,7 +428,7 @@ public class CommandService : IDisposable if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) { ApplyModSettings(design, actor, applyMods); - _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); + _stateManager.ApplyDesign(design, state, StateSource.Manual); } } } diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index 83b22ac..0b3204f 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -68,13 +68,13 @@ public class ActorState => Unlock(1337); /// This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. - public readonly StateSource Source = new(); + public StateSources Sources = new(); internal ActorState(ActorIdentifier identifier) => Identifier = identifier.CreatePermanent(); public CustomizeParameterFlag OnlyChangedParameters() - => CustomizeParameterExtensions.AllFlags.Where(f => Source[f] is not StateChanged.Source.Game) + => CustomizeParameterExtensions.AllFlags.Where(f => Sources[f] is not StateSource.Game) .Aggregate((CustomizeParameterFlag)0, (a, b) => a | b); public bool UpdateTerritory(ushort territory) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index d497006..906beb6 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,4 +1,5 @@ -using Glamourer.Events; +using Glamourer.Designs; +using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -118,7 +119,7 @@ public class StateApplier( // If the source is not IPC we do not want to apply restrictions. var data = GetData(state); if (apply) - ChangeArmor(data, slot, state.ModelData.Armor(slot), state.Source[slot, false] is not StateChanged.Source.Ipc, + ChangeArmor(data, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc, state.ModelData.IsHatVisible()); return data; @@ -200,67 +201,44 @@ public class StateApplier( _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain)); } - /// Change the visor state of actors only on the draw object. - public void ChangeVisor(ActorData data, bool value) + /// Change a meta state. + public unsafe void ChangeMetaState(ActorData data, MetaIndex index, bool value) { - foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _visor.SetVisorState(actor.Model, value); + switch (index) + { + case MetaIndex.Wetness: + { + foreach (var actor in data.Objects.Where(a => a.IsCharacter)) + actor.AsCharacter->IsGPoseWet = value; + return; + } + case MetaIndex.HatState: + { + foreach (var actor in data.Objects.Where(a => a.IsCharacter)) + _metaService.SetHatState(actor, value); + return; + } + case MetaIndex.WeaponState: + { + foreach (var actor in data.Objects.Where(a => a.IsCharacter)) + _metaService.SetWeaponState(actor, value); + return; + } + case MetaIndex.VisorState: + { + foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) + _visor.SetVisorState(actor.Model, value); + return; + } + } } - /// - public ActorData ChangeVisor(ActorState state, bool apply) + /// + public ActorData ChangeMetaState(ActorState state, MetaIndex index, bool apply) { var data = GetData(state); if (apply) - ChangeVisor(data, state.ModelData.IsVisorToggled()); - return data; - } - - /// Change the forced wetness state on actors. - public unsafe void ChangeWetness(ActorData data, bool value) - { - foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - actor.AsCharacter->IsGPoseWet = value; - } - - /// - public ActorData ChangeWetness(ActorState state, bool apply) - { - var data = GetData(state); - if (apply) - ChangeWetness(data, state.ModelData.IsWet()); - return data; - } - - /// Change the hat-visibility state on actors. - public void ChangeHatState(ActorData data, bool value) - { - foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - _metaService.SetHatState(actor, value); - } - - /// - public ActorData ChangeHatState(ActorState state, bool apply) - { - var data = GetData(state); - if (apply) - ChangeHatState(data, state.ModelData.IsHatVisible()); - return data; - } - - /// Change the weapon-visibility state on actors. - public void ChangeWeaponState(ActorData data, bool value) - { - foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - _metaService.SetWeaponState(actor, value); - } - - /// - public ActorData ChangeWeaponState(ActorState state, bool apply) - { - var data = GetData(state); - if (apply) - ChangeWeaponState(data, state.ModelData.IsWeaponVisible()); + ChangeMetaState(data, index, state.ModelData.GetMeta(index)); return data; } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index abb3716..403c106 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -13,7 +13,7 @@ public class StateEditor(CustomizeService customizations, HumanModelList humans, { /// Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. /// We currently only allow changing things to humans, not humans to monsters. - public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateChanged.Source source, + public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateSource source, out uint oldModelId, uint key = 0) { oldModelId = state.ModelData.ModelId; @@ -45,21 +45,21 @@ public class StateEditor(CustomizeService customizations, HumanModelList humans, state.ModelData.SetHatVisible(true); state.ModelData.SetWeaponVisible(true); state.ModelData.SetVisor(false); - state.Source[MetaIndex.ModelId] = source; - state.Source[MetaIndex.HatState] = source; - state.Source[MetaIndex.WeaponState] = source; - state.Source[MetaIndex.VisorState] = source; + state.Sources[MetaIndex.ModelId] = source; + state.Sources[MetaIndex.HatState] = source; + state.Sources[MetaIndex.WeaponState] = source; + state.Sources[MetaIndex.VisorState] = source; foreach (var slot in EquipSlotExtensions.FullSlots) { - state.Source[slot, true] = source; - state.Source[slot, false] = source; + state.Sources[slot, true] = source; + state.Sources[slot, false] = source; } - state.Source[CustomizeIndex.Clan] = source; - state.Source[CustomizeIndex.Gender] = source; + state.Sources[CustomizeIndex.Clan] = source; + state.Sources[CustomizeIndex.Gender] = source; var set = customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); foreach (var index in Enum.GetValues().Where(set.IsAvailable)) - state.Source[index] = source; + state.Sources[index] = source; } else { @@ -67,14 +67,14 @@ public class StateEditor(CustomizeService customizations, HumanModelList humans, return false; state.ModelData.LoadNonHuman(modelId, customize, equipData); - state.Source[MetaIndex.ModelId] = source; + state.Sources[MetaIndex.ModelId] = source; } return true; } /// Change a customization value. - public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source, + public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateSource source, out CustomizeValue old, uint key = 0) { old = state.ModelData.Customize[idx]; @@ -82,12 +82,12 @@ public class StateEditor(CustomizeService customizations, HumanModelList humans, return false; state.ModelData.Customize[idx] = value; - state.Source[idx] = source; + state.Sources[idx] = source; return true; } /// Change an entire customization array according to flags. - public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, StateChanged.Source source, + public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, StateSource source, out CustomizeArray old, out CustomizeFlag changed, uint key = 0) { old = state.ModelData.Customize; @@ -104,14 +104,14 @@ public class StateEditor(CustomizeService customizations, HumanModelList humans, foreach (var type in Enum.GetValues()) { if (applied.HasFlag(type.ToFlag())) - state.Source[type] = source; + state.Sources[type] = source; } return true; } /// Change a single piece of equipment without stain. - public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, out EquipItem oldItem, uint key = 0) + public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateSource source, out EquipItem oldItem, uint key = 0) { oldItem = state.ModelData.Item(slot); if (!state.CanUnlock(key)) @@ -128,17 +128,17 @@ public class StateEditor(CustomizeService customizations, HumanModelList humans, gPose.AddActionOnLeave(() => { if (old.Type == state.BaseData.Item(slot).Type) - ChangeItem(state, slot, old, state.Source[slot, false], out _, key); + ChangeItem(state, slot, old, state.Sources[slot, false], out _, key); }); } state.ModelData.SetItem(slot, item); - state.Source[slot, false] = source; + state.Sources[slot, false] = source; return true; } /// Change a single piece of equipment including stain. - public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source, out EquipItem oldItem, + public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, out EquipItem oldItem, out StainId oldStain, uint key = 0) { oldItem = state.ModelData.Item(slot); @@ -158,43 +158,43 @@ public class StateEditor(CustomizeService customizations, HumanModelList humans, gPose.AddActionOnLeave(() => { if (old.Type == state.BaseData.Item(slot).Type) - ChangeEquip(state, slot, old, oldS, state.Source[slot, false], out _, out _, key); + ChangeEquip(state, slot, old, oldS, state.Sources[slot, false], out _, out _, key); }); } state.ModelData.SetItem(slot, item); state.ModelData.SetStain(slot, stain); - state.Source[slot, false] = source; - state.Source[slot, true] = source; + state.Sources[slot, false] = source; + state.Sources[slot, true] = source; return true; } /// Change only the stain of an equipment piece. - public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateChanged.Source source, out StainId oldStain, uint key = 0) + public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, out StainId oldStain, uint key = 0) { oldStain = state.ModelData.Stain(slot); if (!state.CanUnlock(key)) return false; state.ModelData.SetStain(slot, stain); - state.Source[slot, true] = source; + state.Sources[slot, true] = source; return true; } /// Change the crest of an equipment piece. - public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, out bool oldCrest, uint key = 0) + public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateSource source, out bool oldCrest, uint key = 0) { oldCrest = state.ModelData.Crest(slot); if (!state.CanUnlock(key)) return false; state.ModelData.SetCrest(slot, crest); - state.Source[slot] = source; + state.Sources[slot] = source; return true; } /// Change the customize flags of a character. - public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateChanged.Source source, + public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateSource source, out CustomizeParameterValue oldValue, uint key = 0) { oldValue = state.ModelData.Parameters[flag]; @@ -202,29 +202,20 @@ public class StateEditor(CustomizeService customizations, HumanModelList humans, return false; state.ModelData.Parameters.Set(flag, value); - state.Source[flag] = source; + state.Sources[flag] = source; return true; } - public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateChanged.Source source, out bool oldValue, + public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue, uint key = 0) { - (var setter, oldValue) = index switch - { - MetaIndex.Wetness => ((Func)(v => state.ModelData.SetIsWet(v)), state.ModelData.IsWet()), - MetaIndex.HatState => ((Func)(v => state.ModelData.SetHatVisible(v)), state.ModelData.IsHatVisible()), - MetaIndex.VisorState => ((Func)(v => state.ModelData.SetVisor(v)), state.ModelData.IsVisorToggled()), - MetaIndex.WeaponState => ((Func)(v => state.ModelData.SetWeaponVisible(v)), - state.ModelData.IsWeaponVisible()), - _ => throw new Exception("Invalid MetaIndex."), - }; - + oldValue = state.ModelData.GetMeta(index); if (!state.CanUnlock(key)) return false; - setter(value); - state.Source[index] = source; + state.ModelData.SetMeta(index, value); + state.Sources[index] = source; return true; } } diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs new file mode 100644 index 0000000..906b003 --- /dev/null +++ b/Glamourer/State/StateIndex.cs @@ -0,0 +1,235 @@ +using Glamourer.Designs; +using Glamourer.GameData; +using Penumbra.GameData.Enums; + +namespace Glamourer.State; + +public readonly record struct StateIndex(int Value) : IEqualityOperators +{ + public static readonly StateIndex Invalid = new(-1); + + public static implicit operator StateIndex(EquipFlag flag) + => flag switch + { + EquipFlag.Head => new StateIndex(EquipHead), + EquipFlag.Body => new StateIndex(EquipBody), + EquipFlag.Hands => new StateIndex(EquipHands), + EquipFlag.Legs => new StateIndex(EquipLegs), + EquipFlag.Feet => new StateIndex(EquipFeet), + EquipFlag.Ears => new StateIndex(EquipEars), + EquipFlag.Neck => new StateIndex(EquipNeck), + EquipFlag.Wrist => new StateIndex(EquipWrist), + EquipFlag.RFinger => new StateIndex(EquipRFinger), + EquipFlag.LFinger => new StateIndex(EquipLFinger), + EquipFlag.Mainhand => new StateIndex(EquipMainhand), + EquipFlag.Offhand => new StateIndex(EquipOffhand), + EquipFlag.HeadStain => new StateIndex(StainHead), + EquipFlag.BodyStain => new StateIndex(StainBody), + EquipFlag.HandsStain => new StateIndex(StainHands), + EquipFlag.LegsStain => new StateIndex(StainLegs), + EquipFlag.FeetStain => new StateIndex(StainFeet), + EquipFlag.EarsStain => new StateIndex(StainEars), + EquipFlag.NeckStain => new StateIndex(StainNeck), + EquipFlag.WristStain => new StateIndex(StainWrist), + EquipFlag.RFingerStain => new StateIndex(StainRFinger), + EquipFlag.LFingerStain => new StateIndex(StainLFinger), + EquipFlag.MainhandStain => new StateIndex(StainMainhand), + EquipFlag.OffhandStain => new StateIndex(StainOffhand), + _ => Invalid, + }; + + public static implicit operator StateIndex(CustomizeIndex index) + => index switch + { + CustomizeIndex.Race => new StateIndex(CustomizeRace), + CustomizeIndex.Gender => new StateIndex(CustomizeGender), + CustomizeIndex.BodyType => new StateIndex(CustomizeBodyType), + CustomizeIndex.Height => new StateIndex(CustomizeHeight), + CustomizeIndex.Clan => new StateIndex(CustomizeClan), + CustomizeIndex.Face => new StateIndex(CustomizeFace), + CustomizeIndex.Hairstyle => new StateIndex(CustomizeHairstyle), + CustomizeIndex.Highlights => new StateIndex(CustomizeHighlights), + CustomizeIndex.SkinColor => new StateIndex(CustomizeSkinColor), + CustomizeIndex.EyeColorRight => new StateIndex(CustomizeEyeColorRight), + CustomizeIndex.HairColor => new StateIndex(CustomizeHairColor), + CustomizeIndex.HighlightsColor => new StateIndex(CustomizeHighlightsColor), + CustomizeIndex.FacialFeature1 => new StateIndex(CustomizeFacialFeature1), + CustomizeIndex.FacialFeature2 => new StateIndex(CustomizeFacialFeature2), + CustomizeIndex.FacialFeature3 => new StateIndex(CustomizeFacialFeature3), + CustomizeIndex.FacialFeature4 => new StateIndex(CustomizeFacialFeature4), + CustomizeIndex.FacialFeature5 => new StateIndex(CustomizeFacialFeature5), + CustomizeIndex.FacialFeature6 => new StateIndex(CustomizeFacialFeature6), + CustomizeIndex.FacialFeature7 => new StateIndex(CustomizeFacialFeature7), + CustomizeIndex.LegacyTattoo => new StateIndex(CustomizeLegacyTattoo), + CustomizeIndex.TattooColor => new StateIndex(CustomizeTattooColor), + CustomizeIndex.Eyebrows => new StateIndex(CustomizeEyebrows), + CustomizeIndex.EyeColorLeft => new StateIndex(CustomizeEyeColorLeft), + CustomizeIndex.EyeShape => new StateIndex(CustomizeEyeShape), + CustomizeIndex.SmallIris => new StateIndex(CustomizeSmallIris), + CustomizeIndex.Nose => new StateIndex(CustomizeNose), + CustomizeIndex.Jaw => new StateIndex(CustomizeJaw), + CustomizeIndex.Mouth => new StateIndex(CustomizeMouth), + CustomizeIndex.Lipstick => new StateIndex(CustomizeLipstick), + CustomizeIndex.LipColor => new StateIndex(CustomizeLipColor), + CustomizeIndex.MuscleMass => new StateIndex(CustomizeMuscleMass), + CustomizeIndex.TailShape => new StateIndex(CustomizeTailShape), + CustomizeIndex.BustSize => new StateIndex(CustomizeBustSize), + CustomizeIndex.FacePaint => new StateIndex(CustomizeFacePaint), + CustomizeIndex.FacePaintReversed => new StateIndex(CustomizeFacePaintReversed), + CustomizeIndex.FacePaintColor => new StateIndex(CustomizeFacePaintColor), + _ => Invalid, + }; + + public static implicit operator StateIndex(MetaIndex meta) + => new((int)meta); + + public static implicit operator StateIndex(CrestFlag crest) + => crest switch + { + CrestFlag.OffHand => new StateIndex(CrestOffhand), + CrestFlag.Head => new StateIndex(CrestHead), + CrestFlag.Body => new StateIndex(CrestBody), + _ => Invalid, + }; + + public static implicit operator StateIndex(CustomizeParameterFlag param) + => param switch + { + CustomizeParameterFlag.SkinDiffuse => new StateIndex(ParamSkinDiffuse), + CustomizeParameterFlag.MuscleTone => new StateIndex(ParamMuscleTone), + CustomizeParameterFlag.SkinSpecular => new StateIndex(ParamSkinSpecular), + CustomizeParameterFlag.LipDiffuse => new StateIndex(ParamLipDiffuse), + CustomizeParameterFlag.HairDiffuse => new StateIndex(ParamHairDiffuse), + CustomizeParameterFlag.HairSpecular => new StateIndex(ParamHairSpecular), + CustomizeParameterFlag.HairHighlight => new StateIndex(ParamHairHighlight), + CustomizeParameterFlag.LeftEye => new StateIndex(ParamLeftEye), + CustomizeParameterFlag.RightEye => new StateIndex(ParamRightEye), + CustomizeParameterFlag.FeatureColor => new StateIndex(ParamFeatureColor), + CustomizeParameterFlag.FacePaintUvMultiplier => new StateIndex(ParamFacePaintUvMultiplier), + CustomizeParameterFlag.FacePaintUvOffset => new StateIndex(ParamFacePaintUvOffset), + CustomizeParameterFlag.DecalColor => new StateIndex(ParamDecalColor), + _ => Invalid, + }; + + public const int EquipHead = 0; + public const int EquipBody = 1; + public const int EquipHands = 2; + public const int EquipLegs = 3; + public const int EquipFeet = 4; + public const int EquipEars = 5; + public const int EquipNeck = 6; + public const int EquipWrist = 7; + public const int EquipRFinger = 8; + public const int EquipLFinger = 9; + public const int EquipMainhand = 10; + public const int EquipOffhand = 11; + + public const int StainHead = 12; + public const int StainBody = 13; + public const int StainHands = 14; + public const int StainLegs = 15; + public const int StainFeet = 16; + public const int StainEars = 17; + public const int StainNeck = 18; + public const int StainWrist = 19; + public const int StainRFinger = 20; + public const int StainLFinger = 21; + public const int StainMainhand = 22; + public const int StainOffhand = 23; + + public const int CustomizeRace = 24; + public const int CustomizeGender = 25; + public const int CustomizeBodyType = 26; + public const int CustomizeHeight = 27; + public const int CustomizeClan = 28; + public const int CustomizeFace = 29; + public const int CustomizeHairstyle = 30; + public const int CustomizeHighlights = 31; + public const int CustomizeSkinColor = 32; + public const int CustomizeEyeColorRight = 33; + public const int CustomizeHairColor = 34; + public const int CustomizeHighlightsColor = 35; + public const int CustomizeFacialFeature1 = 36; + public const int CustomizeFacialFeature2 = 37; + public const int CustomizeFacialFeature3 = 38; + public const int CustomizeFacialFeature4 = 39; + public const int CustomizeFacialFeature5 = 40; + public const int CustomizeFacialFeature6 = 41; + public const int CustomizeFacialFeature7 = 42; + public const int CustomizeLegacyTattoo = 43; + public const int CustomizeTattooColor = 44; + public const int CustomizeEyebrows = 45; + public const int CustomizeEyeColorLeft = 46; + public const int CustomizeEyeShape = 47; + public const int CustomizeSmallIris = 48; + public const int CustomizeNose = 49; + public const int CustomizeJaw = 50; + public const int CustomizeMouth = 51; + public const int CustomizeLipstick = 52; + public const int CustomizeLipColor = 53; + public const int CustomizeMuscleMass = 54; + public const int CustomizeTailShape = 55; + public const int CustomizeBustSize = 56; + public const int CustomizeFacePaint = 57; + public const int CustomizeFacePaintReversed = 58; + public const int CustomizeFacePaintColor = 59; + + public const int MetaWetness = 60; + public const int MetaHatState = 61; + public const int MetaVisorState = 62; + public const int MetaWeaponState = 63; + public const int MetaModelId = 64; + + public const int CrestHead = 65; + public const int CrestBody = 66; + public const int CrestOffhand = 67; + + public const int ParamSkinDiffuse = 68; + public const int ParamMuscleTone = 69; + public const int ParamSkinSpecular = 70; + public const int ParamLipDiffuse = 71; + public const int ParamHairDiffuse = 72; + public const int ParamHairSpecular = 73; + public const int ParamHairHighlight = 74; + public const int ParamLeftEye = 75; + public const int ParamRightEye = 76; + public const int ParamFeatureColor = 77; + public const int ParamFacePaintUvMultiplier = 78; + public const int ParamFacePaintUvOffset = 79; + public const int ParamDecalColor = 80; + + public const int Size = 81; +} + +public static class StateExtensions +{ + public static StateIndex ToState(this EquipSlot slot, bool stain = false) + => (slot, stain) switch + { + (EquipSlot.Head, true) => new StateIndex(StateIndex.EquipHead), + (EquipSlot.Body, true) => new StateIndex(StateIndex.EquipBody), + (EquipSlot.Hands, true) => new StateIndex(StateIndex.EquipHands), + (EquipSlot.Legs, true) => new StateIndex(StateIndex.EquipLegs), + (EquipSlot.Feet, true) => new StateIndex(StateIndex.EquipFeet), + (EquipSlot.Ears, true) => new StateIndex(StateIndex.EquipEars), + (EquipSlot.Neck, true) => new StateIndex(StateIndex.EquipNeck), + (EquipSlot.Wrists, true) => new StateIndex(StateIndex.EquipWrist), + (EquipSlot.RFinger, true) => new StateIndex(StateIndex.EquipRFinger), + (EquipSlot.LFinger, true) => new StateIndex(StateIndex.EquipLFinger), + (EquipSlot.MainHand, true) => new StateIndex(StateIndex.EquipMainhand), + (EquipSlot.OffHand, true) => new StateIndex(StateIndex.EquipOffhand), + (EquipSlot.Head, false) => new StateIndex(StateIndex.StainHead), + (EquipSlot.Body, false) => new StateIndex(StateIndex.StainBody), + (EquipSlot.Hands, false) => new StateIndex(StateIndex.StainHands), + (EquipSlot.Legs, false) => new StateIndex(StateIndex.StainLegs), + (EquipSlot.Feet, false) => new StateIndex(StateIndex.StainFeet), + (EquipSlot.Ears, false) => new StateIndex(StateIndex.StainEars), + (EquipSlot.Neck, false) => new StateIndex(StateIndex.StainNeck), + (EquipSlot.Wrists, false) => new StateIndex(StateIndex.StainWrist), + (EquipSlot.RFinger, false) => new StateIndex(StateIndex.StainRFinger), + (EquipSlot.LFinger, false) => new StateIndex(StateIndex.StainLFinger), + (EquipSlot.MainHand, false) => new StateIndex(StateIndex.StainMainhand), + (EquipSlot.OffHand, false) => new StateIndex(StateIndex.StainOffhand), + _ => StateIndex.Invalid, + }; +} diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index a8c8dd5..09c0673 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -164,21 +164,21 @@ public class StateListener : IDisposable var model = state.ModelData.Customize; if (customize.Gender != model.Gender || customize.Clan != model.Clan) { - _manager.ChangeCustomize(state, in customize, CustomizeFlagExtensions.AllRelevant, StateChanged.Source.Game); + _manager.ChangeCustomize(state, in customize, CustomizeFlagExtensions.AllRelevant, StateSource.Game); return; } var set = _customizations.Manager.GetSet(model.Clan, model.Gender); foreach (var index in CustomizationExtensions.AllBasic) { - if (state.Source[index] is not StateChanged.Source.Fixed) + if (state.Sources[index] is not StateSource.Fixed) { var newValue = customize[index]; var oldValue = model[index]; if (newValue != oldValue) { if (set.Validate(index, newValue, out _, model.Face)) - _manager.ChangeCustomize(state, index, newValue, StateChanged.Source.Game); + _manager.ChangeCustomize(state, index, newValue, StateSource.Game); else customize[index] = oldValue; } @@ -214,7 +214,7 @@ public class StateListener : IDisposable && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor); - locked = state.Source[slot, false] is StateChanged.Source.Ipc; + locked = state.Sources[slot, false] is StateSource.Ipc; } _funModule.ApplyFunToSlot(actor, ref armor, slot); @@ -241,10 +241,10 @@ public class StateListener : IDisposable continue; var changed = changedItem.Weapon(stain); - if (current.Value == changed.Value && state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (current.Value == changed.Value && state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) { - _manager.ChangeItem(state, slot, currentItem, StateChanged.Source.Game); - _manager.ChangeStain(state, slot, current.Stain, StateChanged.Source.Game); + _manager.ChangeItem(state, slot, currentItem, StateSource.Game); + _manager.ChangeStain(state, slot, current.Stain, StateSource.Game); switch (slot) { case EquipSlot.MainHand: @@ -252,7 +252,7 @@ public class StateListener : IDisposable _applier.ChangeWeapon(objects, slot, currentItem, stain); break; default: - _applier.ChangeArmor(objects, slot, current.ToArmor(), state.Source[slot, false] is not StateChanged.Source.Ipc, + _applier.ChangeArmor(objects, slot, current.ToArmor(), state.Sources[slot, false] is not StateSource.Ipc, state.ModelData.IsHatVisible()); break; } @@ -286,13 +286,13 @@ public class StateListener : IDisposable // Do nothing. But this usually can not happen because the hooked function also writes to game objects later. case UpdateState.Transformed: break; case UpdateState.Change: - if (state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) - _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game); + if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) + _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateSource.Game); else apply = true; - if (state.Source[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) - _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); + if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc) + _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateSource.Game); else apply = true; break; @@ -385,13 +385,13 @@ public class StateListener : IDisposable // Update model state if not on fixed design. case UpdateState.Change: var apply = false; - if (state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) - _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game); + if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) + _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateSource.Game); else apply = true; - if (state.Source[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) - _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); + if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc) + _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateSource.Game); else apply = true; @@ -419,8 +419,8 @@ public class StateListener : IDisposable switch (UpdateBaseCrest(actor, state, slot, value)) { case UpdateState.Change: - if (state.Source[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) - _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game); + if (state.Sources[slot] is not StateSource.Fixed and not StateSource.Ipc) + _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateSource.Game); else value = state.ModelData.Crest(slot); break; @@ -565,10 +565,10 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state.Source[MetaIndex.VisorState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) + if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed or StateSource.Ipc) value = state.ModelData.IsVisorToggled(); else - _manager.ChangeVisorState(state, value, StateChanged.Source.Game); + _manager.ChangeMeta(state, MetaIndex.VisorState, value, StateSource.Game); } else { @@ -598,10 +598,10 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state.Source[MetaIndex.HatState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) + if (state.Sources[MetaIndex.HatState] is StateSource.Fixed or StateSource.Ipc) value = state.ModelData.IsHatVisible(); else - _manager.ChangeHatState(state, value, StateChanged.Source.Game); + _manager.ChangeMeta(state, MetaIndex.HatState, value, StateSource.Game); } else { @@ -631,10 +631,10 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state.Source[MetaIndex.WeaponState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) + if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed or StateSource.Ipc) value = state.ModelData.IsWeaponVisible(); else - _manager.ChangeWeaponState(state, value, StateChanged.Source.Game); + _manager.ChangeMeta(state, MetaIndex.WeaponState, value, StateSource.Game); } else { @@ -700,9 +700,9 @@ public class StateListener : IDisposable return; var data = new ActorData(gameObject, _creatingIdentifier.ToName()); - _applier.ChangeHatState(data, _creatingState.ModelData.IsHatVisible()); - _applier.ChangeWeaponState(data, _creatingState.ModelData.IsWeaponVisible()); - _applier.ChangeWetness(data, _creatingState.ModelData.IsWet()); + _applier.ChangeMetaState(data, MetaIndex.HatState, _creatingState.ModelData.IsHatVisible()); + _applier.ChangeMetaState(data, MetaIndex.Wetness, _creatingState.ModelData.IsWet()); + _applier.ChangeMetaState(data, MetaIndex.WeaponState, _creatingState.ModelData.IsWeaponVisible()); ApplyParameters(_creatingState, drawObject); } @@ -733,30 +733,30 @@ public class StateListener : IDisposable foreach (var flag in CustomizeParameterExtensions.AllFlags) { var newValue = data[flag]; - switch (state.Source[flag]) + switch (state.Sources[flag]) { - case StateChanged.Source.Game: + case StateSource.Game: if (state.BaseData.Parameters.Set(flag, newValue)) - _manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game); + _manager.ChangeCustomizeParameter(state, flag, newValue, StateSource.Game); break; - case StateChanged.Source.Manual: + case StateSource.Manual: if (state.BaseData.Parameters.Set(flag, newValue)) - _manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game); + _manager.ChangeCustomizeParameter(state, flag, newValue, StateSource.Game); else if (_config.UseAdvancedParameters) model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; - case StateChanged.Source.Fixed: + case StateSource.Fixed: state.BaseData.Parameters.Set(flag, newValue); if (_config.UseAdvancedParameters) model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; - case StateChanged.Source.Ipc: + case StateSource.Ipc: state.BaseData.Parameters.Set(flag, newValue); model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; - case StateChanged.Source.Pending: + case StateSource.Pending: state.BaseData.Parameters.Set(flag, newValue); - state.Source[flag] = StateChanged.Source.Manual; + state.Sources[flag] = StateSource.Manual; if (_config.UseAdvancedParameters) model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 89b8c24..4ccf42c 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -207,58 +207,58 @@ public class StateManager( #region Change Values /// Turn an actor human. - public void TurnHuman(ActorState state, StateChanged.Source source, uint key = 0) + public void TurnHuman(ActorState state, StateSource source, uint key = 0) => ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key); /// Turn an actor to. - public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateChanged.Source source, + public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source, uint key = 0) { if (!_editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key)) return; - var actors = _applier.ForceRedraw(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); + var actors = _applier.ForceRedraw(state, source is StateSource.Manual or StateSource.Ipc); Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId)); } /// Change a customization value. - public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source, uint key = 0) + public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateSource source, uint key = 0) { if (!_editor.ChangeCustomize(state, idx, value, source, out var old, key)) return; - var actors = _applier.ChangeCustomize(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); + var actors = _applier.ChangeCustomize(state, source is StateSource.Manual or StateSource.Ipc); Glamourer.Log.Verbose( $"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(StateChanged.Type.Customize, source, state, actors, (old, value, idx)); } /// Change an entire customization array according to flags. - public void ChangeCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag apply, StateChanged.Source source, + public void ChangeCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag apply, StateSource source, uint key = 0) { if (!_editor.ChangeHumanCustomize(state, customizeInput, apply, source, out var old, out var applied, key)) return; - var actors = _applier.ChangeCustomize(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); + var actors = _applier.ChangeCustomize(state, source is StateSource.Manual or StateSource.Ipc); Glamourer.Log.Verbose( $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(StateChanged.Type.EntireCustomize, source, state, actors, (old, applied)); } /// Change a single piece of equipment without stain. - /// Do not use this in the same frame as ChangeStain, use instead. - public void ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, uint key = 0) + /// Do not use this in the same frame as ChangeStain, use instead. + public void ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateSource source, uint key = 0) { if (!_editor.ChangeItem(state, slot, item, source, out var old, key)) return; var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon; var actors = type is StateChanged.Type.Equip - ? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc) - : _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc, + ? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc) + : _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc, item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); 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")}.]"); @@ -266,15 +266,15 @@ public class StateManager( } /// Change a single piece of equipment including stain. - public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source, uint key = 0) + public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, uint key = 0) { if (!_editor.ChangeEquip(state, slot, item, stain, source, out var old, out var oldStain, key)) return; var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon; var actors = type is StateChanged.Type.Equip - ? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc) - : _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc, + ? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc) + : _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc, item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); Glamourer.Log.Verbose( $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}) and its stain from {oldStain.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); @@ -283,25 +283,25 @@ public class StateManager( } /// Change only the stain of an equipment piece. - /// Do not use this in the same frame as ChangeEquip, use instead. - public void ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateChanged.Source source, uint key = 0) + /// Do not use this in the same frame as ChangeEquip, use instead. + public void ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, uint key = 0) { if (!_editor.ChangeStain(state, slot, stain, source, out var old, key)) return; - var actors = _applier.ChangeStain(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); + var actors = _applier.ChangeStain(state, slot, source is StateSource.Manual or StateSource.Ipc); Glamourer.Log.Verbose( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot)); } /// Change the crest of an equipment piece. - public void ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, uint key = 0) + public void ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateSource source, uint key = 0) { if (!_editor.ChangeCrest(state, slot, crest, source, out var old, key)) return; - var actors = _applier.ChangeCrests(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); + var actors = _applier.ChangeCrests(state, source is StateSource.Manual or StateSource.Ipc); Glamourer.Log.Verbose( $"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot)); @@ -309,7 +309,7 @@ public class StateManager( /// Change the crest of an equipment piece. public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, - StateChanged.Source source, uint key = 0) + StateSource source, uint key = 0) { // Also apply main color to highlights when highlights is off. if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse) @@ -319,63 +319,27 @@ public class StateManager( return; var @new = state.ModelData.Parameters[flag]; - var actors = _applier.ChangeParameters(state, flag, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); + var actors = _applier.ChangeParameters(state, flag, source is StateSource.Manual or StateSource.Ipc); Glamourer.Log.Verbose( $"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(StateChanged.Type.Parameter, source, state, actors, (old, @new, flag)); } - /// Change hat visibility. - public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0) + /// Change meta state. + public void ChangeMeta(ActorState state, MetaIndex meta, bool value, StateSource source, uint key = 0) { - if (!_editor.ChangeMetaState(state, MetaIndex.HatState, value, source, out var old, key)) + if (!_editor.ChangeMetaState(state, meta, value, source, out var old, key)) return; - var actors = _applier.ChangeHatState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); + var actors = _applier.ChangeMetaState(state, meta, source is StateSource.Manual or StateSource.Ipc); Glamourer.Log.Verbose( $"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.HatState)); } - /// Change weapon visibility. - public void ChangeWeaponState(ActorState state, bool value, StateChanged.Source source, uint key = 0) - { - if (!_editor.ChangeMetaState(state, MetaIndex.WeaponState, value, source, out var old, key)) - return; - - var actors = _applier.ChangeWeaponState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); - Glamourer.Log.Verbose( - $"Set Weapon Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.WeaponState)); - } - - /// Change visor state. - public void ChangeVisorState(ActorState state, bool value, StateChanged.Source source, uint key = 0) - { - if (!_editor.ChangeMetaState(state, MetaIndex.VisorState, value, source, out var old, key)) - return; - - var actors = _applier.ChangeVisor(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); - Glamourer.Log.Verbose( - $"Set Visor State in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.VisorState)); - } - - /// Set GPose Wetness. - public void ChangeWetness(ActorState state, bool value, StateChanged.Source source, uint key = 0) - { - if (!_editor.ChangeMetaState(state, MetaIndex.Wetness, value, source, out var old, key)) - return; - - var actors = _applier.ChangeWetness(state, true); - Glamourer.Log.Verbose( - $"Set Wetness in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, state.Source[MetaIndex.Wetness], state, actors, (old, value, MetaIndex.Wetness)); - } - #endregion - public void ApplyDesign(DesignBase design, ActorState state, StateChanged.Source source, uint key = 0) + public void ApplyDesign(DesignBase design, ActorState state, StateSource source, uint key = 0) { if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(), source, @@ -383,16 +347,16 @@ public class StateManager( return; var redraw = oldModelId != design.DesignData.ModelId || !design.DesignData.IsHuman; - if (design.DoApplyWetness()) + if (design.DoApplyMeta(MetaIndex.Wetness)) _editor.ChangeMetaState(state, MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key); if (state.ModelData.IsHuman) { - if (design.DoApplyHatVisible()) + if (design.DoApplyMeta(MetaIndex.HatState)) _editor.ChangeMetaState(state, MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key); - if (design.DoApplyWeaponVisible()) + if (design.DoApplyMeta(MetaIndex.WeaponState)) _editor.ChangeMetaState(state, MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key); - if (design.DoApplyVisorToggle()) + if (design.DoApplyMeta(MetaIndex.VisorState)) _editor.ChangeMetaState(state, MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key); var flags = state.AllowsRedraw(_condition) @@ -407,8 +371,8 @@ public class StateManager( foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest)) _editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key); - var paramSource = source is StateChanged.Source.Manual - ? StateChanged.Source.Pending + var paramSource = source is StateSource.Manual + ? StateSource.Pending : source; foreach (var flag in CustomizeParameterExtensions.AllFlags.Where(design.DoApplyParameter)) @@ -418,13 +382,13 @@ public class StateManager( if (!state.ModelData.Customize.Highlights) _editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight, state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse], - state.Source[CustomizeParameterFlag.HairDiffuse], out _, key); + state.Sources[CustomizeParameterFlag.HairDiffuse], out _, key); } var actors = ApplyAll(state, redraw, false); Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Design, state.Source[MetaIndex.Wetness], state, actors, design); + _event.Invoke(StateChanged.Type.Design, state.Sources[MetaIndex.Wetness], state, actors, design); return; void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain) @@ -442,7 +406,7 @@ public class StateManager( private ActorData ApplyAll(ActorState state, bool redraw, bool withLock) { - var actors = _applier.ChangeWetness(state, true); + var actors = _applier.ChangeMetaState(state, MetaIndex.Wetness, true); if (redraw) { if (withLock) @@ -454,7 +418,7 @@ public class StateManager( _applier.ChangeCustomize(actors, state.ModelData.Customize); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - _applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Source[slot, false] is not StateChanged.Source.Ipc, + _applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc, state.ModelData.IsHatVisible()); } @@ -466,9 +430,9 @@ public class StateManager( if (state.ModelData.IsHuman) { - _applier.ChangeHatState(actors, state.ModelData.IsHatVisible()); - _applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible()); - _applier.ChangeVisor(actors, state.ModelData.IsVisorToggled()); + _applier.ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible()); + _applier.ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); + _applier.ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); _applier.ChangeCrests(actors, state.ModelData.CrestVisibility); _applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); } @@ -476,7 +440,7 @@ public class StateManager( return actors; } - public void ResetState(ActorState state, StateChanged.Source source, uint key = 0) + public void ResetState(ActorState state, StateSource source, uint key = 0) { if (!state.Unlock(key)) return; @@ -488,25 +452,25 @@ public class StateManager( state.ModelData = state.BaseData; state.ModelData.SetIsWet(false); foreach (var index in Enum.GetValues()) - state.Source[index] = StateChanged.Source.Game; + state.Sources[index] = StateSource.Game; foreach (var slot in EquipSlotExtensions.FullSlots) { - state.Source[slot, true] = StateChanged.Source.Game; - state.Source[slot, false] = StateChanged.Source.Game; + state.Sources[slot, true] = StateSource.Game; + state.Sources[slot, false] = StateSource.Game; } foreach (var type in Enum.GetValues()) - state.Source[type] = StateChanged.Source.Game; + state.Sources[type] = StateSource.Game; foreach (var slot in CrestExtensions.AllRelevantSet) - state.Source[slot] = StateChanged.Source.Game; + state.Sources[slot] = StateSource.Game; foreach (var flag in CustomizeParameterExtensions.AllFlags) - state.Source[flag] = StateChanged.Source.Game; + state.Sources[flag] = StateSource.Game; var actors = ActorData.Invalid; - if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) + if (source is StateSource.Manual or StateSource.Ipc) actors = ApplyAll(state, redraw, true); Glamourer.Log.Verbose( @@ -514,7 +478,7 @@ public class StateManager( _event.Invoke(StateChanged.Type.Reset, source, state, actors, null); } - public void ResetAdvancedState(ActorState state, StateChanged.Source source, uint key = 0) + public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) { if (!state.Unlock(key) || !state.ModelData.IsHuman) return; @@ -522,10 +486,10 @@ public class StateManager( state.ModelData.Parameters = state.BaseData.Parameters; foreach (var flag in CustomizeParameterExtensions.AllFlags) - state.Source[flag] = StateChanged.Source.Game; + state.Sources[flag] = StateSource.Game; var actors = ActorData.Invalid; - if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) + if (source is StateSource.Manual or StateSource.Ipc) actors = _applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); Glamourer.Log.Verbose( $"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); @@ -537,69 +501,69 @@ public class StateManager( if (!state.Unlock(key)) return; - foreach (var index in Enum.GetValues().Where(i => state.Source[i] is StateChanged.Source.Fixed)) + foreach (var index in Enum.GetValues().Where(i => state.Sources[i] is StateSource.Fixed)) { - state.Source[index] = StateChanged.Source.Game; + state.Sources[index] = StateSource.Game; state.ModelData.Customize[index] = state.BaseData.Customize[index]; } foreach (var slot in EquipSlotExtensions.FullSlots) { - if (state.Source[slot, true] is StateChanged.Source.Fixed) + if (state.Sources[slot, true] is StateSource.Fixed) { - state.Source[slot, true] = StateChanged.Source.Game; + state.Sources[slot, true] = StateSource.Game; state.ModelData.SetStain(slot, state.BaseData.Stain(slot)); } - if (state.Source[slot, false] is StateChanged.Source.Fixed) + if (state.Sources[slot, false] is StateSource.Fixed) { - state.Source[slot, false] = StateChanged.Source.Game; + state.Sources[slot, false] = StateSource.Game; state.ModelData.SetItem(slot, state.BaseData.Item(slot)); } } foreach (var slot in CrestExtensions.AllRelevantSet) { - if (state.Source[slot] is StateChanged.Source.Fixed) + if (state.Sources[slot] is StateSource.Fixed) { - state.Source[slot] = StateChanged.Source.Game; + state.Sources[slot] = StateSource.Game; state.ModelData.SetCrest(slot, state.BaseData.Crest(slot)); } } foreach (var flag in CustomizeParameterExtensions.AllFlags) { - switch (state.Source[flag]) + switch (state.Sources[flag]) { - case StateChanged.Source.Fixed: - case StateChanged.Source.Manual when !respectManualPalettes: - state.Source[flag] = StateChanged.Source.Game; + case StateSource.Fixed: + case StateSource.Manual when !respectManualPalettes: + state.Sources[flag] = StateSource.Game; state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag]; break; } } - if (state.Source[MetaIndex.HatState] is StateChanged.Source.Fixed) + if (state.Sources[MetaIndex.HatState] is StateSource.Fixed) { - state.Source[MetaIndex.HatState] = StateChanged.Source.Game; + state.Sources[MetaIndex.HatState] = StateSource.Game; state.ModelData.SetHatVisible(state.BaseData.IsHatVisible()); } - if (state.Source[MetaIndex.VisorState] is StateChanged.Source.Fixed) + if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed) { - state.Source[MetaIndex.VisorState] = StateChanged.Source.Game; + state.Sources[MetaIndex.VisorState] = StateSource.Game; state.ModelData.SetVisor(state.BaseData.IsVisorToggled()); } - if (state.Source[MetaIndex.WeaponState] is StateChanged.Source.Fixed) + if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed) { - state.Source[MetaIndex.WeaponState] = StateChanged.Source.Game; + state.Sources[MetaIndex.WeaponState] = StateSource.Game; state.ModelData.SetWeaponVisible(state.BaseData.IsWeaponVisible()); } - if (state.Source[MetaIndex.Wetness] is StateChanged.Source.Fixed) + if (state.Sources[MetaIndex.Wetness] is StateSource.Fixed) { - state.Source[MetaIndex.Wetness] = StateChanged.Source.Game; + state.Sources[MetaIndex.Wetness] = StateSource.Game; state.ModelData.SetIsWet(state.BaseData.IsWet()); } } diff --git a/Glamourer/State/StateSource.cs b/Glamourer/State/StateSource.cs index 80899d2..1d66a46 100644 --- a/Glamourer/State/StateSource.cs +++ b/Glamourer/State/StateSource.cs @@ -1,49 +1,75 @@ -using Glamourer.GameData; -using Penumbra.GameData.Enums; -using static Glamourer.Events.StateChanged; +using Penumbra.GameData.Enums; namespace Glamourer.State; -public readonly struct StateSource +public enum StateSource : byte { - public static readonly int Size = EquipFlagExtensions.NumEquipFlags - + CustomizationExtensions.NumIndices - + 5 - + CrestExtensions.AllRelevantSet.Count - + CustomizeParameterExtensions.AllFlags.Count; + Game, + Manual, + Fixed, + Ipc, + + // Only used for CustomizeParameters. + Pending, +} + +public unsafe struct StateSources +{ + public const int Size = (StateIndex.Size + 1) / 2; + private fixed byte _data[Size]; - private readonly Source[] _data = Enumerable.Repeat(Source.Game, Size).ToArray(); - - public StateSource() + public StateSources() { } - public ref Source this[EquipSlot slot, bool stain] - => ref _data[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)]; + public StateSource this[StateIndex index] + { + get + { + var val = _data[index.Value / 2]; + return (StateSource)((index.Value & 1) == 1 ? val >> 4 : val & 0x0F); + } + set + { + var val = _data[index.Value / 2]; + if ((index.Value & 1) == 1) + val = (byte)((val & 0x0F) | ((byte)value << 4)); + else + val = (byte)((val & 0xF0) | (byte)value); + _data[index.Value / 2] = val; + } + } - public ref Source this[CrestFlag slot] - => ref _data[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()]; - - public ref Source this[CustomizeIndex type] - => ref _data[EquipFlagExtensions.NumEquipFlags + (int)type]; - - public ref Source this[MetaIndex index] - => ref _data[(int)index]; - - public ref Source this[CustomizeParameterFlag flag] - => ref _data[ - EquipFlagExtensions.NumEquipFlags - + CustomizationExtensions.NumIndices - + 5 - + CrestExtensions.AllRelevantSet.Count - + flag.ToInternalIndex()]; + public StateSource this[EquipSlot slot, bool stain] + { + get => this[slot.ToState(stain)]; + set => this[slot.ToState(stain)] = value; + } public void RemoveFixedDesignSources() { - for (var i = 0; i < _data.Length; ++i) + for (var i = 0; i < Size; ++i) { - if (_data[i] is Source.Fixed) - _data[i] = Source.Manual; + var value = _data[i]; + switch (value) + { + case (byte)StateSource.Fixed | ((byte)StateSource.Fixed << 4): + _data[i] = (byte)StateSource.Manual | ((byte)StateSource.Manual << 4); + break; + + case (byte)StateSource.Game | ((byte)StateSource.Fixed << 4): + case (byte)StateSource.Manual | ((byte)StateSource.Fixed << 4): + case (byte)StateSource.Ipc | ((byte)StateSource.Fixed << 4): + case (byte)StateSource.Pending | ((byte)StateSource.Fixed << 4): + _data[i] = (byte)((value & 0x0F) | ((byte)StateSource.Manual << 4)); + break; + case (byte)StateSource.Fixed: + case ((byte)StateSource.Manual << 4) | (byte)StateSource.Fixed: + case ((byte)StateSource.Ipc << 4) | (byte)StateSource.Fixed: + case ((byte)StateSource.Pending << 4) | (byte)StateSource.Fixed: + _data[i] = (byte)((value & 0xF0) | (byte)StateSource.Manual); + break; + } } } } From 5fd4a83aa4a4b5b356e0a009299430e624bdde25 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 23 Jan 2024 18:13:33 +0100 Subject: [PATCH 202/786] Improve StateIndex. --- Glamourer/Designs/MetaIndex.cs | 10 ++ Glamourer/State/StateIndex.cs | 221 +++++++++++++++++++++++++++++++++ Penumbra.GameData | 2 +- 3 files changed, 232 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/MetaIndex.cs b/Glamourer/Designs/MetaIndex.cs index 2dd77cd..edbf7b6 100644 --- a/Glamourer/Designs/MetaIndex.cs +++ b/Glamourer/Designs/MetaIndex.cs @@ -37,6 +37,16 @@ public static class MetaExtensions _ => (MetaFlag)byte.MaxValue, }; + public static MetaIndex ToIndex(this MetaFlag index) + => index switch + { + MetaFlag.Wetness => MetaIndex.Wetness, + MetaFlag.HatState => MetaIndex.HatState, + MetaFlag.VisorState => MetaIndex.VisorState, + MetaFlag.WeaponState => MetaIndex.WeaponState, + _ => (MetaIndex)byte.MaxValue, + }; + public static string ToName(this MetaIndex index) => index switch { diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs index 906b003..b877e9c 100644 --- a/Glamourer/State/StateIndex.cs +++ b/Glamourer/State/StateIndex.cs @@ -199,6 +199,227 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators All + => Enumerable.Range(0, Size - 1).Select(i => new StateIndex(i)); + + public bool GetApply(DesignBase data) + => GetFlag() switch + { + EquipFlag e => data.ApplyEquip.HasFlag(e), + CustomizeFlag c => data.ApplyCustomize.HasFlag(c), + MetaFlag m => data.ApplyMeta.HasFlag(m), + CrestFlag c => data.ApplyCrest.HasFlag(c), + CustomizeParameterFlag c => data.ApplyParameters.HasFlag(c), + bool v => v, + _ => false, + }; + + public string ToName() + => GetFlag() switch + { + EquipFlag e => GetName(e), + CustomizeFlag c => c.ToIndex().ToDefaultName(), + MetaFlag m => m.ToIndex().ToName(), + CrestFlag c => c.ToLabel(), + CustomizeParameterFlag c => c.ToName(), + bool v => "Model ID", + _ => "Unknown", + }; + + public object GetFlag() + => Value switch + { + EquipHead => EquipFlag.Head, + EquipBody => EquipFlag.Body, + EquipHands => EquipFlag.Hands, + EquipLegs => EquipFlag.Legs, + EquipFeet => EquipFlag.Feet, + EquipEars => EquipFlag.Ears, + EquipNeck => EquipFlag.Neck, + EquipWrist => EquipFlag.Wrist, + EquipRFinger => EquipFlag.RFinger, + EquipLFinger => EquipFlag.LFinger, + EquipMainhand => EquipFlag.Mainhand, + EquipOffhand => EquipFlag.Offhand, + + StainHead => EquipFlag.HeadStain, + StainBody => EquipFlag.BodyStain, + StainHands => EquipFlag.HandsStain, + StainLegs => EquipFlag.LegsStain, + StainFeet => EquipFlag.FeetStain, + StainEars => EquipFlag.EarsStain, + StainNeck => EquipFlag.NeckStain, + StainWrist => EquipFlag.WristStain, + StainRFinger => EquipFlag.RFingerStain, + StainLFinger => EquipFlag.LFingerStain, + StainMainhand => EquipFlag.MainhandStain, + StainOffhand => EquipFlag.OffhandStain, + + CustomizeRace => CustomizeFlag.Race, + CustomizeGender => CustomizeFlag.Gender, + CustomizeBodyType => CustomizeFlag.BodyType, + CustomizeHeight => CustomizeFlag.Height, + CustomizeClan => CustomizeFlag.Clan, + CustomizeFace => CustomizeFlag.Face, + CustomizeHairstyle => CustomizeFlag.Hairstyle, + CustomizeHighlights => CustomizeFlag.Highlights, + CustomizeSkinColor => CustomizeFlag.SkinColor, + CustomizeEyeColorRight => CustomizeFlag.EyeColorRight, + CustomizeHairColor => CustomizeFlag.HairColor, + CustomizeHighlightsColor => CustomizeFlag.HighlightsColor, + CustomizeFacialFeature1 => CustomizeFlag.FacialFeature1, + CustomizeFacialFeature2 => CustomizeFlag.FacialFeature2, + CustomizeFacialFeature3 => CustomizeFlag.FacialFeature3, + CustomizeFacialFeature4 => CustomizeFlag.FacialFeature4, + CustomizeFacialFeature5 => CustomizeFlag.FacialFeature5, + CustomizeFacialFeature6 => CustomizeFlag.FacialFeature6, + CustomizeFacialFeature7 => CustomizeFlag.FacialFeature7, + CustomizeLegacyTattoo => CustomizeFlag.LegacyTattoo, + CustomizeTattooColor => CustomizeFlag.TattooColor, + CustomizeEyebrows => CustomizeFlag.Eyebrows, + CustomizeEyeColorLeft => CustomizeFlag.EyeColorLeft, + CustomizeEyeShape => CustomizeFlag.EyeShape, + CustomizeSmallIris => CustomizeFlag.SmallIris, + CustomizeNose => CustomizeFlag.Nose, + CustomizeJaw => CustomizeFlag.Jaw, + CustomizeMouth => CustomizeFlag.Mouth, + CustomizeLipstick => CustomizeFlag.Lipstick, + CustomizeLipColor => CustomizeFlag.LipColor, + CustomizeMuscleMass => CustomizeFlag.MuscleMass, + CustomizeTailShape => CustomizeFlag.TailShape, + CustomizeBustSize => CustomizeFlag.BustSize, + CustomizeFacePaint => CustomizeFlag.FacePaint, + CustomizeFacePaintReversed => CustomizeFlag.FacePaintReversed, + CustomizeFacePaintColor => CustomizeFlag.FacePaintColor, + + MetaWetness => MetaFlag.Wetness, + MetaHatState => MetaFlag.HatState, + MetaVisorState => MetaFlag.VisorState, + MetaWeaponState => MetaFlag.WeaponState, + MetaModelId => true, + + CrestHead => CrestFlag.Head, + CrestBody => CrestFlag.Body, + CrestOffhand => CrestFlag.OffHand, + + ParamSkinDiffuse => CustomizeParameterFlag.SkinDiffuse, + ParamMuscleTone => CustomizeParameterFlag.MuscleTone, + ParamSkinSpecular => CustomizeParameterFlag.SkinSpecular, + ParamLipDiffuse => CustomizeParameterFlag.LipDiffuse, + ParamHairDiffuse => CustomizeParameterFlag.HairDiffuse, + ParamHairSpecular => CustomizeParameterFlag.HairSpecular, + ParamHairHighlight => CustomizeParameterFlag.HairHighlight, + ParamLeftEye => CustomizeParameterFlag.LeftEye, + ParamRightEye => CustomizeParameterFlag.RightEye, + ParamFeatureColor => CustomizeParameterFlag.FeatureColor, + ParamFacePaintUvMultiplier => CustomizeParameterFlag.FacePaintUvMultiplier, + ParamFacePaintUvOffset => CustomizeParameterFlag.FacePaintUvOffset, + ParamDecalColor => CustomizeParameterFlag.DecalColor, + + _ => -1, + }; + + public object? GetValue(in DesignData data) + { + return Value switch + { + EquipHead => data.Item(EquipSlot.Head), + EquipBody => data.Item(EquipSlot.Body), + EquipHands => data.Item(EquipSlot.Hands), + EquipLegs => data.Item(EquipSlot.Legs), + EquipFeet => data.Item(EquipSlot.Feet), + EquipEars => data.Item(EquipSlot.Ears), + EquipNeck => data.Item(EquipSlot.Neck), + EquipWrist => data.Item(EquipSlot.Wrists), + EquipRFinger => data.Item(EquipSlot.RFinger), + EquipLFinger => data.Item(EquipSlot.LFinger), + EquipMainhand => data.Item(EquipSlot.MainHand), + EquipOffhand => data.Item(EquipSlot.OffHand), + + StainHead => data.Stain(EquipSlot.Head), + StainBody => data.Stain(EquipSlot.Body), + StainHands => data.Stain(EquipSlot.Hands), + StainLegs => data.Stain(EquipSlot.Legs), + StainFeet => data.Stain(EquipSlot.Feet), + StainEars => data.Stain(EquipSlot.Ears), + StainNeck => data.Stain(EquipSlot.Neck), + StainWrist => data.Stain(EquipSlot.Wrists), + StainRFinger => data.Stain(EquipSlot.RFinger), + StainLFinger => data.Stain(EquipSlot.LFinger), + StainMainhand => data.Stain(EquipSlot.MainHand), + StainOffhand => data.Stain(EquipSlot.OffHand), + + CustomizeRace => data.Customize[CustomizeIndex.Race], + CustomizeGender => data.Customize[CustomizeIndex.Gender], + CustomizeBodyType => data.Customize[CustomizeIndex.BodyType], + CustomizeHeight => data.Customize[CustomizeIndex.Height], + CustomizeClan => data.Customize[CustomizeIndex.Clan], + CustomizeFace => data.Customize[CustomizeIndex.Face], + CustomizeHairstyle => data.Customize[CustomizeIndex.Hairstyle], + CustomizeHighlights => data.Customize[CustomizeIndex.Highlights], + CustomizeSkinColor => data.Customize[CustomizeIndex.SkinColor], + CustomizeEyeColorRight => data.Customize[CustomizeIndex.EyeColorRight], + CustomizeHairColor => data.Customize[CustomizeIndex.HairColor], + CustomizeHighlightsColor => data.Customize[CustomizeIndex.HighlightsColor], + CustomizeFacialFeature1 => data.Customize[CustomizeIndex.FacialFeature1], + CustomizeFacialFeature2 => data.Customize[CustomizeIndex.FacialFeature2], + CustomizeFacialFeature3 => data.Customize[CustomizeIndex.FacialFeature3], + CustomizeFacialFeature4 => data.Customize[CustomizeIndex.FacialFeature4], + CustomizeFacialFeature5 => data.Customize[CustomizeIndex.FacialFeature5], + CustomizeFacialFeature6 => data.Customize[CustomizeIndex.FacialFeature6], + CustomizeFacialFeature7 => data.Customize[CustomizeIndex.FacialFeature7], + CustomizeLegacyTattoo => data.Customize[CustomizeIndex.LegacyTattoo], + CustomizeTattooColor => data.Customize[CustomizeIndex.TattooColor], + CustomizeEyebrows => data.Customize[CustomizeIndex.Eyebrows], + CustomizeEyeColorLeft => data.Customize[CustomizeIndex.EyeColorLeft], + CustomizeEyeShape => data.Customize[CustomizeIndex.EyeShape], + CustomizeSmallIris => data.Customize[CustomizeIndex.SmallIris], + CustomizeNose => data.Customize[CustomizeIndex.Nose], + CustomizeJaw => data.Customize[CustomizeIndex.Jaw], + CustomizeMouth => data.Customize[CustomizeIndex.Mouth], + CustomizeLipstick => data.Customize[CustomizeIndex.Lipstick], + CustomizeLipColor => data.Customize[CustomizeIndex.LipColor], + CustomizeMuscleMass => data.Customize[CustomizeIndex.MuscleMass], + CustomizeTailShape => data.Customize[CustomizeIndex.TailShape], + CustomizeBustSize => data.Customize[CustomizeIndex.BustSize], + CustomizeFacePaint => data.Customize[CustomizeIndex.FacePaint], + CustomizeFacePaintReversed => data.Customize[CustomizeIndex.FacePaintReversed], + CustomizeFacePaintColor => data.Customize[CustomizeIndex.FacePaintColor], + + MetaWetness => data.GetMeta(MetaIndex.Wetness), + MetaHatState => data.GetMeta(MetaIndex.HatState), + MetaVisorState => data.GetMeta(MetaIndex.VisorState), + MetaWeaponState => data.GetMeta(MetaIndex.WeaponState), + MetaModelId => data.ModelId, + + CrestHead => data.Crest(CrestFlag.Head), + CrestBody => data.Crest(CrestFlag.Body), + CrestOffhand => data.Crest(CrestFlag.OffHand), + + ParamSkinDiffuse => data.Parameters[CustomizeParameterFlag.SkinDiffuse], + ParamMuscleTone => data.Parameters[CustomizeParameterFlag.MuscleTone], + ParamSkinSpecular => data.Parameters[CustomizeParameterFlag.SkinSpecular], + ParamLipDiffuse => data.Parameters[CustomizeParameterFlag.LipDiffuse], + ParamHairDiffuse => data.Parameters[CustomizeParameterFlag.HairDiffuse], + ParamHairSpecular => data.Parameters[CustomizeParameterFlag.HairSpecular], + ParamHairHighlight => data.Parameters[CustomizeParameterFlag.HairHighlight], + ParamLeftEye => data.Parameters[CustomizeParameterFlag.LeftEye], + ParamRightEye => data.Parameters[CustomizeParameterFlag.RightEye], + ParamFeatureColor => data.Parameters[CustomizeParameterFlag.FeatureColor], + ParamFacePaintUvMultiplier => data.Parameters[CustomizeParameterFlag.FacePaintUvMultiplier], + ParamFacePaintUvOffset => data.Parameters[CustomizeParameterFlag.FacePaintUvOffset], + ParamDecalColor => data.Parameters[CustomizeParameterFlag.DecalColor], + + _ => null, + }; + } + + private static string GetName(EquipFlag flag) + { + var slot = flag.ToSlot(out var stain); + return stain ? $"{slot.ToName()} Stain" : slot.ToName(); + } } public static class StateExtensions diff --git a/Penumbra.GameData b/Penumbra.GameData index 17d302a..260ac69 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 17d302a7d634cbb2c49c10d5b95236a40dbff370 +Subproject commit 260ac69cd6f17050eaf9b7e0b5ce9a8843edfee4 From b6549899e851fecbb52bd49e9091e4a83d313617 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 24 Jan 2024 15:42:24 +0100 Subject: [PATCH 203/786] Add option to apply entire weapon. --- Glamourer/Configuration.cs | 1 + Glamourer/Designs/DesignManager.cs | 85 ++++++++++++++++++++---------- Glamourer/Events/DesignChanged.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab.cs | 3 ++ Glamourer/State/StateManager.cs | 36 +++++++++++-- 5 files changed, 92 insertions(+), 35 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 9e01d18..78d1a3b 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -41,6 +41,7 @@ public class Configuration : IPluginConfiguration, ISavable 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 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/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index ee4bd13..3b3b43d 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -4,7 +4,6 @@ using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop.Penumbra; using Glamourer.Services; -using Glamourer.State; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; @@ -17,6 +16,7 @@ namespace Glamourer.Designs; public class DesignManager { private readonly CustomizeService _customizations; + private readonly Configuration _config; private readonly ItemManager _items; private readonly HumanModelList _humans; private readonly SaveService _saveService; @@ -28,14 +28,15 @@ public class DesignManager => _designs; public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, - DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader) + DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration config) { - _designs = storage; - _saveService = saveService; - _items = items; - _customizations = customizations; - _event = @event; - _humans = humans; + _designs = storage; + _config = config; + _saveService = saveService; + _items = items; + _customizations = customizations; + _event = @event; + _humans = humans; LoadDesigns(designLinkLoader); CreateDesignFolder(saveService); @@ -382,26 +383,18 @@ public class DesignManager switch (slot) { case EquipSlot.MainHand: - var newOff = currentOff; + if (!_items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item)) return; - if (item.Type != currentMain.Type) - { - var defaultOffhand = _items.GetDefaultOffhand(item); - if (!_items.IsOffhandValid(item, defaultOffhand.ItemId, out newOff)) - return; - } - - if (!(design.GetDesignDataRef().SetItem(EquipSlot.MainHand, item) - | design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff))) + if (!ChangeMainhandPeriphery(design, currentMain, currentOff, item, out var newOff, out var newGauntlets)) return; design.LastEdit = DateTimeOffset.UtcNow; _saveService.QueueSave(design); Glamourer.Log.Debug( $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId})."); - _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff)); + _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff, newGauntlets)); return; case EquipSlot.OffHand: @@ -415,7 +408,7 @@ public class DesignManager _saveService.QueueSave(design); Glamourer.Log.Debug( $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId})."); - _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item)); + _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item, (EquipItem?)null)); return; default: return; } @@ -503,15 +496,7 @@ public class DesignManager /// Change the bool value of one of the meta flags. public void ChangeMeta(Design design, MetaIndex metaIndex, bool value) { - var change = metaIndex switch - { - MetaIndex.Wetness => design.GetDesignDataRef().SetIsWet(value), - MetaIndex.HatState => design.GetDesignDataRef().SetHatVisible(value), - MetaIndex.VisorState => design.GetDesignDataRef().SetVisor(value), - MetaIndex.WeaponState => design.GetDesignDataRef().SetWeaponVisible(value), - _ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null), - }; - if (!change) + if (!design.GetDesignDataRef().SetMeta(metaIndex, value)) return; design.LastEdit = DateTimeOffset.UtcNow; @@ -753,4 +738,46 @@ public class DesignManager return (actualName, path); } + + /// Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. + private bool ChangeMainhandPeriphery(Design design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, out EquipItem? newGauntlets) + { + newOff = null; + newGauntlets = null; + if (newMain.Type != currentMain.Type) + { + var defaultOffhand = _items.GetDefaultOffhand(newMain); + if (!_items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o)) + return false; + + newOff = o; + } + else if (_config.ChangeEntireItem) + { + var defaultOffhand = _items.GetDefaultOffhand(newMain); + if (_items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o)) + newOff = o; + + if (newMain.Type is FullEquipType.Fists && _items.ItemData.Tertiary.TryGetValue(newMain.ItemId, out var g)) + newGauntlets = g; + } + + if (!design.GetDesignDataRef().SetItem(EquipSlot.MainHand, newMain)) + return false; + + if (newOff.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff.Value)) + { + design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain); + return false; + } + + if (newGauntlets.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.Hands, newGauntlets.Value)) + { + design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain); + design.GetDesignDataRef().SetItem(EquipSlot.OffHand, currentOff); + return false; + } + + return true; + } } diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 9b75d5a..121c58c 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -59,7 +59,7 @@ public sealed class DesignChanged() /// An existing design had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. Equip, - /// An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. + /// An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand, the new offhand (if any) and the new gauntlets (if any). [(EquipItem, EquipItem, EquipItem, EquipItem?, EquipItem?)]. Weapon, /// An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index b883dd9..e8435ce 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -68,6 +68,9 @@ public class SettingsTab( if (!ImGui.CollapsingHeader("Glamourer Behavior")) return; + Checkbox("Always Apply Entire Weapon for Mainhand", + "When manually applying a mainhand item, will also apply a corresponding offhand and potentially gauntlets for certain fist weapons.", + config.ChangeEntireItem, v => config.ChangeEntireItem = v); Checkbox("Use Replacement Gear for Gear Unavailable to Your Race or Gender", "Use different gender- and race-appropriate models as a substitute when detecting certain items not available for a characters current gender and race.", config.UseRestrictedGearProtection, v => config.UseRestrictedGearProtection = v); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 4ccf42c..518b435 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -20,7 +20,8 @@ public class StateManager( StateEditor _editor, HumanModelList _humans, ICondition _condition, - IClientState _clientState) + IClientState _clientState, + Configuration _config) : IReadOnlyDictionary { private readonly Dictionary _states = []; @@ -260,6 +261,10 @@ public class StateManager( ? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc) : _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc, item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); + + if (slot is EquipSlot.MainHand) + ApplyMainhandPeriphery(state, item, source, key); + 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")}.]"); _event.Invoke(type, source, state, actors, (old, item, slot)); @@ -276,6 +281,10 @@ public class StateManager( ? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc) : _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc, item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); + + if (slot is EquipSlot.MainHand) + ApplyMainhandPeriphery(state, item, source, key); + Glamourer.Log.Verbose( $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}) and its stain from {oldStain.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(type, source, state, actors, (old, item, slot)); @@ -290,6 +299,7 @@ public class StateManager( return; var actors = _applier.ChangeStain(state, slot, source is StateSource.Manual or StateSource.Ipc); + Glamourer.Log.Verbose( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); _event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot)); @@ -430,9 +440,9 @@ public class StateManager( if (state.ModelData.IsHuman) { - _applier.ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible()); + _applier.ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible()); _applier.ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); - _applier.ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); + _applier.ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); _applier.ChangeCrests(actors, state.ModelData.CrestVisibility); _applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); } @@ -503,7 +513,7 @@ public class StateManager( foreach (var index in Enum.GetValues().Where(i => state.Sources[i] is StateSource.Fixed)) { - state.Sources[index] = StateSource.Game; + state.Sources[index] = StateSource.Game; state.ModelData.Customize[index] = state.BaseData.Customize[index]; } @@ -537,7 +547,7 @@ public class StateManager( { case StateSource.Fixed: case StateSource.Manual when !respectManualPalettes: - state.Sources[flag] = StateSource.Game; + state.Sources[flag] = StateSource.Game; state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag]; break; } @@ -579,4 +589,20 @@ public class StateManager( public void DeleteState(ActorIdentifier identifier) => _states.Remove(identifier); + + /// Apply offhand item and potentially gauntlets if configured. + private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StateSource source, uint key = 0) + { + if (!_config.ChangeEntireItem || source is not StateSource.Manual) + return; + + var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); + var offhand = newMainhand != null ? _items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand); + if (offhand.Valid) + ChangeEquip(state, EquipSlot.OffHand, offhand, state.ModelData.Stain(EquipSlot.OffHand), source, key); + + 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), source, key); + } } From a284d5adc59ff23eed46762e2b204b35059f5c64 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 24 Jan 2024 15:42:35 +0100 Subject: [PATCH 204/786] Improve StateIndex. --- Glamourer/State/StateIndex.cs | 162 +++++++++++++++++----------------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs index b877e9c..a55d6b1 100644 --- a/Glamourer/State/StateIndex.cs +++ b/Glamourer/State/StateIndex.cs @@ -112,93 +112,93 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators All => Enumerable.Range(0, Size - 1).Select(i => new StateIndex(i)); From fce8b058b07e3253d6be2a452bf38be4b355d811 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 25 Jan 2024 15:56:49 +0100 Subject: [PATCH 205/786] Improve DesignStorage slightly. --- Glamourer/Api/GlamourerIpc.Apply.cs | 3 +- Glamourer/Automation/AutoDesignManager.cs | 3 +- Glamourer/Designs/DesignColors.cs | 29 +++++--------- Glamourer/Designs/DesignManager.cs | 47 +++++++++++------------ Glamourer/Designs/DesignStorage.cs | 14 ++++++- Glamourer/Services/CommandService.cs | 2 +- Glamourer/Services/IGamePathParser.cs | 6 --- 7 files changed, 48 insertions(+), 56 deletions(-) delete mode 100644 Glamourer/Services/IGamePathParser.cs diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs index 08ccff4..c2d68aa 100644 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ b/Glamourer/Api/GlamourerIpc.Apply.cs @@ -1,7 +1,6 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using Glamourer.Designs; -using Glamourer.Events; using Glamourer.Interop.Structs; using Glamourer.State; using Penumbra.Api.Helpers; @@ -138,5 +137,5 @@ public partial class GlamourerIpc } private void ApplyDesignByGuid(Guid identifier, IEnumerable actors, uint lockCode) - => ApplyDesign(_designManager.Designs.FirstOrDefault(x => x.Identifier == identifier), actors, DesignConverter.Version, lockCode); + => ApplyDesign(_designManager.Designs.ByIdentifier(identifier), actors, DesignConverter.Version, lockCode); } diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 72b8daf..2e77c01 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -480,8 +480,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos return null; } - design = _designs.Designs.FirstOrDefault(d => d.Identifier == guid); - if (design == null) + if (!_designs.Designs.TryGetValue(guid, out design)) { Glamourer.Messager.NotificationMessage( $"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.", diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index 4026a98..bd192be 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -11,21 +11,10 @@ using OtterGui.Classes; namespace Glamourer.Designs; -public class DesignColorUi +public class DesignColorUi(DesignColors colors, Configuration config) { - private readonly DesignColors _colors; - private readonly DesignManager _designs; - private readonly Configuration _config; - private string _newName = string.Empty; - public DesignColorUi(DesignColors colors, DesignManager designs, Configuration config) - { - _colors = colors; - _designs = designs; - _config = config; - } - public void Draw() { using var table = ImRaii.Table("designColors", 3, ImGuiTableFlags.RowBg); @@ -44,7 +33,7 @@ public class DesignColorUi ImGui.TableNextColumn(); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Recycle.ToIconString(), buttonSize, - "Revert the color used for missing design colors to its default.", _colors.MissingColor == DesignColors.MissingColorDefault, + "Revert the color used for missing design colors to its default.", colors.MissingColor == DesignColors.MissingColorDefault, true)) { changeString = DesignColors.MissingColorName; @@ -52,7 +41,7 @@ public class DesignColorUi } ImGui.TableNextColumn(); - if (DrawColorButton(DesignColors.MissingColorName, _colors.MissingColor, out var newColor)) + if (DrawColorButton(DesignColors.MissingColorName, colors.MissingColor, out var newColor)) { changeString = DesignColors.MissingColorName; changeValue = newColor; @@ -64,12 +53,12 @@ public class DesignColorUi ImGuiUtil.HoverTooltip("This color is used when the color specified in a design is not available."); - var disabled = !_config.DeleteDesignModifier.IsActive(); + var disabled = !config.DeleteDesignModifier.IsActive(); var tt = "Delete this color. This does not remove it from designs using it."; if (disabled) - tt += $"\nHold {_config.DeleteDesignModifier} to delete."; + tt += $"\nHold {config.DeleteDesignModifier} to delete."; - foreach (var ((name, color), idx) in _colors.WithIndex()) + foreach (var ((name, color), idx) in colors.WithIndex()) { using var id = ImRaii.PushId(idx); ImGui.TableNextColumn(); @@ -97,7 +86,7 @@ public class DesignColorUi ? ("Specify a name for a new color first.", true) : _newName is DesignColors.MissingColorName or DesignColors.AutomaticName ? ($"You can not use the name {DesignColors.MissingColorName} or {DesignColors.AutomaticName}, choose a different one.", true) - : _colors.ContainsKey(_newName) + : colors.ContainsKey(_newName) ? ($"The color {_newName} already exists, please choose a different name.", true) : ($"Add a new color {_newName} to your list.", false); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, disabled, true)) @@ -119,9 +108,9 @@ public class DesignColorUi if (changeString.Length > 0) { if (!changeValue.HasValue) - _colors.DeleteColor(changeString); + colors.DeleteColor(changeString); else - _colors.SetColor(changeString, changeValue.Value); + colors.SetColor(changeString, changeValue.Value); } } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 3b3b43d..c3ff2ac 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -21,16 +21,14 @@ public class DesignManager private readonly HumanModelList _humans; private readonly SaveService _saveService; private readonly DesignChanged _event; - private readonly DesignStorage _designs; private readonly Dictionary _undoStore = []; - public IReadOnlyList Designs - => _designs; + public DesignStorage Designs { get; } public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration config) { - _designs = storage; + Designs = storage; _config = config; _saveService = saveService; _items = items; @@ -55,7 +53,7 @@ public class DesignManager _items.ItemData.Awaiter.Wait(); var stopwatch = Stopwatch.StartNew(); - _designs.Clear(); + Designs.Clear(); var skipped = 0; ThreadLocal> designs = new(() => [], true); Parallel.ForEach(_saveService.FileNames.Designs(), (f, _) => @@ -79,15 +77,15 @@ public class DesignManager { if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(path)) invalidNames.Add((design, path)); - if (_designs.Any(d => d.Identifier == design.Identifier)) + if (Designs.Contains(design.Identifier)) { Glamourer.Log.Error($"Could not load design, skipped: Identifier {design.Identifier} was not unique."); ++skipped; continue; } - design.Index = _designs.Count; - _designs.Add(design); + design.Index = Designs.Count; + Designs.Add(design); } var failed = MoveInvalidNames(invalidNames); @@ -96,7 +94,7 @@ public class DesignManager $"Moved {invalidNames.Count - failed} designs to correct names.{(failed > 0 ? $" Failed to move {failed} designs to correct names." : string.Empty)}"); Glamourer.Log.Information( - $"Loaded {_designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); + $"Loaded {Designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); _event.Invoke(DesignChanged.Type.ReloadedAll, null!, null); } @@ -118,9 +116,9 @@ public class DesignManager LastEdit = DateTimeOffset.UtcNow, Identifier = CreateNewGuid(), Name = actualName, - Index = _designs.Count, + Index = Designs.Count, }; - _designs.Add(design); + Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier}."); _saveService.ImmediateSave(design); _event.Invoke(DesignChanged.Type.Created, design, path); @@ -137,10 +135,10 @@ public class DesignManager LastEdit = DateTimeOffset.UtcNow, Identifier = CreateNewGuid(), Name = actualName, - Index = _designs.Count, + Index = Designs.Count, }; - _designs.Add(design); + Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design."); _saveService.ImmediateSave(design); _event.Invoke(DesignChanged.Type.Created, design, path); @@ -157,9 +155,9 @@ public class DesignManager LastEdit = DateTimeOffset.UtcNow, Identifier = CreateNewGuid(), Name = actualName, - Index = _designs.Count, + Index = Designs.Count, }; - _designs.Add(design); + Designs.Add(design); Glamourer.Log.Debug( $"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}."); _saveService.ImmediateSave(design); @@ -170,9 +168,9 @@ public class DesignManager /// Delete a design. public void Delete(Design design) { - foreach (var d in _designs.Skip(design.Index + 1)) + foreach (var d in Designs.Skip(design.Index + 1)) --d.Index; - _designs.RemoveAt(design.Index); + Designs.RemoveAt(design.Index); _saveService.ImmediateDelete(design); _event.Invoke(DesignChanged.Type.Deleted, design, null); } @@ -591,7 +589,7 @@ public class DesignManager var errors = 0; var skips = 0; var successes = 0; - var oldDesigns = _designs.ToList(); + var oldDesigns = Designs.ToList(); try { var text = File.ReadAllText(_saveService.FileNames.MigrationDesignFile); @@ -697,7 +695,7 @@ public class DesignManager while (true) { var guid = Guid.NewGuid(); - if (_designs.All(d => d.Identifier != guid)) + if (!Designs.Contains(guid)) return guid; } } @@ -709,11 +707,11 @@ public class DesignManager /// private bool Add(Design design, string? message) { - if (_designs.Any(d => d == design || d.Identifier == design.Identifier)) + if (Designs.Any(d => d == design || d.Identifier == design.Identifier)) return false; - design.Index = _designs.Count; - _designs.Add(design); + design.Index = Designs.Count; + Designs.Add(design); if (!message.IsNullOrEmpty()) Glamourer.Log.Debug(message); _saveService.ImmediateSave(design); @@ -740,9 +738,10 @@ public class DesignManager } /// Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. - private bool ChangeMainhandPeriphery(Design design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, out EquipItem? newGauntlets) + private bool ChangeMainhandPeriphery(Design design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, + out EquipItem? newGauntlets) { - newOff = null; + newOff = null; newGauntlets = null; if (newMain.Type != currentMain.Type) { diff --git a/Glamourer/Designs/DesignStorage.cs b/Glamourer/Designs/DesignStorage.cs index 297f3be..a87415c 100644 --- a/Glamourer/Designs/DesignStorage.cs +++ b/Glamourer/Designs/DesignStorage.cs @@ -3,4 +3,16 @@ namespace Glamourer.Designs; public class DesignStorage : List, IService -{} +{ + public bool TryGetValue(Guid identifier, [NotNullWhen(true)] out Design? design) + { + design = ByIdentifier(identifier); + return design != null; + } + + public Design? ByIdentifier(Guid identifier) + => this.FirstOrDefault(d => d.Identifier == identifier); + + public bool Contains(Guid identifier) + => ByIdentifier(identifier) != null; +} diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 41a2052..e300ab4 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -587,7 +587,7 @@ public class CommandService : IDisposable if (Guid.TryParse(argument, out var guid)) { - design = _designManager.Designs.FirstOrDefault(d => d.Identifier == guid); + design = _designManager.Designs.ByIdentifier(guid); } else { diff --git a/Glamourer/Services/IGamePathParser.cs b/Glamourer/Services/IGamePathParser.cs deleted file mode 100644 index 28364d1..0000000 --- a/Glamourer/Services/IGamePathParser.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Glamourer.Services -{ - internal interface IGamePathParser - { - } -} \ No newline at end of file From 46fcac6c7d0ae218a83128367aa19361a6f39d72 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 25 Jan 2024 16:14:27 +0100 Subject: [PATCH 206/786] Misc. --- Glamourer/Automation/AutoDesignManager.cs | 10 +++++----- Glamourer/Designs/DesignData.cs | 4 ++-- Glamourer/Designs/DesignFileSystem.cs | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 2e77c01..e45b8e0 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -231,9 +231,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos { var newDesign = new AutoDesign() { - Design = design, - Type = ApplicationType.All, - Jobs = _jobs.JobGroups[1], + Design = design, + Type = ApplicationType.All, + Jobs = _jobs.JobGroups[1], }; set.Designs.Add(newDesign); Save(); @@ -494,8 +494,8 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos var ret = new AutoDesign { - Design = design, - Type = applicationType & ApplicationType.All, + Design = design, + Type = applicationType & ApplicationType.All, }; return ParseConditions(setName, jObj, ret) ? ret : null; } diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 5b573d3..6b84768 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -62,10 +62,10 @@ public unsafe struct DesignData => CrestVisibility.HasFlag(slot); - public FullEquipType MainhandType + public readonly FullEquipType MainhandType => _typeMainhand; - public FullEquipType OffhandType + public readonly FullEquipType OffhandType => _typeOffhand; public readonly EquipItem Item(EquipSlot slot) diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index 2f346a1..00277c2 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -105,7 +105,8 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable } catch (Exception ex) { - Glamourer.Messager.NotificationMessage(ex, $"Could not move design to {path} because the folder could not be created.", NotificationType.Error); + Glamourer.Messager.NotificationMessage(ex, $"Could not move design to {path} because the folder could not be created.", + NotificationType.Error); } CreateDuplicateLeaf(parent, design.Name.Text, design); From b92dc03eb5481c67dc248c97bca5a918d26aeb7f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 25 Jan 2024 16:51:37 +0100 Subject: [PATCH 207/786] Use IDesignEditor for designs. --- Glamourer/Designs/DesignEditor.cs | 314 +++++++++++++++++ Glamourer/Designs/DesignManager.cs | 413 +++++------------------ Glamourer/Designs/IDesignEditor.cs | 49 +++ Glamourer/Events/DesignChanged.cs | 3 + Glamourer/Gui/Equipment/EquipDrawData.cs | 4 +- Glamourer/Gui/ToggleDrawData.cs | 2 +- 6 files changed, 448 insertions(+), 337 deletions(-) create mode 100644 Glamourer/Designs/DesignEditor.cs create mode 100644 Glamourer/Designs/IDesignEditor.cs diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs new file mode 100644 index 0000000..eb4fefe --- /dev/null +++ b/Glamourer/Designs/DesignEditor.cs @@ -0,0 +1,314 @@ +using Glamourer.Designs.Links; +using Glamourer.Events; +using Glamourer.GameData; +using Glamourer.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Designs; + +public class DesignEditor( + SaveService saveService, + DesignChanged designChanged, + CustomizeService customizations, + ItemManager items, + Configuration config) + : IDesignEditor +{ + protected readonly DesignChanged DesignChanged = designChanged; + protected readonly SaveService SaveService = saveService; + protected readonly ItemManager Items = items; + protected readonly CustomizeService Customizations = customizations; + protected readonly Configuration Config = config; + protected readonly Dictionary UndoStore = []; + + private bool _forceFullItemOff = false; + + /// Whether an Undo for the given design is possible. + public bool CanUndo(Design? design) + => design != null && UndoStore.ContainsKey(design.Identifier); + + /// + public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings _ = default) + { + if (data is not Design design) + return; + + var oldValue = design.DesignData.Customize[idx]; + + switch (idx) + { + case CustomizeIndex.Race: + case CustomizeIndex.BodyType: + Glamourer.Log.Error("Somehow race or body type was changed in a design. This should not happen."); + return; + case CustomizeIndex.Clan: + { + var customize = design.DesignData.Customize; + if (Customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0) + return; + if (!design.SetCustomize(Customizations, customize)) + return; + + break; + } + case CustomizeIndex.Gender: + { + var customize = design.DesignData.Customize; + if (Customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0) + return; + if (!design.SetCustomize(Customizations, customize)) + return; + + break; + } + default: + if (!Customizations.IsCustomizationValid(design.DesignData.Customize.Clan, design.DesignData.Customize.Gender, + design.DesignData.Customize.Face, idx, value) + || !design.GetDesignDataRef().Customize.Set(idx, value)) + return; + + break; + } + + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}."); + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx)); + } + + /// + public void ChangeEntireCustomize(object data, in CustomizeArray customize, CustomizeFlag apply, ApplySettings _ = default) + { + if (data is not Design design) + return; + + var (newCustomize, applied, changed) = Customizations.Combine(design.DesignData.Customize, customize, apply, true); + if (changed == 0) + return; + + var oldCustomize = design.DesignData.Customize; + design.SetCustomize(Customizations, newCustomize); + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug($"Changed entire customize with resulting flags {applied} and {changed}."); + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.EntireCustomize, design, (oldCustomize, applied, changed)); + } + + /// + public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings _ = default) + { + if (data is not Design design) + return; + + var old = design.DesignData.Parameters[flag]; + if (!design.GetDesignDataRef().Parameters.Set(flag, value)) + return; + + var @new = design.DesignData.Parameters[flag]; + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}."); + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.Parameter, design, (old, @new, flag)); + } + + /// + public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings _ = default) + { + if (data is not Design design) + return; + + switch (slot) + { + case EquipSlot.MainHand: + { + var currentMain = design.DesignData.Item(EquipSlot.MainHand); + var currentOff = design.DesignData.Item(EquipSlot.OffHand); + if (!Items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item)) + return; + + if (!ChangeMainhandPeriphery(design, currentMain, currentOff, item, out var newOff, out var newGauntlets)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug( + $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId})."); + DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff, newGauntlets)); + return; + } + case EquipSlot.OffHand: + { + var currentMain = design.DesignData.Item(EquipSlot.MainHand); + var currentOff = design.DesignData.Item(EquipSlot.OffHand); + if (!Items.IsOffhandValid(currentOff.Type, item.ItemId, out item)) + return; + + if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug( + $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId})."); + DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item)); + return; + } + default: + { + if (!Items.IsItemValid(slot, item.Id, out item)) + return; + + var old = design.DesignData.Item(slot); + if (!design.GetDesignDataRef().SetItem(slot, item)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug( + $"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId})."); + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.Equip, design, (old, item, slot)); + return; + } + } + } + + /// + public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings _ = default) + { + if (data is not Design design) + return; + + if (Items.ValidateStain(stain, out var _, false).Length > 0) + return; + + var oldStain = design.DesignData.Stain(slot); + if (!design.GetDesignDataRef().SetStain(slot, stain)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}."); + DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot)); + } + + /// + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings _ = default) + { + if (item.HasValue) + ChangeItem(data, slot, item.Value, _); + if (stain.HasValue) + ChangeStain(data, slot, stain.Value, _); + } + + /// + public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings _ = default) + { + if (data is not Design design) + return; + + var oldCrest = design.DesignData.Crest(slot); + if (!design.GetDesignDataRef().SetCrest(slot, crest)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}."); + DesignChanged.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot)); + } + + /// + public void ChangeMetaState(object data, MetaIndex metaIndex, bool value, ApplySettings _ = default) + { + if (data is not Design design) + return; + + if (!design.GetDesignDataRef().SetMeta(metaIndex, value)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set value of {metaIndex} to {value}."); + DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value)); + } + + /// + public void ApplyDesign(object data, MergedDesign other, ApplySettings _ = default) + => ApplyDesign(data, other.Design); + + /// + public void ApplyDesign(object data, DesignBase other, ApplySettings _ = default) + { + if (data is not Design design) + return; + + UndoStore[design.Identifier] = design.DesignData; + foreach (var index in MetaExtensions.AllRelevant.Where(other.DoApplyMeta)) + design.GetDesignDataRef().SetMeta(index, other.DesignData.GetMeta(index)); + + if (!design.DesignData.IsHuman) + return; + + ChangeEntireCustomize(design, other.DesignData.Customize, other.ApplyCustomize); + + _forceFullItemOff = true; + foreach (var slot in EquipSlotExtensions.FullSlots) + { + if (other.DoApplyEquip(slot)) + ChangeItem(design, slot, other.DesignData.Item(slot)); + + if (other.DoApplyStain(slot)) + ChangeStain(design, slot, other.DesignData.Stain(slot)); + } + _forceFullItemOff = false; + + foreach (var slot in Enum.GetValues().Where(other.DoApplyCrest)) + ChangeCrest(design, slot, other.DesignData.Crest(slot)); + + foreach (var parameter in CustomizeParameterExtensions.AllFlags.Where(other.DoApplyParameter)) + ChangeCustomizeParameter(design, parameter, other.DesignData.Parameters[parameter]); + } + + /// Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. + private bool ChangeMainhandPeriphery(Design design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, + out EquipItem? newGauntlets) + { + newOff = null; + newGauntlets = null; + if (newMain.Type != currentMain.Type) + { + var defaultOffhand = Items.GetDefaultOffhand(newMain); + if (!Items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o)) + return false; + + newOff = o; + } + else if (!_forceFullItemOff && Config.ChangeEntireItem) + { + var defaultOffhand = Items.GetDefaultOffhand(newMain); + if (Items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o)) + newOff = o; + + if (newMain.Type is FullEquipType.Fists && Items.ItemData.Tertiary.TryGetValue(newMain.ItemId, out var g)) + newGauntlets = g; + } + + if (!design.GetDesignDataRef().SetItem(EquipSlot.MainHand, newMain)) + return false; + + if (newOff.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff.Value)) + { + design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain); + return false; + } + + if (newGauntlets.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.Hands, newGauntlets.Value)) + { + design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain); + design.GetDesignDataRef().SetItem(EquipSlot.OffHand, currentOff); + return false; + } + + return true; + } +} diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index c3ff2ac..c247396 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -9,32 +9,20 @@ using Newtonsoft.Json.Linq; using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; namespace Glamourer.Designs; -public class DesignManager +public class DesignManager : DesignEditor { - private readonly CustomizeService _customizations; - private readonly Configuration _config; - private readonly ItemManager _items; - private readonly HumanModelList _humans; - private readonly SaveService _saveService; - private readonly DesignChanged _event; - private readonly Dictionary _undoStore = []; - - public DesignStorage Designs { get; } + public readonly DesignStorage Designs; + private readonly HumanModelList _humans; public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration config) + : base(saveService, @event, customizations, items, config) { - Designs = storage; - _config = config; - _saveService = saveService; - _items = items; - _customizations = customizations; - _event = @event; - _humans = humans; + Designs = storage; + _humans = humans; LoadDesigns(designLinkLoader); CreateDesignFolder(saveService); @@ -42,27 +30,29 @@ public class DesignManager designLinkLoader.SetAllObjects(); } + #region Design Management + /// /// Clear currently loaded designs and load all designs anew from file. /// Invalid data is fixed, but changes are not saved until manual changes. /// - public void LoadDesigns(DesignLinkLoader linkLoader) + private void LoadDesigns(DesignLinkLoader linkLoader) { _humans.Awaiter.Wait(); - _customizations.Awaiter.Wait(); - _items.ItemData.Awaiter.Wait(); + Customizations.Awaiter.Wait(); + Items.ItemData.Awaiter.Wait(); var stopwatch = Stopwatch.StartNew(); Designs.Clear(); var skipped = 0; ThreadLocal> designs = new(() => [], true); - Parallel.ForEach(_saveService.FileNames.Designs(), (f, _) => + Parallel.ForEach(SaveService.FileNames.Designs(), (f, _) => { try { var text = File.ReadAllText(f.FullName); var data = JObject.Parse(text); - var design = Design.LoadDesign(_customizations, _items, linkLoader, data); + var design = Design.LoadDesign(Customizations, Items, linkLoader, data); designs.Value!.Add((design, f.FullName)); } catch (Exception ex) @@ -95,22 +85,18 @@ public class DesignManager Glamourer.Log.Information( $"Loaded {Designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); - _event.Invoke(DesignChanged.Type.ReloadedAll, null!, null); + DesignChanged.Invoke(DesignChanged.Type.ReloadedAll, null!, null); } - /// Whether an Undo for the given design is possible. - public bool CanUndo(Design? design) - => design != null && _undoStore.ContainsKey(design.Identifier); - /// Create a new temporary design without adding it to the manager. public DesignBase CreateTemporary() - => new(_customizations, _items); + => new(Customizations, Items); /// Create a new design of a given name. public Design CreateEmpty(string name, bool handlePath) { var (actualName, path) = ParseName(name, handlePath); - var design = new Design(_customizations, _items) + var design = new Design(Customizations, Items) { CreationDate = DateTimeOffset.UtcNow, LastEdit = DateTimeOffset.UtcNow, @@ -120,8 +106,8 @@ public class DesignManager }; Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier}."); - _saveService.ImmediateSave(design); - _event.Invoke(DesignChanged.Type.Created, design, path); + SaveService.ImmediateSave(design); + DesignChanged.Invoke(DesignChanged.Type.Created, design, path); return design; } @@ -140,8 +126,8 @@ public class DesignManager Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design."); - _saveService.ImmediateSave(design); - _event.Invoke(DesignChanged.Type.Created, design, path); + SaveService.ImmediateSave(design); + DesignChanged.Invoke(DesignChanged.Type.Created, design, path); return design; } @@ -160,8 +146,8 @@ public class DesignManager Designs.Add(design); Glamourer.Log.Debug( $"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}."); - _saveService.ImmediateSave(design); - _event.Invoke(DesignChanged.Type.Created, design, path); + SaveService.ImmediateSave(design); + DesignChanged.Invoke(DesignChanged.Type.Created, design, path); return design; } @@ -171,10 +157,14 @@ public class DesignManager foreach (var d in Designs.Skip(design.Index + 1)) --d.Index; Designs.RemoveAt(design.Index); - _saveService.ImmediateDelete(design); - _event.Invoke(DesignChanged.Type.Deleted, design, null); + SaveService.ImmediateDelete(design); + DesignChanged.Invoke(DesignChanged.Type.Deleted, design, null); } + #endregion + + #region Edit Information + /// Rename a design. public void Rename(Design design, string newName) { @@ -184,9 +174,9 @@ public class DesignManager design.Name = newName; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.Renamed, design, oldName); + DesignChanged.Invoke(DesignChanged.Type.Renamed, design, oldName); } /// Change the description of a design. @@ -198,9 +188,9 @@ public class DesignManager design.Description = description; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Changed description of design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription); + DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription); } public void ChangeColor(Design design, string newColor) @@ -211,9 +201,9 @@ public class DesignManager design.Color = newColor; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Changed color of design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.ChangedColor, design, oldColor); + DesignChanged.Invoke(DesignChanged.Type.ChangedColor, design, oldColor); } /// Add a new tag to a design. The tags remain sorted. @@ -225,15 +215,11 @@ public class DesignManager design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray(); design.LastEdit = DateTimeOffset.UtcNow; var idx = design.Tags.IndexOf(tag); - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.AddedTag, design, (tag, idx)); + DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, (tag, idx)); } - /// Remove a tag from a design if it exists. - public void RemoveTag(Design design, string tag) - => RemoveTag(design, design.Tags.IndexOf(tag)); - /// Remove a tag from a design by its index. public void RemoveTag(Design design, int tagIdx) { @@ -243,9 +229,9 @@ public class DesignManager var oldTag = design.Tags[tagIdx]; design.Tags = design.Tags.Take(tagIdx).Concat(design.Tags.Skip(tagIdx + 1)).ToArray(); design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.RemovedTag, design, (oldTag, tagIdx)); + DesignChanged.Invoke(DesignChanged.Type.RemovedTag, design, (oldTag, tagIdx)); } /// Rename a tag from a design by its index. The tags stay sorted. @@ -258,9 +244,9 @@ public class DesignManager design.Tags[tagIdx] = newTag; Array.Sort(design.Tags); design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags."); - _event.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx)); + DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx)); } /// Add an associated mod to a design. @@ -270,9 +256,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} to design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings)); + DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings)); } /// Remove an associated mod from a design. @@ -282,9 +268,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Removed associated mod {mod.DirectoryName} from design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings)); + DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings)); } /// Set the write protection status of a design. @@ -293,56 +279,14 @@ public class DesignManager if (!design.SetWriteProtected(value)) return; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set design {design.Identifier} to {(value ? "no longer be " : string.Empty)} write-protected."); - _event.Invoke(DesignChanged.Type.WriteProtection, design, value); + DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, value); } - /// Change a customization value. - public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value) - { - var oldValue = design.DesignData.Customize[idx]; + #endregion - switch (idx) - { - case CustomizeIndex.Race: - case CustomizeIndex.BodyType: - Glamourer.Log.Error("Somehow race or body type was changed in a design. This should not happen."); - return; - case CustomizeIndex.Clan: - { - var customize = design.DesignData.Customize; - if (_customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0) - return; - if (!design.SetCustomize(_customizations, customize)) - return; - - break; - } - case CustomizeIndex.Gender: - { - var customize = design.DesignData.Customize; - if (_customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0) - return; - if (!design.SetCustomize(_customizations, customize)) - return; - - break; - } - default: - if (!_customizations.IsCustomizationValid(design.DesignData.Customize.Clan, design.DesignData.Customize.Gender, - design.DesignData.Customize.Face, idx, value) - || !design.GetDesignDataRef().Customize.Set(idx, value)) - return; - - break; - } - - design.LastEdit = DateTimeOffset.UtcNow; - Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}."); - _saveService.QueueSave(design); - _event.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx)); - } + #region Edit Application Rules /// Change whether to apply a specific customize value. public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value) @@ -351,79 +295,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of customization {idx.ToDefaultName()} to {value}."); - _event.Invoke(DesignChanged.Type.ApplyCustomize, design, idx); - } - - /// Change a non-weapon equipment piece. - public void ChangeEquip(Design design, EquipSlot slot, EquipItem item) - { - if (!_items.IsItemValid(slot, item.Id, out item)) - return; - - var old = design.DesignData.Item(slot); - if (!design.GetDesignDataRef().SetItem(slot, item)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - Glamourer.Log.Debug( - $"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId})."); - _saveService.QueueSave(design); - _event.Invoke(DesignChanged.Type.Equip, design, (old, item, slot)); - } - - /// Change a weapon. - public void ChangeWeapon(Design design, EquipSlot slot, EquipItem item) - { - var currentMain = design.DesignData.Item(EquipSlot.MainHand); - var currentOff = design.DesignData.Item(EquipSlot.OffHand); - switch (slot) - { - case EquipSlot.MainHand: - - if (!_items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item)) - return; - - if (!ChangeMainhandPeriphery(design, currentMain, currentOff, item, out var newOff, out var newGauntlets)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); - Glamourer.Log.Debug( - $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId})."); - _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff, newGauntlets)); - - return; - case EquipSlot.OffHand: - if (!_items.IsOffhandValid(currentOff.Type, item.ItemId, out item)) - return; - - if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); - Glamourer.Log.Debug( - $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId})."); - _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item, (EquipItem?)null)); - return; - default: return; - } - } - - /// Change a customize parameter. - public void ChangeCustomizeParameter(Design design, CustomizeParameterFlag flag, CustomizeParameterValue value) - { - var old = design.DesignData.Parameters[flag]; - if (!design.GetDesignDataRef().Parameters.Set(flag, value)) - return; - - var @new = design.DesignData.Parameters[flag]; - design.LastEdit = DateTimeOffset.UtcNow; - Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}."); - _saveService.QueueSave(design); - _event.Invoke(DesignChanged.Type.Parameter, design, (old, @new, flag)); + DesignChanged.Invoke(DesignChanged.Type.ApplyCustomize, design, idx); } /// Change whether to apply a specific equipment piece. @@ -433,25 +307,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}."); - _event.Invoke(DesignChanged.Type.ApplyEquip, design, slot); - } - - /// Change the stain for any equipment piece. - public void ChangeStain(Design design, EquipSlot slot, StainId stain) - { - if (_items.ValidateStain(stain, out _, false).Length > 0) - return; - - var oldStain = design.DesignData.Stain(slot); - if (!design.GetDesignDataRef().SetStain(slot, stain)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); - Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}."); - _event.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot)); + DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, slot); } /// Change whether to apply a specific stain. @@ -461,22 +319,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}."); - _event.Invoke(DesignChanged.Type.ApplyStain, design, slot); - } - - /// Change the crest visibility for any equipment piece. - public void ChangeCrest(Design design, CrestFlag slot, bool crest) - { - var oldCrest = design.DesignData.Crest(slot); - if (!design.GetDesignDataRef().SetCrest(slot, crest)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); - Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}."); - _event.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot)); + DesignChanged.Invoke(DesignChanged.Type.ApplyStain, design, slot); } /// Change whether to apply a specific crest visibility. @@ -486,21 +331,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}."); - _event.Invoke(DesignChanged.Type.ApplyCrest, design, slot); - } - - /// Change the bool value of one of the meta flags. - public void ChangeMeta(Design design, MetaIndex metaIndex, bool value) - { - if (!design.GetDesignDataRef().SetMeta(metaIndex, value)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); - Glamourer.Log.Debug($"Set value of {metaIndex} to {value}."); - _event.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value)); + DesignChanged.Invoke(DesignChanged.Type.ApplyCrest, design, slot); } /// Change the application value of one of the meta flags. @@ -510,9 +343,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {metaIndex} to {value}."); - _event.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value)); + DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value)); } /// Change the application value of a customize parameter. @@ -522,68 +355,26 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of parameter {flag} to {value}."); - _event.Invoke(DesignChanged.Type.ApplyParameter, design, flag); + DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, flag); } - /// Apply an entire design based on its appliance rules piece by piece. - public void ApplyDesign(Design design, DesignBase other) - { - _undoStore[design.Identifier] = design.DesignData; - foreach (var index in MetaExtensions.AllRelevant.Where(other.DoApplyMeta)) - design.GetDesignDataRef().SetMeta(index, other.DesignData.GetMeta(index)); - - if (design.DesignData.IsHuman) - { - foreach (var index in Enum.GetValues()) - { - if (other.DoApplyCustomize(index)) - ChangeCustomize(design, index, other.DesignData.Customize[index]); - } - - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - if (other.DoApplyEquip(slot)) - ChangeEquip(design, slot, other.DesignData.Item(slot)); - - if (other.DoApplyStain(slot)) - ChangeStain(design, slot, other.DesignData.Stain(slot)); - } - - foreach (var slot in Enum.GetValues()) - { - if (other.DoApplyCrest(slot)) - ChangeCrest(design, slot, other.DesignData.Crest(slot)); - } - } - - if (other.DoApplyEquip(EquipSlot.MainHand)) - ChangeWeapon(design, EquipSlot.MainHand, other.DesignData.Item(EquipSlot.MainHand)); - - if (other.DoApplyEquip(EquipSlot.OffHand)) - ChangeWeapon(design, EquipSlot.OffHand, other.DesignData.Item(EquipSlot.OffHand)); - - if (other.DoApplyStain(EquipSlot.MainHand)) - ChangeStain(design, EquipSlot.MainHand, other.DesignData.Stain(EquipSlot.MainHand)); - - if (other.DoApplyStain(EquipSlot.OffHand)) - ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand)); - } + #endregion public void UndoDesignChange(Design design) { - if (!_undoStore.Remove(design.Identifier, out var otherData)) + if (!UndoStore.Remove(design.Identifier, out var otherData)) return; var other = CreateTemporary(); - other.SetDesignData(_customizations, otherData); + other.SetDesignData(Customizations, otherData); ApplyDesign(design, other); } private void MigrateOldDesigns() { - if (!File.Exists(_saveService.FileNames.MigrationDesignFile)) + if (!File.Exists(SaveService.FileNames.MigrationDesignFile)) return; var errors = 0; @@ -592,7 +383,7 @@ public class DesignManager var oldDesigns = Designs.ToList(); try { - var text = File.ReadAllText(_saveService.FileNames.MigrationDesignFile); + var text = File.ReadAllText(SaveService.FileNames.MigrationDesignFile); var dict = JsonConvert.DeserializeObject>(text) ?? new Dictionary(); var migratedFileSystemPaths = new Dictionary(dict.Count); foreach (var (name, base64) in dict) @@ -600,14 +391,14 @@ public class DesignManager try { var actualName = Path.GetFileName(name); - var design = new Design(_customizations, _items) + var design = new Design(Customizations, Items) { - CreationDate = File.GetCreationTimeUtc(_saveService.FileNames.MigrationDesignFile), - LastEdit = File.GetLastWriteTimeUtc(_saveService.FileNames.MigrationDesignFile), + CreationDate = File.GetCreationTimeUtc(SaveService.FileNames.MigrationDesignFile), + LastEdit = File.GetLastWriteTimeUtc(SaveService.FileNames.MigrationDesignFile), Identifier = CreateNewGuid(), Name = actualName, }; - design.MigrateBase64(_customizations, _items, _humans, base64); + design.MigrateBase64(Customizations, Items, _humans, base64); if (!oldDesigns.Any(d => d.Name == design.Name && d.CreationDate == design.CreationDate)) { Add(design, $"Migrated old design to {design.Identifier}."); @@ -628,24 +419,24 @@ public class DesignManager } } - DesignFileSystem.MigrateOldPaths(_saveService, migratedFileSystemPaths); + DesignFileSystem.MigrateOldPaths(SaveService, migratedFileSystemPaths); Glamourer.Log.Information( $"Successfully migrated {successes} old designs. Skipped {skips} already migrated designs. Failed to migrate {errors} designs."); } catch (Exception e) { - Glamourer.Log.Error($"Could not migrate old design file {_saveService.FileNames.MigrationDesignFile}:\n{e}"); + Glamourer.Log.Error($"Could not migrate old design file {SaveService.FileNames.MigrationDesignFile}:\n{e}"); } try { - File.Move(_saveService.FileNames.MigrationDesignFile, - Path.ChangeExtension(_saveService.FileNames.MigrationDesignFile, ".json.bak")); - Glamourer.Log.Information($"Moved migrated design file {_saveService.FileNames.MigrationDesignFile} to backup file."); + File.Move(SaveService.FileNames.MigrationDesignFile, + Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak")); + Glamourer.Log.Information($"Moved migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file."); } catch (Exception ex) { - Glamourer.Log.Error($"Could not move migrated design file {_saveService.FileNames.MigrationDesignFile} to backup file:\n{ex}"); + Glamourer.Log.Error($"Could not move migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file:\n{ex}"); } } @@ -675,7 +466,7 @@ public class DesignManager { try { - var correctName = _saveService.FileNames.DesignFile(design); + var correctName = SaveService.FileNames.DesignFile(design); File.Move(name, correctName, false); Glamourer.Log.Information($"Moved invalid design file from {Path.GetFileName(name)} to {Path.GetFileName(correctName)}."); } @@ -705,18 +496,17 @@ public class DesignManager /// Returns false if the design is already contained or if the identifier is already in use. /// The design is treated as newly created and invokes an event. /// - private bool Add(Design design, string? message) + private void Add(Design design, string? message) { if (Designs.Any(d => d == design || d.Identifier == design.Identifier)) - return false; + return; design.Index = Designs.Count; Designs.Add(design); if (!message.IsNullOrEmpty()) Glamourer.Log.Debug(message); - _saveService.ImmediateSave(design); - _event.Invoke(DesignChanged.Type.Created, design, null); - return true; + SaveService.ImmediateSave(design); + DesignChanged.Invoke(DesignChanged.Type.Created, design, null); } /// Split a given string into its folder path and its name, if is true. @@ -736,47 +526,4 @@ public class DesignManager return (actualName, path); } - - /// Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. - private bool ChangeMainhandPeriphery(Design design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, - out EquipItem? newGauntlets) - { - newOff = null; - newGauntlets = null; - if (newMain.Type != currentMain.Type) - { - var defaultOffhand = _items.GetDefaultOffhand(newMain); - if (!_items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o)) - return false; - - newOff = o; - } - else if (_config.ChangeEntireItem) - { - var defaultOffhand = _items.GetDefaultOffhand(newMain); - if (_items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o)) - newOff = o; - - if (newMain.Type is FullEquipType.Fists && _items.ItemData.Tertiary.TryGetValue(newMain.ItemId, out var g)) - newGauntlets = g; - } - - if (!design.GetDesignDataRef().SetItem(EquipSlot.MainHand, newMain)) - return false; - - if (newOff.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff.Value)) - { - design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain); - return false; - } - - if (newGauntlets.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.Hands, newGauntlets.Value)) - { - design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain); - design.GetDesignDataRef().SetItem(EquipSlot.OffHand, currentOff); - return false; - } - - return true; - } } diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs new file mode 100644 index 0000000..8cf1a87 --- /dev/null +++ b/Glamourer/Designs/IDesignEditor.cs @@ -0,0 +1,49 @@ +using Glamourer.Designs.Links; +using Glamourer.GameData; +using Glamourer.State; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Designs; + +public readonly record struct ApplySettings( + uint Key = 0, + StateSource Source = StateSource.Manual, + bool RespectManual = false, + bool FromJobChange = false, + bool UseSingleSource = false); + +public interface IDesignEditor +{ + /// Change a customization value. + public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings settings = default); + + /// Change an entire customize array according to the given flags. + public void ChangeEntireCustomize(object data, in CustomizeArray customizeInput, CustomizeFlag apply, ApplySettings settings = default); + + /// Change a customize parameter. + public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue v, ApplySettings settings = default); + + /// Change an equipment piece. + public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default) + => ChangeEquip(data, slot, item, null, settings); + + /// Change the stain for any equipment piece. + public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings = default) + => ChangeEquip(data, slot, null, stain, settings); + + /// Change an equipment piece and its stain at the same time. + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings = default); + + /// Change the crest visibility for any equipment piece. + public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings = default); + + /// Change the bool value of one of the meta flags. + public void ChangeMetaState(object data, MetaIndex slot, bool value, ApplySettings settings = default); + + /// Change all values applies from the given design. + public void ApplyDesign(object data, MergedDesign design, ApplySettings settings = default); + + /// Change all values applies from the given design. + public void ApplyDesign(object data, DesignBase design, ApplySettings settings = default); +} diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 121c58c..f3ebb5f 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -56,6 +56,9 @@ public sealed class DesignChanged() /// An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. Customize, + /// An existing design had its entire customize array changed. Data is the old array, the applied flags and the changed flags. [(CustomizeArray, CustomizeFlag, CustomizeFlag)]. + EntireCustomize, + /// An existing design had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. Equip, diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 1450fb5..f8dd42c 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -31,9 +31,7 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot) => new(slot, design.DesignData) { - ItemSetter = slot.IsEquipment() || slot.IsAccessory() - ? i => manager.ChangeEquip(design, slot, i) - : i => manager.ChangeWeapon(design, slot, i), + ItemSetter = i => manager.ChangeItem(design, slot, i), StainSetter = i => manager.ChangeStain(design, slot, i), ApplySetter = b => manager.ChangeApplyEquip(design, slot, b), ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b), diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index 5758229..deb1908 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -30,7 +30,7 @@ public ref struct ToggleDrawData DisplayApplication = true, CurrentValue = design.DesignData.GetMeta(index), CurrentApply = design.DoApplyMeta(index), - SetValue = b => manager.ChangeMeta(design, index, b), + SetValue = b => manager.ChangeMetaState(design, index, b), SetApply = b => manager.ChangeApplyMeta(design, index, b), }; From 25ddbb1310573a4dbc19b44c890cb4ca9e59e43d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 26 Jan 2024 00:18:08 +0100 Subject: [PATCH 208/786] Fix issue with buttons sharing state. --- Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index a4f71c0..cb401a1 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -128,7 +128,11 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Date: Fri, 26 Jan 2024 13:23:33 +0100 Subject: [PATCH 209/786] Make states work. --- Glamourer/Api/GlamourerIpc.Apply.cs | 2 +- Glamourer/Api/GlamourerIpc.Set.cs | 5 +- Glamourer/Automation/AutoDesignApplier.cs | 320 ++--------- Glamourer/Designs/DesignEditor.cs | 9 +- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Designs/IDesignEditor.cs | 21 +- Glamourer/Designs/Links/DesignMerger.cs | 2 +- Glamourer/Designs/Links/MergedDesign.cs | 18 - .../CustomizeParameterDrawData.cs | 2 +- Glamourer/Gui/DesignQuickBar.cs | 6 +- Glamourer/Gui/Equipment/EquipDrawData.cs | 4 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 28 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 23 +- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 7 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 5 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 6 +- Glamourer/Gui/ToggleDrawData.cs | 4 +- Glamourer/Interop/ContextMenuService.cs | 14 +- Glamourer/Services/CommandService.cs | 4 +- Glamourer/Services/ServiceManager.cs | 2 +- Glamourer/State/ActorState.cs | 1 - Glamourer/State/InternalStateEditor.cs | 234 ++++++++ Glamourer/State/JobChangeState.cs | 30 + Glamourer/State/StateApplier.cs | 42 +- Glamourer/State/StateEditor.cs | 523 +++++++++++------- Glamourer/State/StateListener.cs | 32 +- Glamourer/State/StateManager.cs | 298 +--------- 27 files changed, 787 insertions(+), 857 deletions(-) create mode 100644 Glamourer/State/InternalStateEditor.cs create mode 100644 Glamourer/State/JobChangeState.cs diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs index c2d68aa..d155cf2 100644 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ b/Glamourer/Api/GlamourerIpc.Apply.cs @@ -130,7 +130,7 @@ public partial class GlamourerIpc if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode)) { - _stateManager.ApplyDesign(design, state, StateSource.Ipc, lockCode); + _stateManager.ApplyDesign(state, design, new ApplySettings(Source:StateSource.Ipc, Key:lockCode)); state.Lock(lockCode); } } diff --git a/Glamourer/Api/GlamourerIpc.Set.cs b/Glamourer/Api/GlamourerIpc.Set.cs index 983fd71..db5941f 100644 --- a/Glamourer/Api/GlamourerIpc.Set.cs +++ b/Glamourer/Api/GlamourerIpc.Set.cs @@ -1,5 +1,6 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; +using Glamourer.Designs; using Glamourer.Events; using Glamourer.Services; using Glamourer.State; @@ -56,7 +57,7 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeEquip(state, slot, item, stainId, StateSource.Ipc, key); + _stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key:key)); return GlamourerErrorCode.Success; } @@ -83,7 +84,7 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeEquip(state, slot, item, stainId, StateSource.Ipc, key); + _stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key: key)); found = true; } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index db4058f..f8ca397 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -3,12 +3,9 @@ using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Designs; using Glamourer.Designs.Links; using Glamourer.Events; -using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Structs; -using Glamourer.Services; using Glamourer.State; -using Glamourer.Unlocks; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -18,52 +15,39 @@ namespace Glamourer.Automation; public sealed class AutoDesignApplier : IDisposable { - private readonly Configuration _config; - private readonly AutoDesignManager _manager; - private readonly StateManager _state; - private readonly JobService _jobs; - private readonly EquippedGearset _equippedGearset; - private readonly ActorManager _actors; - private readonly CustomizeService _customizations; - private readonly CustomizeUnlockManager _customizeUnlocks; - private readonly ItemUnlockManager _itemUnlocks; - private readonly AutomationChanged _event; - private readonly ObjectManager _objects; - private readonly WeaponLoading _weapons; - private readonly HumanModelList _humans; - private readonly DesignMerger _designMerger; - private readonly IClientState _clientState; + private readonly Configuration _config; + private readonly AutoDesignManager _manager; + private readonly StateManager _state; + private readonly JobService _jobs; + private readonly EquippedGearset _equippedGearset; + private readonly ActorManager _actors; + private readonly AutomationChanged _event; + private readonly ObjectManager _objects; + private readonly WeaponLoading _weapons; + private readonly HumanModelList _humans; + private readonly DesignMerger _designMerger; + private readonly IClientState _clientState; - private ActorState? _jobChangeState; - private readonly Dictionary _jobChange = []; + private readonly JobChangeState _jobChangeState; - private void ResetJobChange() - { - _jobChangeState = null; - _jobChange.Clear(); - } - - public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, - CustomizeService customizations, ActorManager actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, + public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors, AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, - EquippedGearset equippedGearset, DesignMerger designMerger) + EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState) { - _config = config; - _manager = manager; - _state = state; - _jobs = jobs; - _customizations = customizations; - _actors = actors; - _itemUnlocks = itemUnlocks; - _customizeUnlocks = customizeUnlocks; - _event = @event; - _objects = objects; - _weapons = weapons; - _humans = humans; - _clientState = clientState; - _equippedGearset = equippedGearset; - _designMerger = designMerger; - _jobs.JobChanged += OnJobChange; + _config = config; + _manager = manager; + _state = state; + _jobs = jobs; + _actors = actors; + _event = @event; + _objects = objects; + _weapons = weapons; + _humans = humans; + _clientState = clientState; + _equippedGearset = equippedGearset; + _designMerger = designMerger; + _jobChangeState = jobChangeState; + _jobs.JobChanged += OnJobChange; _event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier); _weapons.Subscribe(OnWeaponLoading, WeaponLoading.Priority.AutoDesignApplier); _equippedGearset.Subscribe(OnEquippedGearset, EquippedGearset.Priority.AutoDesignApplier); @@ -79,45 +63,46 @@ public sealed class AutoDesignApplier : IDisposable private void OnWeaponLoading(Actor actor, EquipSlot slot, ref CharacterWeapon weapon) { - if (_jobChangeState == null || !_config.EnableAutoDesigns) + if (!_jobChangeState.HasState || !_config.EnableAutoDesigns) return; var id = actor.GetIdentifier(_actors); if (id == _jobChangeState.Identifier) { - var current = _jobChangeState.BaseData.Item(slot); + var state = _jobChangeState.State!; + var current = state.BaseData.Item(slot); switch (slot) { case EquipSlot.MainHand: { - if (_jobChange.TryGetValue(current.Type, out var data)) + if (_jobChangeState.TryGetValue(current.Type, out var data)) { Glamourer.Log.Verbose( - $"Changing Mainhand from {_jobChangeState.ModelData.Weapon(EquipSlot.MainHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); - _state.ChangeItem(_jobChangeState, EquipSlot.MainHand, data.Item1, data.Item2); - weapon = _jobChangeState.ModelData.Weapon(EquipSlot.MainHand); + $"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); + _state.ChangeItem(_jobChangeState, EquipSlot.MainHand, data.Item1, new ApplySettings(Source: data.Item2)); + weapon = state.ModelData.Weapon(EquipSlot.MainHand); } break; } - case EquipSlot.OffHand when current.Type == _jobChangeState.BaseData.MainhandType.Offhand(): + case EquipSlot.OffHand when current.Type == state.BaseData.MainhandType.Offhand(): { - if (_jobChange.TryGetValue(current.Type, out var data)) + if (_jobChangeState.TryGetValue(current.Type, out var data)) { Glamourer.Log.Verbose( - $"Changing Offhand from {_jobChangeState.ModelData.Weapon(EquipSlot.OffHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); - _state.ChangeItem(_jobChangeState, EquipSlot.OffHand, data.Item1, data.Item2); - weapon = _jobChangeState.ModelData.Weapon(EquipSlot.OffHand); + $"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); + _state.ChangeItem(_jobChangeState, EquipSlot.OffHand, data.Item1, new ApplySettings(Source: data.Item2)); + weapon = state.ModelData.Weapon(EquipSlot.OffHand); } - ResetJobChange(); + _jobChangeState.Reset(); break; } } } else { - ResetJobChange(); + _jobChangeState.Reset(); } } @@ -135,7 +120,7 @@ public sealed class AutoDesignApplier : IDisposable break; case AutomationChanged.Type.ChangeIdentifier when set.Enabled: // Remove fixed state from the old identifiers assigned and the old enabled set, if any. - var (oldIds, _, oldSet) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!; + var (oldIds, _, _) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!; RemoveOld(oldIds); ApplyNew(set); // Does not need to disable oldSet because same identifiers. break; @@ -277,8 +262,9 @@ public sealed class AutoDesignApplier : IDisposable if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) return; - var mergedDesign = _designMerger.Merge(set.Designs.Where(d => d.IsActive(actor)).Select(d => ((DesignBase?) d.Design, d.Type)), state.ModelData, true, false); - ApplyToState(state, mergedDesign, respectManual, fromJobChange, StateSource.Fixed); + var mergedDesign = _designMerger.Merge(set.Designs.Where(d => d.IsActive(actor)).Select(d => ((DesignBase?)d.Design, d.Type)), + state.ModelData, true, false); + _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false)); } /// Get world-specific first and all-world afterward. @@ -304,218 +290,6 @@ public sealed class AutoDesignApplier : IDisposable } } - private void ApplyToState(ActorState state, MergedDesign mergedDesign, bool respectManual, bool fromJobChange, StateSource source) - { - foreach (var slot in CrestExtensions.AllRelevantSet.Where(mergedDesign.Design.DoApplyCrest)) - if (!respectManual || state.Sources[slot] is not StateSource.Manual) - _state.ChangeCrest(state, slot, mergedDesign.Design.DesignData.Crest(slot), mergedDesign.GetSource(slot, source)); - - foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate()) - if (!respectManual || state.Sources[parameter] is not StateSource.Manual and not StateSource.Pending) - _state.ChangeCustomizeParameter(state, parameter, mergedDesign.Design.DesignData.Parameters[parameter], mergedDesign.GetSource(parameter, source)); - - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - if (mergedDesign.Design.DoApplyEquip(slot)) - { - if (!respectManual || state.Sources[slot, false] is not StateSource.Manual) - _state.ChangeItem(state, slot, mergedDesign.Design.DesignData.Item(slot), mergedDesign.GetSource(slot, false, source)); - } - - if (mergedDesign.Design.DoApplyStain(slot)) - { - if (!respectManual || state.Sources[slot, true] is not StateSource.Manual) - _state.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot), mergedDesign.GetSource(slot, true, source)); - } - } - - foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots) - { - if (mergedDesign.Design.DoApplyStain(weaponSlot)) - { - if (!respectManual || state.Sources[weaponSlot, true] is not StateSource.Manual) - _state.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot), mergedDesign.GetSource(weaponSlot, true, source)); - } - - if (!mergedDesign.Design.DoApplyEquip(weaponSlot)) - continue; - - if (respectManual && state.Sources[weaponSlot, false] is StateSource.Manual) - continue; - - var currentType = state.ModelData.Item(weaponSlot).Type; - if (fromJobChange) - { - foreach (var (key, (weapon, weaponSource)) in mergedDesign.Weapons) - if (key.ToSlot() == weaponSlot) - _jobChange.TryAdd(key, (weapon, MergedDesign.GetSource(weaponSource, source))); - _jobChangeState = state; - } - else if (mergedDesign.Weapons.TryGetValue(currentType, out var weapon)) - { - _state.ChangeItem(state, weaponSlot, weapon.Item1, MergedDesign.GetSource(weapon.Item2, source)); - } - } - } - - private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual, - StateSource source, bool fromJobChange) - { - equipFlags &= ~totalEquipFlags; - if (equipFlags == 0) - return; - - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - var flag = slot.ToFlag(); - if (equipFlags.HasFlag(flag)) - { - var item = design.Item(slot); - if (!_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _)) - { - if (!respectManual || state.Sources[slot, false] is not StateSource.Manual) - _state.ChangeItem(state, slot, item, source); - totalEquipFlags |= flag; - } - } - - var stainFlag = slot.ToStainFlag(); - if (equipFlags.HasFlag(stainFlag)) - { - if (!respectManual || state.Sources[slot, true] is not StateSource.Manual) - _state.ChangeStain(state, slot, design.Stain(slot), source); - totalEquipFlags |= stainFlag; - } - } - - if (equipFlags.HasFlag(EquipFlag.Mainhand)) - { - var item = design.Item(EquipSlot.MainHand); - var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); - var checkState = !respectManual || state.Sources[EquipSlot.MainHand, false] is not StateSource.Manual; - if (checkUnlock && checkState) - { - if (fromJobChange) - { - _jobChange.TryAdd(item.Type, (item, source)); - _jobChangeState = state; - } - else if (state.ModelData.Item(EquipSlot.MainHand).Type == item.Type) - { - _state.ChangeItem(state, EquipSlot.MainHand, item, source); - totalEquipFlags |= EquipFlag.Mainhand; - } - } - } - - if (equipFlags.HasFlag(EquipFlag.Offhand)) - { - var item = design.Item(EquipSlot.OffHand); - var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); - var checkState = !respectManual || state.Sources[EquipSlot.OffHand, false] is not StateSource.Manual; - if (checkUnlock && checkState) - { - if (fromJobChange) - { - _jobChange.TryAdd(item.Type, (item, source)); - _jobChangeState = state; - } - else if (state.ModelData.Item(EquipSlot.OffHand).Type == item.Type) - { - _state.ChangeItem(state, EquipSlot.OffHand, item, source); - totalEquipFlags |= EquipFlag.Offhand; - } - } - } - - if (equipFlags.HasFlag(EquipFlag.MainhandStain)) - { - if (!respectManual || state.Sources[EquipSlot.MainHand, true] is not StateSource.Manual) - _state.ChangeStain(state, EquipSlot.MainHand, design.Stain(EquipSlot.MainHand), source); - totalEquipFlags |= EquipFlag.MainhandStain; - } - - if (equipFlags.HasFlag(EquipFlag.OffhandStain)) - { - if (!respectManual || state.Sources[EquipSlot.OffHand, true] is not StateSource.Manual) - _state.ChangeStain(state, EquipSlot.OffHand, design.Stain(EquipSlot.OffHand), source); - totalEquipFlags |= EquipFlag.OffhandStain; - } - } - - private void ReduceCustomize(ActorState state, in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag totalCustomizeFlags, - bool respectManual, StateSource source) - { - customizeFlags &= ~totalCustomizeFlags; - if (customizeFlags == 0) - return; - - var customize = state.ModelData.Customize; - CustomizeFlag fixFlags = 0; - - // Skip anything not human. - if (!state.ModelData.IsHuman || !design.IsHuman) - return; - - if (customizeFlags.HasFlag(CustomizeFlag.Clan)) - { - if (!respectManual || state.Sources[CustomizeIndex.Clan] is not StateSource.Manual) - fixFlags |= _customizations.ChangeClan(ref customize, design.Customize.Clan); - customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race); - totalCustomizeFlags |= CustomizeFlag.Clan | CustomizeFlag.Race; - } - - if (customizeFlags.HasFlag(CustomizeFlag.Gender)) - { - if (!respectManual || state.Sources[CustomizeIndex.Gender] is not StateSource.Manual) - fixFlags |= _customizations.ChangeGender(ref customize, design.Customize.Gender); - customizeFlags &= ~CustomizeFlag.Gender; - totalCustomizeFlags |= CustomizeFlag.Gender; - } - - if (fixFlags != 0) - _state.ChangeCustomize(state, customize, fixFlags, source); - - if (customizeFlags.HasFlag(CustomizeFlag.Face)) - { - if (!respectManual || state.Sources[CustomizeIndex.Face] is not StateSource.Manual) - _state.ChangeCustomize(state, CustomizeIndex.Face, design.Customize.Face, source); - customizeFlags &= ~CustomizeFlag.Face; - totalCustomizeFlags |= CustomizeFlag.Face; - } - - var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); - var face = state.ModelData.Customize.Face; - foreach (var index in Enum.GetValues()) - { - var flag = index.ToFlag(); - if (!customizeFlags.HasFlag(flag)) - continue; - - var value = design.Customize[index]; - if (CustomizeService.IsCustomizationValid(set, face, index, value, out var data)) - { - if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _)) - continue; - - if (!respectManual || state.Sources[index] is not StateSource.Manual) - _state.ChangeCustomize(state, index, value, source); - totalCustomizeFlags |= flag; - } - } - } - - private void ReduceMeta(ActorState state, in DesignData design, MetaFlag apply, ref MetaFlag totalMetaFlags, bool respectManual, StateSource source) - { - apply &= ~totalMetaFlags; - foreach (var index in MetaExtensions.AllRelevant) - { - if (!respectManual || state.Sources[index] is not StateSource.Manual) - _state.ChangeMeta(state, index, design.GetMeta(index), source); - totalMetaFlags |= index.ToFlag(); - } - } - internal static int NewGearsetId = -1; private void OnEquippedGearset(string name, int id, int prior, byte _, byte job) diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index eb4fefe..d150db5 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -254,12 +254,11 @@ public class DesignEditor( _forceFullItemOff = true; foreach (var slot in EquipSlotExtensions.FullSlots) { - if (other.DoApplyEquip(slot)) - ChangeItem(design, slot, other.DesignData.Item(slot)); - - if (other.DoApplyStain(slot)) - ChangeStain(design, slot, other.DesignData.Stain(slot)); + ChangeEquip(design, slot, + other.DoApplyEquip(slot) ? other.DesignData.Item(slot) : null, + other.DoApplyStain(slot) ? other.DesignData.Stain(slot) : null); } + _forceFullItemOff = false; foreach (var slot in Enum.GetValues().Where(other.DoApplyCrest)) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index c247396..408cf27 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -12,7 +12,7 @@ using Penumbra.GameData.Enums; namespace Glamourer.Designs; -public class DesignManager : DesignEditor +public sealed class DesignManager : DesignEditor { public readonly DesignStorage Designs; private readonly HumanModelList _humans; diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index 8cf1a87..a4da53c 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -11,7 +11,26 @@ public readonly record struct ApplySettings( StateSource Source = StateSource.Manual, bool RespectManual = false, bool FromJobChange = false, - bool UseSingleSource = false); + bool UseSingleSource = false) +{ + public static readonly ApplySettings Manual = new() + { + Key = 0, + Source = StateSource.Manual, + FromJobChange = false, + RespectManual = false, + UseSingleSource = false, + }; + + public static readonly ApplySettings Game = new() + { + Key = 0, + Source = StateSource.Game, + FromJobChange = false, + RespectManual = false, + UseSingleSource = false, + }; +} public interface IDesignEditor { diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index ef77226..7670498 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -224,7 +224,7 @@ public class DesignMerger( ret.Sources[CustomizeIndex.Face] = source; } - var set = ret.Design.CustomizeSet; + var set = _customize.Manager.GetSet(customize.Clan, customize.Gender); var face = customize.Face; foreach (var index in Enum.GetValues()) { diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index 74e65ca..bcfaa39 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -48,22 +48,4 @@ public sealed class MergedDesign public readonly Dictionary Weapons = new(4); public readonly SortedList AssociatedMods = []; public StateSources Sources = new(); - - public StateSource GetSource(EquipSlot slot, bool stain, StateSource actualSource) - => GetSource(Sources[slot, stain], actualSource); - - public StateSource GetSource(CrestFlag slot, StateSource actualSource) - => GetSource(Sources[slot], actualSource); - - public StateSource GetSource(CustomizeIndex type, StateSource actualSource) - => GetSource(Sources[type], actualSource); - - public StateSource GetSource(MetaIndex index, StateSource actualSource) - => GetSource(Sources[index], actualSource); - - public StateSource GetSource(CustomizeParameterFlag flag, StateSource actualSource) - => GetSource(Sources[flag], actualSource); - - public static StateSource GetSource(StateSource given, StateSource actualSource) - => given is StateSource.Game ? StateSource.Game : actualSource; } diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs index 07f3486..d092cde 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs @@ -33,7 +33,7 @@ public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in Des { Locked = state.IsLocked, DisplayApplication = false, - ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, StateSource.Manual), + ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, ApplySettings.Manual), GameValue = state.BaseData.Parameters[flag], AllowRevert = true, }; diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 37c37e7..78e813e 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -5,7 +5,7 @@ using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; using Glamourer.Automation; -using Glamourer.Events; +using Glamourer.Designs; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.State; @@ -32,7 +32,7 @@ public sealed class DesignQuickBar : Window, IDisposable private readonly ImRaii.Style _windowPadding = new(); private readonly ImRaii.Color _windowColor = new(); private DateTime _keyboardToggle = DateTime.UnixEpoch; - private int _numButtons = 0; + private int _numButtons; public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState, ObjectManager objects, AutoDesignApplier autoDesignApplier) @@ -163,7 +163,7 @@ public sealed class DesignQuickBar : Window, IDisposable var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); - _stateManager.ApplyDesign(design, state, StateSource.Manual); + _stateManager.ApplyDesign(state, design, ApplySettings.Manual); } public void DrawRevertButton(Vector2 buttonSize) diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index f8dd42c..6bedb02 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -44,8 +44,8 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public static EquipDrawData FromState(StateManager manager, ActorState state, EquipSlot slot) => new(slot, state.ModelData) { - ItemSetter = i => manager.ChangeItem(state, slot, i, StateSource.Manual), - StainSetter = i => manager.ChangeStain(state, slot, i, StateSource.Manual), + ItemSetter = i => manager.ChangeItem(state, slot, i, ApplySettings.Manual), + StainSetter = i => manager.ChangeStain(state, slot, i, ApplySettings.Manual), Locked = state.IsLocked, DisplayApplication = false, GameItem = state.BaseData.Item(slot), diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index ecea9ad..4cb11d0 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -1,4 +1,4 @@ -using Glamourer.Events; +using Glamourer.Designs; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; @@ -11,7 +11,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui; -public class PenumbraChangedItemTooltip : IDisposable +public sealed class PenumbraChangedItemTooltip : IDisposable { private readonly PenumbraService _penumbra; private readonly StateManager _stateManager; @@ -111,24 +111,24 @@ public class PenumbraChangedItemTooltip : IDisposable switch (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) { case (false, false): - Glamourer.Log.Information($"Applying {item.Name} to Right Finger."); + Glamourer.Log.Debug($"Applying {item.Name} to Right Finger."); SetLastItem(EquipSlot.RFinger, item, state); - _stateManager.ChangeItem(state, EquipSlot.RFinger, item, StateSource.Manual); + _stateManager.ChangeItem(state, EquipSlot.RFinger, item, ApplySettings.Manual); break; case (false, true): - Glamourer.Log.Information($"Applying {item.Name} to Left Finger."); + Glamourer.Log.Debug($"Applying {item.Name} to Left Finger."); SetLastItem(EquipSlot.LFinger, item, state); - _stateManager.ChangeItem(state, EquipSlot.LFinger, item, StateSource.Manual); + _stateManager.ChangeItem(state, EquipSlot.LFinger, item, ApplySettings.Manual); break; case (true, false) when last.Valid: - Glamourer.Log.Information($"Re-Applying {last.Name} to Right Finger."); + Glamourer.Log.Debug($"Re-Applying {last.Name} to Right Finger."); SetLastItem(EquipSlot.RFinger, default, state); - _stateManager.ChangeItem(state, EquipSlot.RFinger, last, StateSource.Manual); + _stateManager.ChangeItem(state, EquipSlot.RFinger, last, ApplySettings.Manual); break; case (true, true) when _lastItems[EquipSlot.LFinger.ToIndex()].Valid: - Glamourer.Log.Information($"Re-Applying {last.Name} to Left Finger."); + Glamourer.Log.Debug($"Re-Applying {last.Name} to Left Finger."); SetLastItem(EquipSlot.LFinger, default, state); - _stateManager.ChangeItem(state, EquipSlot.LFinger, last, StateSource.Manual); + _stateManager.ChangeItem(state, EquipSlot.LFinger, last, ApplySettings.Manual); break; } @@ -136,15 +136,15 @@ public class PenumbraChangedItemTooltip : IDisposable default: if (ImGui.GetIO().KeyCtrl && last.Valid) { - Glamourer.Log.Information($"Re-Applying {last.Name} to {slot.ToName()}."); + Glamourer.Log.Debug($"Re-Applying {last.Name} to {slot.ToName()}."); SetLastItem(slot, default, state); - _stateManager.ChangeItem(state, slot, last, StateSource.Manual); + _stateManager.ChangeItem(state, slot, last, ApplySettings.Manual); } else { - Glamourer.Log.Information($"Applying {item.Name} to {slot.ToName()}."); + Glamourer.Log.Debug($"Applying {item.Name} to {slot.ToName()}."); SetLastItem(slot, item, state); - _stateManager.ChangeItem(state, slot, item, StateSource.Manual); + _stateManager.ChangeItem(state, slot, item, ApplySettings.Manual); } return; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index e1d0eef..15f5bd0 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -5,7 +5,6 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Automation; using Glamourer.Designs; -using Glamourer.Events; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop; @@ -61,13 +60,13 @@ public class ActorPanel( if (_importService.CreateDatTarget(out var dat)) { - _stateManager.ChangeCustomize(_state!, dat.Customize, CustomizeApplicationFlags, StateSource.Manual); + _stateManager.ChangeEntireCustomize(_state!, dat.Customize, CustomizeApplicationFlags, ApplySettings.Manual); Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_state.Identifier}.", NotificationType.Success, false); } else if (_importService.CreateCharaTarget(out var designBase, out var name)) { - _stateManager.ApplyDesign(designBase, _state!, StateSource.Manual); + _stateManager.ApplyDesign(_state!, designBase, ApplySettings.Manual); Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_state.Identifier}.", NotificationType.Success, false); } @@ -139,7 +138,7 @@ public class ActorPanel( return; if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw)) - _stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateSource.Manual); + _stateManager.ChangeEntireCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, ApplySettings.Manual); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.Wetness, _stateManager, _state)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); @@ -159,7 +158,7 @@ public class ActorPanel( var data = EquipDrawData.FromState(_stateManager, _state!, slot); _equipmentDrawer.DrawEquip(data); if (usedAllStain) - _stateManager.ChangeStain(_state, slot, newAllStain, StateSource.Manual); + _stateManager.ChangeStain(_state, slot, newAllStain, ApplySettings.Manual); } var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); @@ -316,9 +315,9 @@ public class ActorPanel( private void SaveDesignOpen() { ImGui.OpenPopup("Save as Design"); - _newName = _state!.Identifier.ToName(); + _newName = _state!.Identifier.ToName(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); - _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest, applyParameters); + _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest, applyParameters); } private void SaveDesignDrawPopup() @@ -340,7 +339,7 @@ public class ActorPanel( var text = ImGui.GetClipboardText(); var design = _converter.FromBase64(text, applyCustomize, applyGear, out _) ?? throw new Exception("The clipboard did not contain valid data."); - _stateManager.ApplyDesign(design, _state!, StateSource.Manual); + _stateManager.ApplyDesign(_state!, design, ApplySettings.Manual); } catch (Exception ex) { @@ -395,8 +394,8 @@ public class ActorPanel( var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state, - StateSource.Manual); + _stateManager.ApplyDesign(state, _converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), + ApplySettings.Manual); } private void DrawApplyToTarget() @@ -413,7 +412,7 @@ public class ActorPanel( var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state, - StateSource.Manual); + _stateManager.ApplyDesign(state, _converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), + ApplySettings.Manual); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 90302b8..04537b5 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -2,7 +2,6 @@ using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; -using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; using Glamourer.State; @@ -67,9 +66,9 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled)) { foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true)) - _state.ChangeEquip(state!, slot, item, stain, StateSource.Manual); - _state.ChangeMeta(state!, MetaIndex.VisorState, data.VisorToggled, StateSource.Manual); - _state.ChangeCustomize(state!, data.Customize, CustomizeFlagExtensions.All, StateSource.Manual); + _state.ChangeEquip(state!, slot, item, stain, ApplySettings.Manual); + _state.ChangeMetaState(state!, MetaIndex.VisorState, data.VisorToggled, ApplySettings.Manual); + _state.ChangeEntireCustomize(state!, data.Customize, CustomizeFlagExtensions.All, ApplySettings.Manual); } ImGui.TableNextColumn(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 803a226..173a84d 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -4,7 +4,6 @@ using Dalamud.Interface.Internal.Notifications; using FFXIVClientStructs.FFXIV.Client.System.Framework; using Glamourer.Automation; using Glamourer.Designs; -using Glamourer.Events; using Glamourer.GameData; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; @@ -439,7 +438,7 @@ public class DesignPanel( { var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); - _state.ApplyDesign(_selector.Selected!, state, StateSource.Manual); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual); } } @@ -458,7 +457,7 @@ public class DesignPanel( { var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); - _state.ApplyDesign(_selector.Selected!, state, StateSource.Manual); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual); } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 829a681..bd04a57 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -2,14 +2,12 @@ using Dalamud.Interface.Internal.Notifications; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; -using Glamourer.Events; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.State; using ImGuiNET; -using Lumina.Data.Parsing.Scd; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; @@ -202,7 +200,7 @@ public class NpcPanel( { var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags(); var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0); - _state.ApplyDesign(design, state, StateSource.Manual); + _state.ApplyDesign(state, design, ApplySettings.Manual); } } @@ -221,7 +219,7 @@ public class NpcPanel( { var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags(); var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0); - _state.ApplyDesign(design, state, StateSource.Manual); + _state.ApplyDesign(state, design, ApplySettings.Manual); } } diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index deb1908..e325152 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -54,7 +54,7 @@ public ref struct ToggleDrawData Tooltip = "Hide or show your free company crest on this piece of gear.", Locked = state.IsLocked, CurrentValue = state.ModelData.Crest(slot), - SetValue = v => manager.ChangeCrest(state, slot, v, StateSource.Manual), + SetValue = v => manager.ChangeCrest(state, slot, v, ApplySettings.Manual), }; public static ToggleDrawData FromState(MetaIndex index, StateManager manager, ActorState state) @@ -65,7 +65,7 @@ public ref struct ToggleDrawData Tooltip = index.ToTooltip(), Locked = state.IsLocked, CurrentValue = state.ModelData.GetMeta(index), - SetValue = b => manager.ChangeMeta(state, index, b, StateSource.Manual), + SetValue = b => manager.ChangeMetaState(state, index, b, ApplySettings.Manual), }; } diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 6bdac74..3cfca50 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -3,7 +3,7 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin; using Dalamud.Plugin.Services; -using Glamourer.Events; +using Glamourer.Designs; using Glamourer.Services; using Glamourer.State; using Penumbra.GameData.Enums; @@ -118,14 +118,14 @@ public class ContextMenuService : IDisposable return; var slot = item.Type.ToSlot(); - _state.ChangeEquip(state, slot, item, 0, StateSource.Manual); + _state.ChangeEquip(state, slot, item, 0, ApplySettings.Manual); if (item.Type.ValidOffhand().IsOffhandType()) { if (item.PrimaryId.Id is > 1600 and < 1651 && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) - _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateSource.Manual); + _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, ApplySettings.Manual); if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) - _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateSource.Manual); + _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, ApplySettings.Manual); } }; } @@ -142,14 +142,14 @@ public class ContextMenuService : IDisposable return; var slot = item.Type.ToSlot(); - _state.ChangeEquip(state, slot, item, 0, StateSource.Manual); + _state.ChangeEquip(state, slot, item, 0, ApplySettings.Manual); if (item.Type.ValidOffhand().IsOffhandType()) { if (item.PrimaryId.Id is > 1600 and < 1651 && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) - _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateSource.Manual); + _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, ApplySettings.Manual); if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) - _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateSource.Manual); + _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, ApplySettings.Manual); } }; } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index e300ab4..ddc7217 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -419,7 +419,7 @@ public class CommandService : IDisposable if (!_objects.TryGetValue(identifier, out var actors)) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ApplyDesign(design, state, StateSource.Manual); + _stateManager.ApplyDesign(state, design, ApplySettings.Manual); } else { @@ -428,7 +428,7 @@ public class CommandService : IDisposable if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) { ApplyModSettings(design, actor, applyMods); - _stateManager.ApplyDesign(design, state, StateSource.Manual); + _stateManager.ApplyDesign(state, design, ApplySettings.Manual); } } } diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 2f64cf8..d9d76b4 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -121,7 +121,7 @@ public static class ServiceManagerA private static ServiceManager AddState(this ServiceManager services) => services.AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index 0b3204f..b5126da 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -1,5 +1,4 @@ using Glamourer.Designs; -using Glamourer.Events; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Dalamud.Game.ClientState.Conditions; diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs new file mode 100644 index 0000000..2bc50c3 --- /dev/null +++ b/Glamourer/State/InternalStateEditor.cs @@ -0,0 +1,234 @@ +using Dalamud.Plugin.Services; +using Glamourer.Designs; +using Glamourer.Events; +using Glamourer.GameData; +using Glamourer.Services; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.State; + +public class InternalStateEditor( + CustomizeService customizations, + HumanModelList humans, + ItemManager items, + GPoseService gPose, + ICondition condition) +{ + /// Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. + /// We currently only allow changing things to humans, not humans to monsters. + public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateSource source, + out uint oldModelId, uint key = 0) + { + oldModelId = state.ModelData.ModelId; + + // TODO think about this. + if (modelId != 0) + return false; + + if (!state.CanUnlock(key)) + return false; + + var oldIsHuman = state.ModelData.IsHuman; + state.ModelData.IsHuman = humans.IsHuman(modelId); + if (state.ModelData.IsHuman) + { + if (oldModelId == modelId) + return true; + + state.ModelData.ModelId = modelId; + if (oldIsHuman) + return true; + + if (!state.AllowsRedraw(condition)) + return false; + + // Fix up everything else to make sure the result is a valid human. + state.ModelData.Customize = CustomizeArray.Default; + state.ModelData.SetDefaultEquipment(items); + state.ModelData.SetHatVisible(true); + state.ModelData.SetWeaponVisible(true); + state.ModelData.SetVisor(false); + state.Sources[MetaIndex.ModelId] = source; + state.Sources[MetaIndex.HatState] = source; + state.Sources[MetaIndex.WeaponState] = source; + state.Sources[MetaIndex.VisorState] = source; + foreach (var slot in EquipSlotExtensions.FullSlots) + { + state.Sources[slot, true] = source; + state.Sources[slot, false] = source; + } + + state.Sources[CustomizeIndex.Clan] = source; + state.Sources[CustomizeIndex.Gender] = source; + var set = customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); + foreach (var index in Enum.GetValues().Where(set.IsAvailable)) + state.Sources[index] = source; + } + else + { + if (!state.AllowsRedraw(condition)) + return false; + + state.ModelData.LoadNonHuman(modelId, customize, equipData); + state.Sources[MetaIndex.ModelId] = source; + } + + return true; + } + + /// Change a customization value. + public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateSource source, + out CustomizeValue old, uint key = 0) + { + old = state.ModelData.Customize[idx]; + if (!state.CanUnlock(key)) + return false; + + state.ModelData.Customize[idx] = value; + state.Sources[idx] = source; + return true; + } + + /// Change an entire customization array according to functions. + public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, + Func source, out CustomizeArray old, out CustomizeFlag changed, uint key = 0) + { + old = state.ModelData.Customize; + changed = 0; + if (!state.CanUnlock(key)) + return false; + + (var customize, var applied, changed) = customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich, true); + if (changed == 0) + return false; + + state.ModelData.Customize = customize; + applied |= changed; + foreach (var type in Enum.GetValues()) + { + if (applied.HasFlag(type.ToFlag())) + state.Sources[type] = source(type); + } + + return true; + } + + /// Change an entire customization array according to functions. + public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, Func applyWhich, + Func source, out CustomizeArray old, out CustomizeFlag changed, uint key = 0) + { + var apply = Enum.GetValues().Where(applyWhich).Aggregate((CustomizeFlag)0, (current, type) => current | type.ToFlag()); + return ChangeHumanCustomize(state, customizeInput, apply, source, out old, out changed, key); + } + + /// Change a single piece of equipment without stain. + public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateSource source, out EquipItem oldItem, uint key = 0) + { + oldItem = state.ModelData.Item(slot); + if (!state.CanUnlock(key)) + return false; + + // Can not change weapon type from expected type in state. + if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType + || slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType) + { + if (!gPose.InGPose) + return false; + + var old = oldItem; + gPose.AddActionOnLeave(() => + { + if (old.Type == state.BaseData.Item(slot).Type) + ChangeItem(state, slot, old, state.Sources[slot, false], out _, key); + }); + } + + state.ModelData.SetItem(slot, item); + state.Sources[slot, false] = source; + return true; + } + + /// Change a single piece of equipment including stain. + public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, out EquipItem oldItem, + out StainId oldStain, uint key = 0) + { + oldItem = state.ModelData.Item(slot); + oldStain = state.ModelData.Stain(slot); + if (!state.CanUnlock(key)) + return false; + + // Can not change weapon type from expected type in state. + if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType + || slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType) + { + if (!gPose.InGPose) + return false; + + var old = oldItem; + var oldS = oldStain; + gPose.AddActionOnLeave(() => + { + if (old.Type == state.BaseData.Item(slot).Type) + ChangeEquip(state, slot, old, oldS, state.Sources[slot, false], out _, out _, key); + }); + } + + state.ModelData.SetItem(slot, item); + state.ModelData.SetStain(slot, stain); + state.Sources[slot, false] = source; + state.Sources[slot, true] = source; + return true; + } + + /// Change only the stain of an equipment piece. + public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, out StainId oldStain, uint key = 0) + { + oldStain = state.ModelData.Stain(slot); + if (!state.CanUnlock(key)) + return false; + + state.ModelData.SetStain(slot, stain); + state.Sources[slot, true] = source; + return true; + } + + /// Change the crest of an equipment piece. + public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateSource source, out bool oldCrest, uint key = 0) + { + oldCrest = state.ModelData.Crest(slot); + if (!state.CanUnlock(key)) + return false; + + state.ModelData.SetCrest(slot, crest); + state.Sources[slot] = source; + return true; + } + + /// Change the customize flags of a character. + public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateSource source, + out CustomizeParameterValue oldValue, uint key = 0) + { + oldValue = state.ModelData.Parameters[flag]; + if (!state.CanUnlock(key)) + return false; + + state.ModelData.Parameters.Set(flag, value); + state.Sources[flag] = source; + + return true; + } + + public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue, + uint key = 0) + { + oldValue = state.ModelData.GetMeta(index); + if (!state.CanUnlock(key)) + return false; + + state.ModelData.SetMeta(index, value); + state.Sources[index] = source; + return true; + } +} diff --git a/Glamourer/State/JobChangeState.cs b/Glamourer/State/JobChangeState.cs new file mode 100644 index 0000000..84aa3cc --- /dev/null +++ b/Glamourer/State/JobChangeState.cs @@ -0,0 +1,30 @@ +using OtterGui.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.State; + +public sealed class JobChangeState : Dictionary, IService +{ + public ActorState? State { get; private set; } + + public void Reset() + { + State = null; + Clear(); + } + + public bool HasState + => State != null; + + public ActorIdentifier Identifier + => State?.Identifier ?? ActorIdentifier.Invalid; + + public void Set(ActorState state, IEnumerable<(EquipItem, StateSource)> items) + { + foreach (var (item, source) in items.Where(p => p.Item1.Valid)) + TryAdd(item.Type, (item, source)); + State = state; + } +} diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 906beb6..7222a4b 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,5 +1,4 @@ using Glamourer.Designs; -using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -277,6 +276,47 @@ public class StateApplier( return data; } + /// Apply the entire state of an actor to all relevant actors, either via immediate redraw or piecewise. + /// The state to apply. + /// Whether a redraw should be forced. + /// Whether a temporary lock should be applied for the redraw. + /// The actor data for the actors who got changed. + public ActorData ApplyAll(ActorState state, bool redraw, bool withLock) + { + var actors = ChangeMetaState(state, MetaIndex.Wetness, true); + if (redraw) + { + if (withLock) + state.TempLock(); + ForceRedraw(actors); + } + else + { + ChangeCustomize(actors, state.ModelData.Customize); + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc, + state.ModelData.IsHatVisible()); + } + + var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; + ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); + var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors; + ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand)); + } + + if (state.ModelData.IsHuman) + { + ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible()); + ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); + ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); + ChangeCrests(actors, state.ModelData.CrestVisibility); + ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); + } + + return actors; + } + private ActorData GetData(ActorState state) { _objects.Update(); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 403c106..527daa6 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,221 +1,334 @@ -using Dalamud.Plugin.Services; using Glamourer.Designs; +using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; +using Glamourer.Interop.Structs; using Glamourer.Services; -using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.State; -public class StateEditor(CustomizeService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition) +public class StateEditor( + InternalStateEditor editor, + StateApplier applier, + StateChanged stateChanged, + JobChangeState jobChange, + Configuration config, + ItemManager items) : IDesignEditor { - /// Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. - /// We currently only allow changing things to humans, not humans to monsters. - public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateSource source, - out uint oldModelId, uint key = 0) - { - oldModelId = state.ModelData.ModelId; + protected readonly InternalStateEditor Editor = editor; + protected readonly StateApplier Applier = applier; + protected readonly StateChanged StateChanged = stateChanged; + protected readonly Configuration Config = config; + protected readonly ItemManager Items = items; - // TODO think about this. - if (modelId != 0) - return false; - - if (!state.CanUnlock(key)) - return false; - - var oldIsHuman = state.ModelData.IsHuman; - state.ModelData.IsHuman = humans.IsHuman(modelId); - if (state.ModelData.IsHuman) - { - if (oldModelId == modelId) - return true; - - state.ModelData.ModelId = modelId; - if (oldIsHuman) - return true; - - if (!state.AllowsRedraw(condition)) - return false; - - // Fix up everything else to make sure the result is a valid human. - state.ModelData.Customize = CustomizeArray.Default; - state.ModelData.SetDefaultEquipment(items); - state.ModelData.SetHatVisible(true); - state.ModelData.SetWeaponVisible(true); - state.ModelData.SetVisor(false); - state.Sources[MetaIndex.ModelId] = source; - state.Sources[MetaIndex.HatState] = source; - state.Sources[MetaIndex.WeaponState] = source; - state.Sources[MetaIndex.VisorState] = source; - foreach (var slot in EquipSlotExtensions.FullSlots) - { - state.Sources[slot, true] = source; - state.Sources[slot, false] = source; - } - - state.Sources[CustomizeIndex.Clan] = source; - state.Sources[CustomizeIndex.Gender] = source; - var set = customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); - foreach (var index in Enum.GetValues().Where(set.IsAvailable)) - state.Sources[index] = source; - } - else - { - if (!state.AllowsRedraw(condition)) - return false; - - state.ModelData.LoadNonHuman(modelId, customize, equipData); - state.Sources[MetaIndex.ModelId] = source; - } - - return true; - } - - /// Change a customization value. - public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateSource source, - out CustomizeValue old, uint key = 0) - { - old = state.ModelData.Customize[idx]; - if (!state.CanUnlock(key)) - return false; - - state.ModelData.Customize[idx] = value; - state.Sources[idx] = source; - return true; - } - - /// Change an entire customization array according to flags. - public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, StateSource source, - out CustomizeArray old, out CustomizeFlag changed, uint key = 0) - { - old = state.ModelData.Customize; - changed = 0; - if (!state.CanUnlock(key)) - return false; - - (var customize, var applied, changed) = customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich, true); - if (changed == 0) - return false; - - state.ModelData.Customize = customize; - applied |= changed; - foreach (var type in Enum.GetValues()) - { - if (applied.HasFlag(type.ToFlag())) - state.Sources[type] = source; - } - - return true; - } - - /// Change a single piece of equipment without stain. - public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateSource source, out EquipItem oldItem, uint key = 0) - { - oldItem = state.ModelData.Item(slot); - if (!state.CanUnlock(key)) - return false; - - // Can not change weapon type from expected type in state. - if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType - || slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType) - { - if (!gPose.InGPose) - return false; - - var old = oldItem; - gPose.AddActionOnLeave(() => - { - if (old.Type == state.BaseData.Item(slot).Type) - ChangeItem(state, slot, old, state.Sources[slot, false], out _, key); - }); - } - - state.ModelData.SetItem(slot, item); - state.Sources[slot, false] = source; - return true; - } - - /// Change a single piece of equipment including stain. - public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, out EquipItem oldItem, - out StainId oldStain, uint key = 0) - { - oldItem = state.ModelData.Item(slot); - oldStain = state.ModelData.Stain(slot); - if (!state.CanUnlock(key)) - return false; - - // Can not change weapon type from expected type in state. - if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType - || slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType) - { - if (!gPose.InGPose) - return false; - - var old = oldItem; - var oldS = oldStain; - gPose.AddActionOnLeave(() => - { - if (old.Type == state.BaseData.Item(slot).Type) - ChangeEquip(state, slot, old, oldS, state.Sources[slot, false], out _, out _, key); - }); - } - - state.ModelData.SetItem(slot, item); - state.ModelData.SetStain(slot, stain); - state.Sources[slot, false] = source; - state.Sources[slot, true] = source; - return true; - } - - /// Change only the stain of an equipment piece. - public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, out StainId oldStain, uint key = 0) - { - oldStain = state.ModelData.Stain(slot); - if (!state.CanUnlock(key)) - return false; - - state.ModelData.SetStain(slot, stain); - state.Sources[slot, true] = source; - return true; - } - - /// Change the crest of an equipment piece. - public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateSource source, out bool oldCrest, uint key = 0) - { - oldCrest = state.ModelData.Crest(slot); - if (!state.CanUnlock(key)) - return false; - - state.ModelData.SetCrest(slot, crest); - state.Sources[slot] = source; - return true; - } - - /// Change the customize flags of a character. - public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateSource source, - out CustomizeParameterValue oldValue, uint key = 0) - { - oldValue = state.ModelData.Parameters[flag]; - if (!state.CanUnlock(key)) - return false; - - state.ModelData.Parameters.Set(flag, value); - state.Sources[flag] = source; - - return true; - } - - public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue, + /// Turn an actor to. + public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source, uint key = 0) { - oldValue = state.ModelData.GetMeta(index); - if (!state.CanUnlock(key)) - return false; + if (!Editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key)) + return; - state.ModelData.SetMeta(index, value); - state.Sources[index] = source; - return true; + var actors = Applier.ForceRedraw(state, source is StateSource.Manual or StateSource.Ipc); + Glamourer.Log.Verbose( + $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId)); + } + + /// + public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings settings) + { + if (data is not ActorState state) + return; + + if (!Editor.ChangeCustomize(state, idx, value, settings.Source, out var old, settings.Key)) + return; + + var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc); + Glamourer.Log.Verbose( + $"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChanged.Type.Customize, settings.Source, state, actors, (old, value, idx)); + } + + /// + public void ChangeEntireCustomize(object data, in CustomizeArray customizeInput, CustomizeFlag apply, ApplySettings settings) + { + if (data is not ActorState state) + return; + + if (!Editor.ChangeHumanCustomize(state, customizeInput, apply, _ => settings.Source, out var old, out var applied, settings.Key)) + return; + + var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc); + Glamourer.Log.Verbose( + $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChanged.Type.EntireCustomize, settings.Source, state, actors, (old, applied)); + } + + /// + public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default) + { + if (data is not ActorState state) + return; + + if (!Editor.ChangeItem(state, slot, item, settings.Source, out var old, settings.Key)) + return; + + var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon; + var actors = type is StateChanged.Type.Equip + ? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc) + : Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc, + item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); + + if (slot is EquipSlot.MainHand) + ApplyMainhandPeriphery(state, item, 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")}.]"); + StateChanged.Invoke(type, settings.Source, state, actors, (old, item, slot)); + } + + /// + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings) + { + switch (item.HasValue, stain.HasValue) + { + case (false, false): return; + case (true, false): + ChangeItem(data, slot, item!.Value, settings); + return; + case (false, true): + ChangeStain(data, slot, stain!.Value, settings); + return; + } + + if (data is not ActorState state) + return; + + if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stain ?? state.ModelData.Stain(slot), settings.Source, + out var old, out var oldStain, settings.Key)) + return; + + var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon; + var actors = type is StateChanged.Type.Equip + ? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc) + : Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc, + item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); + + if (slot is EquipSlot.MainHand) + ApplyMainhandPeriphery(state, item, 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")}.]"); + StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot)); + StateChanged.Invoke(StateChanged.Type.Stain, settings.Source, state, actors, (oldStain, stain!.Value, slot)); + } + + /// + public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings) + { + if (data is not ActorState state) + return; + + if (!Editor.ChangeStain(state, slot, stain, settings.Source, out var old, settings.Key)) + return; + + var actors = Applier.ChangeStain(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc); + Glamourer.Log.Verbose( + $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChanged.Type.Stain, settings.Source, state, actors, (old, stain, slot)); + } + + /// + public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings) + { + if (data is not ActorState state) + return; + + if (!Editor.ChangeCrest(state, slot, crest, settings.Source, out var old, settings.Key)) + return; + + var actors = Applier.ChangeCrests(state, settings.Source is StateSource.Manual or StateSource.Ipc); + Glamourer.Log.Verbose( + $"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChanged.Type.Crest, settings.Source, state, actors, (old, crest, slot)); + } + + /// + public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings settings) + { + if (data is not ActorState state) + return; + + // Also apply main color to highlights when highlights is off. + if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse) + ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, settings); + + if (!Editor.ChangeParameter(state, flag, value, settings.Source, out var old, settings.Key)) + return; + + var @new = state.ModelData.Parameters[flag]; + var actors = Applier.ChangeParameters(state, flag, settings.Source is StateSource.Manual or StateSource.Ipc); + Glamourer.Log.Verbose( + $"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag)); + } + + /// + public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings) + { + if (data is not ActorState state) + return; + + if (!Editor.ChangeMetaState(state, index, value, settings.Source, out var old, settings.Key)) + return; + + var actors = Applier.ChangeMetaState(state, index, settings.Source is StateSource.Manual or StateSource.Ipc); + Glamourer.Log.Verbose( + $"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChanged.Type.Other, settings.Source, state, actors, (old, value, MetaIndex.HatState)); + } + + /// + public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings) + { + if (data is not ActorState state) + return; + + if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize, + mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key)) + return; + + var requiresRedraw = oldModelId != mergedDesign.Design.DesignData.ModelId || !mergedDesign.Design.DesignData.IsHuman; + + if (state.ModelData.IsHuman) + { + foreach (var slot in CrestExtensions.AllRelevantSet.Where(mergedDesign.Design.DoApplyCrest)) + { + if (!settings.RespectManual || state.Sources[slot] is not StateSource.Manual) + Editor.ChangeCrest(state, slot, mergedDesign.Design.DesignData.Crest(slot), Source(slot), + out _, settings.Key); + } + + var customizeFlags = mergedDesign.Design.ApplyCustomizeRaw; + if (mergedDesign.Design.DoApplyCustomize(CustomizeIndex.Clan)) + customizeFlags |= CustomizeFlag.Race; + + Func applyWhich = settings.RespectManual + ? i => customizeFlags.HasFlag(i.ToFlag()) && state.Sources[i] is not StateSource.Manual + : i => customizeFlags.HasFlag(i.ToFlag()); + + if (Editor.ChangeHumanCustomize(state, mergedDesign.Design.DesignData.Customize, applyWhich, i => Source(i), out _, out var changed, + settings.Key)) + requiresRedraw |= changed.RequiresRedraw(); + + foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate()) + { + if (settings.RespectManual && state.Sources[parameter] is StateSource.Manual or StateSource.Pending) + continue; + + var source = Source(parameter); + if (source is StateSource.Manual) + source = StateSource.Pending; + Editor.ChangeParameter(state, parameter, mergedDesign.Design.DesignData.Parameters[parameter], source, out _, settings.Key); + } + + // Do not apply highlights from a design if highlights is unchecked. + if (!state.ModelData.Customize.Highlights) + Editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight, + state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse], + state.Sources[CustomizeParameterFlag.HairDiffuse], out _, settings.Key); + + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + if (mergedDesign.Design.DoApplyEquip(slot)) + if (!settings.RespectManual || state.Sources[slot, false] is not StateSource.Manual) + Editor.ChangeItem(state, slot, mergedDesign.Design.DesignData.Item(slot), + Source(slot.ToState()), out _, settings.Key); + + if (mergedDesign.Design.DoApplyStain(slot)) + if (!settings.RespectManual || state.Sources[slot, true] is not StateSource.Manual) + Editor.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot), + Source(slot.ToState(true)), out _, settings.Key); + } + + foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots) + { + if (mergedDesign.Design.DoApplyStain(weaponSlot)) + if (!settings.RespectManual || state.Sources[weaponSlot, true] is not StateSource.Manual) + Editor.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot), + Source(weaponSlot.ToState(true)), out _, settings.Key); + + if (!mergedDesign.Design.DoApplyEquip(weaponSlot)) + continue; + + if (settings.RespectManual && state.Sources[weaponSlot, false] is StateSource.Manual) + continue; + + var currentType = state.ModelData.Item(weaponSlot).Type; + if (!settings.FromJobChange && mergedDesign.Weapons.TryGetValue(currentType, out var weapon)) + { + var source = settings.UseSingleSource ? settings.Source : + weapon.Item2 is StateSource.Game ? StateSource.Game : weapon.Item2; + Editor.ChangeItem(state, weaponSlot, weapon.Item1, source, out _, + settings.Key); + } + } + + if (settings.FromJobChange) + jobChange.Set(state, mergedDesign.Weapons.Values.Select(m => + (m.Item1, settings.UseSingleSource ? settings.Source : + m.Item2 is StateSource.Game ? StateSource.Game : m.Item2))); + + foreach (var meta in MetaExtensions.AllRelevant) + { + if (!settings.RespectManual || state.Sources[meta] is not StateSource.Manual) + Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key); + } + } + + var actors = settings.Source is StateSource.Manual or StateSource.Ipc + ? Applier.ApplyAll(state, requiresRedraw, false) + : ActorData.Invalid; + + Glamourer.Log.Verbose( + $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChanged.Type.Design, state.Sources[MetaIndex.Wetness], state, actors, mergedDesign.Design); + + return; + + StateSource Source(StateIndex index) + { + if (settings.UseSingleSource) + return settings.Source; + + var source = mergedDesign.Sources[index]; + return source is StateSource.Game ? StateSource.Game : settings.Source; + } + } + + public void ApplyDesign(object data, DesignBase design, ApplySettings settings) + => ApplyDesign(data, new MergedDesign(design), settings with + { + FromJobChange = false, + RespectManual = false, + UseSingleSource = true, + }); + + /// Apply offhand item and potentially gauntlets if configured. + private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, ApplySettings settings) + { + if (!Config.ChangeEntireItem || settings.Source is not StateSource.Manual) + return; + + var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); + var offhand = newMainhand != null ? Items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand); + if (offhand.Valid) + ChangeEquip(state, EquipSlot.OffHand, offhand, state.ModelData.Stain(EquipSlot.OffHand), 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); } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 09c0673..1833660 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -139,7 +139,7 @@ public class StateListener : IDisposable ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender); } - private unsafe void OnCustomizeChange(Model model, ref CustomizeArray customize) + private void OnCustomizeChange(Model model, ref CustomizeArray customize) { if (!model.IsHuman) return; @@ -164,7 +164,7 @@ public class StateListener : IDisposable var model = state.ModelData.Customize; if (customize.Gender != model.Gender || customize.Clan != model.Clan) { - _manager.ChangeCustomize(state, in customize, CustomizeFlagExtensions.AllRelevant, StateSource.Game); + _manager.ChangeEntireCustomize(state, in customize, CustomizeFlagExtensions.All, ApplySettings.Game); return; } @@ -178,7 +178,7 @@ public class StateListener : IDisposable if (newValue != oldValue) { if (set.Validate(index, newValue, out _, model.Face)) - _manager.ChangeCustomize(state, index, newValue, StateSource.Game); + _manager.ChangeCustomize(state, index, newValue, ApplySettings.Game); else customize[index] = oldValue; } @@ -243,8 +243,8 @@ public class StateListener : IDisposable var changed = changedItem.Weapon(stain); if (current.Value == changed.Value && state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) { - _manager.ChangeItem(state, slot, currentItem, StateSource.Game); - _manager.ChangeStain(state, slot, current.Stain, StateSource.Game); + _manager.ChangeItem(state, slot, currentItem, ApplySettings.Game); + _manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game); switch (slot) { case EquipSlot.MainHand: @@ -287,12 +287,12 @@ public class StateListener : IDisposable case UpdateState.Transformed: break; case UpdateState.Change: if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) - _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateSource.Game); + _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game); else apply = true; if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc) - _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateSource.Game); + _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); else apply = true; break; @@ -386,12 +386,12 @@ public class StateListener : IDisposable case UpdateState.Change: var apply = false; if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) - _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateSource.Game); + _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game); else apply = true; if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc) - _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateSource.Game); + _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); else apply = true; @@ -420,7 +420,7 @@ public class StateListener : IDisposable { case UpdateState.Change: if (state.Sources[slot] is not StateSource.Fixed and not StateSource.Ipc) - _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateSource.Game); + _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), ApplySettings.Game); else value = state.ModelData.Crest(slot); break; @@ -568,7 +568,7 @@ public class StateListener : IDisposable if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed or StateSource.Ipc) value = state.ModelData.IsVisorToggled(); else - _manager.ChangeMeta(state, MetaIndex.VisorState, value, StateSource.Game); + _manager.ChangeMetaState(state, MetaIndex.VisorState, value, ApplySettings.Game); } else { @@ -601,7 +601,7 @@ public class StateListener : IDisposable if (state.Sources[MetaIndex.HatState] is StateSource.Fixed or StateSource.Ipc) value = state.ModelData.IsHatVisible(); else - _manager.ChangeMeta(state, MetaIndex.HatState, value, StateSource.Game); + _manager.ChangeMetaState(state, MetaIndex.HatState, value, ApplySettings.Game); } else { @@ -634,7 +634,7 @@ public class StateListener : IDisposable if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed or StateSource.Ipc) value = state.ModelData.IsWeaponVisible(); else - _manager.ChangeMeta(state, MetaIndex.WeaponState, value, StateSource.Game); + _manager.ChangeMetaState(state, MetaIndex.WeaponState, value, ApplySettings.Game); } else { @@ -724,7 +724,7 @@ public class StateListener : IDisposable _customizeState = null; } - private unsafe void ApplyParameters(ActorState state, Model model) + private void ApplyParameters(ActorState state, Model model) { if (!model.IsHuman) return; @@ -737,11 +737,11 @@ public class StateListener : IDisposable { case StateSource.Game: if (state.BaseData.Parameters.Set(flag, newValue)) - _manager.ChangeCustomizeParameter(state, flag, newValue, StateSource.Game); + _manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game); break; case StateSource.Manual: if (state.BaseData.Parameters.Set(flag, newValue)) - _manager.ChangeCustomizeParameter(state, flag, newValue, StateSource.Game); + _manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game); else if (_config.UseAdvancedParameters) model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 518b435..5d0e01f 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -12,17 +12,17 @@ using Penumbra.GameData.Structs; namespace Glamourer.State; -public class StateManager( +public sealed class StateManager( ActorManager _actors, - ItemManager _items, - StateChanged _event, - StateApplier _applier, - StateEditor _editor, + ItemManager items, + StateChanged @event, + StateApplier applier, + InternalStateEditor editor, HumanModelList _humans, - ICondition _condition, IClientState _clientState, - Configuration _config) - : IReadOnlyDictionary + Configuration config, + JobChangeState jobChange) + : StateEditor(editor, applier, @event, jobChange, config, items), IReadOnlyDictionary { private readonly Dictionary _states = []; @@ -93,7 +93,7 @@ public class StateManager( // If the given actor is not a character, just return a default character. if (!actor.IsCharacter) { - ret.SetDefaultEquipment(_items); + ret.SetDefaultEquipment(Items); return ret; } @@ -127,7 +127,7 @@ public class StateManager( // We can not use the head slot data from the draw object if the hat is hidden. var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head); - var headItem = _items.Identify(EquipSlot.Head, head.Set, head.Variant); + var headItem = Items.Identify(EquipSlot.Head, head.Set, head.Variant); ret.SetItem(EquipSlot.Head, headItem); ret.SetStain(EquipSlot.Head, head.Stain); @@ -135,7 +135,7 @@ public class StateManager( foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1)) { var armor = model.GetArmor(slot); - var item = _items.Identify(slot, armor.Set, armor.Variant); + var item = Items.Identify(slot, armor.Set, armor.Variant); ret.SetItem(slot, item); ret.SetStain(slot, armor.Stain); } @@ -157,7 +157,7 @@ public class StateManager( foreach (var slot in EquipSlotExtensions.EqdpSlots) { var armor = actor.GetArmor(slot); - var item = _items.Identify(slot, armor.Set, armor.Variant); + var item = Items.Identify(slot, armor.Set, armor.Variant); ret.SetItem(slot, item); ret.SetStain(slot, armor.Stain); } @@ -172,8 +172,8 @@ public class StateManager( } // Set the weapons regardless of source. - var mainItem = _items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant); - var offItem = _items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type); + var mainItem = Items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant); + var offItem = Items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type); ret.SetItem(EquipSlot.MainHand, mainItem); ret.SetStain(EquipSlot.MainHand, main.Stain); ret.SetItem(EquipSlot.OffHand, offItem); @@ -197,7 +197,7 @@ public class StateManager( if (mainhand.Skeleton.Id is < 1601 or >= 1651) return; - var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id); + var gauntlets = Items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id); offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50); offhand.Variant = mainhand.Variant; offhand.Weapon = mainhand.Weapon; @@ -205,251 +205,10 @@ public class StateManager( ret.SetStain(EquipSlot.Hands, mainhand.Stain); } - #region Change Values - /// Turn an actor human. public void TurnHuman(ActorState state, StateSource source, uint key = 0) => ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key); - /// Turn an actor to. - public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source, - uint key = 0) - { - if (!_editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key)) - return; - - var actors = _applier.ForceRedraw(state, source is StateSource.Manual or StateSource.Ipc); - Glamourer.Log.Verbose( - $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId)); - } - - /// Change a customization value. - public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateSource source, uint key = 0) - { - if (!_editor.ChangeCustomize(state, idx, value, source, out var old, key)) - return; - - var actors = _applier.ChangeCustomize(state, source is StateSource.Manual or StateSource.Ipc); - Glamourer.Log.Verbose( - $"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Customize, source, state, actors, (old, value, idx)); - } - - /// Change an entire customization array according to flags. - public void ChangeCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag apply, StateSource source, - uint key = 0) - { - if (!_editor.ChangeHumanCustomize(state, customizeInput, apply, source, out var old, out var applied, key)) - return; - - var actors = _applier.ChangeCustomize(state, source is StateSource.Manual or StateSource.Ipc); - Glamourer.Log.Verbose( - $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.EntireCustomize, source, state, actors, (old, applied)); - } - - /// Change a single piece of equipment without stain. - /// Do not use this in the same frame as ChangeStain, use instead. - public void ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateSource source, uint key = 0) - { - if (!_editor.ChangeItem(state, slot, item, source, out var old, key)) - return; - - var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon; - var actors = type is StateChanged.Type.Equip - ? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc) - : _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc, - item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); - - if (slot is EquipSlot.MainHand) - ApplyMainhandPeriphery(state, item, source, key); - - 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")}.]"); - _event.Invoke(type, source, state, actors, (old, item, slot)); - } - - /// Change a single piece of equipment including stain. - public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, uint key = 0) - { - if (!_editor.ChangeEquip(state, slot, item, stain, source, out var old, out var oldStain, key)) - return; - - var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon; - var actors = type is StateChanged.Type.Equip - ? _applier.ChangeArmor(state, slot, source is StateSource.Manual or StateSource.Ipc) - : _applier.ChangeWeapon(state, slot, source is StateSource.Manual or StateSource.Ipc, - item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); - - if (slot is EquipSlot.MainHand) - ApplyMainhandPeriphery(state, item, source, key); - - Glamourer.Log.Verbose( - $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}) and its stain from {oldStain.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(type, source, state, actors, (old, item, slot)); - _event.Invoke(StateChanged.Type.Stain, source, state, actors, (oldStain, stain, slot)); - } - - /// Change only the stain of an equipment piece. - /// Do not use this in the same frame as ChangeEquip, use instead. - public void ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, uint key = 0) - { - if (!_editor.ChangeStain(state, slot, stain, source, out var old, key)) - return; - - var actors = _applier.ChangeStain(state, slot, source is StateSource.Manual or StateSource.Ipc); - - Glamourer.Log.Verbose( - $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot)); - } - - /// Change the crest of an equipment piece. - public void ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateSource source, uint key = 0) - { - if (!_editor.ChangeCrest(state, slot, crest, source, out var old, key)) - return; - - var actors = _applier.ChangeCrests(state, source is StateSource.Manual or StateSource.Ipc); - Glamourer.Log.Verbose( - $"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot)); - } - - /// Change the crest of an equipment piece. - public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, - StateSource source, uint key = 0) - { - // Also apply main color to highlights when highlights is off. - if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse) - ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, source, key); - - if (!_editor.ChangeParameter(state, flag, value, source, out var old, key)) - return; - - var @new = state.ModelData.Parameters[flag]; - var actors = _applier.ChangeParameters(state, flag, source is StateSource.Manual or StateSource.Ipc); - Glamourer.Log.Verbose( - $"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Parameter, source, state, actors, (old, @new, flag)); - } - - /// Change meta state. - public void ChangeMeta(ActorState state, MetaIndex meta, bool value, StateSource source, uint key = 0) - { - if (!_editor.ChangeMetaState(state, meta, value, source, out var old, key)) - return; - - var actors = _applier.ChangeMetaState(state, meta, source is StateSource.Manual or StateSource.Ipc); - Glamourer.Log.Verbose( - $"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.HatState)); - } - - #endregion - - public void ApplyDesign(DesignBase design, ActorState state, StateSource source, uint key = 0) - { - if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(), - source, - out var oldModelId, key)) - return; - - var redraw = oldModelId != design.DesignData.ModelId || !design.DesignData.IsHuman; - if (design.DoApplyMeta(MetaIndex.Wetness)) - _editor.ChangeMetaState(state, MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key); - - if (state.ModelData.IsHuman) - { - if (design.DoApplyMeta(MetaIndex.HatState)) - _editor.ChangeMetaState(state, MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key); - if (design.DoApplyMeta(MetaIndex.WeaponState)) - _editor.ChangeMetaState(state, MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key); - if (design.DoApplyMeta(MetaIndex.VisorState)) - _editor.ChangeMetaState(state, MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key); - - var flags = state.AllowsRedraw(_condition) - ? design.ApplyCustomize - : design.ApplyCustomize & ~CustomizeFlagExtensions.RedrawRequired; - _editor.ChangeHumanCustomize(state, design.DesignData.Customize, flags, source, out _, out var applied, key); - redraw |= applied.RequiresRedraw(); - - foreach (var slot in EquipSlotExtensions.FullSlots) - HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot)); - - foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest)) - _editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key); - - var paramSource = source is StateSource.Manual - ? StateSource.Pending - : source; - - foreach (var flag in CustomizeParameterExtensions.AllFlags.Where(design.DoApplyParameter)) - _editor.ChangeParameter(state, flag, design.DesignData.Parameters[flag], paramSource, out _, key); - - // Do not apply highlights from a design if highlights is unchecked. - if (!state.ModelData.Customize.Highlights) - _editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight, - state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse], - state.Sources[CustomizeParameterFlag.HairDiffuse], out _, key); - } - - var actors = ApplyAll(state, redraw, false); - Glamourer.Log.Verbose( - $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Design, state.Sources[MetaIndex.Wetness], state, actors, design); - return; - - void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain) - { - var unused = (applyPiece, applyStain) switch - { - (false, false) => false, - (true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key), - (false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key), - (true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _, - out _, key), - }; - } - } - - private ActorData ApplyAll(ActorState state, bool redraw, bool withLock) - { - var actors = _applier.ChangeMetaState(state, MetaIndex.Wetness, true); - if (redraw) - { - if (withLock) - state.TempLock(); - _applier.ForceRedraw(actors); - } - else - { - _applier.ChangeCustomize(actors, state.ModelData.Customize); - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - _applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc, - state.ModelData.IsHatVisible()); - } - - var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; - _applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); - var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors; - _applier.ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand)); - } - - if (state.ModelData.IsHuman) - { - _applier.ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible()); - _applier.ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); - _applier.ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); - _applier.ChangeCrests(actors, state.ModelData.CrestVisibility); - _applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); - } - - return actors; - } - public void ResetState(ActorState state, StateSource source, uint key = 0) { if (!state.Unlock(key)) @@ -481,11 +240,11 @@ public class StateManager( var actors = ActorData.Invalid; if (source is StateSource.Manual or StateSource.Ipc) - actors = ApplyAll(state, redraw, true); + actors = Applier.ApplyAll(state, redraw, true); Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Reset, source, state, actors, null); + StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -500,10 +259,10 @@ public class StateManager( var actors = ActorData.Invalid; if (source is StateSource.Manual or StateSource.Ipc) - actors = _applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); + actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); Glamourer.Log.Verbose( $"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Reset, source, state, actors, null); + StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) @@ -583,26 +342,11 @@ public class StateManager( if (!GetOrCreate(actor, out var state)) return; - ApplyAll(state, !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), + Applier.ApplyAll(state, + !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); } public void DeleteState(ActorIdentifier identifier) => _states.Remove(identifier); - - /// Apply offhand item and potentially gauntlets if configured. - private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StateSource source, uint key = 0) - { - if (!_config.ChangeEntireItem || source is not StateSource.Manual) - return; - - var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); - var offhand = newMainhand != null ? _items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand); - if (offhand.Valid) - ChangeEquip(state, EquipSlot.OffHand, offhand, state.ModelData.Stain(EquipSlot.OffHand), source, key); - - 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), source, key); - } } From 2219d9293fd6c3502bb09aceb04977923cb529cb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 26 Jan 2024 15:04:36 +0100 Subject: [PATCH 210/786] Use new functionality. --- Glamourer/Designs/DesignEditor.cs | 38 +++------ Glamourer/Designs/DesignManager.cs | 2 +- .../CustomizeParameterDrawData.cs | 24 ++++-- .../Customization/CustomizeParameterDrawer.cs | 14 ++-- Glamourer/Gui/Equipment/EquipDrawData.cs | 62 +++++++++------ Glamourer/Gui/Equipment/EquipmentDrawer.cs | 48 ++++++------ Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- Glamourer/Gui/ToggleDrawData.cs | 77 ++++++++++++++----- Glamourer/State/StateEditor.cs | 32 ++------ 9 files changed, 163 insertions(+), 136 deletions(-) diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index d150db5..ab258d7 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -2,6 +2,7 @@ using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Services; +using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -22,7 +23,7 @@ public class DesignEditor( protected readonly Configuration Config = config; protected readonly Dictionary UndoStore = []; - private bool _forceFullItemOff = false; + private bool _forceFullItemOff; /// Whether an Undo for the given design is possible. public bool CanUndo(Design? design) @@ -31,11 +32,8 @@ public class DesignEditor( /// public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings _ = default) { - if (data is not Design design) - return; - + var design = (Design)data; var oldValue = design.DesignData.Customize[idx]; - switch (idx) { case CustomizeIndex.Race: @@ -80,9 +78,7 @@ public class DesignEditor( /// public void ChangeEntireCustomize(object data, in CustomizeArray customize, CustomizeFlag apply, ApplySettings _ = default) { - if (data is not Design design) - return; - + var design = (Design)data; var (newCustomize, applied, changed) = Customizations.Combine(design.DesignData.Customize, customize, apply, true); if (changed == 0) return; @@ -98,9 +94,7 @@ public class DesignEditor( /// public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings _ = default) { - if (data is not Design design) - return; - + var design = (Design)data; var old = design.DesignData.Parameters[flag]; if (!design.GetDesignDataRef().Parameters.Set(flag, value)) return; @@ -115,9 +109,7 @@ public class DesignEditor( /// public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings _ = default) { - if (data is not Design design) - return; - + var design = (Design)data; switch (slot) { case EquipSlot.MainHand: @@ -176,9 +168,7 @@ public class DesignEditor( /// public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings _ = default) { - if (data is not Design design) - return; - + var design = (Design)data; if (Items.ValidateStain(stain, out var _, false).Length > 0) return; @@ -204,9 +194,7 @@ public class DesignEditor( /// public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings _ = default) { - if (data is not Design design) - return; - + var design = (Design)data; var oldCrest = design.DesignData.Crest(slot); if (!design.GetDesignDataRef().SetCrest(slot, crest)) return; @@ -220,9 +208,7 @@ public class DesignEditor( /// public void ChangeMetaState(object data, MetaIndex metaIndex, bool value, ApplySettings _ = default) { - if (data is not Design design) - return; - + var design = (Design)data; if (!design.GetDesignDataRef().SetMeta(metaIndex, value)) return; @@ -239,9 +225,7 @@ public class DesignEditor( /// public void ApplyDesign(object data, DesignBase other, ApplySettings _ = default) { - if (data is not Design design) - return; - + var design = (Design)data; UndoStore[design.Identifier] = design.DesignData; foreach (var index in MetaExtensions.AllRelevant.Where(other.DoApplyMeta)) design.GetDesignDataRef().SetMeta(index, other.DesignData.GetMeta(index)); @@ -269,7 +253,7 @@ public class DesignEditor( } /// Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. - private bool ChangeMainhandPeriphery(Design design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, + private bool ChangeMainhandPeriphery(DesignBase design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, out EquipItem? newGauntlets) { newOff = null; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 408cf27..c3d8664 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -301,7 +301,7 @@ public sealed class DesignManager : DesignEditor } /// Change whether to apply a specific equipment piece. - public void ChangeApplyEquip(Design design, EquipSlot slot, bool value) + public void ChangeApplyItem(Design design, EquipSlot slot, bool value) { if (!design.SetApplyEquip(slot, value)) return; diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs index d092cde..aa43b79 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs @@ -1,19 +1,28 @@ using Glamourer.Designs; -using Glamourer.Events; using Glamourer.GameData; using Glamourer.State; namespace Glamourer.Gui.Customization; -public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data) +public struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data) { + private IDesignEditor _editor; + private object _object; public readonly CustomizeParameterFlag Flag = flag; public bool Locked; public bool DisplayApplication; public bool AllowRevert; - public Action ValueSetter = null!; - public Action ApplySetter = null!; + public readonly void ChangeParameter(CustomizeParameterValue value) + => _editor.ChangeCustomizeParameter(_object, Flag, value, ApplySettings.Manual); + + public readonly void ChangeApplyParameter(bool value) + { + var manager = (DesignManager)_editor; + var design = (Design)_object; + manager.ChangeApplyParameter(design, Flag, value); + } + public CustomizeParameterValue CurrentValue = data.Parameters[flag]; public CustomizeParameterValue GameValue; public bool CurrentApply; @@ -21,19 +30,20 @@ public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in Des public static CustomizeParameterDrawData FromDesign(DesignManager manager, Design design, CustomizeParameterFlag flag) => new(flag, design.DesignData) { + _editor = manager, + _object = design, Locked = design.WriteProtected(), DisplayApplication = true, CurrentApply = design.DoApplyParameter(flag), - ValueSetter = v => manager.ChangeCustomizeParameter(design, flag, v), - ApplySetter = v => manager.ChangeApplyParameter(design, flag, v), }; public static CustomizeParameterDrawData FromState(StateManager manager, ActorState state, CustomizeParameterFlag flag) => new(flag, state.ModelData) { + _editor = manager, + _object = state, Locked = state.IsLocked, DisplayApplication = false, - ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, ApplySettings.Manual), GameValue = state.BaseData.Parameters[flag], AllowRevert = true, }; diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 414398e..9bfb2f8 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -195,7 +195,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import using (_ = ImRaii.Disabled(data.Locked || noHighlights)) { if (ImGui.ColorEdit3("##value", ref value, GetFlags())) - data.ValueSetter(new CustomizeParameterValue(value)); + data.ChangeParameter(new CustomizeParameterValue(value)); } if (noHighlights) @@ -215,7 +215,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import using (_ = ImRaii.Disabled(data.Locked)) { if (ImGui.ColorEdit4("##value", ref value, GetFlags() | ImGuiColorEditFlags.AlphaPreviewHalf)) - data.ValueSetter(new CustomizeParameterValue(value)); + data.ChangeParameter(new CustomizeParameterValue(value)); } DrawRevert(data); @@ -231,7 +231,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import using (_ = ImRaii.Disabled(data.Locked)) { if (ImGui.InputFloat("##value", ref value, 0.1f, 0.5f)) - data.ValueSetter(new CustomizeParameterValue(value)); + data.ChangeParameter(new CustomizeParameterValue(value)); } DrawRevert(data); @@ -247,7 +247,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import using (_ = ImRaii.Disabled(data.Locked)) { if (ImGui.SliderFloat("##value", ref value, -100f, 300, "%.2f")) - data.ValueSetter(new CustomizeParameterValue(value / 100f)); + data.ChangeParameter(new CustomizeParameterValue(value / 100f)); ImGuiUtil.HoverTooltip("You can control-click this to enter arbitrary values by hand instead of dragging."); } @@ -262,7 +262,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import return; if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) - data.ValueSetter(data.GameValue); + data.ChangeParameter(data.GameValue); ImGuiUtil.HoverTooltip("Hold Control and Right-click to revert to game values."); } @@ -271,7 +271,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import { if (UiHelpers.DrawCheckbox("##apply", "Apply this custom parameter when applying the Design.", data.CurrentApply, out var enabled, data.Locked)) - data.ApplySetter(enabled); + data.ChangeApplyParameter(enabled); } private void DrawApplyAndLabel(in CustomizeParameterDrawData data) @@ -310,6 +310,6 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), new Vector2(ImGui.GetFrameHeight()), _copy.HasValue ? "Paste the currently copied value." : "No value copied yet.", locked || !_copy.HasValue, true)) - data.ValueSetter(_copy!.Value); + data.ChangeParameter(_copy!.Value); } } diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 6bedb02..67c6a9e 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -1,29 +1,45 @@ -using Dalamud.Game.Inventory; -using Glamourer.Designs; -using Glamourer.Events; +using Glamourer.Designs; using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) +public struct EquipDrawData(EquipSlot slot, in DesignData designData) { - public readonly EquipSlot Slot = slot; - public bool Locked; - public bool DisplayApplication; - public bool AllowRevert; + private IDesignEditor _editor; + private object _object; + public readonly EquipSlot Slot = slot; + public bool Locked; + public bool DisplayApplication; + public bool AllowRevert; - public Action ItemSetter = null!; - public Action StainSetter = null!; - public Action ApplySetter = null!; - public Action ApplyStainSetter = null!; - public EquipItem CurrentItem = designData.Item(slot); - public StainId CurrentStain = designData.Stain(slot); - public EquipItem GameItem = default; - public StainId GameStain = default; - public bool CurrentApply; - public bool CurrentApplyStain; + public readonly void SetItem(EquipItem item) + => _editor.ChangeItem(_object, Slot, item, ApplySettings.Manual); + + public readonly void SetStain(StainId stain) + => _editor.ChangeStain(_object, Slot, stain, ApplySettings.Manual); + + public readonly void SetApplyItem(bool value) + { + var manager = (DesignManager)_editor; + var design = (Design)_object; + manager.ChangeApplyItem(design, Slot, value); + } + + public readonly void SetApplyStain(bool value) + { + var manager = (DesignManager)_editor; + var design = (Design)_object; + manager.ChangeApplyStain(design, Slot, value); + } + + public EquipItem CurrentItem = designData.Item(slot); + public StainId CurrentStain = designData.Stain(slot); + public EquipItem GameItem = default; + public StainId GameStain = default; + public bool CurrentApply; + public bool CurrentApplyStain; public readonly Gender CurrentGender = designData.Customize.Gender; public readonly Race CurrentRace = designData.Customize.Race; @@ -31,10 +47,8 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot) => new(slot, design.DesignData) { - ItemSetter = i => manager.ChangeItem(design, slot, i), - StainSetter = i => manager.ChangeStain(design, slot, i), - ApplySetter = b => manager.ChangeApplyEquip(design, slot, b), - ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b), + _editor = manager, + _object = design, CurrentApply = design.DoApplyEquip(slot), CurrentApplyStain = design.DoApplyStain(slot), Locked = design.WriteProtected(), @@ -44,8 +58,8 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public static EquipDrawData FromState(StateManager manager, ActorState state, EquipSlot slot) => new(slot, state.ModelData) { - ItemSetter = i => manager.ChangeItem(state, slot, i, ApplySettings.Manual), - StainSetter = i => manager.ChangeStain(state, slot, i, ApplySettings.Manual), + _editor = manager, + _object = state, Locked = state.IsLocked, DisplayApplication = false, GameItem = state.BaseData.Item(slot), diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 22f40d7..8dfa50c 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -205,7 +205,7 @@ public class EquipmentDrawer { var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue); if (newSetId.Id != current.CurrentItem.PrimaryId.Id) - current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant)); + current.SetItem(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant)); } ImGui.SameLine(); @@ -214,7 +214,7 @@ public class EquipmentDrawer { var newType = (SecondaryId)Math.Clamp(type, 0, ushort.MaxValue); if (newType.Id != current.CurrentItem.SecondaryId.Id) - current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant)); + current.SetItem(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant)); } ImGui.SameLine(); @@ -223,7 +223,7 @@ public class EquipmentDrawer { var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue); if (newVariant.Id != current.CurrentItem.Variant.Id) - current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, current.CurrentItem.SecondaryId, + current.SetItem(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, current.CurrentItem.SecondaryId, newVariant)); } } @@ -239,7 +239,7 @@ public class EquipmentDrawer var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue); if (newStainId != data.CurrentStain.Id) - data.StainSetter(newStainId); + data.SetStain(newStainId); } /// Draw an input for armor that can set arbitrary values instead of choosing items. @@ -252,7 +252,7 @@ public class EquipmentDrawer { var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue); if (newSetId.Id != data.CurrentItem.PrimaryId.Id) - data.ItemSetter(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant)); + data.SetItem(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant)); } ImGui.SameLine(); @@ -261,7 +261,7 @@ public class EquipmentDrawer { var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue); if (newVariant != data.CurrentItem.Variant) - data.ItemSetter(_items.Identify(data.Slot, data.CurrentItem.PrimaryId, newVariant)); + data.SetItem(_items.Identify(data.Slot, data.CurrentItem.PrimaryId, newVariant)); } } @@ -365,7 +365,7 @@ public class EquipmentDrawer mainhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.MainHand); var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); ImGui.SameLine(); - using (var group = ImRaii.Group()) + using (ImRaii.Group()) { DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, false, left); if (mainhand.DisplayApplication) @@ -391,7 +391,7 @@ public class EquipmentDrawer var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); left = ImGui.IsItemClicked(ImGuiMouseButton.Left); ImGui.SameLine(); - using (var group = ImRaii.Group()) + using (ImRaii.Group()) { DrawOffhand(mainhand, offhand, out var offhandLabel, false, right, left); if (offhand.DisplayApplication) @@ -420,12 +420,12 @@ public class EquipmentDrawer : _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength); if (change) if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain)) - data.StainSetter(stain.RowIndex); + data.SetStain(stain.RowIndex); else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) - data.StainSetter(Stain.None.RowIndex); + data.SetStain(Stain.None.RowIndex); - if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out var id)) - data.StainSetter(Stain.None.RowIndex); + if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out _)) + data.SetStain(Stain.None.RowIndex); } private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open) @@ -441,13 +441,13 @@ public class EquipmentDrawer var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); if (change) - data.ItemSetter(combo.CurrentSelection); + data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) - data.ItemSetter(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); + data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, ItemManager.NothingItem(data.Slot), out var item)) - data.ItemSetter(item); + data.SetItem(item); } private static bool ResetOrClear(bool locked, bool clicked, bool allowRevert, bool allowClear, @@ -467,9 +467,9 @@ public class EquipmentDrawer (true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.", revertItem, true), (true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.", clearItem, true), (true, false, true) => ("Control and Right-Click to revert to game.", revertItem, true), - (true, false, false) => ("Control and Right-Click to revert to game.", (T?)default, false), + (true, false, false) => ("Control and Right-Click to revert to game.", default, false), (false, true, _) => ("Right-click to clear.", clearItem, true), - (false, false, _) => (string.Empty, (T?)default, false), + (false, false, _) => (string.Empty, default, false), }; ImGuiUtil.HoverTooltip(tt); @@ -502,11 +502,11 @@ public class EquipmentDrawer if (changedItem != null) { - mainhand.ItemSetter(changedItem.Value); + mainhand.SetItem(changedItem.Value); if (changedItem.Value.Type.ValidOffhand() != mainhand.CurrentItem.Type.ValidOffhand()) { offhand.CurrentItem = _items.GetDefaultOffhand(changedItem.Value); - offhand.ItemSetter(offhand.CurrentItem); + offhand.SetItem(offhand.CurrentItem); } mainhand.CurrentItem = changedItem.Value; @@ -533,18 +533,18 @@ public class EquipmentDrawer UiHelpers.OpenCombo($"##{combo.Label}"); if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth)) - offhand.ItemSetter(combo.CurrentSelection); + offhand.SetItem(combo.CurrentSelection); var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem); - if (ResetOrClear(locked, open, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item)) - offhand.ItemSetter(item); + if (ResetOrClear(locked, clear, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item)) + offhand.SetItem(item); } private static void DrawApply(in EquipDrawData data) { if (UiHelpers.DrawCheckbox($"##apply{data.Slot}", "Apply this item when applying the Design.", data.CurrentApply, out var enabled, data.Locked)) - data.ApplySetter(enabled); + data.SetApplyItem(enabled); } private static void DrawApplyStain(in EquipDrawData data) @@ -552,7 +552,7 @@ public class EquipmentDrawer if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this item when applying the Design.", data.CurrentApplyStain, out var enabled, data.Locked)) - data.ApplyStainSetter(enabled); + data.SetApplyStain(enabled); } #endregion diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 173a84d..f7fc253 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -256,7 +256,7 @@ public class DesignPanel( { var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selector.Selected!.DoApplyEquip(slot); if (ImGui.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange) - _manager.ChangeApplyEquip(_selector.Selected!, slot, apply); + _manager.ChangeApplyItem(_selector.Selected!, slot, apply); } } diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index e325152..55b1b7a 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -4,74 +4,109 @@ using Penumbra.GameData.Enums; namespace Glamourer.Gui; -public ref struct ToggleDrawData +public struct ToggleDrawData { + private IDesignEditor _editor = null!; + private object _data = null!; + private StateIndex _index; + public bool Locked; public bool DisplayApplication; public bool CurrentValue; public bool CurrentApply; - public Action SetValue = null!; - public Action SetApply = null!; - public string Label = string.Empty; public string Tooltip = string.Empty; + public ToggleDrawData() { } + public readonly void SetValue(bool value) + { + switch (_index.GetFlag()) + { + case MetaIndex index: + _editor.ChangeMetaState(_data, index, value, ApplySettings.Manual); + break; + case CrestFlag flag: + _editor.ChangeCrest(_data, flag, value, ApplySettings.Manual); + break; + } + } + + public readonly void SetApply(bool value) + { + var manager = (DesignManager)_editor; + var design = (Design)_data; + switch (_index.GetFlag()) + { + case MetaIndex index: + manager.ChangeApplyMeta(design, index, value); + break; + case CrestFlag flag: + manager.ChangeApplyCrest(design, flag, value); + break; + } + } + public static ToggleDrawData FromDesign(MetaIndex index, DesignManager manager, Design design) => new() { + _index = index, + _editor = manager, + _data = design, Label = index.ToName(), Tooltip = string.Empty, Locked = design.WriteProtected(), DisplayApplication = true, CurrentValue = design.DesignData.GetMeta(index), CurrentApply = design.DoApplyMeta(index), - SetValue = b => manager.ChangeMetaState(design, index, b), - SetApply = b => manager.ChangeApplyMeta(design, index, b), + }; + + public static ToggleDrawData FromState(MetaIndex index, StateManager manager, ActorState state) + => new() + { + _index = index, + _editor = manager, + _data = state, + Label = index.ToName(), + Tooltip = index.ToTooltip(), + Locked = state.IsLocked, + CurrentValue = state.ModelData.GetMeta(index), }; public static ToggleDrawData CrestFromDesign(CrestFlag slot, DesignManager manager, Design design) => new() { + _index = slot, + _editor = manager, + _data = design, Label = $"{slot.ToLabel()} Crest", Tooltip = string.Empty, Locked = design.WriteProtected(), DisplayApplication = true, CurrentValue = design.DesignData.Crest(slot), CurrentApply = design.DoApplyCrest(slot), - SetValue = v => manager.ChangeCrest(design, slot, v), - SetApply = v => manager.ChangeApplyCrest(design, slot, v), }; public static ToggleDrawData CrestFromState(CrestFlag slot, StateManager manager, ActorState state) => new() { + _index = slot, + _editor = manager, + _data = state, Label = $"{slot.ToLabel()} Crest", Tooltip = "Hide or show your free company crest on this piece of gear.", Locked = state.IsLocked, CurrentValue = state.ModelData.Crest(slot), - SetValue = v => manager.ChangeCrest(state, slot, v, ApplySettings.Manual), }; - public static ToggleDrawData FromState(MetaIndex index, StateManager manager, ActorState state) - { - return new ToggleDrawData - { - Label = index.ToName(), - Tooltip = index.ToTooltip(), - Locked = state.IsLocked, - CurrentValue = state.ModelData.GetMeta(index), - SetValue = b => manager.ChangeMetaState(state, index, b, ApplySettings.Manual), - }; - } - public static ToggleDrawData FromValue(MetaIndex index, bool value) => new() { + _index = index, Label = index.ToName(), Tooltip = index.ToTooltip(), Locked = true, diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 527daa6..48b2c8b 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -39,9 +39,7 @@ public class StateEditor( /// public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings settings) { - if (data is not ActorState state) - return; - + var state = (ActorState)data; if (!Editor.ChangeCustomize(state, idx, value, settings.Source, out var old, settings.Key)) return; @@ -54,9 +52,7 @@ public class StateEditor( /// public void ChangeEntireCustomize(object data, in CustomizeArray customizeInput, CustomizeFlag apply, ApplySettings settings) { - if (data is not ActorState state) - return; - + var state = (ActorState)data; if (!Editor.ChangeHumanCustomize(state, customizeInput, apply, _ => settings.Source, out var old, out var applied, settings.Key)) return; @@ -69,9 +65,7 @@ public class StateEditor( /// public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default) { - if (data is not ActorState state) - return; - + var state = (ActorState)data; if (!Editor.ChangeItem(state, slot, item, settings.Source, out var old, settings.Key)) return; @@ -103,9 +97,7 @@ public class StateEditor( return; } - if (data is not ActorState state) - return; - + var state = (ActorState)data; if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stain ?? state.ModelData.Stain(slot), settings.Source, out var old, out var oldStain, settings.Key)) return; @@ -128,9 +120,7 @@ public class StateEditor( /// public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings) { - if (data is not ActorState state) - return; - + var state = (ActorState)data; if (!Editor.ChangeStain(state, slot, stain, settings.Source, out var old, settings.Key)) return; @@ -143,9 +133,7 @@ public class StateEditor( /// public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings) { - if (data is not ActorState state) - return; - + var state = (ActorState)data; if (!Editor.ChangeCrest(state, slot, crest, settings.Source, out var old, settings.Key)) return; @@ -178,9 +166,7 @@ public class StateEditor( /// public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings) { - if (data is not ActorState state) - return; - + var state = (ActorState)data; if (!Editor.ChangeMetaState(state, index, value, settings.Source, out var old, settings.Key)) return; @@ -193,9 +179,7 @@ public class StateEditor( /// public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings) { - if (data is not ActorState state) - return; - + var state = (ActorState)data; if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize, mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key)) return; From 282d6df1652c202f953c5041ded2ebb556977023 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 26 Jan 2024 16:10:09 +0100 Subject: [PATCH 211/786] Add UI. --- Glamourer/Automation/ApplicationType.cs | 10 + Glamourer/Designs/Links/DesignLink.cs | 1 + Glamourer/Designs/Links/DesignLinkManager.cs | 23 ++- Glamourer/Designs/Links/LinkContainer.cs | 26 ++- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 32 +--- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 171 ++++++++++++++---- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- 7 files changed, 190 insertions(+), 75 deletions(-) diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs index e99d948..03e3a2d 100644 --- a/Glamourer/Automation/ApplicationType.cs +++ b/Glamourer/Automation/ApplicationType.cs @@ -18,6 +18,16 @@ public enum ApplicationType : byte public static class ApplicationTypeExtensions { + public static readonly IReadOnlyList<(ApplicationType, string)> Types = new[] + { + (ApplicationType.Customizations, + "Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."), + (ApplicationType.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."), + (ApplicationType.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."), + (ApplicationType.GearCustomization, "Apply all dye and crest changes that are enabled in this design."), + (ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), + }; + public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat( this ApplicationType type, DesignBase? design) { diff --git a/Glamourer/Designs/Links/DesignLink.cs b/Glamourer/Designs/Links/DesignLink.cs index 2adc055..a9fb805 100644 --- a/Glamourer/Designs/Links/DesignLink.cs +++ b/Glamourer/Designs/Links/DesignLink.cs @@ -15,4 +15,5 @@ public enum LinkOrder : byte Self, After, Before, + None, }; diff --git a/Glamourer/Designs/Links/DesignLinkManager.cs b/Glamourer/Designs/Links/DesignLinkManager.cs index e3fe094..76d9c9a 100644 --- a/Glamourer/Designs/Links/DesignLinkManager.cs +++ b/Glamourer/Designs/Links/DesignLinkManager.cs @@ -56,6 +56,17 @@ public sealed class DesignLinkManager : IService, IDisposable _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); } + public void ChangeApplicationType(Design parent, int idx, LinkOrder order, ApplicationType applicationType) + { + applicationType &= ApplicationType.All; + if (!parent.Links.ChangeApplicationRules(idx, order, applicationType, out var old)) + return; + + _saveService.QueueSave(parent); + Glamourer.Log.Debug($"Changed link application type from {old} to {applicationType} for design link {order} {idx + 1} in design {parent.Identifier}."); + _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + } + private void OnDesignChanged(DesignChanged.Type type, Design deletedDesign, object? _) { if (type is not DesignChanged.Type.Deleted) @@ -63,12 +74,12 @@ public sealed class DesignLinkManager : IService, IDisposable foreach (var design in _storage) { - if (design.Links.Remove(deletedDesign)) - { - design.LastEdit = DateTimeOffset.UtcNow; - Glamourer.Log.Debug($"Removed {deletedDesign.Identifier} from {design.Identifier} links due to deletion."); - _saveService.QueueSave(design); - } + if (!design.Links.Remove(deletedDesign)) + continue; + + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug($"Removed {deletedDesign.Identifier} from {design.Identifier} links due to deletion."); + _saveService.QueueSave(design); } } } diff --git a/Glamourer/Designs/Links/LinkContainer.cs b/Glamourer/Designs/Links/LinkContainer.cs index 08a1b6d..ef67688 100644 --- a/Glamourer/Designs/Links/LinkContainer.cs +++ b/Glamourer/Designs/Links/LinkContainer.cs @@ -57,29 +57,45 @@ public sealed class LinkContainer : List return true; } + public bool ChangeApplicationRules(int idx, LinkOrder order, ApplicationType type, out ApplicationType old) + { + var list = order switch + { + LinkOrder.Before => Before, + LinkOrder.After => After, + _ => throw new ArgumentException("Invalid link order."), + }; + old = list[idx].Type; + if (idx < 0 || idx >= list.Count || old == type) + return false; + + list[idx] = list[idx] with { Type = type }; + return true; + } + public static bool CanAddLink(Design parent, Design child, LinkOrder order, out string error) { if (parent == child) { - error = $"Can not link {parent.Identifier} with itself."; + error = $"Can not link {parent.Incognito} with itself."; return false; } if (parent.Links.Contains(child)) { - error = $"Design {parent.Identifier} already contains a direct link to {child.Identifier}."; + error = $"Design {parent.Incognito} already contains a direct link to {child.Incognito}."; return false; } if (GetAllLinks(parent).Any(l => l.Link.Link == child && l.Order != order)) { - error = $"Adding {child.Identifier} to {parent.Identifier}s links would create a circle, the parent already links to the child in the opposite direction."; + error = $"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the parent already links to the child in the opposite direction."; return false; } if (GetAllLinks(child).Any(l => l.Link.Link == parent && l.Order == order)) { - error = $"Adding {child.Identifier} to {parent.Identifier}s links would create a circle, the child already links to the parent in the opposite direction."; + error = $"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the child already links to the parent in the opposite direction."; return false; } @@ -161,7 +177,7 @@ public sealed class LinkContainer : List var after = new JArray(); foreach (var link in After) { - before.Add(new JObject + after.Add(new JObject { ["Design"] = link.Link.Identifier, ["Type"] = (uint)link.Type, diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 2cb1ede..0387d58 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -384,7 +384,7 @@ public class SetPanel( { void Box(int idx) { - var (type, description) = Types[idx]; + var (type, description) = ApplicationTypeExtensions.Types[idx]; var value = design.Type.HasFlag(type); if (ImGui.Checkbox($"##{(byte)type}", ref value)) newType = value ? newType | type : newType & ~type; @@ -428,40 +428,20 @@ public class SetPanel( _manager.ChangeIdentifier(setIndex, _identifierDrawer.MannequinIdentifier); } - - private static readonly IReadOnlyList<(ApplicationType, string)> Types = new[] + private sealed class JobGroupCombo(AutoDesignManager manager, JobService jobs, Logger log) + : FilterComboCache(() => jobs.JobGroups.Values.ToList(), log) { - (ApplicationType.Customizations, - "Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."), - (ApplicationType.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."), - (ApplicationType.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."), - (ApplicationType.GearCustomization, "Apply all dye and crest changes that are enabled in this design."), - (ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), - }; - - private sealed class JobGroupCombo : FilterComboCache - { - private readonly AutoDesignManager _manager; - private readonly JobService _jobs; - - public JobGroupCombo(AutoDesignManager manager, JobService jobs, Logger log) - : base(() => jobs.JobGroups.Values.ToList(), log) - { - _manager = manager; - _jobs = jobs; - } - public void Draw(AutoDesignSet set, AutoDesign design, int autoDesignIndex) { CurrentSelection = design.Jobs; - CurrentSelectionIdx = _jobs.JobGroups.Values.IndexOf(j => j.Id == design.Jobs.Id); + CurrentSelectionIdx = jobs.JobGroups.Values.IndexOf(j => j.Id == design.Jobs.Id); if (Draw("##JobGroups", design.Jobs.Name, "Select for which job groups this design should be applied.\nControl + Right-Click to set to all classes.", ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeightWithSpacing()) && CurrentSelectionIdx >= 0) - _manager.ChangeJobCondition(set, autoDesignIndex, CurrentSelection); + manager.ChangeJobCondition(set, autoDesignIndex, CurrentSelection); else if (ImGui.GetIO().KeyCtrl && ImGui.IsItemClicked(ImGuiMouseButton.Right)) - _manager.ChangeJobCondition(set, autoDesignIndex, _jobs.JobGroups[1]); + manager.ChangeJobCondition(set, autoDesignIndex, jobs.JobGroups[1]); } protected override string ToString(JobGroup obj) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index 774ee3c..f45f936 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -1,4 +1,6 @@ using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Designs.Links; using ImGuiNET; @@ -11,9 +13,9 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, DesignCombo _combo) : IUiService { private int _dragDropIndex = -1; - private LinkOrder _dragDropOrder = LinkOrder.Self; + private LinkOrder _dragDropOrder = LinkOrder.None; private int _dragDropTargetIndex = -1; - private LinkOrder _dragDropTargetOrder = LinkOrder.Self; + private LinkOrder _dragDropTargetOrder = LinkOrder.None; public void Draw() { @@ -21,30 +23,76 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe if (!header) return; - var width = ImGui.GetContentRegionAvail().X / 2; - DrawList(_selector.Selected!.Links.Before, LinkOrder.Before, width); - ImGui.SameLine(); - DrawList(_selector.Selected!.Links.After, LinkOrder.After, width); - - if (_dragDropTargetIndex < 0 - || _dragDropIndex < 0) - return; - - _linkManager.MoveDesignLink(_selector.Selected!, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder); - _dragDropIndex = -1; - _dragDropTargetIndex = -1; - _dragDropOrder = LinkOrder.Self; - _dragDropTargetOrder = LinkOrder.Self; + DrawList(); } - private void DrawList(IReadOnlyList list, LinkOrder order, float width) + private void MoveLink() { - using var id = ImRaii.PushId((int)order); - using var table = ImRaii.Table("table", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter, - new Vector2(width, list.Count * ImGui.GetFrameHeightWithSpacing())); + if (_dragDropTargetIndex < 0 || _dragDropIndex < 0) + return; + + if (_dragDropOrder is LinkOrder.Self) + switch (_dragDropTargetOrder) + { + case LinkOrder.Before: + for (var i = _selector.Selected!.Links.Before.Count - 1; i >= _dragDropTargetIndex; --i) + _linkManager.MoveDesignLink(_selector.Selected!, i, LinkOrder.Before, 0, LinkOrder.After); + break; + case LinkOrder.After: + for (var i = 0; i <= _dragDropTargetIndex; ++i) + { + _linkManager.MoveDesignLink(_selector.Selected!, 0, LinkOrder.After, _selector.Selected!.Links.Before.Count, + LinkOrder.Before); + } + + break; + } + else if (_dragDropTargetOrder is LinkOrder.Self) + _linkManager.MoveDesignLink(_selector.Selected!, _dragDropIndex, _dragDropOrder, _selector.Selected!.Links.Before.Count, + LinkOrder.Before); + else + _linkManager.MoveDesignLink(_selector.Selected!, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder); + + _dragDropIndex = -1; + _dragDropTargetIndex = -1; + _dragDropOrder = LinkOrder.None; + _dragDropTargetOrder = LinkOrder.None; + } + + private void DrawList() + { + using var table = ImRaii.Table("table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter); if (!table) return; + ImGui.TableSetupColumn("Del", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight()); + ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("Detail", ImGuiTableColumnFlags.WidthFixed, + 6 * ImGui.GetFrameHeight() + 5 * ImGui.GetStyle().ItemInnerSpacing.X); + + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); + DrawSubList(_selector.Selected!.Links.Before, LinkOrder.Before); + DrawSelf(); + DrawSubList(_selector.Selected!.Links.After, LinkOrder.After); + DrawNew(); + MoveLink(); + } + + private void DrawSelf() + { + using var id = ImRaii.PushId((int)LinkOrder.Self); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.Selectable(_selector.IncognitoMode ? _selector.Selected!.Incognito : _selector.Selected!.Name.Text); + DrawDragDrop(_selector.Selected!, LinkOrder.Self, 0); + ImGui.TableNextColumn(); + } + + private void DrawSubList(IReadOnlyList list, LinkOrder order) + { + using var id = ImRaii.PushId((int)order); + var buttonSize = new Vector2(ImGui.GetFrameHeight()); for (var i = 0; i < list.Count; ++i) { @@ -52,11 +100,6 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe ImGui.TableNextColumn(); var delete = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this link.", false, true); - - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted($"#{i:D2}"); - var (design, flags) = list[i]; ImGui.TableNextColumn(); @@ -66,32 +109,48 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(flags.ToString()); + DrawApplicationBoxes(i, order, flags); if (delete) _linkManager.RemoveDesignLink(_selector.Selected!, i--, order); } + } + private void DrawNew() + { + var buttonSize = new Vector2(ImGui.GetFrameHeight()); ImGui.TableNextColumn(); - string tt; - bool canAdd; + ImGui.TableNextColumn(); + _combo.Draw(ImGui.GetContentRegionAvail().X); + ImGui.TableNextColumn(); + string ttBefore, ttAfter; + bool canAddBefore, canAddAfter; if (_combo.Design == null) { - tt = "Select a design first."; - canAdd = false; + ttAfter = ttBefore = "Select a design first."; + canAddBefore = canAddAfter = false; } else { - canAdd = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, order, out var error); - tt = canAdd ? $"Add a link to {_combo.Design.Name}." : $"Can not add a link to {_combo.Design.Name}: {error}"; + canAddBefore = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, LinkOrder.Before, out var error); + ttBefore = canAddBefore + ? $"Add a link at the top of the list to {_combo.Design.Name}." + : $"Can not add a link to {_combo.Design.Name}:\n{error}"; + canAddAfter = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, LinkOrder.After, out error); + ttAfter = canAddAfter + ? $"Add a link at the bottom of the list to {_combo.Design.Name}." + : $"Can not add a link to {_combo.Design.Name}:\n{error}"; } - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, !canAdd, true)) - _linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, order); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowCircleUp.ToIconString(), buttonSize, ttBefore, !canAddBefore, true)) + { + _linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, LinkOrder.Before); + _linkManager.MoveDesignLink(_selector.Selected!, _selector.Selected!.Links.Before.Count - 1, LinkOrder.Before, 0, LinkOrder.Before); + } - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - _combo.Draw(200); + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowCircleDown.ToIconString(), buttonSize, ttAfter, !canAddAfter, true)) + _linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, LinkOrder.After); } private void DrawDragDrop(Design design, LinkOrder order, int index) @@ -117,4 +176,42 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe _dragDropTargetIndex = index; _dragDropTargetOrder = order; } + + private void DrawApplicationBoxes(int idx, LinkOrder order, ApplicationType current) + { + var newType = current; + var newTypeInt = (uint)newType; + using (ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale)) + { + using var _ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()); + if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)ApplicationType.All)) + newType = (ApplicationType)newTypeInt; + } + + ImGuiUtil.HoverTooltip("Toggle all application modes at once."); + + ImGui.SameLine(); + Box(0); + ImGui.SameLine(); + Box(1); + ImGui.SameLine(); + + Box(2); + ImGui.SameLine(); + Box(3); + ImGui.SameLine(); + Box(4); + if (newType != current) + _linkManager.ChangeApplicationType(_selector.Selected!, idx, order, current); + return; + + void Box(int i) + { + var (applicationType, description) = ApplicationTypeExtensions.Types[i]; + var value = applicationType.HasFlag(applicationType); + if (ImGui.Checkbox($"##{(byte)applicationType}", ref value)) + newType = value ? newType | applicationType : newType & ~applicationType; + ImGuiUtil.HoverTooltip(description); + } + } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index f7fc253..83da487 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -303,7 +303,7 @@ public class DesignPanel( { var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selector.Selected!.DoApplyMeta(index); if (ImGui.Checkbox(label, ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, apply); + _manager.ChangeApplyMeta(_selector.Selected!, index, apply); } } From 5992b86e4f91e84c3f200f17bec383141943259b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 26 Jan 2024 16:45:31 +0100 Subject: [PATCH 212/786] Add working links. --- Glamourer/Automation/AutoDesignApplier.cs | 3 ++- Glamourer/Designs/Design.cs | 3 +++ Glamourer/Designs/IDesignEditor.cs | 5 ++++- Glamourer/Designs/Links/DesignMerger.cs | 3 +++ Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 4 ++-- Glamourer/Services/CommandService.cs | 4 ++-- Glamourer/State/StateEditor.cs | 12 ++++++++++-- Glamourer/State/StateManager.cs | 6 ++++-- 9 files changed, 31 insertions(+), 11 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index f8ca397..55359ca 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -262,7 +262,8 @@ public sealed class AutoDesignApplier : IDisposable if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) return; - var mergedDesign = _designMerger.Merge(set.Designs.Where(d => d.IsActive(actor)).Select(d => ((DesignBase?)d.Design, d.Type)), + var mergedDesign = _designMerger.Merge( + set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks ?? [(d.Design, d.Type)]), state.ModelData, true, false); _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false)); } diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 9f74af0..89dd62f 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -46,6 +46,9 @@ public sealed class Design : DesignBase, ISavable public string Incognito => Identifier.ToString()[..8]; + public IEnumerable<(DesignBase? Design, ApplicationType Flags)> AllLinks + => LinkContainer.GetAllLinks(this).Select(t => ((DesignBase?)t.Link.Link, t.Link.Type)); + #endregion #region Serialization diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index a4da53c..b655327 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -11,7 +11,8 @@ public readonly record struct ApplySettings( StateSource Source = StateSource.Manual, bool RespectManual = false, bool FromJobChange = false, - bool UseSingleSource = false) + bool UseSingleSource = false, + bool MergeLinks = false) { public static readonly ApplySettings Manual = new() { @@ -20,6 +21,7 @@ public readonly record struct ApplySettings( FromJobChange = false, RespectManual = false, UseSingleSource = false, + MergeLinks = false, }; public static readonly ApplySettings Game = new() @@ -29,6 +31,7 @@ public readonly record struct ApplySettings( FromJobChange = false, RespectManual = false, UseSingleSource = false, + MergeLinks = false, }; } diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 7670498..438c0f4 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -15,6 +15,9 @@ public class DesignMerger( ItemUnlockManager _itemUnlocks, CustomizeUnlockManager _customizeUnlocks) : IService { + public MergedDesign Merge(LinkContainer designs, in DesignData baseRef, bool respectOwnership, bool modAssociations) + => Merge(designs.Select(d => ((DesignBase?) d.Link, d.Type)), baseRef, respectOwnership, modAssociations); + public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef, bool respectOwnership, bool modAssociations) { diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 15f5bd0..bef2ea5 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -339,7 +339,7 @@ public class ActorPanel( var text = ImGui.GetClipboardText(); var design = _converter.FromBase64(text, applyCustomize, applyGear, out _) ?? throw new Exception("The clipboard did not contain valid data."); - _stateManager.ApplyDesign(_state!, design, ApplySettings.Manual); + _stateManager.ApplyDesign(_state!, design, ApplySettings.Manual with { MergeLinks = true }); } catch (Exception ex) { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 83da487..93ebad5 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -438,7 +438,7 @@ public class DesignPanel( { var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual with { MergeLinks = true }); } } @@ -457,7 +457,7 @@ public class DesignPanel( { var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual with {MergeLinks = true}); } } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index ddc7217..eb0e792 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -419,7 +419,7 @@ public class CommandService : IDisposable if (!_objects.TryGetValue(identifier, out var actors)) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ApplyDesign(state, design, ApplySettings.Manual); + _stateManager.ApplyDesign(state, design, ApplySettings.Manual with { MergeLinks = true }); } else { @@ -428,7 +428,7 @@ public class CommandService : IDisposable if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) { ApplyModSettings(design, actor, applyMods); - _stateManager.ApplyDesign(state, design, ApplySettings.Manual); + _stateManager.ApplyDesign(state, design, ApplySettings.Manual with { MergeLinks = true }); } } } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 48b2c8b..2b1337e 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -15,7 +15,8 @@ public class StateEditor( StateChanged stateChanged, JobChangeState jobChange, Configuration config, - ItemManager items) : IDesignEditor + ItemManager items, + DesignMerger merger) : IDesignEditor { protected readonly InternalStateEditor Editor = editor; protected readonly StateApplier Applier = applier; @@ -293,12 +294,19 @@ public class StateEditor( } public void ApplyDesign(object data, DesignBase design, ApplySettings settings) - => ApplyDesign(data, new MergedDesign(design), settings with + { + var merged = settings.MergeLinks && design is Design d + ? merger.Merge(d.AllLinks, ((ActorState)data).ModelData, false, false) + : new MergedDesign(design); + + ApplyDesign(data, merged, settings with { FromJobChange = false, RespectManual = false, UseSingleSource = true, }); + } + /// Apply offhand item and potentially gauntlets if configured. private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, ApplySettings settings) diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 5d0e01f..2643271 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin.Services; using Glamourer.Designs; +using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; @@ -21,8 +22,9 @@ public sealed class StateManager( HumanModelList _humans, IClientState _clientState, Configuration config, - JobChangeState jobChange) - : StateEditor(editor, applier, @event, jobChange, config, items), IReadOnlyDictionary + JobChangeState jobChange, + DesignMerger merger) + : StateEditor(editor, applier, @event, jobChange, config, items, merger), IReadOnlyDictionary { private readonly Dictionary _states = []; From 0e3d3d18399943166cf16579de708a66e7330b4f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 26 Jan 2024 16:51:07 +0100 Subject: [PATCH 213/786] Fix application rules disrespect for auto designs. --- Glamourer/Automation/AutoDesignApplier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 55359ca..dd327b3 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -263,7 +263,7 @@ public sealed class AutoDesignApplier : IDisposable return; var mergedDesign = _designMerger.Merge( - set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks ?? [(d.Design, d.Type)]), + set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks.Select(l => (l.Design, l.Flags & d.Type)) ?? [(d.Design, d.Type)]), state.ModelData, true, false); _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false)); } From 447e748ed7eb21eb81e1e48b61fcb122fe2bad40 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 19 Jan 2024 13:23:37 +0100 Subject: [PATCH 214/786] start --- Glamourer/GameData/CustomizeParameterValue.cs | 4 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 18 ++ .../Interop/Material/DirectXTextureHelper.cs | 116 ++++++++ Glamourer/Interop/Material/MaterialService.cs | 130 +++++++++ .../Interop/Material/MaterialValueIndex.cs | 266 ++++++++++++++++++ .../Interop/Material/MaterialValueManager.cs | 162 +++++++++++ Glamourer/Interop/Material/PrepareColorSet.cs | 162 +++++++++++ .../Interop/Material/SafeTextureHandle.cs | 49 ++++ Glamourer/Services/ServiceManager.cs | 3 +- 9 files changed, 906 insertions(+), 4 deletions(-) create mode 100644 Glamourer/Interop/Material/DirectXTextureHelper.cs create mode 100644 Glamourer/Interop/Material/MaterialService.cs create mode 100644 Glamourer/Interop/Material/MaterialValueIndex.cs create mode 100644 Glamourer/Interop/Material/MaterialValueManager.cs create mode 100644 Glamourer/Interop/Material/PrepareColorSet.cs create mode 100644 Glamourer/Interop/Material/SafeTextureHandle.cs diff --git a/Glamourer/GameData/CustomizeParameterValue.cs b/Glamourer/GameData/CustomizeParameterValue.cs index e1e0943..0e22d18 100644 --- a/Glamourer/GameData/CustomizeParameterValue.cs +++ b/Glamourer/GameData/CustomizeParameterValue.cs @@ -1,6 +1,4 @@ -using Newtonsoft.Json; - -namespace Glamourer.GameData; +namespace Glamourer.GameData; public readonly struct CustomizeParameterValue { diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index bef2ea5..68eb89e 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -8,6 +8,7 @@ using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Interop; +using Glamourer.Interop.Material; using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; @@ -97,6 +98,12 @@ public class ActorPanel( return (_selector.IncognitoMode ? _identifier.Incognito(null) : _identifier.ToString(), Actor.Null); } + private Vector3 _test; + private int _rowId; + private MaterialValueIndex.ColorTableIndex _index; + private int _materialId; + private int _slotId; + private unsafe void DrawPanel() { using var child = ImRaii.Child("##Panel", -Vector2.One, true); @@ -114,6 +121,17 @@ public class ActorPanel( RevertButtons(); + ImGui.InputInt("Row", ref _rowId); + ImGui.InputInt("Material", ref _materialId); + ImGui.InputInt("Slot", ref _slotId); + ImGuiUtil.GenericEnumCombo("Value", 300, _index, out _index); + + var index = new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte) _slotId, (byte) _materialId, (byte)_rowId, _index); + index.TryGetValue(_actor, out _test); + if (ImGui.ColorPicker3("TestPicker", ref _test) && _actor.Valid) + MaterialService.Test(_actor, index, _test); + + using var disabled = ImRaii.Disabled(transformationId != 0); if (_state.ModelData.IsHuman) DrawHumanPanel(); diff --git a/Glamourer/Interop/Material/DirectXTextureHelper.cs b/Glamourer/Interop/Material/DirectXTextureHelper.cs new file mode 100644 index 0000000..9932abc --- /dev/null +++ b/Glamourer/Interop/Material/DirectXTextureHelper.cs @@ -0,0 +1,116 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using Penumbra.GameData.Files; +using Penumbra.String.Functions; +using SharpGen.Runtime; +using Vortice.Direct3D11; +using Vortice.DXGI; +using MapFlags = Vortice.Direct3D11.MapFlags; + +namespace Glamourer.Interop.Material; + +public static unsafe class DirectXTextureHelper +{ + /// Try to turn a color table GPU-loaded texture (R16G16B16A16Float, 4 Width, 16 Height) into an actual color table. + /// A pointer to the internal texture struct containing the GPU handle. + /// The returned color table. + /// Whether the table could be fetched. + public static bool TryGetColorTable(Texture* texture, out MtrlFile.ColorTable table) + { + if (texture == null) + { + table = default; + return false; + } + + try + { + // Create direct x resource and ensure that it is kept alive. + using var tex = new ID3D11Texture2D1((nint)texture->D3D11Texture2D); + tex.AddRef(); + + table = GetResourceData(tex, CreateStagedClone, GetTextureData); + return true; + } + catch + { + return false; + } + } + + /// Create a staging clone of the existing texture handle for stability reasons. + private static ID3D11Texture2D1 CreateStagedClone(ID3D11Texture2D1 resource) + { + var desc = resource.Description1 with + { + Usage = ResourceUsage.Staging, + BindFlags = 0, + CPUAccessFlags = CpuAccessFlags.Read, + MiscFlags = 0, + }; + + return resource.Device.As().CreateTexture2D1(desc); + } + + /// Turn a mapped texture into a color table. + private static MtrlFile.ColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map) + { + var desc = resource.Description1; + + if (desc.Format is not Format.R16G16B16A16_Float + || desc.Width != MaterialService.TextureWidth + || desc.Height != MaterialService.TextureHeight + || map.DepthPitch != map.RowPitch * desc.Height) + throw new InvalidDataException("The texture was not a valid color table texture."); + + return ReadTexture(map.DataPointer, map.DepthPitch, desc.Height, map.RowPitch); + } + + /// Transform the GPU data into the color table. + /// The pointer to the raw texture data. + /// The size of the raw texture data. + /// The height of the texture. (Needs to be 16). + /// The stride in the texture data. + /// + private static MtrlFile.ColorTable 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(MtrlFile.ColorTable) != expectedSize || height != MaterialService.TextureHeight) + return default; + + var ret = new MtrlFile.ColorTable(); + var target = (byte*)&ret; + // If the stride is the same as in the table, just copy. + if (pitch == MaterialService.TextureWidth) + MemoryUtility.MemCpyUnchecked(target, (void*)data, length); + // Otherwise, adapt the stride. + else + + for (var y = 0; y < height; ++y) + { + MemoryUtility.MemCpyUnchecked(target + y * MaterialService.TextureWidth * sizeof(Half) * 4, (byte*)data + y * pitch, + MaterialService.TextureWidth * sizeof(Half) * 4); + } + + return ret; + } + + /// Get resources of a texture. + private static TRet GetResourceData(T res, Func cloneResource, Func getData) + where T : ID3D11Resource + { + using var stagingRes = cloneResource(res); + + res.Device.ImmediateContext.CopyResource(stagingRes, res); + stagingRes.Device.ImmediateContext.Map(stagingRes, 0, MapMode.Read, MapFlags.None, out var mapInfo).CheckError(); + + try + { + return getData(stagingRes, mapInfo); + } + finally + { + stagingRes.Device.ImmediateContext.Unmap(stagingRes, 0); + } + } +} diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs new file mode 100644 index 0000000..91635c5 --- /dev/null +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -0,0 +1,130 @@ +using Dalamud.Interface.Utility.Raii; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using Glamourer.Interop.Structs; +using ImGuiNET; +using Lumina.Data.Files; +using OtterGui; +using OtterGui.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using static Penumbra.GameData.Files.MtrlFile; +using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture; + +namespace Glamourer.Interop.Material; + + +public class MaterialServiceDrawer(ActorManager actors) : IService +{ + private ActorIdentifier _openIdentifier; + private uint _openSlotIndex; + + public unsafe void PopupButton(Actor actor, EquipSlot slot) + { + var slotIndex = slot.ToIndex(); + + var identifier = actor.GetIdentifier(actors); + var buttonActive = actor.Valid + && identifier.IsValid + && actor.Model.IsCharacterBase + && slotIndex < actor.Model.AsCharacterBase->SlotCount + && (actor.Model.AsCharacterBase->HasModelInSlotLoaded & (1 << (int)slotIndex)) != 0; + using var id = ImRaii.PushId((int)slot); + if (ImGuiUtil.DrawDisabledButton("Advanced", Vector2.Zero, "Open advanced window.", !buttonActive)) + { + _openIdentifier = identifier; + _openSlotIndex = slotIndex; + ImGui.OpenPopup($"Popup{slot}"); + } + } +} + +public static unsafe class MaterialService +{ + public const int TextureWidth = 4; + public const int TextureHeight = ColorTable.NumRows; + public const int MaterialsPerModel = 4; + + /// 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 static bool GenerateColorTable(Texture** original, in ColorTable colorTable) + { + if (original == null) + return false; + + var textureSize = stackalloc int[2]; + textureSize[0] = TextureWidth; + textureSize[1] = TextureHeight; + + using var texture = new SafeTextureHandle(Device.Instance()->CreateTexture2D(textureSize, 1, (uint)TexFile.TextureFormat.R16G16B16A16F, + (uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7), false); + if (texture.IsInvalid) + return false; + + fixed(ColorTable* ptr = &colorTable) + { + if (!texture.Texture->InitializeContents(ptr)) + return false; + } + + texture.Exchange(ref *(nint*)original); + return true; + } + + + /// Obtain a pointer to the models pointer to a specific color table texture. + /// + /// + /// + /// + public static Texture** GetColorTableTexture(Model model, int modelSlot, byte materialSlot) + { + if (!model.IsCharacterBase) + return null; + + var index = modelSlot * MaterialsPerModel + materialSlot; + if (index < 0 || index >= model.AsCharacterBase->ColorTableTexturesSpan.Length) + return null; + + var texture = (Texture**)Unsafe.AsPointer(ref model.AsCharacterBase->ColorTableTexturesSpan[index]); + return texture; + } + + /// Obtain a pointer to the color table of a certain material from a model. + /// The draw object. + /// 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) + { + if (!model.IsCharacterBase) + return null; + + var index = modelSlot * MaterialsPerModel + materialSlot; + if (index < 0 || index >= model.AsCharacterBase->MaterialsSpan.Length) + return null; + + var material = (MaterialResourceHandle*)model.AsCharacterBase->MaterialsSpan[index].Value; + if (material == null || material->ColorTable == null) + return null; + + return (ColorTable*)material->ColorTable; + } + + public static void Test(Actor actor, MaterialValueIndex index, Vector3 value) + { + if (!index.TryGetColorTable(actor, out var table)) + return; + + ref var row = ref table[index.RowIndex]; + if (!index.DataIndex.SetValue(ref row, value)) + return; + + var texture = GetColorTableTexture(index.TryGetModel(actor, out var model) ? model : Model.Null, index.SlotIndex, + index.MaterialIndex); + if (texture != null) + GenerateColorTable(texture, table); + } +} diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs new file mode 100644 index 0000000..4cbc116 --- /dev/null +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -0,0 +1,266 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.Interop; +using Glamourer.Interop.Structs; +using Newtonsoft.Json; +using Penumbra.GameData.Files; + +namespace Glamourer.Interop.Material; + +[JsonConverter(typeof(Converter))] +public readonly record struct MaterialValueIndex( + MaterialValueIndex.DrawObjectType DrawObject, + byte SlotIndex, + byte MaterialIndex, + byte RowIndex, + MaterialValueIndex.ColorTableIndex DataIndex) +{ + public uint Key + => ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex, DataIndex); + + public bool Valid + => Validate(DrawObject) && ValidateSlot(SlotIndex) && ValidateMaterial(MaterialIndex) && ValidateRow(RowIndex) && Validate(DataIndex); + + public static bool FromKey(uint key, out MaterialValueIndex index) + { + index = new MaterialValueIndex(key); + return index.Valid; + } + + public unsafe bool TryGetModel(Actor actor, out Model model) + { + if (!actor.Valid) + { + model = Model.Null; + return false; + } + + model = DrawObject switch + { + DrawObjectType.Human => actor.Model, + DrawObjectType.Mainhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject : Model.Null, + DrawObjectType.Offhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject : Model.Null, + _ => Model.Null, + }; + return model.IsCharacterBase; + } + + public unsafe bool TryGetTextures(Actor actor, out ReadOnlySpan> textures) + { + if (!TryGetModel(actor, out var model) + || SlotIndex >= model.AsCharacterBase->SlotCount + || model.AsCharacterBase->ColorTableTexturesSpan.Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel) + { + textures = []; + return false; + } + + textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(SlotIndex * MaterialService.MaterialsPerModel, + MaterialService.MaterialsPerModel); + return true; + } + + public unsafe bool TryGetTexture(Actor actor, out Texture* texture) + { + if (!TryGetTextures(actor, out var textures) || MaterialIndex >= MaterialService.MaterialsPerModel) + { + texture = null; + return false; + } + + texture = textures[MaterialIndex].Value; + return texture != null; + } + + public unsafe bool TryGetColorTable(Actor actor, out MtrlFile.ColorTable table) + { + if (TryGetTexture(actor, out var texture)) + return DirectXTextureHelper.TryGetColorTable(texture, out table); + + table = default; + return false; + } + + public unsafe bool TryGetColorRow(Actor actor, out MtrlFile.ColorTable.Row row) + { + if (!TryGetColorTable(actor, out var table)) + { + row = default; + return false; + } + + row = table[RowIndex]; + return true; + } + + public unsafe bool TryGetValue(Actor actor, out Vector3 value) + { + if (!TryGetColorRow(actor, out var row)) + { + value = Vector3.Zero; + return false; + } + + value = DataIndex switch + { + ColorTableIndex.Diffuse => row.Diffuse, + ColorTableIndex.Specular => row.Specular, + ColorTableIndex.SpecularStrength => new Vector3(row.SpecularStrength, 0, 0), + ColorTableIndex.Emissive => row.Emissive, + ColorTableIndex.GlossStrength => new Vector3(row.GlossStrength, 0, 0), + ColorTableIndex.TileSet => new Vector3(row.TileSet), + ColorTableIndex.MaterialRepeat => new Vector3(row.MaterialRepeat, 0), + ColorTableIndex.MaterialSkew => new Vector3(row.MaterialSkew, 0), + _ => new Vector3(float.NaN), + }; + return !float.IsNaN(value.X); + } + + public static MaterialValueIndex FromKey(uint key) + => new(key); + + public static MaterialValueIndex Min(DrawObjectType drawObject = 0, byte slotIndex = 0, byte materialIndex = 0, byte rowIndex = 0, + ColorTableIndex dataIndex = 0) + => new(drawObject, slotIndex, materialIndex, rowIndex, dataIndex); + + public static MaterialValueIndex Max(DrawObjectType drawObject = (DrawObjectType)byte.MaxValue, byte slotIndex = byte.MaxValue, + byte materialIndex = byte.MaxValue, byte rowIndex = byte.MaxValue, + ColorTableIndex dataIndex = (ColorTableIndex)byte.MaxValue) + => new(drawObject, slotIndex, materialIndex, rowIndex, dataIndex); + + public enum DrawObjectType : byte + { + Human, + Mainhand, + Offhand, + }; + + public enum ColorTableIndex : byte + { + Diffuse, + Specular, + SpecularStrength, + Emissive, + GlossStrength, + TileSet, + MaterialRepeat, + MaterialSkew, + } + + public static bool Validate(DrawObjectType type) + => Enum.IsDefined(type); + + public static bool ValidateSlot(byte slotIndex) + => slotIndex < 10; + + public static bool ValidateMaterial(byte materialIndex) + => materialIndex < MaterialService.MaterialsPerModel; + + public static bool ValidateRow(byte rowIndex) + => rowIndex < MtrlFile.ColorTable.NumRows; + + public static bool Validate(ColorTableIndex dataIndex) + => Enum.IsDefined(dataIndex); + + private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex, ColorTableIndex index) + { + var result = (uint)index & 0xFF; + result |= (uint)(rowIndex & 0xFF) << 8; + result |= (uint)(materialIndex & 0xF) << 16; + result |= (uint)(slotIndex & 0xFF) << 20; + result |= (uint)((byte)type & 0xF) << 28; + return result; + } + + private MaterialValueIndex(uint key) + : this((DrawObjectType)((key >> 28) & 0xF), (byte)(key >> 20), (byte)((key >> 16) & 0xF), (byte)(key >> 8), + (ColorTableIndex)(key & 0xFF)) + { } + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, MaterialValueIndex value, JsonSerializer serializer) + => serializer.Serialize(writer, value.Key); + + public override MaterialValueIndex ReadJson(JsonReader reader, Type objectType, MaterialValueIndex existingValue, bool hasExistingValue, + JsonSerializer serializer) + => FromKey(serializer.Deserialize(reader), out var value) ? value : throw new Exception($"Invalid material key {value.Key}."); + } +} + +public static class MaterialExtensions +{ + public static bool TryGetValue(this MaterialValueIndex.ColorTableIndex index, in MtrlFile.ColorTable.Row row, out Vector3 value) + { + value = index switch + { + MaterialValueIndex.ColorTableIndex.Diffuse => row.Diffuse, + MaterialValueIndex.ColorTableIndex.Specular => row.Specular, + MaterialValueIndex.ColorTableIndex.SpecularStrength => new Vector3(row.SpecularStrength, 0, 0), + MaterialValueIndex.ColorTableIndex.Emissive => row.Emissive, + MaterialValueIndex.ColorTableIndex.GlossStrength => new Vector3(row.GlossStrength, 0, 0), + MaterialValueIndex.ColorTableIndex.TileSet => new Vector3(row.TileSet), + MaterialValueIndex.ColorTableIndex.MaterialRepeat => new Vector3(row.MaterialRepeat, 0), + MaterialValueIndex.ColorTableIndex.MaterialSkew => new Vector3(row.MaterialSkew, 0), + _ => new Vector3(float.NaN), + }; + return !float.IsNaN(value.X); + } + + public static bool SetValue(this MaterialValueIndex.ColorTableIndex index, ref MtrlFile.ColorTable.Row row, in Vector3 value) + { + switch (index) + { + case MaterialValueIndex.ColorTableIndex.Diffuse: + if (value == row.Diffuse) + return false; + + row.Diffuse = value; + return true; + + case MaterialValueIndex.ColorTableIndex.Specular: + if (value == row.Specular) + return false; + + row.Specular = value; + return true; + case MaterialValueIndex.ColorTableIndex.SpecularStrength: + if (value.X == row.SpecularStrength) + return false; + + row.SpecularStrength = value.X; + return true; + case MaterialValueIndex.ColorTableIndex.Emissive: + if (value == row.Emissive) + return false; + + row.Emissive = value; + return true; + case MaterialValueIndex.ColorTableIndex.GlossStrength: + if (value.X == row.GlossStrength) + return false; + + row.GlossStrength = value.X; + return true; + case MaterialValueIndex.ColorTableIndex.TileSet: + var @ushort = (ushort)(value.X + 0.5f); + if (@ushort == row.TileSet) + return false; + + row.TileSet = @ushort; + return true; + case MaterialValueIndex.ColorTableIndex.MaterialRepeat: + if (value.X == row.MaterialRepeat.X && value.Y == row.MaterialRepeat.Y) + return false; + + row.MaterialRepeat = new Vector2(value.X, value.Y); + return true; + case MaterialValueIndex.ColorTableIndex.MaterialSkew: + if (value.X == row.MaterialSkew.X && value.Y == row.MaterialSkew.Y) + return false; + + row.MaterialSkew = new Vector2(value.X, value.Y); + return true; + default: return false; + } + } +} diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs new file mode 100644 index 0000000..257dfc8 --- /dev/null +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -0,0 +1,162 @@ +namespace Glamourer.Interop.Material; + +public readonly struct MaterialValueManager +{ + private readonly List<(uint Key, Vector3 Value)> _values = []; + + public MaterialValueManager() + { } + + public bool TryGetValue(MaterialValueIndex index, out Vector3 value) + { + if (_values.Count == 0) + { + value = Vector3.Zero; + return false; + } + + var idx = Search(index.Key); + if (idx >= 0) + { + value = _values[idx].Value; + return true; + } + + value = Vector3.Zero; + return false; + } + + public bool TryAddValue(MaterialValueIndex index, in Vector3 value) + { + var key = index.Key; + var idx = Search(key); + if (idx >= 0) + return false; + + _values.Insert(~idx, (key, value)); + return true; + } + + public bool RemoveValue(MaterialValueIndex index) + { + if (_values.Count == 0) + return false; + + var idx = Search(index.Key); + if (idx < 0) + return false; + + _values.RemoveAt(idx); + return true; + } + + public void AddOrUpdateValue(MaterialValueIndex index, in Vector3 value) + { + var key = index.Key; + var idx = Search(key); + if (idx < 0) + _values.Insert(~idx, (key, value)); + else + _values[idx] = (key, value); + } + + public bool UpdateValue(MaterialValueIndex index, in Vector3 value, out Vector3 oldValue) + { + if (_values.Count == 0) + { + oldValue = Vector3.Zero; + return false; + } + + var key = index.Key; + var idx = Search(key); + if (idx < 0) + { + oldValue = Vector3.Zero; + return false; + } + + oldValue = _values[idx].Value; + _values[idx] = (key, value); + return true; + } + + public int RemoveValues(MaterialValueIndex min, MaterialValueIndex max) + { + var (minIdx, maxIdx) = GetMinMax(CollectionsMarshal.AsSpan(_values), min.Key, max.Key); + if (minIdx < 0) + return 0; + + var count = maxIdx - minIdx; + _values.RemoveRange(minIdx, count); + return count; + } + + public ReadOnlySpan<(uint key, Vector3 Value)> GetValues(MaterialValueIndex min, MaterialValueIndex max) + => Filter(CollectionsMarshal.AsSpan(_values), min, max); + + public static ReadOnlySpan<(uint Key, Vector3 Value)> Filter(ReadOnlySpan<(uint Key, Vector3 Value)> values, MaterialValueIndex min, + MaterialValueIndex max) + { + var (minIdx, maxIdx) = GetMinMax(values, min.Key, max.Key); + return minIdx < 0 ? [] : values[minIdx..(maxIdx - minIdx)]; + } + + /// Obtain the minimum index and maximum index for a minimum and maximum key. + private static (int MinIdx, int MaxIdx) GetMinMax(ReadOnlySpan<(uint Key, Vector3 Value)> values, uint minKey, uint maxKey) + { + // Find the minimum index by binary search. + var idx = values.BinarySearch((minKey, Vector3.Zero), Comparer.Instance); + var minIdx = idx; + + // If the key does not exist, check if it is an invalid range or set it correctly. + if (minIdx < 0) + { + minIdx = ~minIdx; + if (minIdx == values.Length || values[minIdx].Key > maxKey) + return (-1, -1); + + idx = minIdx; + } + else + { + // If it does exist, go upwards until the first key is reached that is actually smaller. + while (minIdx > 0 && values[minIdx - 1].Key >= minKey) + --minIdx; + } + + // Check if the range can be valid. + if (values[minIdx].Key < minKey || values[minIdx].Key > maxKey) + return (-1, -1); + + + // Do pretty much the same but in the other direction with the maximum key. + var maxIdx = values[idx..].BinarySearch((maxKey, Vector3.Zero), Comparer.Instance); + if (maxIdx < 0) + { + maxIdx = ~maxIdx; + return maxIdx > minIdx ? (minIdx, maxIdx - 1) : (-1, -1); + } + + while (maxIdx < values.Length - 1 && values[maxIdx + 1].Key <= maxKey) + ++maxIdx; + + if (values[maxIdx].Key < minKey || values[maxIdx].Key > maxKey) + return (-1, -1); + + return (minIdx, maxIdx); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private int Search(uint key) + => _values.BinarySearch((key, Vector3.Zero), Comparer.Instance); + + private class Comparer : IComparer<(uint Key, Vector3 Value)> + { + public static readonly Comparer Instance = new(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public int Compare((uint Key, Vector3 Value) x, (uint Key, Vector3 Value) y) + => x.Key.CompareTo(y.Key); + } +} diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs new file mode 100644 index 0000000..8d44ee9 --- /dev/null +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -0,0 +1,162 @@ +using Dalamud.Hooking; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using Glamourer.Interop.Penumbra; +using Glamourer.Interop.Structs; +using Glamourer.State; +using OtterGui.Classes; +using OtterGui.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Structs; + +namespace Glamourer.Interop.Material; + +public sealed unsafe class PrepareColorSet + : EventWrapperPtr12Ref34, IHookService +{ + public enum Priority + { + /// + MaterialManager = 0, + } + + public PrepareColorSet(HookManager hooks) + : base("Prepare Color Set ") + => _task = hooks.CreateHook(Name, "40 55 56 41 56 48 83 EC ?? 80 BA", Detour, true); + + private readonly Task> _task; + + public nint Address + => (nint)CharacterBase.MemberFunctionPointers.Destroy; + + public void Enable() + => _task.Result.Enable(); + + public void Disable() + => _task.Result.Disable(); + + public Task Awaiter + => _task; + + public bool Finished + => _task.IsCompletedSuccessfully; + + private delegate Texture* Delegate(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId); + + private Texture* Detour(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId) + { + Glamourer.Log.Excessive($"[{Name}] Triggered with 0x{(nint)characterBase:X} 0x{(nint)material:X} {stainId.Id}."); + var ret = nint.Zero; + Invoke(characterBase, material, ref stainId, ref ret); + if (ret != nint.Zero) + return (Texture*)ret; + + return _task.Result.Original(characterBase, material, stainId); + } +} + +public sealed unsafe class MaterialManager : IRequiredService, IDisposable +{ + private readonly PrepareColorSet _event; + private readonly StateManager _stateManager; + private readonly PenumbraService _penumbra; + private readonly ActorManager _actors; + + private int _lastSlot; + + public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra) + { + _stateManager = stateManager; + _actors = actors; + _penumbra = penumbra; + _event = prepareColorSet; + + _event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager); + } + + public void Dispose() + => _event.Unsubscribe(OnPrepareColorSet); + + private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainId stain, ref nint ret) + { + var actor = _penumbra.GameObjectFromDrawObject(characterBase); + var validType = FindType(characterBase, actor, out var type); + var (slotId, materialId) = FindMaterial(characterBase, material); + Glamourer.Log.Information( + $" Triggered with 0x{(nint)characterBase:X} 0x{(nint)material:X} {stain.Id} --- Actor: 0x{actor.Address:X} Slot: {slotId} Material: {materialId} DrawObject: {type}."); + if (!validType + || slotId == byte.MaxValue + || !actor.Identifier(_actors, out var identifier) + || !_stateManager.TryGetValue(identifier, out var state)) + return; + + var min = MaterialValueIndex.Min(type, slotId, materialId); + var max = MaterialValueIndex.Max(type, slotId, materialId); + var manager = new MaterialValueManager(); + var values = manager.GetValues(min, max); + foreach (var (key, value) in values) + ; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private (byte SlotId, byte MaterialId) FindMaterial(CharacterBase* characterBase, MaterialResourceHandle* material) + { + for (var i = _lastSlot; i < characterBase->SlotCount; ++i) + { + var idx = MaterialService.MaterialsPerModel * i; + for (var j = 0; j < MaterialService.MaterialsPerModel; ++j) + { + var mat = (nint)characterBase->Materials[idx++]; + if (mat != (nint)material) + continue; + + _lastSlot = i; + return ((byte)i, (byte)j); + } + } + + for (var i = 0; i < _lastSlot; ++i) + { + var idx = MaterialService.MaterialsPerModel * i; + for (var j = 0; j < MaterialService.MaterialsPerModel; ++j) + { + var mat = (nint)characterBase->Materials[idx++]; + if (mat != (nint)material) + continue; + + _lastSlot = i; + return ((byte)i, (byte)j); + } + } + + return (byte.MaxValue, byte.MaxValue); + } + + private static bool FindType(CharacterBase* characterBase, Actor actor, out MaterialValueIndex.DrawObjectType type) + { + type = MaterialValueIndex.DrawObjectType.Human; + if (!actor.Valid) + return false; + + if (actor.Model.AsCharacterBase == characterBase) + return true; + + if (!actor.AsObject->IsCharacter()) + return false; + + if (actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject == characterBase) + { + type = MaterialValueIndex.DrawObjectType.Mainhand; + return true; + } + + if (actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject == characterBase) + { + type = MaterialValueIndex.DrawObjectType.Offhand; + return true; + } + + return false; + } +} diff --git a/Glamourer/Interop/Material/SafeTextureHandle.cs b/Glamourer/Interop/Material/SafeTextureHandle.cs new file mode 100644 index 0000000..20e6f65 --- /dev/null +++ b/Glamourer/Interop/Material/SafeTextureHandle.cs @@ -0,0 +1,49 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; + +namespace Glamourer.Interop.Material; + +public unsafe class SafeTextureHandle : SafeHandle +{ + public Texture* Texture + => (Texture*)handle; + + public override bool IsInvalid + => handle == 0; + + public SafeTextureHandle(Texture* handle, bool incRef, bool ownsHandle = true) + : base(0, ownsHandle) + { + if (incRef && !ownsHandle) + throw new ArgumentException("Non-owning SafeTextureHandle with IncRef is unsupported"); + + if (incRef && handle != null) + handle->IncRef(); + SetHandle((nint)handle); + } + + public void Exchange(ref nint ppTexture) + { + lock (this) + { + handle = Interlocked.Exchange(ref ppTexture, handle); + } + } + + public static SafeTextureHandle CreateInvalid() + => new(null, false); + + protected override bool ReleaseHandle() + { + nint handle; + lock (this) + { + handle = this.handle; + this.handle = 0; + } + + if (handle != 0) + ((Texture*)handle)->DecRef(); + + return true; + } +} \ No newline at end of file diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index d9d76b4..dd06e3a 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -20,6 +20,7 @@ using Glamourer.Unlocks; using Microsoft.Extensions.DependencyInjection; using OtterGui.Classes; using OtterGui.Log; +using OtterGui.Raii; using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Data; @@ -47,7 +48,7 @@ public static class ServiceManagerA DalamudServices.AddServices(services, pi); services.AddIServices(typeof(EquipItem).Assembly); services.AddIServices(typeof(Glamourer).Assembly); - services.AddIServices(typeof(EquipFlag).Assembly); + services.AddIServices(typeof(ImRaii).Assembly); services.CreateProvider(); return services; } From beff7adec477841181f7eb130afd46d97382ea2e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 19 Jan 2024 13:24:57 +0100 Subject: [PATCH 215/786] Add Vortice. --- Glamourer/Glamourer.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 2ee5538..87954e9 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -89,6 +89,7 @@ + From 5e5ce4d234e161724223f6675d867ea26e5a0aee Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 26 Jan 2024 17:16:57 +0100 Subject: [PATCH 216/786] Add functions to compute current color table. --- Glamourer/Interop/Material/PrepareColorSet.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 8d44ee9..137228e 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -1,4 +1,5 @@ -using Dalamud.Hooking; +using Dalamud.Game; +using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; @@ -8,6 +9,8 @@ using Glamourer.State; using OtterGui.Classes; using OtterGui.Services; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Files; using Penumbra.GameData.Structs; namespace Glamourer.Interop.Material; @@ -54,6 +57,31 @@ public sealed unsafe class PrepareColorSet return _task.Result.Original(characterBase, material, stainId); } + + public static MtrlFile.ColorTable GetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId) + { + var table = new MtrlFile.ColorTable(); + characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&table)); + return table; + } + + public static bool TryGetColorTable(Model model, byte slotIdx, out MtrlFile.ColorTable table) + { + var table2 = new MtrlFile.ColorTable(); + if (!model.IsCharacterBase || slotIdx < model.AsCharacterBase->SlotCount) + return false; + + var resource = (MaterialResourceHandle*)model.AsCharacterBase->Materials[slotIdx]; + var stain = model.AsCharacterBase->GetModelType() switch + { + CharacterBase.ModelType.Human => model.GetArmor(EquipSlotExtensions.ToEquipSlot(slotIdx)).Stain, + CharacterBase.ModelType.Weapon => (StainId)model.AsWeapon->ModelUnknown, + _ => (StainId)0, + }; + model.AsCharacterBase->ReadStainingTemplate(resource, stain.Id, (Half*)(&table2)); + table = table2; + return true; + } } public sealed unsafe class MaterialManager : IRequiredService, IDisposable @@ -85,6 +113,9 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable var (slotId, materialId) = FindMaterial(characterBase, material); Glamourer.Log.Information( $" Triggered with 0x{(nint)characterBase:X} 0x{(nint)material:X} {stain.Id} --- Actor: 0x{actor.Address:X} Slot: {slotId} Material: {materialId} DrawObject: {type}."); + var table = PrepareColorSet.GetColorTable(characterBase, material, stain); + Glamourer.Log.Information($"{table[15].Diffuse}"); + if (!validType || slotId == byte.MaxValue || !actor.Identifier(_actors, out var identifier) From cb45221be27f22fbeee00fe6d8c2f200fc2549d6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 27 Jan 2024 00:32:48 +0100 Subject: [PATCH 217/786] Things are progressing at a satisfying rate. --- Glamourer/Gui/Materials/MaterialDrawer.cs | 178 ++++++++++++++++++ Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 28 ++- Glamourer/Interop/Material/MaterialService.cs | 69 ++----- .../Interop/Material/MaterialValueManager.cs | 53 ++++-- Glamourer/Interop/Material/PrepareColorSet.cs | 80 +++++--- Glamourer/State/ActorState.cs | 5 +- Glamourer/State/StateManager.cs | 2 + 7 files changed, 312 insertions(+), 103 deletions(-) create mode 100644 Glamourer/Gui/Materials/MaterialDrawer.cs diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs new file mode 100644 index 0000000..1114911 --- /dev/null +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -0,0 +1,178 @@ +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Glamourer.Interop.Material; +using Glamourer.Interop.Structs; +using ImGuiNET; +using OtterGui.Services; +using Penumbra.GameData.Files; + +namespace Glamourer.Gui.Materials; + +public unsafe class MaterialDrawer : IService +{ + private static readonly IReadOnlyList Types = + [ + MaterialValueIndex.DrawObjectType.Human, + MaterialValueIndex.DrawObjectType.Mainhand, + MaterialValueIndex.DrawObjectType.Offhand, + ]; + + public void DrawPanel(Actor actor) + { + if (!actor.IsCharacter) + return; + + foreach (var type in Types) + { + var index = new MaterialValueIndex(type, 0, 0, 0, 0); + if (index.TryGetModel(actor, out var model)) + DrawModelType(model, index); + } + } + + private void DrawModelType(Model model, MaterialValueIndex sourceIndex) + { + using var tree = ImRaii.TreeNode(sourceIndex.DrawObject.ToString()); + if (!tree) + return; + + var names = model.AsCharacterBase->GetModelType() is CharacterBase.ModelType.Human + ? SlotNamesHuman + : SlotNames; + for (byte i = 0; i < model.AsCharacterBase->SlotCount; ++i) + { + var index = sourceIndex with { SlotIndex = i }; + DrawSlot(model, names, index); + } + } + + private void DrawSlot(Model model, IReadOnlyList names, MaterialValueIndex sourceIndex) + { + using var tree = ImRaii.TreeNode(names[sourceIndex.SlotIndex]); + if (!tree) + return; + + for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) + { + var index = sourceIndex with { MaterialIndex = i }; + var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + i; + if (*texture == null) + continue; + + if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table)) + continue; + + DrawMaterial(ref table, texture, index); + } + } + + private void DrawMaterial(ref MtrlFile.ColorTable table, Texture** texture, MaterialValueIndex sourceIndex) + { + using var tree = ImRaii.TreeNode($"Material {sourceIndex.MaterialIndex + 1}"); + if (!tree) + return; + + for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i) + { + var index = sourceIndex with { RowIndex = i }; + ref var row = ref table[i]; + DrawRow(ref table, ref row, texture, index); + } + } + + private void DrawRow(ref MtrlFile.ColorTable table, ref MtrlFile.ColorTable.Row row, Texture** texture, MaterialValueIndex sourceIndex) + { + using var id = ImRaii.PushId(sourceIndex.RowIndex); + var diffuse = row.Diffuse; + var specular = row.Specular; + var emissive = row.Emissive; + var glossStrength = row.GlossStrength; + var specularStrength = row.SpecularStrength; + if (ImGui.ColorEdit3("Diffuse", ref diffuse, ImGuiColorEditFlags.NoInputs)) + { + var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Diffuse }; + row.Diffuse = diffuse; + MaterialService.ReplaceColorTable(texture, table); + } + ImGui.SameLine(); + if (ImGui.ColorEdit3("Specular", ref specular, ImGuiColorEditFlags.NoInputs)) + { + var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Specular }; + row.Specular = specular; + MaterialService.ReplaceColorTable(texture, table); + } + ImGui.SameLine(); + if (ImGui.ColorEdit3("Emissive", ref emissive, ImGuiColorEditFlags.NoInputs)) + { + var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Emissive }; + row.Emissive = emissive; + MaterialService.ReplaceColorTable(texture, table); + } + ImGui.SameLine(); + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + if (ImGui.DragFloat("Gloss", ref glossStrength, 0.1f)) + { + var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.GlossStrength }; + row.GlossStrength = glossStrength; + MaterialService.ReplaceColorTable(texture, table); + } + ImGui.SameLine(); + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + if (ImGui.DragFloat("Specular Strength", ref specularStrength, 0.1f)) + { + var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.SpecularStrength }; + row.SpecularStrength = specularStrength; + MaterialService.ReplaceColorTable(texture, table); + } + } + + private static readonly IReadOnlyList SlotNames = + [ + "Slot 1", + "Slot 2", + "Slot 3", + "Slot 4", + "Slot 5", + "Slot 6", + "Slot 7", + "Slot 8", + "Slot 9", + "Slot 10", + "Slot 11", + "Slot 12", + "Slot 13", + "Slot 14", + "Slot 15", + "Slot 16", + "Slot 17", + "Slot 18", + "Slot 19", + "Slot 20", + ]; + + private static readonly IReadOnlyList SlotNamesHuman = + [ + "Head", + "Body", + "Hands", + "Legs", + "Feet", + "Earrings", + "Neck", + "Wrists", + "Right Finger", + "Left Finger", + "Slot 11", + "Slot 12", + "Slot 13", + "Slot 14", + "Slot 15", + "Slot 16", + "Slot 17", + "Slot 18", + "Slot 19", + "Slot 20", + ]; +} diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 68eb89e..6ec62f5 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -7,6 +7,7 @@ using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; +using Glamourer.Gui.Materials; using Glamourer.Interop; using Glamourer.Interop.Material; using Glamourer.Interop.Structs; @@ -34,7 +35,8 @@ public class ActorPanel( ImportService _importService, ICondition _conditions, DictModelChara _modelChara, - CustomizeParameterDrawer _parameterDrawer) + CustomizeParameterDrawer _parameterDrawer, + MaterialDrawer _materialDrawer) { private ActorIdentifier _identifier; private string _actorName = string.Empty; @@ -121,16 +123,36 @@ public class ActorPanel( RevertButtons(); + + if (ImGui.CollapsingHeader("Material Shit")) + _materialDrawer.DrawPanel(_actor); ImGui.InputInt("Row", ref _rowId); ImGui.InputInt("Material", ref _materialId); ImGui.InputInt("Slot", ref _slotId); ImGuiUtil.GenericEnumCombo("Value", 300, _index, out _index); var index = new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte) _slotId, (byte) _materialId, (byte)_rowId, _index); - index.TryGetValue(_actor, out _test); + index.TryGetValue(_actor, out var current); + _test = current; if (ImGui.ColorPicker3("TestPicker", ref _test) && _actor.Valid) - MaterialService.Test(_actor, index, _test); + _state.Materials.AddOrUpdateValue(index, new MaterialValueState(current, _test, StateSource.Manual)); + if (ImGui.ColorPicker3("TestPicker2", ref _test) && _actor.Valid) + _state.Materials.AddOrUpdateValue(index, new MaterialValueState(current, _test, StateSource.Fixed)); + + foreach (var value in _state.Materials.Values) + { + var id = MaterialValueIndex.FromKey(value.Key); + ImGui.TextUnformatted($"{id.DrawObject} {id.SlotIndex} {id.MaterialIndex} {id.RowIndex} {id.DataIndex} "); + ImGui.SameLine(0, 0); + var game = ImGui.ColorConvertFloat4ToU32(new Vector4(value.Value.Game, 1)); + ImGuiUtil.DrawTextButton(" ", Vector2.Zero, game); + ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X); + var model = ImGui.ColorConvertFloat4ToU32(new Vector4(value.Value.Model, 1)); + ImGuiUtil.DrawTextButton(" ", Vector2.Zero, model); + ImGui.SameLine(0, 0); + ImGui.TextUnformatted($" {value.Value.Source}"); + } using var disabled = ImRaii.Disabled(transformationId != 0); if (_state.ModelData.IsHuman) diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index 91635c5..6b49d3d 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -1,44 +1,12 @@ -using Dalamud.Interface.Utility.Raii; -using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using Glamourer.Interop.Structs; -using ImGuiNET; using Lumina.Data.Files; -using OtterGui; -using OtterGui.Services; -using Penumbra.GameData.Actors; -using Penumbra.GameData.Enums; using static Penumbra.GameData.Files.MtrlFile; using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture; namespace Glamourer.Interop.Material; - -public class MaterialServiceDrawer(ActorManager actors) : IService -{ - private ActorIdentifier _openIdentifier; - private uint _openSlotIndex; - - public unsafe void PopupButton(Actor actor, EquipSlot slot) - { - var slotIndex = slot.ToIndex(); - - var identifier = actor.GetIdentifier(actors); - var buttonActive = actor.Valid - && identifier.IsValid - && actor.Model.IsCharacterBase - && slotIndex < actor.Model.AsCharacterBase->SlotCount - && (actor.Model.AsCharacterBase->HasModelInSlotLoaded & (1 << (int)slotIndex)) != 0; - using var id = ImRaii.PushId((int)slot); - if (ImGuiUtil.DrawDisabledButton("Advanced", Vector2.Zero, "Open advanced window.", !buttonActive)) - { - _openIdentifier = identifier; - _openSlotIndex = slotIndex; - ImGui.OpenPopup($"Popup{slot}"); - } - } -} - public static unsafe class MaterialService { public const int TextureWidth = 4; @@ -49,7 +17,7 @@ public static unsafe class MaterialService /// The original texture that will be replaced with a new one. /// The input color table. /// Success or failure. - public static bool GenerateColorTable(Texture** original, in ColorTable colorTable) + public static bool ReplaceColorTable(Texture** original, in ColorTable colorTable) { if (original == null) return false; @@ -63,7 +31,7 @@ public static unsafe class MaterialService if (texture.IsInvalid) return false; - fixed(ColorTable* ptr = &colorTable) + fixed (ColorTable* ptr = &colorTable) { if (!texture.Texture->InitializeContents(ptr)) return false; @@ -73,6 +41,22 @@ public static unsafe class MaterialService return true; } + public static bool GenerateNewColorTable(in ColorTable colorTable, out Texture* texture) + { + var textureSize = stackalloc int[2]; + textureSize[0] = TextureWidth; + textureSize[1] = TextureHeight; + + texture = Device.Instance()->CreateTexture2D(textureSize, 1, (uint)TexFile.TextureFormat.R16G16B16A16F, + (uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7); + if (texture == null) + return false; + + fixed (ColorTable* ptr = &colorTable) + { + return texture->InitializeContents(ptr); + } + } /// Obtain a pointer to the models pointer to a specific color table texture. /// @@ -112,19 +96,4 @@ public static unsafe class MaterialService return (ColorTable*)material->ColorTable; } - - public static void Test(Actor actor, MaterialValueIndex index, Vector3 value) - { - if (!index.TryGetColorTable(actor, out var table)) - return; - - ref var row = ref table[index.RowIndex]; - if (!index.DataIndex.SetValue(ref row, value)) - return; - - var texture = GetColorTableTexture(index.TryGetModel(actor, out var model) ? model : Model.Null, index.SlotIndex, - index.MaterialIndex); - if (texture != null) - GenerateColorTable(texture, table); - } } diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 257dfc8..d4aec65 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -1,17 +1,27 @@ -namespace Glamourer.Interop.Material; +global using StateMaterialManager = Glamourer.Interop.Material.MaterialValueManager; +global using DesignMaterialManager = Glamourer.Interop.Material.MaterialValueManager; +using Glamourer.State; -public readonly struct MaterialValueManager + +namespace Glamourer.Interop.Material; + +public record struct MaterialValueState(Vector3 Game, Vector3 Model, StateSource Source); + +public readonly struct MaterialValueManager { - private readonly List<(uint Key, Vector3 Value)> _values = []; + private readonly List<(uint Key, T Value)> _values = []; public MaterialValueManager() { } - public bool TryGetValue(MaterialValueIndex index, out Vector3 value) + public void Clear() + => _values.Clear(); + + public bool TryGetValue(MaterialValueIndex index, out T value) { if (_values.Count == 0) { - value = Vector3.Zero; + value = default!; return false; } @@ -22,11 +32,11 @@ public readonly struct MaterialValueManager return true; } - value = Vector3.Zero; + value = default!; return false; } - public bool TryAddValue(MaterialValueIndex index, in Vector3 value) + public bool TryAddValue(MaterialValueIndex index, in T value) { var key = index.Key; var idx = Search(key); @@ -50,7 +60,7 @@ public readonly struct MaterialValueManager return true; } - public void AddOrUpdateValue(MaterialValueIndex index, in Vector3 value) + public void AddOrUpdateValue(MaterialValueIndex index, in T value) { var key = index.Key; var idx = Search(key); @@ -60,11 +70,11 @@ public readonly struct MaterialValueManager _values[idx] = (key, value); } - public bool UpdateValue(MaterialValueIndex index, in Vector3 value, out Vector3 oldValue) + public bool UpdateValue(MaterialValueIndex index, in T value, out T oldValue) { if (_values.Count == 0) { - oldValue = Vector3.Zero; + oldValue = default!; return false; } @@ -72,7 +82,7 @@ public readonly struct MaterialValueManager var idx = Search(key); if (idx < 0) { - oldValue = Vector3.Zero; + oldValue = default!; return false; } @@ -81,6 +91,9 @@ public readonly struct MaterialValueManager return true; } + public IReadOnlyList<(uint Key, T Value)> Values + => _values; + public int RemoveValues(MaterialValueIndex min, MaterialValueIndex max) { var (minIdx, maxIdx) = GetMinMax(CollectionsMarshal.AsSpan(_values), min.Key, max.Key); @@ -92,21 +105,21 @@ public readonly struct MaterialValueManager return count; } - public ReadOnlySpan<(uint key, Vector3 Value)> GetValues(MaterialValueIndex min, MaterialValueIndex max) + public ReadOnlySpan<(uint key, T Value)> GetValues(MaterialValueIndex min, MaterialValueIndex max) => Filter(CollectionsMarshal.AsSpan(_values), min, max); - public static ReadOnlySpan<(uint Key, Vector3 Value)> Filter(ReadOnlySpan<(uint Key, Vector3 Value)> values, MaterialValueIndex min, + public static ReadOnlySpan<(uint Key, T Value)> Filter(ReadOnlySpan<(uint Key, T Value)> values, MaterialValueIndex min, MaterialValueIndex max) { var (minIdx, maxIdx) = GetMinMax(values, min.Key, max.Key); - return minIdx < 0 ? [] : values[minIdx..(maxIdx - minIdx)]; + return minIdx < 0 ? [] : values[minIdx..(maxIdx - minIdx + 1)]; } /// Obtain the minimum index and maximum index for a minimum and maximum key. - private static (int MinIdx, int MaxIdx) GetMinMax(ReadOnlySpan<(uint Key, Vector3 Value)> values, uint minKey, uint maxKey) + private static (int MinIdx, int MaxIdx) GetMinMax(ReadOnlySpan<(uint Key, T Value)> values, uint minKey, uint maxKey) { // Find the minimum index by binary search. - var idx = values.BinarySearch((minKey, Vector3.Zero), Comparer.Instance); + var idx = values.BinarySearch((minKey, default!), Comparer.Instance); var minIdx = idx; // If the key does not exist, check if it is an invalid range or set it correctly. @@ -131,7 +144,7 @@ public readonly struct MaterialValueManager // Do pretty much the same but in the other direction with the maximum key. - var maxIdx = values[idx..].BinarySearch((maxKey, Vector3.Zero), Comparer.Instance); + var maxIdx = values[idx..].BinarySearch((maxKey, default!), Comparer.Instance); if (maxIdx < 0) { maxIdx = ~maxIdx; @@ -149,14 +162,14 @@ public readonly struct MaterialValueManager [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private int Search(uint key) - => _values.BinarySearch((key, Vector3.Zero), Comparer.Instance); + => _values.BinarySearch((key, default!), Comparer.Instance); - private class Comparer : IComparer<(uint Key, Vector3 Value)> + private class Comparer : IComparer<(uint Key, T Value)> { public static readonly Comparer Instance = new(); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public int Compare((uint Key, Vector3 Value) x, (uint Key, Vector3 Value) y) + int IComparer<(uint Key, T Value)>.Compare((uint Key, T Value) x, (uint Key, T Value) y) => x.Key.CompareTo(y.Key); } } diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 137228e..7cfbf6c 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -9,7 +9,6 @@ using Glamourer.State; using OtterGui.Classes; using OtterGui.Services; using Penumbra.GameData.Actors; -using Penumbra.GameData.Enums; using Penumbra.GameData.Files; using Penumbra.GameData.Structs; @@ -58,28 +57,18 @@ public sealed unsafe class PrepareColorSet return _task.Result.Original(characterBase, material, stainId); } - public static MtrlFile.ColorTable GetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId) + public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId, out MtrlFile.ColorTable table) { - var table = new MtrlFile.ColorTable(); - characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&table)); - return table; - } - - public static bool TryGetColorTable(Model model, byte slotIdx, out MtrlFile.ColorTable table) - { - var table2 = new MtrlFile.ColorTable(); - if (!model.IsCharacterBase || slotIdx < model.AsCharacterBase->SlotCount) - return false; - - var resource = (MaterialResourceHandle*)model.AsCharacterBase->Materials[slotIdx]; - var stain = model.AsCharacterBase->GetModelType() switch + if (material->ColorTable == null) { - CharacterBase.ModelType.Human => model.GetArmor(EquipSlotExtensions.ToEquipSlot(slotIdx)).Stain, - CharacterBase.ModelType.Weapon => (StainId)model.AsWeapon->ModelUnknown, - _ => (StainId)0, - }; - model.AsCharacterBase->ReadStainingTemplate(resource, stain.Id, (Half*)(&table2)); - table = table2; + table = default; + return false; + } + + var newTable = *(MtrlFile.ColorTable*)material->ColorTable; + if(stainId.Id != 0) + characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); + table = newTable; return true; } } @@ -111,10 +100,6 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable var actor = _penumbra.GameObjectFromDrawObject(characterBase); var validType = FindType(characterBase, actor, out var type); var (slotId, materialId) = FindMaterial(characterBase, material); - Glamourer.Log.Information( - $" Triggered with 0x{(nint)characterBase:X} 0x{(nint)material:X} {stain.Id} --- Actor: 0x{actor.Address:X} Slot: {slotId} Material: {materialId} DrawObject: {type}."); - var table = PrepareColorSet.GetColorTable(characterBase, material, stain); - Glamourer.Log.Information($"{table[15].Diffuse}"); if (!validType || slotId == byte.MaxValue @@ -122,12 +107,49 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable || !_stateManager.TryGetValue(identifier, out var state)) return; + var min = MaterialValueIndex.Min(type, slotId, materialId); var max = MaterialValueIndex.Max(type, slotId, materialId); - var manager = new MaterialValueManager(); - var values = manager.GetValues(min, max); - foreach (var (key, value) in values) - ; + var values = state.Materials.GetValues(min, max); + if (values.Length == 0) + return; + + if (!PrepareColorSet.TryGetColorTable(characterBase, material, stain, out var baseColorSet)) + return; + + for (var i = 0; i < values.Length; ++i) + { + var idx = MaterialValueIndex.FromKey(values[i].key); + var (oldGame, model, source) = values[i].Value; + ref var row = ref baseColorSet[idx.RowIndex]; + if (!idx.DataIndex.TryGetValue(row, out var newGame)) + continue; + + if (newGame == oldGame) + { + idx.DataIndex.SetValue(ref row, model); + } + else + { + switch (source) + { + case StateSource.Manual: + case StateSource.Pending: + state.Materials.RemoveValue(idx); + --i; + break; + case StateSource.Ipc: + case StateSource.Fixed: + idx.DataIndex.SetValue(ref row, model); + state.Materials.UpdateValue(idx, new MaterialValueState(newGame, model, source), out _); + break; + + } + } + } + + if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture)) + ret = (nint)texture; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index b5126da..5d582f6 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -30,6 +30,9 @@ public class ActorState /// The territory the draw object was created last. public ushort LastTerritory; + /// State for specific material values. + public readonly StateMaterialManager Materials = new(); + /// Whether the State is locked at all. public bool IsLocked => Combination != 0; @@ -84,4 +87,4 @@ public class ActorState LastTerritory = territory; return true; } -} \ No newline at end of file +} diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 2643271..c6658c5 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -240,6 +240,8 @@ public sealed class StateManager( foreach (var flag in CustomizeParameterExtensions.AllFlags) state.Sources[flag] = StateSource.Game; + state.Materials.Clear(); + var actors = ActorData.Invalid; if (source is StateSource.Manual or StateSource.Ipc) actors = Applier.ApplyAll(state, redraw, true); From 962c4e53ad3a114ce6adeb22142d405f3f5ddfed Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 30 Jan 2024 16:04:56 +0100 Subject: [PATCH 218/786] Better handling of application rules. --- Glamourer/Api/GlamourerIpc.Events.cs | 5 +- .../Api/GlamourerIpc.GetCustomization.cs | 3 +- Glamourer/Designs/ApplicationRules.cs | 84 +++++++++++++++++++ Glamourer/Designs/DesignBase.cs | 15 +++- Glamourer/Designs/DesignConverter.cs | 40 ++++----- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 12 +-- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 15 ++-- Glamourer/Services/CommandService.cs | 7 +- 8 files changed, 130 insertions(+), 51 deletions(-) create mode 100644 Glamourer/Designs/ApplicationRules.cs diff --git a/Glamourer/Api/GlamourerIpc.Events.cs b/Glamourer/Api/GlamourerIpc.Events.cs index 8caa495..982e4a1 100644 --- a/Glamourer/Api/GlamourerIpc.Events.cs +++ b/Glamourer/Api/GlamourerIpc.Events.cs @@ -1,4 +1,5 @@ -using Glamourer.Events; +using Glamourer.Designs; +using Glamourer.Events; using Glamourer.Interop.Structs; using Glamourer.State; using Penumbra.Api.Helpers; @@ -18,7 +19,7 @@ public partial class GlamourerIpc 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))); + _stateChangedProvider.Invoke(type, actor.Address, new Lazy(() => _designConverter.ShareBase64(state, ApplicationRules.AllButParameters(state)))); } private void OnGPoseChanged(bool value) diff --git a/Glamourer/Api/GlamourerIpc.GetCustomization.cs b/Glamourer/Api/GlamourerIpc.GetCustomization.cs index bedf514..4f35a2f 100644 --- a/Glamourer/Api/GlamourerIpc.GetCustomization.cs +++ b/Glamourer/Api/GlamourerIpc.GetCustomization.cs @@ -1,5 +1,6 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; +using Glamourer.Designs; using Penumbra.Api.Helpers; using Penumbra.GameData.Actors; @@ -40,6 +41,6 @@ public partial class GlamourerIpc return null; } - return _designConverter.ShareBase64(state); + return _designConverter.ShareBase64(state, ApplicationRules.AllButParameters(state)); } } diff --git a/Glamourer/Designs/ApplicationRules.cs b/Glamourer/Designs/ApplicationRules.cs new file mode 100644 index 0000000..8fdd5d6 --- /dev/null +++ b/Glamourer/Designs/ApplicationRules.cs @@ -0,0 +1,84 @@ +using Glamourer.GameData; +using Glamourer.State; +using ImGuiNET; +using Penumbra.GameData.Enums; + +namespace Glamourer.Designs; + +public readonly struct ApplicationRules( + EquipFlag equip, + CustomizeFlag customize, + CrestFlag crest, + CustomizeParameterFlag parameters, + MetaFlag meta) +{ + public static readonly ApplicationRules All = new(EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, + CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All); + + public static ApplicationRules FromModifiers(ActorState state) + => FromModifiers(state, ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift); + + public static ApplicationRules NpcFromModifiers() + => NpcFromModifiers(ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift); + + public static ApplicationRules AllButParameters(ActorState state) + => new(All.Equip, All.Customize, All.Crest, ComputeParameters(state.ModelData, state.BaseData, All.Parameters), All.Meta); + + public static ApplicationRules NpcFromModifiers(bool ctrl, bool shift) + => new(ctrl || !shift ? EquipFlagExtensions.All : 0, + !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0, + 0, + 0, + ctrl || !shift ? MetaFlag.VisorState : 0); + + public static ApplicationRules FromModifiers(ActorState state, bool ctrl, bool shift) + { + var equip = ctrl || !shift ? EquipFlagExtensions.All : 0; + var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0; + var crest = equip == 0 ? 0 : CrestExtensions.AllRelevant; + var parameters = customize == 0 ? 0 : CustomizeParameterExtensions.All; + var meta = state.ModelData.IsWet() ? MetaFlag.Wetness : 0; + if (equip != 0) + meta |= MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState; + + return new ApplicationRules(equip, customize, crest, ComputeParameters(state.ModelData, state.BaseData, parameters), meta); + } + + public void Apply(DesignBase design) + { + design.ApplyEquip = Equip; + design.ApplyCustomize = Customize; + design.ApplyCrest = Crest; + design.ApplyParameters = Parameters; + design.ApplyMeta = Meta; + } + + public EquipFlag Equip + => equip & EquipFlagExtensions.All; + + public CustomizeFlag Customize + => customize & CustomizeFlagExtensions.AllRelevant; + + public CrestFlag Crest + => crest & CrestExtensions.AllRelevant; + + public CustomizeParameterFlag Parameters + => parameters & CustomizeParameterExtensions.All; + + public MetaFlag Meta + => meta & MetaExtensions.All; + + public static CustomizeParameterFlag ComputeParameters(in DesignData model, in DesignData game, + CustomizeParameterFlag baseFlags = CustomizeParameterExtensions.All) + { + foreach (var flag in baseFlags.Iterate()) + { + var modelValue = model.Parameters[flag]; + var gameValue = game.Parameters[flag]; + if ((modelValue.InternalQuadruple - gameValue.InternalQuadruple).LengthSquared() > 1e-9f) + baseFlags &= ~flag; + } + + return baseFlags; + } +} diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index a533bc4..9699bc2 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -1,7 +1,7 @@ using Dalamud.Interface.Internal.Notifications; using Glamourer.GameData; +using Glamourer.Interop.Material; using Glamourer.Services; -using Glamourer.State; using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.GameData.Enums; @@ -14,7 +14,16 @@ public class DesignBase { public const int FileVersion = 1; - private DesignData _designData = new(); + private DesignData _designData = new(); + private readonly DesignMaterialManager _materials = new(); + + /// For read-only information about custom material color changes. + public IReadOnlyList<(uint, MaterialValueDesign)> Materials + => _materials.Values; + + /// To make it clear something is edited here. + public DesignMaterialManager GetMaterialDataRef() + => _materials; /// For read-only information about the actual design. public ref readonly DesignData DesignData @@ -30,6 +39,7 @@ public class DesignBase CustomizeSet = SetCustomizationSet(customize); } + /// Used when importing .cma or .chara files. internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) { _designData = designData; @@ -42,6 +52,7 @@ public class DesignBase internal DesignBase(DesignBase clone) { _designData = clone._designData; + _materials = clone._materials.Clone(); CustomizeSet = clone.CustomizeSet; ApplyCustomize = clone.ApplyCustomizeRaw; ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 14c85f6..be70672 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -1,5 +1,4 @@ using Glamourer.Designs.Links; -using Glamourer.GameData; using Glamourer.Services; using Glamourer.State; using Glamourer.Utility; @@ -11,7 +10,12 @@ using Penumbra.GameData.Structs; namespace Glamourer.Designs; -public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans, DesignLinkLoader _linkLoader) +public class DesignConverter( + ItemManager _items, + DesignManager _designs, + CustomizeService _customize, + HumanModelList _humans, + DesignLinkLoader _linkLoader) { public const byte Version = 6; @@ -21,9 +25,9 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi public JObject ShareJObject(Design design) => design.JsonSerialize(); - public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags) + public JObject ShareJObject(ActorState state, in ApplicationRules rules) { - var design = Convert(state, equipFlags, customizeFlags, crestFlags, parameterFlags); + var design = Convert(state, rules); return ShareJObject(design); } @@ -33,32 +37,22 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi public string ShareBase64(DesignBase design) => ShareBase64(ShareJObject(design)); - public string ShareBase64(ActorState state) - => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All, CustomizeParameterExtensions.All); + public string ShareBase64(ActorState state, in ApplicationRules rules) + => ShareBase64(state.ModelData, rules); - public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags) - => ShareBase64(state.ModelData, equipFlags, customizeFlags, crestFlags, parameterFlags); - - public string ShareBase64(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags) + public string ShareBase64(in DesignData data, in ApplicationRules rules) { - var design = Convert(data, equipFlags, customizeFlags, crestFlags, parameterFlags); + var design = Convert(data, rules); return ShareBase64(ShareJObject(design)); } - public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags) - => Convert(state.ModelData, equipFlags, customizeFlags, crestFlags, parameterFlags); + public DesignBase Convert(ActorState state, in ApplicationRules rules) + => Convert(state.ModelData, rules); - public DesignBase Convert(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags) + public DesignBase Convert(in DesignData data, in ApplicationRules rules) { var design = _designs.CreateTemporary(); - design.ApplyEquip = equipFlags & EquipFlagExtensions.All; - design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; - design.ApplyCrest = crestFlags & CrestExtensions.All; - design.ApplyParameters = parameterFlags & CustomizeParameterExtensions.All; - design.SetApplyMeta(MetaIndex.HatState, design.DoApplyEquip(EquipSlot.Head)); - design.SetApplyMeta(MetaIndex.VisorState, design.DoApplyEquip(EquipSlot.Head)); - design.SetApplyMeta(MetaIndex.WeaponState, design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand)); - design.SetApplyMeta(MetaIndex.Wetness, true); + rules.Apply(design); design.SetDesignData(_customize, data); return design; } @@ -139,7 +133,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi return ret; } - private static string ShareBase64(JObject jObject) + private static string ShareBase64(JToken jObject) { var json = jObject.ToString(Formatting.None); var compressed = json.Compress(Version); diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 6ec62f5..94f4b56 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -356,8 +356,7 @@ public class ActorPanel( { ImGui.OpenPopup("Save as Design"); _newName = _state!.Identifier.ToName(); - var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); - _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest, applyParameters); + _newDesign = _converter.Convert(_state, ApplicationRules.FromModifiers(_state)); } private void SaveDesignDrawPopup() @@ -392,8 +391,7 @@ public class ActorPanel( { try { - var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); - var text = _converter.ShareBase64(_state!, applyGear, applyCustomize, applyCrest, applyParameters); + var text = _converter.ShareBase64(_state!, ApplicationRules.FromModifiers(_state!)); ImGui.SetClipboardText(text); } catch (Exception ex) @@ -432,9 +430,8 @@ public class ActorPanel( !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) return; - var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(state, _converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), + _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), ApplySettings.Manual); } @@ -450,9 +447,8 @@ public class ActorPanel( !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) return; - var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) - _stateManager.ApplyDesign(state, _converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), + _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), ApplySettings.Manual); } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index bd04a57..77dafa9 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -84,9 +84,8 @@ public class NpcPanel( { try { - var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); var data = ToDesignData(); - var text = _converter.ShareBase64(data, applyGear, applyCustomize, applyCrest, applyParameters); + var text = _converter.ShareBase64(data, ApplicationRules.NpcFromModifiers()); ImGui.SetClipboardText(text); } catch (Exception ex) @@ -100,11 +99,9 @@ public class NpcPanel( private void SaveDesignOpen() { ImGui.OpenPopup("Save as Design"); - _newName = _selector.Selection.Name; - var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); - + _newName = _selector.Selection.Name; var data = ToDesignData(); - _newDesign = _converter.Convert(data, applyGear, applyCustomize, applyCrest, applyParameters); + _newDesign = _converter.Convert(data, ApplicationRules.NpcFromModifiers()); } private void SaveDesignDrawPopup() @@ -198,8 +195,7 @@ public class NpcPanel( if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags(); - var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0); + var design = _converter.Convert(ToDesignData(), ApplicationRules.NpcFromModifiers()); _state.ApplyDesign(state, design, ApplySettings.Manual); } } @@ -217,8 +213,7 @@ public class NpcPanel( if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags(); - var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0); + var design = _converter.Convert(ToDesignData(), ApplicationRules.NpcFromModifiers()); _state.ApplyDesign(state, design, ApplySettings.Manual); } } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index eb0e792..895ad2f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -3,8 +3,6 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Services; using Glamourer.Automation; using Glamourer.Designs; -using Glamourer.Events; -using Glamourer.GameData; using Glamourer.Gui; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -509,7 +507,7 @@ public class CommandService : IDisposable try { - var text = _converter.ShareBase64(state); + var text = _converter.ShareBase64(state, ApplicationRules.AllButParameters(state)); ImGui.SetClipboardText(text); return true; } @@ -548,8 +546,7 @@ public class CommandService : IDisposable && _stateManager.GetOrCreate(identifier, data.Objects[0], out state))) continue; - var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, - CustomizeParameterExtensions.All); + var design = _converter.Convert(state, ApplicationRules.FromModifiers(state)); _designManager.CreateClone(design, split[0], true); return true; } From 502b2439b40dbc3a9fb72fbe8fed00c25bc8aacb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 30 Jan 2024 16:05:07 +0100 Subject: [PATCH 219/786] Fix application rule display for meta. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 93ebad5..a957975 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -293,10 +293,10 @@ public class DesignPanel( var labels = new[] { + "Apply Wetness", "Apply Hat Visibility", "Apply Visor State", "Apply Weapon Visibility", - "Apply Wetness", }; foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(labels)) From eea4de63d54ae735ac5dc172254a1b0db68fa510 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 30 Jan 2024 16:09:30 +0100 Subject: [PATCH 220/786] Fix inverted logic. --- Glamourer/Designs/ApplicationRules.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Designs/ApplicationRules.cs b/Glamourer/Designs/ApplicationRules.cs index 8fdd5d6..1852c7e 100644 --- a/Glamourer/Designs/ApplicationRules.cs +++ b/Glamourer/Designs/ApplicationRules.cs @@ -75,7 +75,7 @@ public readonly struct ApplicationRules( { var modelValue = model.Parameters[flag]; var gameValue = game.Parameters[flag]; - if ((modelValue.InternalQuadruple - gameValue.InternalQuadruple).LengthSquared() > 1e-9f) + if ((modelValue.InternalQuadruple - gameValue.InternalQuadruple).LengthSquared() < 1e-9f) baseFlags &= ~flag; } From 994b7bfb6c08196a6e5470eb50b223ebb4dd4688 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 30 Jan 2024 16:09:58 +0100 Subject: [PATCH 221/786] Add clone function to MaterialValueManager. --- Glamourer/Interop/Material/MaterialValueManager.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index d4aec65..5dd3001 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -1,10 +1,11 @@ global using StateMaterialManager = Glamourer.Interop.Material.MaterialValueManager; -global using DesignMaterialManager = Glamourer.Interop.Material.MaterialValueManager; +global using DesignMaterialManager = Glamourer.Interop.Material.MaterialValueManager; using Glamourer.State; namespace Glamourer.Interop.Material; +public record struct MaterialValueDesign(Vector3 Value, bool Enabled); public record struct MaterialValueState(Vector3 Game, Vector3 Model, StateSource Source); public readonly struct MaterialValueManager @@ -17,6 +18,13 @@ public readonly struct MaterialValueManager public void Clear() => _values.Clear(); + public MaterialValueManager Clone() + { + var ret = new MaterialValueManager(); + ret._values.AddRange(_values); + return ret; + } + public bool TryGetValue(MaterialValueIndex index, out T value) { if (_values.Count == 0) From 818bf710329b9fa1a10e126e4ae7ffad8979d230 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 30 Jan 2024 17:51:52 +0100 Subject: [PATCH 222/786] Add IpcManual state. --- Glamourer/Api/GlamourerIpc.Apply.cs | 49 ++++++++++++++++--- Glamourer/Api/GlamourerIpc.Revert.cs | 3 +- Glamourer/Api/GlamourerIpc.Set.cs | 24 ++++++--- Glamourer/Api/GlamourerIpc.cs | 31 +++++++++--- Glamourer/Configuration.cs | 1 + .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 1 - Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 46 +++++++++++++++++ .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 9 +++- Glamourer/Gui/Tabs/SettingsTab.cs | 5 ++ Glamourer/Interop/Material/PrepareColorSet.cs | 5 +- Glamourer/State/StateApplier.cs | 10 ++-- Glamourer/State/StateEditor.cs | 46 +++++++++-------- Glamourer/State/StateListener.cs | 49 ++++++++++--------- Glamourer/State/StateManager.cs | 28 ++--------- Glamourer/State/StateSource.cs | 44 +++++++++++++++-- 15 files changed, 242 insertions(+), 109 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs index d155cf2..f09d491 100644 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ b/Glamourer/Api/GlamourerIpc.Apply.cs @@ -11,7 +11,9 @@ 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"; @@ -24,11 +26,15 @@ public partial class GlamourerIpc public const string LabelApplyOnlyCustomizationLock = "Glamourer.ApplyOnlyCustomizationLock"; public const string LabelApplyOnlyCustomizationToCharacterLock = "Glamourer.ApplyOnlyCustomizationToCharacterLock"; - public const string LabelApplyByGuid = "Glamourer.ApplyByGuid"; - public const string LabelApplyByGuidToCharacter = "Glamourer.ApplyByGuidToCharacter"; + 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; @@ -42,14 +48,22 @@ public partial class GlamourerIpc 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); @@ -65,15 +79,27 @@ public partial class GlamourerIpc 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 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); @@ -107,12 +133,18 @@ public partial class GlamourerIpc public void ApplyByGuid(Guid identifier, string characterName) - => ApplyDesignByGuid(identifier, FindActors(characterName), 0); + => 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); + => ApplyDesignByGuid(identifier, FindActors(character), 0, false); - private void ApplyDesign(DesignBase? design, IEnumerable actors, byte version, uint lockCode) + 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; @@ -130,12 +162,13 @@ public partial class GlamourerIpc if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode)) { - _stateManager.ApplyDesign(state, design, new ApplySettings(Source:StateSource.Ipc, Key:lockCode)); + _stateManager.ApplyDesign(state, design, + new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: lockCode)); state.Lock(lockCode); } } } - private void ApplyDesignByGuid(Guid identifier, IEnumerable actors, uint lockCode) - => ApplyDesign(_designManager.Designs.ByIdentifier(identifier), actors, DesignConverter.Version, 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.Revert.cs b/Glamourer/Api/GlamourerIpc.Revert.cs index 44a03aa..c5ca3b3 100644 --- a/Glamourer/Api/GlamourerIpc.Revert.cs +++ b/Glamourer/Api/GlamourerIpc.Revert.cs @@ -1,6 +1,5 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; -using Glamourer.Events; using Glamourer.State; using Penumbra.Api.Helpers; using Penumbra.GameData.Actors; @@ -83,7 +82,7 @@ public partial class GlamourerIpc foreach (var id in actors) { if (_stateManager.TryGetValue(id, out var state)) - _stateManager.ResetState(state, StateSource.Ipc, lockCode); + _stateManager.ResetState(state, StateSource.IpcFixed, lockCode); } } diff --git a/Glamourer/Api/GlamourerIpc.Set.cs b/Glamourer/Api/GlamourerIpc.Set.cs index db5941f..93428da 100644 --- a/Glamourer/Api/GlamourerIpc.Set.cs +++ b/Glamourer/Api/GlamourerIpc.Set.cs @@ -20,20 +20,30 @@ public partial class GlamourerIpc ItemInvalid, } - public const string LabelSetItem = "Glamourer.SetItem"; - public const string LabelSetItemByActorName = "Glamourer.SetItemByActorName"; + 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); - private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key) + 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); @@ -57,11 +67,12 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key:key)); + _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) + private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key, bool once) { if (itemId.Id == 0) itemId = ItemManager.NothingId(slot); @@ -84,7 +95,8 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key: key)); + _stateManager.ChangeEquip(state, slot, item, stainId, + new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key)); found = true; } diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 36d248b..550b7e2 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -46,9 +46,11 @@ public sealed partial class GlamourerIpc : IDisposable _getAllCustomizationFromCharacterProvider = new FuncProvider(pi, LabelGetAllCustomizationFromCharacter, GetAllCustomizationFromCharacter); - _applyAllProvider = new ActionProvider(pi, LabelApplyAll, ApplyAll); - _applyAllToCharacterProvider = new ActionProvider(pi, LabelApplyAllToCharacter, ApplyAllToCharacter); - _applyOnlyEquipmentProvider = new ActionProvider(pi, LabelApplyOnlyEquipment, ApplyOnlyEquipment); + _applyAllProvider = new ActionProvider(pi, LabelApplyAll, ApplyAll); + _applyAllOnceProvider = new ActionProvider(pi, LabelApplyAll, ApplyAllOnce); + _applyAllToCharacterProvider = new ActionProvider(pi, LabelApplyAllToCharacter, ApplyAllToCharacter); + _applyAllOnceToCharacterProvider = new ActionProvider(pi, LabelApplyAllToCharacter, ApplyAllOnceToCharacter); + _applyOnlyEquipmentProvider = new ActionProvider(pi, LabelApplyOnlyEquipment, ApplyOnlyEquipment); _applyOnlyEquipmentToCharacterProvider = new ActionProvider(pi, LabelApplyOnlyEquipmentToCharacter, ApplyOnlyEquipmentToCharacter); _applyOnlyCustomizationProvider = new ActionProvider(pi, LabelApplyOnlyCustomization, ApplyOnlyCustomization); @@ -66,8 +68,11 @@ public sealed partial class GlamourerIpc : IDisposable _applyOnlyCustomizationToCharacterProviderLock = new ActionProvider(pi, LabelApplyOnlyCustomizationToCharacterLock, ApplyOnlyCustomizationToCharacterLock); - _applyByGuidProvider = new ActionProvider(pi, LabelApplyByGuid, ApplyByGuid); + _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); @@ -83,9 +88,14 @@ public sealed partial class GlamourerIpc : IDisposable _gPoseChangedProvider = new EventProvider(pi, LabelGPoseChanged); _setItemProvider = new FuncProvider(pi, LabelSetItem, - (idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key)); - _setItemByActorNameProvider = new FuncProvider(pi, LabelSetItemByActorName, - (name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key)); + (idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, false)); + _setItemOnceProvider = new FuncProvider(pi, LabelSetItem, + (idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, true)); + + _setItemByActorNameProvider = new FuncProvider(pi, LabelSetItemOnceByActorName, + (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); @@ -102,7 +112,9 @@ public sealed partial class GlamourerIpc : IDisposable _getAllCustomizationFromCharacterProvider.Dispose(); _applyAllProvider.Dispose(); + _applyAllOnceProvider.Dispose(); _applyAllToCharacterProvider.Dispose(); + _applyAllOnceToCharacterProvider.Dispose(); _applyOnlyEquipmentProvider.Dispose(); _applyOnlyEquipmentToCharacterProvider.Dispose(); _applyOnlyCustomizationProvider.Dispose(); @@ -113,8 +125,11 @@ public sealed partial class GlamourerIpc : IDisposable _applyOnlyEquipmentToCharacterProviderLock.Dispose(); _applyOnlyCustomizationProviderLock.Dispose(); _applyOnlyCustomizationToCharacterProviderLock.Dispose(); + _applyByGuidProvider.Dispose(); + _applyByGuidOnceProvider.Dispose(); _applyByGuidToCharacterProvider.Dispose(); + _applyByGuidOnceToCharacterProvider.Dispose(); _revertProvider.Dispose(); _revertCharacterProvider.Dispose(); @@ -133,7 +148,9 @@ public sealed partial class GlamourerIpc : IDisposable _getDesignListProvider.Dispose(); _setItemProvider.Dispose(); + _setItemOnceProvider.Dispose(); _setItemByActorNameProvider.Dispose(); + _setItemOnceByActorNameProvider.Dispose(); } private IEnumerable FindActors(string actorName) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 78d1a3b..29b0646 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -42,6 +42,7 @@ public class Configuration : IPluginConfiguration, ISavable 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 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/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 7b3f594..00df06b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -1,7 +1,6 @@ using Dalamud.Interface; using Glamourer.GameData; using Glamourer.Designs; -using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Structs; using Glamourer.State; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index 02348a2..d447f9f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -28,7 +28,9 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag 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 unsafe void Draw() { @@ -77,12 +79,23 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag 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.Objects[_gameObjectIndex] as Character); + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnceToCharacter); + ImGui.TableNextColumn(); + if (ImGui.Button("Apply Once##AllCharacter")) + GlamourerIpc.ApplyAllOnceToCharacterSubscriber(_pluginInterface) + .Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character); + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment); ImGui.TableNextColumn(); if (ImGui.Button("Apply##EquipName")) @@ -111,12 +124,23 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag 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.Objects[_gameObjectIndex] as Character); + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidOnceToCharacter); + ImGui.TableNextColumn(); + if (ImGui.Button("Apply Once##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2Once)) + GlamourerIpc.ApplyByGuidOnceToCharacterSubscriber(_pluginInterface) + .Invoke(guid2Once, _objectManager.Objects[_gameObjectIndex] as Character); + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); ImGui.TableNextColumn(); @@ -149,6 +173,17 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TextUnformatted(_setItemEc.ToString()); } + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemOnce); + ImGui.TableNextColumn(); + if (ImGui.Button("Set Once##SetItem")) + _setItemOnceEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemOnceSubscriber(_pluginInterface) + .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, (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")) @@ -159,6 +194,17 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag 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() diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 00a23cc..ecbf0e7 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -95,9 +95,13 @@ public class DesignDetailTab Glamourer.Messager.NotificationMessage(ex, $"Could not open file {fileName}.", $"Could not open file {fileName}", NotificationType.Warning); } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + ImGui.SetClipboardText(identifier); } - ImGuiUtil.HoverTooltip($"Open the file\n\t{fileName}\ncontaining this design in the .json-editor of your choice."); + ImGuiUtil.HoverTooltip( + $"Open the file\n\t{fileName}\ncontaining this design in the .json-editor of your choice.\n\nRight-Click to copy identifier to clipboard."); ImGuiUtil.DrawFrameColumn("Full Selector Path"); ImGui.TableNextColumn(); @@ -131,9 +135,10 @@ public class DesignDetailTab colorName = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection; _manager.ChangeColor(_selector.Selected!, colorName); } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) _manager.ChangeColor(_selector.Selected!, string.Empty); - + if (_colors.TryGetValue(_selector.Selected!.Color, out var currentColor)) { ImGui.SameLine(); diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index e8435ce..ef75245 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -89,6 +89,11 @@ public class SettingsTab( Checkbox("Enable Advanced Customization Options", "Enable the display and editing of advanced customization options like arbitrary colors.", config.UseAdvancedParameters, paletteChecker.SetAdvancedParameters); + Checkbox("Always Apply Associated Mods", + "Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n" + + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n" + + "If you enable this setting, you are aware that any resulting misconfiguration is your own fault.", + config.AlwaysApplyAssociatedMods, v => config.AlwaysApplyAssociatedMods = v); PaletteImportButton(); ImGui.NewLine(); } diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 7cfbf6c..4230fc1 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -131,19 +131,16 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable } else { - switch (source) + switch (source.Base()) { case StateSource.Manual: - case StateSource.Pending: state.Materials.RemoveValue(idx); --i; break; - case StateSource.Ipc: case StateSource.Fixed: idx.DataIndex.SetValue(ref row, model); state.Materials.UpdateValue(idx, new MaterialValueState(newGame, model, source), out _); break; - } } } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 7222a4b..e9b74fd 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -118,8 +118,7 @@ public class StateApplier( // If the source is not IPC we do not want to apply restrictions. var data = GetData(state); if (apply) - ChangeArmor(data, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc, - state.ModelData.IsHatVisible()); + ChangeArmor(data, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), state.ModelData.IsHatVisible()); return data; } @@ -267,7 +266,7 @@ public class StateApplier( actor.Model.ApplyParameterData(flags, values); } - /// + /// public ActorData ChangeParameters(ActorState state, CustomizeParameterFlag flags, bool apply) { var data = GetData(state); @@ -294,10 +293,7 @@ public class StateApplier( { ChangeCustomize(actors, state.ModelData.Customize); foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc, - state.ModelData.IsHatVisible()); - } + ChangeArmor(actors, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), state.ModelData.IsHatVisible()); var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 2b1337e..48b74ab 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -31,7 +31,7 @@ public class StateEditor( if (!Editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key)) return; - var actors = Applier.ForceRedraw(state, source is StateSource.Manual or StateSource.Ipc); + var actors = Applier.ForceRedraw(state, source.RequiresChange()); Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId)); @@ -44,7 +44,7 @@ public class StateEditor( if (!Editor.ChangeCustomize(state, idx, value, settings.Source, out var old, settings.Key)) return; - var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc); + var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChanged.Type.Customize, settings.Source, state, actors, (old, value, idx)); @@ -57,7 +57,7 @@ public class StateEditor( if (!Editor.ChangeHumanCustomize(state, customizeInput, apply, _ => settings.Source, out var old, out var applied, settings.Key)) return; - var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc); + var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChanged.Type.EntireCustomize, settings.Source, state, actors, (old, applied)); @@ -72,8 +72,8 @@ public class StateEditor( var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon; var actors = type is StateChanged.Type.Equip - ? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc) - : Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc, + ? Applier.ChangeArmor(state, slot, settings.Source.RequiresChange()) + : Applier.ChangeWeapon(state, slot, settings.Source.RequiresChange(), item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); if (slot is EquipSlot.MainHand) @@ -105,8 +105,8 @@ public class StateEditor( var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon; var actors = type is StateChanged.Type.Equip - ? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc) - : Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc, + ? Applier.ChangeArmor(state, slot, settings.Source.RequiresChange()) + : Applier.ChangeWeapon(state, slot, settings.Source.RequiresChange(), item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); if (slot is EquipSlot.MainHand) @@ -125,7 +125,7 @@ public class StateEditor( if (!Editor.ChangeStain(state, slot, stain, settings.Source, out var old, settings.Key)) return; - var actors = Applier.ChangeStain(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc); + var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChanged.Type.Stain, settings.Source, state, actors, (old, stain, slot)); @@ -138,7 +138,7 @@ public class StateEditor( if (!Editor.ChangeCrest(state, slot, crest, settings.Source, out var old, settings.Key)) return; - var actors = Applier.ChangeCrests(state, settings.Source is StateSource.Manual or StateSource.Ipc); + var actors = Applier.ChangeCrests(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChanged.Type.Crest, settings.Source, state, actors, (old, crest, slot)); @@ -158,7 +158,7 @@ public class StateEditor( return; var @new = state.ModelData.Parameters[flag]; - var actors = Applier.ChangeParameters(state, flag, settings.Source is StateSource.Manual or StateSource.Ipc); + var actors = Applier.ChangeParameters(state, flag, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag)); @@ -171,7 +171,7 @@ public class StateEditor( if (!Editor.ChangeMetaState(state, index, value, settings.Source, out var old, settings.Key)) return; - var actors = Applier.ChangeMetaState(state, index, settings.Source is StateSource.Manual or StateSource.Ipc); + var actors = Applier.ChangeMetaState(state, index, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChanged.Type.Other, settings.Source, state, actors, (old, value, MetaIndex.HatState)); @@ -191,7 +191,7 @@ public class StateEditor( { foreach (var slot in CrestExtensions.AllRelevantSet.Where(mergedDesign.Design.DoApplyCrest)) { - if (!settings.RespectManual || state.Sources[slot] is not StateSource.Manual) + if (!settings.RespectManual || !state.Sources[slot].IsManual()) Editor.ChangeCrest(state, slot, mergedDesign.Design.DesignData.Crest(slot), Source(slot), out _, settings.Key); } @@ -201,7 +201,7 @@ public class StateEditor( customizeFlags |= CustomizeFlag.Race; Func applyWhich = settings.RespectManual - ? i => customizeFlags.HasFlag(i.ToFlag()) && state.Sources[i] is not StateSource.Manual + ? i => customizeFlags.HasFlag(i.ToFlag()) && !state.Sources[i].IsManual() : i => customizeFlags.HasFlag(i.ToFlag()); if (Editor.ChangeHumanCustomize(state, mergedDesign.Design.DesignData.Customize, applyWhich, i => Source(i), out _, out var changed, @@ -210,12 +210,10 @@ public class StateEditor( foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate()) { - if (settings.RespectManual && state.Sources[parameter] is StateSource.Manual or StateSource.Pending) + if (settings.RespectManual && state.Sources[parameter].IsManual()) continue; - var source = Source(parameter); - if (source is StateSource.Manual) - source = StateSource.Pending; + var source = Source(parameter).SetPending(); Editor.ChangeParameter(state, parameter, mergedDesign.Design.DesignData.Parameters[parameter], source, out _, settings.Key); } @@ -228,12 +226,12 @@ public class StateEditor( foreach (var slot in EquipSlotExtensions.EqdpSlots) { if (mergedDesign.Design.DoApplyEquip(slot)) - if (!settings.RespectManual || state.Sources[slot, false] is not StateSource.Manual) + if (!settings.RespectManual || !state.Sources[slot, false].IsManual()) Editor.ChangeItem(state, slot, mergedDesign.Design.DesignData.Item(slot), Source(slot.ToState()), out _, settings.Key); if (mergedDesign.Design.DoApplyStain(slot)) - if (!settings.RespectManual || state.Sources[slot, true] is not StateSource.Manual) + if (!settings.RespectManual || !state.Sources[slot, true].IsManual()) Editor.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot), Source(slot.ToState(true)), out _, settings.Key); } @@ -241,14 +239,14 @@ public class StateEditor( foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots) { if (mergedDesign.Design.DoApplyStain(weaponSlot)) - if (!settings.RespectManual || state.Sources[weaponSlot, true] is not StateSource.Manual) + if (!settings.RespectManual || !state.Sources[weaponSlot, true].IsManual()) Editor.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot), Source(weaponSlot.ToState(true)), out _, settings.Key); if (!mergedDesign.Design.DoApplyEquip(weaponSlot)) continue; - if (settings.RespectManual && state.Sources[weaponSlot, false] is StateSource.Manual) + if (settings.RespectManual && !state.Sources[weaponSlot, false].IsManual()) continue; var currentType = state.ModelData.Item(weaponSlot).Type; @@ -268,12 +266,12 @@ public class StateEditor( foreach (var meta in MetaExtensions.AllRelevant) { - if (!settings.RespectManual || state.Sources[meta] is not StateSource.Manual) + if (!settings.RespectManual || !state.Sources[meta].IsManual()) Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key); } } - var actors = settings.Source is StateSource.Manual or StateSource.Ipc + var actors = settings.Source.RequiresChange() ? Applier.ApplyAll(state, requiresRedraw, false) : ActorData.Invalid; @@ -311,7 +309,7 @@ public class StateEditor( /// Apply offhand item and potentially gauntlets if configured. private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, ApplySettings settings) { - if (!Config.ChangeEntireItem || settings.Source is not StateSource.Manual) + if (!Config.ChangeEntireItem || !settings.Source.IsManual()) return; var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 1833660..23e0f3c 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -171,7 +171,7 @@ public class StateListener : IDisposable var set = _customizations.Manager.GetSet(model.Clan, model.Gender); foreach (var index in CustomizationExtensions.AllBasic) { - if (state.Sources[index] is not StateSource.Fixed) + if (!state.Sources[index].IsFixed()) { var newValue = customize[index]; var oldValue = model[index]; @@ -214,7 +214,7 @@ public class StateListener : IDisposable && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor); - locked = state.Sources[slot, false] is StateSource.Ipc; + locked = state.Sources[slot, false] is StateSource.IpcFixed; } _funModule.ApplyFunToSlot(actor, ref armor, slot); @@ -241,7 +241,7 @@ public class StateListener : IDisposable continue; var changed = changedItem.Weapon(stain); - if (current.Value == changed.Value && state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) + if (current.Value == changed.Value && !state.Sources[slot, false].IsFixed()) { _manager.ChangeItem(state, slot, currentItem, ApplySettings.Game); _manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game); @@ -252,7 +252,7 @@ public class StateListener : IDisposable _applier.ChangeWeapon(objects, slot, currentItem, stain); break; default: - _applier.ChangeArmor(objects, slot, current.ToArmor(), state.Sources[slot, false] is not StateSource.Ipc, + _applier.ChangeArmor(objects, slot, current.ToArmor(), !state.Sources[slot, false].IsFixed(), state.ModelData.IsHatVisible()); break; } @@ -278,20 +278,19 @@ public class StateListener : IDisposable || !_manager.TryGetValue(identifier, out var state)) return; - ref var actorWeapon = ref weapon; - var baseType = state.BaseData.Item(slot).Type; - var apply = false; - switch (UpdateBaseData(actor, state, slot, actorWeapon)) + var baseType = state.BaseData.Item(slot).Type; + var apply = false; + switch (UpdateBaseData(actor, state, slot, weapon)) { // Do nothing. But this usually can not happen because the hooked function also writes to game objects later. case UpdateState.Transformed: break; case UpdateState.Change: - if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) + if (!state.Sources[slot, false].IsFixed()) _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game); else apply = true; - if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc) + if (!state.Sources[slot, true].IsFixed()) _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); else apply = true; @@ -306,9 +305,9 @@ public class StateListener : IDisposable // Only allow overwriting identical weapons var newWeapon = state.ModelData.Weapon(slot); if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene) - actorWeapon = newWeapon; - else if (actorWeapon.Skeleton.Id != 0) - actorWeapon = actorWeapon.With(newWeapon.Stain); + weapon = newWeapon; + else if (weapon.Skeleton.Id != 0) + weapon = weapon.With(newWeapon.Stain); } // Fist Weapon Offhand hack. @@ -385,12 +384,12 @@ public class StateListener : IDisposable // Update model state if not on fixed design. case UpdateState.Change: var apply = false; - if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) + if (!state.Sources[slot, false].IsFixed()) _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game); else apply = true; - if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc) + if (!state.Sources[slot, true].IsFixed()) _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); else apply = true; @@ -419,7 +418,7 @@ public class StateListener : IDisposable switch (UpdateBaseCrest(actor, state, slot, value)) { case UpdateState.Change: - if (state.Sources[slot] is not StateSource.Fixed and not StateSource.Ipc) + if (!state.Sources[slot].IsFixed()) _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), ApplySettings.Game); else value = state.ModelData.Crest(slot); @@ -565,7 +564,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed or StateSource.Ipc) + if (!state.Sources[MetaIndex.VisorState].IsFixed()) value = state.ModelData.IsVisorToggled(); else _manager.ChangeMetaState(state, MetaIndex.VisorState, value, ApplySettings.Game); @@ -598,7 +597,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state.Sources[MetaIndex.HatState] is StateSource.Fixed or StateSource.Ipc) + if (!state.Sources[MetaIndex.HatState].IsFixed()) value = state.ModelData.IsHatVisible(); else _manager.ChangeMetaState(state, MetaIndex.HatState, value, ApplySettings.Game); @@ -631,7 +630,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed or StateSource.Ipc) + if (!state.Sources[MetaIndex.WeaponState].IsFixed()) value = state.ModelData.IsWeaponVisible(); else _manager.ChangeMetaState(state, MetaIndex.WeaponState, value, ApplySettings.Game); @@ -700,8 +699,8 @@ public class StateListener : IDisposable return; var data = new ActorData(gameObject, _creatingIdentifier.ToName()); - _applier.ChangeMetaState(data, MetaIndex.HatState, _creatingState.ModelData.IsHatVisible()); - _applier.ChangeMetaState(data, MetaIndex.Wetness, _creatingState.ModelData.IsWet()); + _applier.ChangeMetaState(data, MetaIndex.HatState, _creatingState.ModelData.IsHatVisible()); + _applier.ChangeMetaState(data, MetaIndex.Wetness, _creatingState.ModelData.IsWet()); _applier.ChangeMetaState(data, MetaIndex.WeaponState, _creatingState.ModelData.IsWeaponVisible()); ApplyParameters(_creatingState, drawObject); @@ -745,12 +744,18 @@ public class StateListener : IDisposable else if (_config.UseAdvancedParameters) model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; + case StateSource.IpcManual: + if (state.BaseData.Parameters.Set(flag, newValue)) + _manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game); + else + model.ApplySingleParameterData(flag, state.ModelData.Parameters); + break; case StateSource.Fixed: state.BaseData.Parameters.Set(flag, newValue); if (_config.UseAdvancedParameters) model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; - case StateSource.Ipc: + case StateSource.IpcFixed: state.BaseData.Parameters.Set(flag, newValue); model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index c6658c5..e6ed657 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -243,7 +243,7 @@ public sealed class StateManager( state.Materials.Clear(); var actors = ActorData.Invalid; - if (source is StateSource.Manual or StateSource.Ipc) + if (source is not StateSource.Game) actors = Applier.ApplyAll(state, redraw, true); Glamourer.Log.Verbose( @@ -262,7 +262,7 @@ public sealed class StateManager( state.Sources[flag] = StateSource.Game; var actors = ActorData.Invalid; - if (source is StateSource.Manual or StateSource.Ipc) + if (source is not StateSource.Game) actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); Glamourer.Log.Verbose( $"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); @@ -316,28 +316,10 @@ public sealed class StateManager( } } - if (state.Sources[MetaIndex.HatState] is StateSource.Fixed) + foreach (var meta in MetaExtensions.AllRelevant.Where(f => state.Sources[f] is StateSource.Fixed)) { - state.Sources[MetaIndex.HatState] = StateSource.Game; - state.ModelData.SetHatVisible(state.BaseData.IsHatVisible()); - } - - if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed) - { - state.Sources[MetaIndex.VisorState] = StateSource.Game; - state.ModelData.SetVisor(state.BaseData.IsVisorToggled()); - } - - if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed) - { - state.Sources[MetaIndex.WeaponState] = StateSource.Game; - state.ModelData.SetWeaponVisible(state.BaseData.IsWeaponVisible()); - } - - if (state.Sources[MetaIndex.Wetness] is StateSource.Fixed) - { - state.Sources[MetaIndex.Wetness] = StateSource.Game; - state.ModelData.SetIsWet(state.BaseData.IsWet()); + state.Sources[meta] = StateSource.Game; + state.ModelData.SetMeta(meta, state.BaseData.GetMeta(meta)); } } diff --git a/Glamourer/State/StateSource.cs b/Glamourer/State/StateSource.cs index 1d66a46..d489814 100644 --- a/Glamourer/State/StateSource.cs +++ b/Glamourer/State/StateSource.cs @@ -7,12 +7,48 @@ public enum StateSource : byte Game, Manual, Fixed, - Ipc, + IpcFixed, + IpcManual, // Only used for CustomizeParameters. Pending, } +public static class StateSourceExtensions +{ + public static StateSource Base(this StateSource source) + => source switch + { + StateSource.Manual or StateSource.IpcManual or StateSource.Pending => StateSource.Manual, + StateSource.Fixed or StateSource.IpcFixed => StateSource.Fixed, + _ => StateSource.Game, + }; + + public static bool IsGame(this StateSource source) + => source.Base() is StateSource.Game; + + public static bool IsManual(this StateSource source) + => source.Base() is StateSource.Manual; + + public static bool IsFixed(this StateSource source) + => source.Base() is StateSource.Fixed; + + public static StateSource SetPending(this StateSource source) + => source is StateSource.Manual ? StateSource.Pending : source; + + public static bool RequiresChange(this StateSource source) + => source switch + { + StateSource.Manual => true, + StateSource.IpcFixed => true, + StateSource.IpcManual => true, + _ => false, + }; + + public static bool IsIpc(this StateSource source) + => source is StateSource.IpcManual or StateSource.IpcFixed; +} + public unsafe struct StateSources { public const int Size = (StateIndex.Size + 1) / 2; @@ -59,14 +95,16 @@ public unsafe struct StateSources case (byte)StateSource.Game | ((byte)StateSource.Fixed << 4): case (byte)StateSource.Manual | ((byte)StateSource.Fixed << 4): - case (byte)StateSource.Ipc | ((byte)StateSource.Fixed << 4): + case (byte)StateSource.IpcFixed | ((byte)StateSource.Fixed << 4): case (byte)StateSource.Pending | ((byte)StateSource.Fixed << 4): + case (byte)StateSource.IpcManual | ((byte)StateSource.Fixed << 4): _data[i] = (byte)((value & 0x0F) | ((byte)StateSource.Manual << 4)); break; case (byte)StateSource.Fixed: case ((byte)StateSource.Manual << 4) | (byte)StateSource.Fixed: - case ((byte)StateSource.Ipc << 4) | (byte)StateSource.Fixed: + case ((byte)StateSource.IpcFixed << 4) | (byte)StateSource.Fixed: case ((byte)StateSource.Pending << 4) | (byte)StateSource.Fixed: + case ((byte)StateSource.IpcManual << 4) | (byte)StateSource.Fixed: _data[i] = (byte)((value & 0xF0) | (byte)StateSource.Manual); break; } From d10043a69a01e45d84b03b19d4e1b1a5b5ef774c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 30 Jan 2024 18:30:51 +0100 Subject: [PATCH 223/786] Add toggle for always applying mod associations. --- Glamourer/Automation/AutoDesignApplier.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab.cs | 2 +- .../Interop/Penumbra/ModSettingApplier.cs | 67 +++++++++++++++++++ Glamourer/Services/CommandService.cs | 21 ++---- Glamourer/State/StateEditor.cs | 7 +- Glamourer/State/StateManager.cs | 6 +- 6 files changed, 84 insertions(+), 21 deletions(-) create mode 100644 Glamourer/Interop/Penumbra/ModSettingApplier.cs diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index dd327b3..65116db 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -264,7 +264,7 @@ public sealed class AutoDesignApplier : IDisposable var mergedDesign = _designMerger.Merge( set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks.Select(l => (l.Design, l.Flags & d.Type)) ?? [(d.Design, d.Type)]), - state.ModelData, true, false); + state.ModelData, true, _config.AlwaysApplyAssociatedMods); _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false)); } diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs index ef75245..11490df 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -89,12 +89,12 @@ public class SettingsTab( Checkbox("Enable Advanced Customization Options", "Enable the display and editing of advanced customization options like arbitrary colors.", config.UseAdvancedParameters, paletteChecker.SetAdvancedParameters); + PaletteImportButton(); Checkbox("Always Apply Associated Mods", "Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n" + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n" + "If you enable this setting, you are aware that any resulting misconfiguration is your own fault.", config.AlwaysApplyAssociatedMods, v => config.AlwaysApplyAssociatedMods = v); - PaletteImportButton(); ImGui.NewLine(); } diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs new file mode 100644 index 0000000..01ea4d7 --- /dev/null +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -0,0 +1,67 @@ +using Glamourer.Designs.Links; +using Glamourer.Interop.Structs; +using Glamourer.State; +using OtterGui.Services; + +namespace Glamourer.Interop.Penumbra; + +public class ModSettingApplier(PenumbraService penumbra, Configuration config, ObjectManager objects) : IService +{ + public void HandleStateApplication(ActorState state, MergedDesign design) + { + if (!config.AlwaysApplyAssociatedMods || design.AssociatedMods.Count == 0) + return; + + objects.Update(); + if (!objects.TryGetValue(state.Identifier, out var data)) + { + Glamourer.Log.Verbose( + $"[Mod Applier] No mod settings applied because no actor for {state.Identifier} could be found to associate collection."); + return; + } + + var collections = new HashSet(); + + foreach (var actor in data.Objects) + { + var collection = penumbra.GetActorCollection(actor); + if (collection.Length == 0) + { + Glamourer.Log.Verbose($"[Mod Applier] Could not obtain associated collection for {actor.Utf8Name}."); + continue; + } + + if (!collections.Add(collection)) + continue; + + foreach (var (mod, setting) in design.AssociatedMods) + { + var message = penumbra.SetMod(mod, setting, collection); + if (message.Length > 0) + Glamourer.Log.Verbose($"[Mod Applier] Error applying mod settings: {message}"); + else + Glamourer.Log.Verbose($"[Mod Applier] Set mod settings for {mod.DirectoryName} in {collection}."); + } + } + } + + public (List Messages, int Applied, string Collection) ApplyModSettings(IReadOnlyDictionary settings, Actor actor) + { + var collection = penumbra.GetActorCollection(actor); + if (collection.Length <= 0) + return ([$"Could not obtain associated collection for {actor.Utf8Name}."], 0, string.Empty); + + var messages = new List(); + var appliedMods = 0; + foreach (var (mod, setting) in settings) + { + var message = penumbra.SetMod(mod, setting, collection); + if (message.Length > 0) + messages.Add($"Error applying mod settings: {message}"); + else + ++appliedMods; + } + + return (messages, appliedMods, collection); + } +} diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 895ad2f..2f3e938 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -33,11 +33,11 @@ public class CommandService : IDisposable private readonly DesignConverter _converter; private readonly DesignFileSystem _designFileSystem; private readonly Configuration _config; - private readonly PenumbraService _penumbra; + private readonly ModSettingApplier _modApplier; 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, PenumbraService penumbra) + DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier) { _commands = commands; _mainWindow = mainWindow; @@ -51,7 +51,7 @@ public class CommandService : IDisposable _designFileSystem = designFileSystem; _autoDesignManager = autoDesignManager; _config = config; - _penumbra = penumbra; + _modApplier = modApplier; _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); _commands.AddHandler(ApplyCommandString, @@ -440,19 +440,10 @@ public class CommandService : IDisposable if (!applyMods || design is not Design d) return; - var collection = _penumbra.GetActorCollection(actor); - if (collection.Length <= 0) - return; + var (messages, appliedMods, collection) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); - var appliedMods = 0; - foreach (var (mod, setting) in d.AssociatedMods) - { - var message = _penumbra.SetMod(mod, setting, collection); - if (message.Length > 0) - Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); - else - ++appliedMods; - } + 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}."); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 48b74ab..a6606e6 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -2,6 +2,7 @@ using Glamourer.Designs; using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; +using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; using Penumbra.GameData.Enums; @@ -16,7 +17,8 @@ public class StateEditor( JobChangeState jobChange, Configuration config, ItemManager items, - DesignMerger merger) : IDesignEditor + DesignMerger merger, + ModSettingApplier modApplier) : IDesignEditor { protected readonly InternalStateEditor Editor = editor; protected readonly StateApplier Applier = applier; @@ -181,6 +183,7 @@ public class StateEditor( public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings) { var state = (ActorState)data; + modApplier.HandleStateApplication(state, mergedDesign); if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize, mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key)) return; @@ -294,7 +297,7 @@ public class StateEditor( public void ApplyDesign(object data, DesignBase design, ApplySettings settings) { var merged = settings.MergeLinks && design is Design d - ? merger.Merge(d.AllLinks, ((ActorState)data).ModelData, false, false) + ? merger.Merge(d.AllLinks, ((ActorState)data).ModelData, false, Config.AlwaysApplyAssociatedMods) : new MergedDesign(design); ApplyDesign(data, merged, settings with diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index e6ed657..32d1237 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -4,6 +4,7 @@ using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; +using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; using Penumbra.GameData.Actors; @@ -23,8 +24,9 @@ public sealed class StateManager( IClientState _clientState, Configuration config, JobChangeState jobChange, - DesignMerger merger) - : StateEditor(editor, applier, @event, jobChange, config, items, merger), IReadOnlyDictionary + DesignMerger merger, + ModSettingApplier modApplier) + : StateEditor(editor, applier, @event, jobChange, config, items, merger, modApplier), IReadOnlyDictionary { private readonly Dictionary _states = []; From 5cdcb9288e0109a188c739adb46a6751dc9f1a6c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 1 Feb 2024 13:51:38 +0100 Subject: [PATCH 224/786] Add NearEqual for vectors. --- Glamourer/Designs/ApplicationRules.cs | 2 +- Glamourer/GameData/CustomizeParameterValue.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/ApplicationRules.cs b/Glamourer/Designs/ApplicationRules.cs index 1852c7e..aaaf1a7 100644 --- a/Glamourer/Designs/ApplicationRules.cs +++ b/Glamourer/Designs/ApplicationRules.cs @@ -75,7 +75,7 @@ public readonly struct ApplicationRules( { var modelValue = model.Parameters[flag]; var gameValue = game.Parameters[flag]; - if ((modelValue.InternalQuadruple - gameValue.InternalQuadruple).LengthSquared() < 1e-9f) + if (modelValue.NearEqual(gameValue)) baseFlags &= ~flag; } diff --git a/Glamourer/GameData/CustomizeParameterValue.cs b/Glamourer/GameData/CustomizeParameterValue.cs index 0e22d18..3bfdf99 100644 --- a/Glamourer/GameData/CustomizeParameterValue.cs +++ b/Glamourer/GameData/CustomizeParameterValue.cs @@ -48,3 +48,18 @@ public readonly struct CustomizeParameterValue public override string ToString() => _data.ToString(); } + +public static class VectorExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + public static bool NearEqual(this Vector3 lhs, Vector3 rhs, float eps = 1e-9f) + => (lhs - rhs).LengthSquared() < eps; + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + public static bool NearEqual(this Vector4 lhs, Vector4 rhs, float eps = 1e-9f) + => (lhs - rhs).LengthSquared() < eps; + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + public static bool NearEqual(this CustomizeParameterValue lhs, CustomizeParameterValue rhs, float eps = 1e-9f) + => NearEqual(lhs.InternalQuadruple, rhs.InternalQuadruple, eps); +} From fb7aac5228bad14e3f2a938598b27c13e4c38a46 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 1 Feb 2024 13:52:20 +0100 Subject: [PATCH 225/786] Add state editing and tracking. --- Glamourer/Events/StateChanged.cs | 3 + Glamourer/Gui/Materials/MaterialDrawer.cs | 97 ++++++---- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 34 ---- Glamourer/Interop/Material/MaterialManager.cs | 148 ++++++++++++++++ .../Interop/Material/MaterialValueIndex.cs | 26 ++- .../Interop/Material/MaterialValueManager.cs | 59 ++++--- Glamourer/Interop/Material/PrepareColorSet.cs | 165 +++--------------- Glamourer/State/InternalStateEditor.cs | 34 ++++ Glamourer/State/StateApplier.cs | 36 ++++ Glamourer/State/StateEditor.cs | 16 +- 10 files changed, 383 insertions(+), 235 deletions(-) create mode 100644 Glamourer/Interop/Material/MaterialManager.cs diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 47b5d20..e5e0cb5 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -42,6 +42,9 @@ namespace Glamourer.Events /// A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. Parameter, + /// A characters saved state had a material color table value changed. Data is the old value, the new value and the index [(Vector3, Vector3, MaterialValueIndex)]. + MaterialValue, + /// A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] Design, diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 1114911..e4a612d 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -1,16 +1,18 @@ -using Dalamud.Interface.Utility; +using Dalamud.Interface; +using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; -using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Glamourer.Designs; using Glamourer.Interop.Material; using Glamourer.Interop.Structs; +using Glamourer.State; using ImGuiNET; using OtterGui.Services; using Penumbra.GameData.Files; namespace Glamourer.Gui.Materials; -public unsafe class MaterialDrawer : IService +public unsafe class MaterialDrawer(StateManager _stateManager) : IService { private static readonly IReadOnlyList Types = [ @@ -19,9 +21,11 @@ public unsafe class MaterialDrawer : IService MaterialValueIndex.DrawObjectType.Offhand, ]; + private ActorState? _state; + public void DrawPanel(Actor actor) { - if (!actor.IsCharacter) + if (!actor.IsCharacter || !_stateManager.GetOrCreate(actor, out _state)) return; foreach (var type in Types) @@ -64,11 +68,11 @@ public unsafe class MaterialDrawer : IService if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table)) continue; - DrawMaterial(ref table, texture, index); + DrawMaterial(ref table, index); } } - private void DrawMaterial(ref MtrlFile.ColorTable table, Texture** texture, MaterialValueIndex sourceIndex) + private void DrawMaterial(ref MtrlFile.ColorTable table, MaterialValueIndex sourceIndex) { using var tree = ImRaii.TreeNode($"Material {sourceIndex.MaterialIndex + 1}"); if (!tree) @@ -76,55 +80,78 @@ public unsafe class MaterialDrawer : IService for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i) { - var index = sourceIndex with { RowIndex = i }; + var index = sourceIndex with { RowIndex = i }; ref var row = ref table[i]; - DrawRow(ref table, ref row, texture, index); + DrawRow(ref row, index); } } - private void DrawRow(ref MtrlFile.ColorTable table, ref MtrlFile.ColorTable.Row row, Texture** texture, MaterialValueIndex sourceIndex) + private void DrawRow(ref MtrlFile.ColorTable.Row row, MaterialValueIndex sourceIndex) { - using var id = ImRaii.PushId(sourceIndex.RowIndex); - var diffuse = row.Diffuse; - var specular = row.Specular; - var emissive = row.Emissive; - var glossStrength = row.GlossStrength; - var specularStrength = row.SpecularStrength; - if (ImGui.ColorEdit3("Diffuse", ref diffuse, ImGuiColorEditFlags.NoInputs)) + var r = _state!.Materials.GetValues( + MaterialValueIndex.Min(sourceIndex.DrawObject, sourceIndex.SlotIndex, sourceIndex.MaterialIndex, sourceIndex.RowIndex), + MaterialValueIndex.Max(sourceIndex.DrawObject, sourceIndex.SlotIndex, sourceIndex.MaterialIndex, sourceIndex.RowIndex)); + + var highlightColor = ColorId.FavoriteStarOn.Value(); + + using var id = ImRaii.PushId(sourceIndex.RowIndex); + var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Diffuse }; + var (diffuse, diffuseGame, changed) = MaterialValueManager.GetSpecific(r, index, out var d) + ? (d.Model, d.Game, true) + : (row.Diffuse, row.Diffuse, false); + using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed)) { - var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Diffuse }; - row.Diffuse = diffuse; - MaterialService.ReplaceColorTable(texture, table); + if (ImGui.ColorEdit3("Diffuse", ref diffuse, ImGuiColorEditFlags.NoInputs)) + _stateManager.ChangeMaterialValue(_state!, index, diffuse, diffuseGame, ApplySettings.Manual); } + + + index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Specular }; + (var specular, var specularGame, changed) = MaterialValueManager.GetSpecific(r, index, out var s) + ? (s.Model, s.Game, true) + : (row.Specular, row.Specular, false); ImGui.SameLine(); - if (ImGui.ColorEdit3("Specular", ref specular, ImGuiColorEditFlags.NoInputs)) + using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed)) { - var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Specular }; - row.Specular = specular; - MaterialService.ReplaceColorTable(texture, table); + if (ImGui.ColorEdit3("Specular", ref specular, ImGuiColorEditFlags.NoInputs)) + _stateManager.ChangeMaterialValue(_state!, index, specular, specularGame, ApplySettings.Manual); } + + index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Emissive }; + (var emissive, var emissiveGame, changed) = MaterialValueManager.GetSpecific(r, index, out var e) + ? (e.Model, e.Game, true) + : (row.Emissive, row.Emissive, false); ImGui.SameLine(); - if (ImGui.ColorEdit3("Emissive", ref emissive, ImGuiColorEditFlags.NoInputs)) + using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed)) { - var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Emissive }; - row.Emissive = emissive; - MaterialService.ReplaceColorTable(texture, table); + if (ImGui.ColorEdit3("Emissive", ref emissive, ImGuiColorEditFlags.NoInputs)) + _stateManager.ChangeMaterialValue(_state!, index, emissive, emissiveGame, ApplySettings.Manual); } + + index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.GlossStrength }; + (var glossStrength, var glossStrengthGame, changed) = MaterialValueManager.GetSpecific(r, index, out var g) + ? (g.Model.X, g.Game.X, true) + : (row.GlossStrength, row.GlossStrength, false); ImGui.SameLine(); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - if (ImGui.DragFloat("Gloss", ref glossStrength, 0.1f)) + using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed)) { - var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.GlossStrength }; - row.GlossStrength = glossStrength; - MaterialService.ReplaceColorTable(texture, table); + if (ImGui.DragFloat("Gloss", ref glossStrength, 0.1f)) + _stateManager.ChangeMaterialValue(_state!, index, new Vector3(glossStrength), new Vector3(glossStrengthGame), + ApplySettings.Manual); } + + index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.SpecularStrength }; + (var specularStrength, var specularStrengthGame, changed) = MaterialValueManager.GetSpecific(r, index, out var ss) + ? (ss.Model.X, ss.Game.X, true) + : (row.SpecularStrength, row.SpecularStrength, false); ImGui.SameLine(); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - if (ImGui.DragFloat("Specular Strength", ref specularStrength, 0.1f)) + using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed)) { - var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.SpecularStrength }; - row.SpecularStrength = specularStrength; - MaterialService.ReplaceColorTable(texture, table); + if (ImGui.DragFloat("Specular Strength", ref specularStrength, 0.1f)) + _stateManager.ChangeMaterialValue(_state!, index, new Vector3(specularStrength), new Vector3(specularStrengthGame), + ApplySettings.Manual); } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 94f4b56..ff6b798 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -100,12 +100,6 @@ public class ActorPanel( return (_selector.IncognitoMode ? _identifier.Incognito(null) : _identifier.ToString(), Actor.Null); } - private Vector3 _test; - private int _rowId; - private MaterialValueIndex.ColorTableIndex _index; - private int _materialId; - private int _slotId; - private unsafe void DrawPanel() { using var child = ImRaii.Child("##Panel", -Vector2.One, true); @@ -126,34 +120,6 @@ public class ActorPanel( if (ImGui.CollapsingHeader("Material Shit")) _materialDrawer.DrawPanel(_actor); - ImGui.InputInt("Row", ref _rowId); - ImGui.InputInt("Material", ref _materialId); - ImGui.InputInt("Slot", ref _slotId); - ImGuiUtil.GenericEnumCombo("Value", 300, _index, out _index); - - var index = new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte) _slotId, (byte) _materialId, (byte)_rowId, _index); - index.TryGetValue(_actor, out var current); - _test = current; - if (ImGui.ColorPicker3("TestPicker", ref _test) && _actor.Valid) - _state.Materials.AddOrUpdateValue(index, new MaterialValueState(current, _test, StateSource.Manual)); - - if (ImGui.ColorPicker3("TestPicker2", ref _test) && _actor.Valid) - _state.Materials.AddOrUpdateValue(index, new MaterialValueState(current, _test, StateSource.Fixed)); - - foreach (var value in _state.Materials.Values) - { - var id = MaterialValueIndex.FromKey(value.Key); - ImGui.TextUnformatted($"{id.DrawObject} {id.SlotIndex} {id.MaterialIndex} {id.RowIndex} {id.DataIndex} "); - ImGui.SameLine(0, 0); - var game = ImGui.ColorConvertFloat4ToU32(new Vector4(value.Value.Game, 1)); - ImGuiUtil.DrawTextButton(" ", Vector2.Zero, game); - ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X); - var model = ImGui.ColorConvertFloat4ToU32(new Vector4(value.Value.Model, 1)); - ImGuiUtil.DrawTextButton(" ", Vector2.Zero, model); - ImGui.SameLine(0, 0); - ImGui.TextUnformatted($" {value.Value.Source}"); - } - using var disabled = ImRaii.Disabled(transformationId != 0); if (_state.ModelData.IsHuman) DrawHumanPanel(); diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs new file mode 100644 index 0000000..8cbe0c5 --- /dev/null +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -0,0 +1,148 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using Glamourer.Designs; +using Glamourer.Interop.Penumbra; +using Glamourer.Interop.Structs; +using Glamourer.State; +using OtterGui.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Structs; + +namespace Glamourer.Interop.Material; + +public sealed unsafe class MaterialManager : IRequiredService, IDisposable +{ + private readonly PrepareColorSet _event; + private readonly StateManager _stateManager; + private readonly PenumbraService _penumbra; + private readonly ActorManager _actors; + + private int _lastSlot; + + public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra) + { + _stateManager = stateManager; + _actors = actors; + _penumbra = penumbra; + _event = prepareColorSet; + + _event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager); + } + + public void Dispose() + => _event.Unsubscribe(OnPrepareColorSet); + + private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainId stain, ref nint ret) + { + var actor = _penumbra.GameObjectFromDrawObject(characterBase); + var validType = FindType(characterBase, actor, out var type); + var (slotId, materialId) = FindMaterial(characterBase, material); + + if (!validType + || slotId == byte.MaxValue + || !actor.Identifier(_actors, out var identifier) + || !_stateManager.TryGetValue(identifier, out var state)) + return; + + var min = MaterialValueIndex.Min(type, slotId, materialId); + var max = MaterialValueIndex.Max(type, slotId, materialId); + var values = state.Materials.GetValues(min, max); + if (values.Length == 0) + return; + + if (!PrepareColorSet.TryGetColorTable(characterBase, material, stain, out var baseColorSet)) + return; + + for (var i = 0; i < values.Length; ++i) + { + var idx = MaterialValueIndex.FromKey(values[i].key); + var (oldGame, model, source) = values[i].Value; + ref var row = ref baseColorSet[idx.RowIndex]; + if (!idx.DataIndex.TryGetValue(row, out var newGame)) + continue; + + if (newGame == oldGame) + { + idx.DataIndex.SetValue(ref row, model); + } + else + { + switch (source.Base()) + { + case StateSource.Manual: + _stateManager.ChangeMaterialValue(state, idx, Vector3.Zero, Vector3.Zero, ApplySettings.Game); + --i; + break; + case StateSource.Fixed: + idx.DataIndex.SetValue(ref row, model); + state.Materials.UpdateValue(idx, new MaterialValueState(newGame, model, source), out _); + break; + } + } + } + + if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture)) + ret = (nint)texture; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private (byte SlotId, byte MaterialId) FindMaterial(CharacterBase* characterBase, MaterialResourceHandle* material) + { + for (var i = _lastSlot; i < characterBase->SlotCount; ++i) + { + var idx = MaterialService.MaterialsPerModel * i; + for (var j = 0; j < MaterialService.MaterialsPerModel; ++j) + { + var mat = (nint)characterBase->Materials[idx++]; + if (mat != (nint)material) + continue; + + _lastSlot = i; + return ((byte)i, (byte)j); + } + } + + for (var i = 0; i < _lastSlot; ++i) + { + var idx = MaterialService.MaterialsPerModel * i; + for (var j = 0; j < MaterialService.MaterialsPerModel; ++j) + { + var mat = (nint)characterBase->Materials[idx++]; + if (mat != (nint)material) + continue; + + _lastSlot = i; + return ((byte)i, (byte)j); + } + } + + return (byte.MaxValue, byte.MaxValue); + } + + private static bool FindType(CharacterBase* characterBase, Actor actor, out MaterialValueIndex.DrawObjectType type) + { + type = MaterialValueIndex.DrawObjectType.Human; + if (!actor.Valid) + return false; + + if (actor.Model.AsCharacterBase == characterBase) + return true; + + if (!actor.AsObject->IsCharacter()) + return false; + + if (actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject == characterBase) + { + type = MaterialValueIndex.DrawObjectType.Mainhand; + return true; + } + + if (actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject == characterBase) + { + type = MaterialValueIndex.DrawObjectType.Offhand; + return true; + } + + return false; + } +} diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 4cbc116..d2d8db1 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -59,27 +59,43 @@ public readonly record struct MaterialValueIndex( return true; } - public unsafe bool TryGetTexture(Actor actor, out Texture* texture) + public unsafe bool TryGetTexture(Actor actor, out Texture** texture) { - if (!TryGetTextures(actor, out var textures) || MaterialIndex >= MaterialService.MaterialsPerModel) + if (TryGetTextures(actor, out var textures)) + return TryGetTexture(textures, out texture); + + texture = null; + return false; + } + + public unsafe bool TryGetTexture(ReadOnlySpan> textures, out Texture** texture) + { + if (MaterialIndex >= textures.Length || textures[MaterialIndex].Value == null) { texture = null; return false; } - texture = textures[MaterialIndex].Value; - return texture != null; + fixed (Pointer* ptr = textures) + { + texture = (Texture**)ptr + MaterialIndex; + } + + return true; } public unsafe bool TryGetColorTable(Actor actor, out MtrlFile.ColorTable table) { if (TryGetTexture(actor, out var texture)) - return DirectXTextureHelper.TryGetColorTable(texture, out table); + return TryGetColorTable(texture, out table); table = default; return false; } + public unsafe bool TryGetColorTable(Texture** texture, out MtrlFile.ColorTable table) + => DirectXTextureHelper.TryGetColorTable(*texture, out table); + public unsafe bool TryGetColorRow(Actor actor, out MtrlFile.ColorTable.Row row) { if (!TryGetColorTable(actor, out var table)) diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 5dd3001..5f7c2dc 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -104,7 +104,7 @@ public readonly struct MaterialValueManager public int RemoveValues(MaterialValueIndex min, MaterialValueIndex max) { - var (minIdx, maxIdx) = GetMinMax(CollectionsMarshal.AsSpan(_values), min.Key, max.Key); + var (minIdx, maxIdx) = MaterialValueManager.GetMinMax(CollectionsMarshal.AsSpan(_values), min.Key, max.Key); if (minIdx < 0) return 0; @@ -114,20 +114,49 @@ public readonly struct MaterialValueManager } public ReadOnlySpan<(uint key, T Value)> GetValues(MaterialValueIndex min, MaterialValueIndex max) - => Filter(CollectionsMarshal.AsSpan(_values), min, max); + => MaterialValueManager.Filter(CollectionsMarshal.AsSpan(_values), min, max); - public static ReadOnlySpan<(uint Key, T Value)> Filter(ReadOnlySpan<(uint Key, T Value)> values, MaterialValueIndex min, + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private int Search(uint key) + => _values.BinarySearch((key, default!), MaterialValueManager.Comparer.Instance); +} + +public static class MaterialValueManager +{ + internal class Comparer : IComparer<(uint Key, T Value)> + { + public static readonly Comparer Instance = new(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + int IComparer<(uint Key, T Value)>.Compare((uint Key, T Value) x, (uint Key, T Value) y) + => x.Key.CompareTo(y.Key); + } + + public static bool GetSpecific(ReadOnlySpan<(uint Key, T Value)> values, MaterialValueIndex index, out T ret) + { + var idx = values.BinarySearch((index.Key, default!), Comparer.Instance); + if (idx < 0) + { + ret = default!; + return false; + } + + ret = values[idx].Value; + return true; + } + + public static ReadOnlySpan<(uint Key, T Value)> Filter(ReadOnlySpan<(uint Key, T Value)> values, MaterialValueIndex min, MaterialValueIndex max) { var (minIdx, maxIdx) = GetMinMax(values, min.Key, max.Key); - return minIdx < 0 ? [] : values[minIdx..(maxIdx - minIdx + 1)]; + return minIdx < 0 ? [] : values[minIdx..(maxIdx + 1)]; } /// Obtain the minimum index and maximum index for a minimum and maximum key. - private static (int MinIdx, int MaxIdx) GetMinMax(ReadOnlySpan<(uint Key, T Value)> values, uint minKey, uint maxKey) + internal static (int MinIdx, int MaxIdx) GetMinMax(ReadOnlySpan<(uint Key, T Value)> values, uint minKey, uint maxKey) { // Find the minimum index by binary search. - var idx = values.BinarySearch((minKey, default!), Comparer.Instance); + var idx = values.BinarySearch((minKey, default!), Comparer.Instance); var minIdx = idx; // If the key does not exist, check if it is an invalid range or set it correctly. @@ -152,12 +181,13 @@ public readonly struct MaterialValueManager // Do pretty much the same but in the other direction with the maximum key. - var maxIdx = values[idx..].BinarySearch((maxKey, default!), Comparer.Instance); + var maxIdx = values[idx..].BinarySearch((maxKey, default!), Comparer.Instance); if (maxIdx < 0) { - maxIdx = ~maxIdx; + maxIdx = ~maxIdx + idx; return maxIdx > minIdx ? (minIdx, maxIdx - 1) : (-1, -1); } + maxIdx += idx; while (maxIdx < values.Length - 1 && values[maxIdx + 1].Key <= maxKey) ++maxIdx; @@ -167,17 +197,4 @@ public readonly struct MaterialValueManager return (minIdx, maxIdx); } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private int Search(uint key) - => _values.BinarySearch((key, default!), Comparer.Instance); - - private class Comparer : IComparer<(uint Key, T Value)> - { - public static readonly Comparer Instance = new(); - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - int IComparer<(uint Key, T Value)>.Compare((uint Key, T Value) x, (uint Key, T Value) y) - => x.Key.CompareTo(y.Key); - } } diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 4230fc1..1fc2f68 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -1,14 +1,11 @@ -using Dalamud.Game; -using Dalamud.Hooking; +using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; -using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; -using Glamourer.State; using OtterGui.Classes; using OtterGui.Services; -using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; using Penumbra.GameData.Files; using Penumbra.GameData.Structs; @@ -57,7 +54,8 @@ public sealed unsafe class PrepareColorSet return _task.Result.Original(characterBase, material, stainId); } - public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId, out MtrlFile.ColorTable table) + public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId, + out MtrlFile.ColorTable table) { if (material->ColorTable == null) { @@ -66,147 +64,40 @@ public sealed unsafe class PrepareColorSet } var newTable = *(MtrlFile.ColorTable*)material->ColorTable; - if(stainId.Id != 0) + if (stainId.Id != 0) characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); table = newTable; return true; } -} -public sealed unsafe class MaterialManager : IRequiredService, IDisposable -{ - private readonly PrepareColorSet _event; - private readonly StateManager _stateManager; - private readonly PenumbraService _penumbra; - private readonly ActorManager _actors; - - private int _lastSlot; - - public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra) + /// Assumes the actor is valid. + public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out MtrlFile.ColorTable table) { - _stateManager = stateManager; - _actors = actors; - _penumbra = penumbra; - _event = prepareColorSet; - - _event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager); - } - - public void Dispose() - => _event.Unsubscribe(OnPrepareColorSet); - - private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainId stain, ref nint ret) - { - var actor = _penumbra.GameObjectFromDrawObject(characterBase); - var validType = FindType(characterBase, actor, out var type); - var (slotId, materialId) = FindMaterial(characterBase, material); - - if (!validType - || slotId == byte.MaxValue - || !actor.Identifier(_actors, out var identifier) - || !_stateManager.TryGetValue(identifier, out var state)) - return; - - - var min = MaterialValueIndex.Min(type, slotId, materialId); - var max = MaterialValueIndex.Max(type, slotId, materialId); - var values = state.Materials.GetValues(min, max); - if (values.Length == 0) - return; - - if (!PrepareColorSet.TryGetColorTable(characterBase, material, stain, out var baseColorSet)) - return; - - for (var i = 0; i < values.Length; ++i) + var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex; + var model = actor.Model.AsCharacterBase; + var handle = (MaterialResourceHandle*)model->Materials[idx]; + if (handle == null) { - var idx = MaterialValueIndex.FromKey(values[i].key); - var (oldGame, model, source) = values[i].Value; - ref var row = ref baseColorSet[idx.RowIndex]; - if (!idx.DataIndex.TryGetValue(row, out var newGame)) - continue; - - if (newGame == oldGame) - { - idx.DataIndex.SetValue(ref row, model); - } - else - { - switch (source.Base()) - { - case StateSource.Manual: - state.Materials.RemoveValue(idx); - --i; - break; - case StateSource.Fixed: - idx.DataIndex.SetValue(ref row, model); - state.Materials.UpdateValue(idx, new MaterialValueState(newGame, model, source), out _); - break; - } - } - } - - if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture)) - ret = (nint)texture; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private (byte SlotId, byte MaterialId) FindMaterial(CharacterBase* characterBase, MaterialResourceHandle* material) - { - for (var i = _lastSlot; i < characterBase->SlotCount; ++i) - { - var idx = MaterialService.MaterialsPerModel * i; - for (var j = 0; j < MaterialService.MaterialsPerModel; ++j) - { - var mat = (nint)characterBase->Materials[idx++]; - if (mat != (nint)material) - continue; - - _lastSlot = i; - return ((byte)i, (byte)j); - } - } - - for (var i = 0; i < _lastSlot; ++i) - { - var idx = MaterialService.MaterialsPerModel * i; - for (var j = 0; j < MaterialService.MaterialsPerModel; ++j) - { - var mat = (nint)characterBase->Materials[idx++]; - if (mat != (nint)material) - continue; - - _lastSlot = i; - return ((byte)i, (byte)j); - } - } - - return (byte.MaxValue, byte.MaxValue); - } - - private static bool FindType(CharacterBase* characterBase, Actor actor, out MaterialValueIndex.DrawObjectType type) - { - type = MaterialValueIndex.DrawObjectType.Human; - if (!actor.Valid) + table = default; return false; - - if (actor.Model.AsCharacterBase == characterBase) - return true; - - if (!actor.AsObject->IsCharacter()) - return false; - - if (actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject == characterBase) - { - type = MaterialValueIndex.DrawObjectType.Mainhand; - return true; } - if (actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject == characterBase) - { - type = MaterialValueIndex.DrawObjectType.Offhand; - return true; - } + return TryGetColorTable(model, handle, GetStain(), out table); - return false; + StainId GetStain() + { + switch (index.DrawObject) + { + case MaterialValueIndex.DrawObjectType.Human: + return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stain : 0; + case MaterialValueIndex.DrawObjectType.Mainhand: + var mainhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject; + return mainhand.IsWeapon ? (StainId)mainhand.AsWeapon->ModelUnknown : 0; + case MaterialValueIndex.DrawObjectType.Offhand: + var offhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject; + return offhand.IsWeapon ? (StainId)offhand.AsWeapon->ModelUnknown : 0; + default: return 0; + } + } } } diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs index 2bc50c3..75cee46 100644 --- a/Glamourer/State/InternalStateEditor.cs +++ b/Glamourer/State/InternalStateEditor.cs @@ -2,6 +2,7 @@ using Glamourer.Designs; using Glamourer.Events; using Glamourer.GameData; +using Glamourer.Interop.Material; using Glamourer.Services; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -220,6 +221,39 @@ public class InternalStateEditor( return true; } + /// Change the value of a single material color table entry. + public bool ChangeMaterialValue(ActorState state, MaterialValueIndex index, Vector3 value, Vector3 gameValue, StateSource source, out Vector3 oldValue, + uint key = 0) + { + // We already have an existing value. + if (state.Materials.TryGetValue(index, out var old)) + { + oldValue = old.Model; + if (!state.CanUnlock(key)) + return false; + + // Remove if overwritten by a game value. + if (source is StateSource.Game) + { + state.Materials.RemoveValue(index); + return true; + } + + // Update if edited. + state.Materials.UpdateValue(index, new MaterialValueState(gameValue, value, source), out _); + return true; + } + + // We do not have an existing value. + oldValue = gameValue; + // Do not do anything if locked or if the game value updates, because then we do not need to add an entry. + if (!state.CanUnlock(key) || source is StateSource.Game) + return false; + + // Only add an entry if it is sufficiently different from the game value. + return !value.NearEqual(gameValue) && state.Materials.TryAddValue(index, new MaterialValueState(gameValue, value, source)); + } + public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue, uint key = 0) { diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index e9b74fd..19c1f3e 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,6 +1,7 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop; +using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; @@ -275,6 +276,41 @@ public class StateApplier( return data; } + public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, Vector3? value, bool force) + { + if (!force && !_config.UseAdvancedParameters) + return; + + foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) + { + if (!index.TryGetTexture(actor, out var texture)) + continue; + + if (!index.TryGetColorTable(texture, out var table)) + continue; + + Vector3 actualValue; + if (value.HasValue) + actualValue = value.Value; + else if (!PrepareColorSet.TryGetColorTable(actor, index, out var baseTable) + || !index.DataIndex.TryGetValue(baseTable[index.RowIndex], out actualValue)) + continue; + + if (!index.DataIndex.SetValue(ref table[index.RowIndex], actualValue)) + continue; + + MaterialService.ReplaceColorTable(texture, table); + } + } + + public ActorData ChangeMaterialValue(ActorState state, MaterialValueIndex index, bool apply) + { + var data = GetData(state); + if (apply) + ChangeMaterialValue(data, index, state.Materials.TryGetValue(index, out var v) ? v.Model : null, state.IsLocked); + return data; + } + /// Apply the entire state of an actor to all relevant actors, either via immediate redraw or piecewise. /// The state to apply. /// Whether a redraw should be forced. diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index a6606e6..e747448 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -2,6 +2,7 @@ using Glamourer.Designs; using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; +using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; @@ -149,9 +150,7 @@ public class StateEditor( /// public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings settings) { - if (data is not ActorState state) - return; - + var state = (ActorState)data; // Also apply main color to highlights when highlights is off. if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse) ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, settings); @@ -166,6 +165,17 @@ public class StateEditor( StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag)); } + public void ChangeMaterialValue(object data, MaterialValueIndex index, Vector3 value, Vector3 gameValue, ApplySettings settings) + { + var state = (ActorState)data; + if (!Editor.ChangeMaterialValue(state, index, value, gameValue, settings.Source, out var oldValue, settings.Key)) + return; + + var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange()); + Glamourer.Log.Verbose($"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, value, index)); + } + /// public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings) { From 5e37f8d2e80ce37f94c862144adcd02ab8f4db5a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 2 Feb 2024 23:27:56 +0100 Subject: [PATCH 226/786] add some glamour debug stuff. --- Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 3 +- .../Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 135 ++++++++++++++++++ 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs diff --git a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs index 35bd136..11f27fd 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs @@ -37,4 +37,4 @@ public class DatFilePanel(ImportService _importService) : IGameDataDrawer ImGui.TextUnformatted(_datFile.Value.Description); } } -} +} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index b2b7bdb..2519b84 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -36,7 +36,8 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), - provider.GetRequiredService() + provider.GetRequiredService(), + provider.GetRequiredService() ); public static DebugTabHeader CreateGameData(IServiceProvider provider) diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs new file mode 100644 index 0000000..2129c1f --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -0,0 +1,135 @@ +using Dalamud.Interface.Utility.Raii; +using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game; +using Glamourer.Designs; +using Glamourer.Interop; +using Glamourer.Services; +using Glamourer.State; +using ImGuiNET; +using OtterGui; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Gui.Debug; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public unsafe class GlamourPlatePanel : IGameDataDrawer +{ + private readonly DesignManager _design; + private readonly ItemManager _items; + private readonly StateManager _state; + private readonly ObjectManager _objects; + + public string Label + => "Glamour Plates"; + + public bool Disabled + => false; + + public GlamourPlatePanel(IGameInteropProvider interop, ItemManager items, DesignManager design, StateManager state, ObjectManager objects) + { + _items = items; + _design = design; + _state = state; + _objects = objects; + interop.InitializeFromAttributes(this); + } + + public void Draw() + { + var manager = MirageManager.Instance(); + using (ImRaii.Group()) + { + ImGui.TextUnformatted("Address:"); + ImGui.TextUnformatted("Number of Glamour Plates:"); + ImGui.TextUnformatted("Glamour Plates Requested:"); + ImGui.TextUnformatted("Glamour Plates Loaded:"); + ImGui.TextUnformatted("Is Applying Glamour Plates:"); + } + + ImGui.SameLine(); + using (ImRaii.Group()) + { + ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)manager:X}"); + ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesSpan.Length.ToString()); + ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesRequested.ToString()); + ImGui.SameLine(); + if (ImGui.SmallButton("Request Update")) + RequestGlamour(); + ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesLoaded.ToString()); + ImGui.TextUnformatted(manager == null ? "-" : manager->IsApplyingGlamourPlate.ToString()); + } + + if (manager == null) + return; + + ActorState? state = null; + var (identifier, data) = _objects.PlayerData; + var enabled = data.Valid && _state.GetOrCreate(identifier, data.Objects[0], out state); + + for (var i = 0; i < manager->GlamourPlatesSpan.Length; ++i) + { + using var tree = ImRaii.TreeNode($"Plate #{i + 1:D2}"); + if (!tree) + continue; + + ref var plate = ref manager->GlamourPlatesSpan[i]; + if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled)) + { + var design = CreateDesign(plate); + _state.ApplyDesign(state!, design, ApplySettings.Manual); + } + + using (ImRaii.Group()) + { + foreach (var slot in EquipSlotExtensions.FullSlots) + ImGui.TextUnformatted(slot.ToName()); + } + + ImGui.SameLine(); + using (ImRaii.Group()) + { + foreach (var (_, index) in EquipSlotExtensions.FullSlots.WithIndex()) + ImGui.TextUnformatted($"{plate.ItemIds[index]:D6}, {plate.StainIds[index]:D3}"); + } + } + } + + [Signature("E8 ?? ?? ?? ?? 32 C0 48 8B 5C 24 ?? 48 8B 6C 24 ?? 48 83 C4 ?? 5F")] + private readonly delegate* unmanaged _requestUpdate = null!; + + public void RequestGlamour() + { + var manager = MirageManager.Instance(); + if (manager == null) + return; + + _requestUpdate(manager); + } + + public DesignBase CreateDesign(in MirageManager.GlamourPlate plate) + { + var design = _design.CreateTemporary(); + design.ApplyCustomize = 0; + design.ApplyCrest = 0; + design.ApplyMeta = 0; + design.ApplyParameters = 0; + design.ApplyEquip = 0; + foreach (var (slot, index) in EquipSlotExtensions.FullSlots.WithIndex()) + { + var itemId = plate.ItemIds[index]; + if (itemId == 0) + continue; + + var item = _items.Resolve(slot, itemId); + if (!item.Valid) + continue; + + design.GetDesignDataRef().SetItem(slot, item); + design.GetDesignDataRef().SetStain(slot, plate.StainIds[index]); + design.ApplyEquip |= slot.ToBothFlags(); + } + + return design; + } +} From 1fefe7366c07afc43ec91328123b58f45aa4e749 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 5 Feb 2024 15:21:29 +0100 Subject: [PATCH 227/786] MouseWheels are pretty great. --- Glamourer/Designs/Design.cs | 4 + Glamourer/Designs/DesignManager.cs | 12 +++ Glamourer/Events/DesignChanged.cs | 3 + .../CustomizationDrawer.Color.cs | 5 + .../Customization/CustomizationDrawer.Icon.cs | 12 ++- .../CustomizationDrawer.Simple.cs | 102 ++++++++++++------ Glamourer/Gui/DesignCombo.cs | 100 ++++++++++------- Glamourer/Gui/DesignQuickBar.cs | 7 +- .../Gui/Equipment/GlamourerColorCombo.cs | 5 +- Glamourer/Gui/Equipment/ItemCombo.cs | 2 +- Glamourer/Gui/Equipment/WeaponCombo.cs | 2 +- .../Gui/Tabs/AutomationTab/HumanNpcCombo.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- .../Gui/Tabs/DesignTab/DesignColorCombo.cs | 9 +- .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 15 ++- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/ModCombo.cs | 2 +- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 43 +++++++- Glamourer/Gui/Tabs/NpcCombo.cs | 2 +- Glamourer/Services/ServiceManager.cs | 3 +- OtterGui | 2 +- Penumbra.GameData | 2 +- 22 files changed, 250 insertions(+), 88 deletions(-) diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 89dd62f..20d85e7 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -26,6 +26,7 @@ public sealed class Design : DesignBase, ISavable { Tags = [.. other.Tags]; Description = other.Description; + QuickDesign = other.QuickDesign; AssociatedMods = new SortedList(other.AssociatedMods); } @@ -39,6 +40,7 @@ public sealed class Design : DesignBase, ISavable public string Description { get; internal set; } = string.Empty; public string[] Tags { get; internal set; } = []; public int Index { get; internal set; } + public bool QuickDesign { get; internal set; } = true; public string Color { get; internal set; } = string.Empty; public SortedList AssociatedMods { get; private set; } = []; public LinkContainer Links { get; private set; } = []; @@ -64,6 +66,7 @@ public sealed class Design : DesignBase, ISavable ["Name"] = Name.Text, ["Description"] = Description, ["Color"] = Color, + ["QuickDesign"] = QuickDesign, ["Tags"] = JArray.FromObject(Tags), ["WriteProtected"] = WriteProtected(), ["Equipment"] = SerializeEquipment(), @@ -124,6 +127,7 @@ public sealed class Design : DesignBase, ISavable Description = json["Description"]?.ToObject() ?? string.Empty, Tags = ParseTags(json), LastEdit = json["LastEdit"]?.ToObject() ?? creationDate, + QuickDesign = json["QuickDesign"]?.ToObject() ?? true, }; if (design.LastEdit < creationDate) design.LastEdit = creationDate; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index c3d8664..da6ed90 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -284,6 +284,18 @@ public sealed class DesignManager : DesignEditor DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, value); } + /// Set the quick design bar display status of a design. + public void SetQuickDesign(Design design, bool value) + { + if (value == design.QuickDesign) + return; + + 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."); + DesignChanged.Invoke(DesignChanged.Type.QuickDesignBar, design, value); + } + #endregion #region Edit Application Rules diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index f3ebb5f..81d56e4 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -92,6 +92,9 @@ public sealed class DesignChanged() /// An existing design changed its write protection status. Data is the new value [bool]. WriteProtection, + /// An existing design changed its display status for the quick design bar. Data is the new value [bool]. + QuickDesignBar, + /// An existing design changed one of the meta flags. Data is the flag, whether it was about their applying and the new value [(MetaFlag, bool, bool)]. Other, } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index f3753ae..ff6e0c5 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -23,6 +23,11 @@ public partial class CustomizationDrawer { if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.None, _framedIconSize)) ImGui.OpenPopup(ColorPickerPopupName); + else if (current >= 0 && CaptureMouseWheel(ref current, 0, _currentCount)) + { + var data = _set.Data(_currentIndex, current, _customize.Face); + UpdateValue(data.Value); + } } var npc = false; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index 4e0f3de..c0c45d2 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -18,8 +18,9 @@ public partial class CustomizationDrawer using var bigGroup = ImRaii.Group(); var label = _currentOption; - var current = _set.DataByValue(index, _currentByte, out var custom, _customize.Face); - var npc = false; + var current = _set.DataByValue(index, _currentByte, out var custom, _customize.Face); + var originalCurrent = current; + var npc = false; if (current < 0) { label = $"{_currentOption} (NPC)"; @@ -32,7 +33,14 @@ public partial class CustomizationDrawer using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) { if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) + { ImGui.OpenPopup(IconSelectorPopup); + } + else if (originalCurrent >= 0 && CaptureMouseWheel(ref current, 0, _currentCount)) + { + var data = _set.Data(_currentIndex, current, _customize.Face); + UpdateValue(data.Value); + } } ImGuiUtil.HoverIconTooltip(icon, _iconSize); diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 02d4def..3a8e021 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -1,6 +1,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGuiInternal; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -34,7 +35,8 @@ public partial class CustomizationDrawer { var tmp = (int)_currentByte.Value; ImGui.SetNextItemWidth(_comboSelectorSize); - if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp)) + if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp) + || CaptureMouseWheel(ref tmp, 0, _currentCount - 1)) UpdateValue((CustomizeValue)tmp); } @@ -42,11 +44,10 @@ public partial class CustomizationDrawer { var tmp = (int)_currentByte.Value; ImGui.SetNextItemWidth(_inputIntSize); + var cap = ImGui.GetIO().KeyCtrl ? byte.MaxValue : _currentCount - 1; if (ImGui.InputInt("##text", ref tmp, 1, 1)) { - var newValue = (CustomizeValue)(ImGui.GetIO().KeyCtrl - ? Math.Clamp(tmp, 0, byte.MaxValue) - : Math.Clamp(tmp, 0, _currentCount - 1)); + var newValue = (CustomizeValue)Math.Clamp(tmp, 0, cap); UpdateValue(newValue); } @@ -73,6 +74,10 @@ public partial class CustomizationDrawer else if (ImGui.GetIO().KeyCtrl) UpdateValue((CustomizeValue)value); } + else + { + CheckWheel(); + } if (!_withApply) ImGuiUtil.HoverTooltip("Hold Control to force updates with invalid/unknown options at your own risk."); @@ -81,15 +86,29 @@ public partial class CustomizationDrawer if (ImGuiUtil.DrawDisabledButton("-", new Vector2(ImGui.GetFrameHeight()), "Select the previous available option in order.", currentIndex <= 0)) UpdateValue(_set.Data(_currentIndex, currentIndex - 1, _customize.Face).Value); + else + CheckWheel(); ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton("+", new Vector2(ImGui.GetFrameHeight()), "Select the next available option in order.", currentIndex >= _currentCount - 1 || npc)) UpdateValue(_set.Data(_currentIndex, currentIndex + 1, _customize.Face).Value); + else + CheckWheel(); + return; + + void CheckWheel() + { + if (currentIndex < 0 || !CaptureMouseWheel(ref currentIndex, 0, _currentCount)) + return; + + var data = _set.Data(_currentIndex, currentIndex, _customize.Face); + UpdateValue(data.Value); + } } private void DrawListSelector(CustomizeIndex index, bool indexedBy1) { - using var id = SetId(index); + using var id = SetId(index); using var bigGroup = ImRaii.Group(); using (_ = ImRaii.Disabled(_locked)) @@ -122,29 +141,31 @@ public partial class CustomizationDrawer private void ListCombo0() { ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale); - var current = _currentByte.Value; - using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{current + 1}"); - - if (!combo) - return; - - for (var i = 0; i < _currentCount; ++i) + var current = (int)_currentByte.Value; + using (var combo = ImRaii.Combo("##combo", $"{_currentOption} #{current + 1}")) { - if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == current)) - UpdateValue((CustomizeValue)i); + if (combo) + + for (var i = 0; i < _currentCount; ++i) + { + if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == current)) + UpdateValue((CustomizeValue)i); + } } + + if (CaptureMouseWheel(ref current, 0, _currentCount)) + UpdateValue((CustomizeValue)current); } private void ListInputInt0() { var tmp = _currentByte.Value + 1; ImGui.SetNextItemWidth(_inputIntSize); + var cap = ImGui.GetIO().KeyCtrl ? byte.MaxValue + 1 : _currentCount; if (ImGui.InputInt("##text", ref tmp, 1, 1)) { - var newValue = (CustomizeValue)(ImGui.GetIO().KeyCtrl - ? Math.Clamp(tmp, 1, byte.MaxValue + 1) - : Math.Clamp(tmp, 1, _currentCount)); - UpdateValue(newValue - 1); + var newValue = Math.Clamp(tmp, 1, cap); + UpdateValue((CustomizeValue)(newValue - 1)); } ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]\n" @@ -154,28 +175,29 @@ public partial class CustomizationDrawer private void ListCombo1() { ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale); - var current = _currentByte.Value; - using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{current}"); - - if (!combo) - return; - - for (var i = 1; i <= _currentCount; ++i) + var current = (int)_currentByte.Value; + using (var combo = ImRaii.Combo("##combo", $"{_currentOption} #{current}")) { - if (ImGui.Selectable($"{_currentOption} #{i}##combo", i == current)) - UpdateValue((CustomizeValue)i); + if (combo) + for (var i = 1; i <= _currentCount; ++i) + { + if (ImGui.Selectable($"{_currentOption} #{i}##combo", i == current)) + UpdateValue((CustomizeValue)i); + } } + + if (CaptureMouseWheel(ref current, 1, _currentCount)) + UpdateValue((CustomizeValue)current); } private void ListInputInt1() { var tmp = (int)_currentByte.Value; ImGui.SetNextItemWidth(_inputIntSize); + var (offset, cap) = ImGui.GetIO().KeyCtrl ? (0, byte.MaxValue) : (1, _currentCount); if (ImGui.InputInt("##text", ref tmp, 1, 1)) { - var newValue = (CustomizeValue)(ImGui.GetIO().KeyCtrl - ? Math.Clamp(tmp, 0, byte.MaxValue) - : Math.Clamp(tmp, 1, _currentCount)); + var newValue = (CustomizeValue)Math.Clamp(tmp, offset, cap); UpdateValue(newValue); } @@ -183,6 +205,26 @@ public partial class CustomizationDrawer + "Hold Control to force updates with invalid/unknown options at your own risk."); } + private static bool CaptureMouseWheel(ref int value, int offset, int cap) + { + if (!ImGui.IsItemHovered()) + return false; + + ImGuiInternal.ItemSetUsingMouseWheel(); + + var mw = (int)ImGui.GetIO().MouseWheel; + if (mw == 0) + return false; + + value -= offset; + value = mw switch + { + < 0 => offset + (value + cap + mw) % cap, + _ => offset + (value + mw) % cap, + }; + return true; + } + // Draw a customize checkbox. private void DrawCheckbox(CustomizeIndex idx) { diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index e255c17..f5e3272 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -1,7 +1,6 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Glamourer.Automation; -using Glamourer.GameData; using Glamourer.Designs; using Glamourer.Events; using Glamourer.Services; @@ -25,7 +24,7 @@ public abstract class DesignComboBase : FilterComboCache>, protected DesignComboBase(Func>> generator, Logger log, DesignChanged designChanged, TabSelected tabSelected, EphemeralConfig config, DesignColors designColors) - : base(generator, log) + : base(generator, MouseWheelType.Unmodified, log) { _designChanged = designChanged; TabSelected = tabSelected; @@ -38,7 +37,10 @@ public abstract class DesignComboBase : FilterComboCache>, => _config.IncognitoMode; void IDisposable.Dispose() - => _designChanged.Unsubscribe(OnDesignChange); + { + _designChanged.Unsubscribe(OnDesignChange); + GC.SuppressFinalize(this); + } protected override bool DrawSelectable(int globalIdx, bool selected) { @@ -118,63 +120,87 @@ public abstract class DesignComboBase : FilterComboCache>, { case DesignChanged.Type.Created: case DesignChanged.Type.Renamed: - Cleanup(); - break; + case DesignChanged.Type.ChangedColor: case DesignChanged.Type.Deleted: - Cleanup(); - if (CurrentSelection?.Item1 == design) + case DesignChanged.Type.QuickDesignBar: + var priorState = IsInitialized; + if (priorState) + Cleanup(); + CurrentSelectionIdx = Items.IndexOf(s => ReferenceEquals(s.Item1, CurrentSelection?.Item1)); + if (CurrentSelectionIdx >= 0) { - CurrentSelectionIdx = Items.Count > 0 ? 0 : -1; - CurrentSelection = Items[CurrentSelectionIdx]; + CurrentSelection = Items[CurrentSelectionIdx]; + } + else if (Items.Count > 0) + { + CurrentSelectionIdx = 0; + CurrentSelection = Items[0]; + } + else + { + CurrentSelection = null; } + if (!priorState) + Cleanup(); break; } } } -public sealed class DesignCombo : DesignComboBase +public abstract class DesignCombo : DesignComboBase { - private readonly DesignManager _manager; - - public DesignCombo(DesignManager designs, DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, TabSelected tabSelected, - EphemeralConfig config, DesignColors designColors) - : base(() => designs.Designs - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) - .OrderBy(d => d.Item2) - .ToList(), log, designChanged, tabSelected, config, designColors) + protected DesignCombo(Logger log, DesignChanged designChanged, TabSelected tabSelected, + EphemeralConfig config, DesignColors designColors, Func>> generator) + : base(generator, log, designChanged, tabSelected, config, designColors) { - _manager = designs; - if (designs.Designs.Count == 0) + if (Items.Count == 0) return; CurrentSelection = Items[0]; CurrentSelectionIdx = 0; + base.Cleanup(); } public Design? Design => CurrentSelection?.Item1; public void Draw(float width) - { - Draw(Design, (Incognito ? Design?.Incognito : Design?.Name.Text) ?? string.Empty, width); - if (ImGui.IsItemHovered() && _manager.Designs.Count > 1) - { - var mouseWheel = -(int)ImGui.GetIO().MouseWheel % _manager.Designs.Count; - CurrentSelectionIdx = mouseWheel switch - { - < 0 when CurrentSelectionIdx < 0 => _manager.Designs.Count - 1 + mouseWheel, - < 0 => (CurrentSelectionIdx + _manager.Designs.Count + mouseWheel) % _manager.Designs.Count, - > 0 when CurrentSelectionIdx < 0 => mouseWheel, - > 0 => (CurrentSelectionIdx + mouseWheel) % _manager.Designs.Count, - _ => CurrentSelectionIdx, - }; - CurrentSelection = Items[CurrentSelectionIdx]; - } - } + => Draw(Design, (Incognito ? Design?.Incognito : Design?.Name.Text) ?? string.Empty, width); } -public sealed class RevertDesignCombo : DesignComboBase, IDisposable +public sealed class QuickDesignCombo( + DesignManager designs, + DesignFileSystem fileSystem, + Logger log, + DesignChanged designChanged, + TabSelected tabSelected, + EphemeralConfig config, + DesignColors designColors) + : DesignCombo(log, designChanged, tabSelected, config, designColors, () => + [ + .. designs.Designs + .Where(d => d.QuickDesign) + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .OrderBy(d => d.Item2), + ]); + +public sealed class LinkDesignCombo( + DesignManager designs, + DesignFileSystem fileSystem, + Logger log, + DesignChanged designChanged, + TabSelected tabSelected, + EphemeralConfig config, + DesignColors designColors) + : DesignCombo(log, designChanged, tabSelected, config, designColors, () => + [ + .. designs.Designs + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .OrderBy(d => d.Item2), + ]); + +public sealed class RevertDesignCombo : DesignComboBase { public const int RevertDesignIndex = -1228; public readonly Design RevertDesign; diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 78e813e..1ad28b0 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -24,7 +24,7 @@ public sealed class DesignQuickBar : Window, IDisposable : ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing; private readonly Configuration _config; - private readonly DesignCombo _designCombo; + private readonly QuickDesignCombo _designCombo; private readonly StateManager _stateManager; private readonly AutoDesignApplier _autoDesignApplier; private readonly ObjectManager _objects; @@ -34,7 +34,7 @@ public sealed class DesignQuickBar : Window, IDisposable private DateTime _keyboardToggle = DateTime.UnixEpoch; private int _numButtons; - public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState, + public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, ObjectManager objects, AutoDesignApplier autoDesignApplier) : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking) { @@ -299,7 +299,8 @@ public sealed class DesignQuickBar : Window, IDisposable (true, false) => 3, (false, false) => 2, }; - Size = new Vector2((7 + _numButtons) * ImGui.GetFrameHeight() + _numButtons * ImGui.GetStyle().ItemInnerSpacing.X, ImGui.GetFrameHeight()); + Size = new Vector2((7 + _numButtons) * ImGui.GetFrameHeight() + _numButtons * ImGui.GetStyle().ItemInnerSpacing.X, + ImGui.GetFrameHeight()); return Size.Value.X; } } diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index 39d63dc..3a791d0 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -10,7 +10,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, FavoriteManager _favorites) - : FilterComboColors(_comboWidth, CreateFunc(_stains, _favorites), Glamourer.Log) + : FilterComboColors(_comboWidth, MouseWheelType.Unmodified, CreateFunc(_stains, _favorites), Glamourer.Log) { protected override bool DrawSelectable(int globalIdx, bool selected) { @@ -36,6 +36,9 @@ public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, Fa return base.DrawSelectable(globalIdx, selected); } + public override bool Draw(string label, uint color, string name, bool found, bool gloss, float previewWidth) + => base.Draw(label, color, name, found, gloss, previewWidth); + private static Func>> CreateFunc(DictStain stains, FavoriteManager favorites) => () => stains.Select(kvp => (kvp, favorites.Contains(kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp) diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index d9fd12a..a6f22b5 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -24,7 +24,7 @@ public sealed class ItemCombo : FilterComboCache public Variant CustomVariant { get; private set; } public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites) - : base(() => GetItems(favorites, items, slot), log) + : base(() => GetItems(favorites, items, slot), MouseWheelType.Unmodified, log) { _favorites = favorites; Label = GetLabel(gameData, slot); diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index 32f383b..b6c3218 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -17,7 +17,7 @@ public sealed class WeaponCombo : FilterComboCache private float _innerWidth; public WeaponCombo(ItemManager items, FullEquipType type, Logger log) - : base(() => GetWeapons(items, type), log) + : base(() => GetWeapons(items, type), MouseWheelType.Unmodified, log) { Label = GetLabel(type); SearchByParts = true; diff --git a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs index 49feae9..530e04a 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs @@ -16,7 +16,7 @@ public sealed class HumanNpcCombo( DictBNpc bNpcs, HumanModelList humans, Logger log) - : FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)>(() => CreateList(modelCharaDict, bNpcNames, bNpcs, humans), log) + : FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)>(() => CreateList(modelCharaDict, bNpcNames, bNpcs, humans), MouseWheelType.None, log) { protected override string ToString((string Name, ObjectKind Kind, uint[] Ids) obj) => obj.Name; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 0387d58..2f4e1d8 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -429,7 +429,7 @@ public class SetPanel( } private sealed class JobGroupCombo(AutoDesignManager manager, JobService jobs, Logger log) - : FilterComboCache(() => jobs.JobGroups.Values.ToList(), log) + : FilterComboCache(() => jobs.JobGroups.Values.ToList(), MouseWheelType.None, log) { public void Draw(AutoDesignSet set, AutoDesign design, int autoDesignIndex) { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs index d00fea8..72d717c 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -10,8 +10,15 @@ public sealed class DesignColorCombo(DesignColors _designColors, bool _skipAutom FilterComboCache(_skipAutomatic ? _designColors.Keys.OrderBy(k => k) : _designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName), - Glamourer.Log) + MouseWheelType.Shift, Glamourer.Log) { + protected override void OnMouseWheel(string preview, ref int current, int steps) + { + if (CurrentSelectionIdx < 0) + CurrentSelectionIdx = Items.IndexOf(preview); + base.OnMouseWheel(preview, ref current, steps); + } + protected override bool DrawSelectable(int globalIdx, bool selected) { var isAutomatic = !_skipAutomatic && globalIdx == 0; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index ecbf0e7..e56ec00 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -125,10 +125,23 @@ public class DesignDetailTab Glamourer.Messager.NotificationMessage(ex, ex.Message, "Could not rename or move design", NotificationType.Error); } + ImGuiUtil.DrawFrameColumn("Quick Design Bar"); + ImGui.TableNextColumn(); + if (ImGui.RadioButton("Display##qdb", _selector.Selected.QuickDesign)) + _manager.SetQuickDesign(_selector.Selected!, true); + var hovered = ImGui.IsItemHovered(); + ImGui.SameLine(); + if (ImGui.RadioButton("Hide##qdb", !_selector.Selected.QuickDesign)) + _manager.SetQuickDesign(_selector.Selected!, false); + if (hovered || ImGui.IsItemHovered()) + ImGui.SetTooltip("Display or hide this design in your quick design bar."); + ImGuiUtil.DrawFrameColumn("Color"); var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color; ImGui.TableNextColumn(); - if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design. Right-Click to revert to automatic coloring.", + if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design.\n" + + "Right-Click to revert to automatic coloring.\n" + + "Hold Control and scroll the mousewheel to scroll.", width.X - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight()) && _colorCombo.CurrentSelection != null) { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index f45f936..7b996e8 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -10,7 +10,7 @@ using OtterGui.Services; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, DesignCombo _combo) : IUiService +public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, LinkDesignCombo _combo) : IUiService { private int _dragDropIndex = -1; private LinkOrder _dragDropOrder = LinkOrder.None; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs index 4cd899f..53501b0 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs @@ -11,7 +11,7 @@ namespace Glamourer.Gui.Tabs.DesignTab; public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)> { public ModCombo(PenumbraService penumbra, Logger log) - : base(penumbra.GetMods, log) + : base(penumbra.GetMods, MouseWheelType.None, log) { SearchByParts = false; } diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 474c2f4..6e5b1b1 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -3,6 +3,7 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using ImGuiNET; using OtterGui; +using OtterGui.Filesystem; using OtterGui.Raii; namespace Glamourer.Gui.Tabs.DesignTab; @@ -21,6 +22,7 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager DrawDesignList(); var offset = DrawMultiTagger(width); DrawMultiColor(width, offset); + DrawMultiQuickDesignBar(offset); } private void DrawDesignList() @@ -35,6 +37,8 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager var sizeMods = availableSizePercent * 35; var sizeFolders = availableSizePercent * 65; + _numQuickDesignEnabled = 0; + _numDesigns = 0; using (var table = ImRaii.Table("mods", 3, ImGuiTableFlags.RowBg)) { if (!table) @@ -61,15 +65,24 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager ImGui.TableNextColumn(); ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(fullName); + + if (path is not DesignFileSystem.Leaf l2) + continue; + + ++_numDesigns; + if (l2.Value.QuickDesign) + ++_numQuickDesignEnabled; } } ImGui.Separator(); } - private string _tag = string.Empty; - private readonly List _addDesigns = []; - private readonly List<(Design, int)> _removeDesigns = []; + private string _tag = string.Empty; + private int _numQuickDesignEnabled; + private int _numDesigns; + private readonly List _addDesigns = []; + private readonly List<(Design, int)> _removeDesigns = []; private float DrawMultiTagger(Vector2 width) { @@ -110,6 +123,30 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager return offset; } + private void DrawMultiQuickDesignBar(float offset) + { + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Multi QDB:"); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var diff = _numDesigns - _numQuickDesignEnabled; + var tt = diff == 0 + ? $"All {_numDesigns} selected designs are already displayed in the quick design bar." + : $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs."; + if (ImGuiUtil.DrawDisabledButton("Display Selected Designs in QDB", buttonWidth, tt, diff == 0)) + foreach(var design in _selector.SelectedPaths.OfType()) + _editor.SetQuickDesign(design.Value, true); + + ImGui.SameLine(); + tt = _numQuickDesignEnabled == 0 + ? $"All {_numDesigns} selected designs are already hidden in the quick design bar." + : $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs."; + if (ImGuiUtil.DrawDisabledButton("Hide Selected Designs in QDB", buttonWidth, tt, _numQuickDesignEnabled == 0)) + foreach (var design in _selector.SelectedPaths.OfType()) + _editor.SetQuickDesign(design.Value, false); + ImGui.Separator(); + } + private void DrawMultiColor(Vector2 width, float offset) { ImGui.AlignTextToFramePadding(); diff --git a/Glamourer/Gui/Tabs/NpcCombo.cs b/Glamourer/Gui/Tabs/NpcCombo.cs index 4b1274c..86eb766 100644 --- a/Glamourer/Gui/Tabs/NpcCombo.cs +++ b/Glamourer/Gui/Tabs/NpcCombo.cs @@ -4,7 +4,7 @@ using OtterGui.Widgets; namespace Glamourer.Gui.Tabs; public class NpcCombo(NpcCustomizeSet npcCustomizeSet) - : FilterComboCache(npcCustomizeSet, Glamourer.Log) + : FilterComboCache(npcCustomizeSet, MouseWheelType.None, Glamourer.Log) { protected override string ToString(NpcData obj) => obj.Name; diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index dd06e3a..a5cc0ee 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -146,7 +146,8 @@ public static class ServiceManagerA .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/OtterGui b/OtterGui index 04eb0b5..2d8a03e 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 04eb0b5ed3930e9cb87ad00dffa9c8be90b58bb3 +Subproject commit 2d8a03eebd80e19c6936a28ab2e3a8c164cc17f3 diff --git a/Penumbra.GameData b/Penumbra.GameData index 260ac69..fb18c80 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 260ac69cd6f17050eaf9b7e0b5ce9a8843edfee4 +Subproject commit fb18c80551203a1cf6cd01ec2b0850fbc8e44240 From 42ac507b863c2884816cc9706dc6a68e77ca172c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 5 Feb 2024 16:21:21 +0100 Subject: [PATCH 228/786] Improve festivals and add dolphins. --- Glamourer/Services/CodeService.cs | 11 ++++++++--- Glamourer/State/FunEquipSet.cs | 11 +++++++++-- Glamourer/State/FunModule.cs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 77fdcaa..71f1a45 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -29,10 +29,11 @@ public class CodeService World = 0x010000, Elephants = 0x020000, Crown = 0x040000, + Dolphins = 0x080000, } - public const CodeFlag DyeCodes = CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants; - public const CodeFlag GearCodes = CodeFlag.Emperor | CodeFlag.World | CodeFlag.Elephants; + public const CodeFlag DyeCodes = CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins; + public const CodeFlag GearCodes = CodeFlag.Emperor | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins; public const CodeFlag RaceCodes = CodeFlag.OopsHyur | CodeFlag.OopsElezen @@ -114,7 +115,9 @@ public class CodeService return null; var badFlags = ~GetMutuallyExclusive(flag); - return v => _enabled = v ? (_enabled | flag) & badFlags : _enabled & ~flag;; + return v => _enabled = v ? (_enabled | flag) & badFlags : _enabled & ~flag; + + ; } public CodeFlag GetCode(string name) @@ -173,6 +176,7 @@ public class CodeService CodeFlag.World => (DyeCodes | GearCodes) & ~CodeFlag.World, CodeFlag.Elephants => (DyeCodes | GearCodes) & ~CodeFlag.Elephants, CodeFlag.Crown => 0, + CodeFlag.Dolphins => (DyeCodes | GearCodes) & ~CodeFlag.Dolphins, _ => 0, }; @@ -198,6 +202,7 @@ public class CodeService CodeFlag.World => [ 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 ], CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ], CodeFlag.Crown => [ 0x43, 0x8E, 0x34, 0x56, 0x24, 0xC9, 0xC6, 0xDE, 0x2A, 0x68, 0x3A, 0x5D, 0xF5, 0x8E, 0xCB, 0xEF, 0x0D, 0x4D, 0x5B, 0xDC, 0x23, 0xF9, 0xF9, 0xBD, 0xD9, 0x60, 0xAD, 0x53, 0xC5, 0xA0, 0x33, 0xC4 ], + 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 ], _ => [], }; } diff --git a/Glamourer/State/FunEquipSet.cs b/Glamourer/State/FunEquipSet.cs index bb56fcb..91e6419 100644 --- a/Glamourer/State/FunEquipSet.cs +++ b/Glamourer/State/FunEquipSet.cs @@ -77,7 +77,8 @@ internal class FunEquipSet new Group(0000, 0, 0137, 2, 0000, 0, 0000, 0, 0000, 0), // Wailing Spirit new Group(0232, 1, 0232, 1, 0279, 1, 0232, 1, 0232, 1), // Eerie Attire new Group(0232, 1, 6036, 1, 0279, 1, 0232, 1, 0232, 1), // Vampire - new Group(0505, 6, 0505, 6, 0505, 6, 0505, 6, 0505, 6) // Manusya Casting + new Group(0505, 6, 0505, 6, 0505, 6, 0505, 6, 0505, 6), // Manusya Casting + new Group(6147, 1, 6147, 1, 6147, 1, 6147, 1, 6147, 1) // Tonberry ); public static readonly FunEquipSet AprilFirst = new @@ -94,7 +95,13 @@ internal class FunEquipSet new Group(0159, 1, 0000, 0, 0000, 0, 0000, 0, 0000, 0), // Slime Crown new Group(6117, 1, 6117, 1, 6117, 1, 6117, 1, 6117, 1), // Clown new Group(6169, 3, 6169, 3, 0279, 1, 6169, 3, 6169, 3), // Chocobo Pajama - new Group(6169, 2, 6169, 2, 0279, 2, 6169, 2, 6169, 2) // Cactuar Pajama + new Group(6169, 2, 6169, 2, 0279, 2, 6169, 2, 6169, 2), // Cactuar Pajama + new Group(6023, 1, 6023, 1, 0000, 0, 0000, 0, 0000, 0), // Swine + new Group(5040, 1, 0000, 0, 0000, 0, 0000, 0, 0000, 0), // Namazu only + new Group(5040, 1, 6023, 1, 0000, 0, 0000, 0, 0000, 0), // Namazu lean + new Group(5040, 1, 6023, 1, 0000, 0, 0000, 0, 0000, 0), // Namazu chonk + new Group(6182, 1, 6182, 1, 0000, 0, 0000, 0, 0000, 0), // Imp + new Group(6147, 1, 6147, 1, 6147, 1, 6147, 1, 6147, 1) // Tonberry ); private FunEquipSet(params Group[] groups) diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 14fc65a..7ddc42f 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -116,6 +116,7 @@ public unsafe class FunModule : IDisposable SetRandomItem(slot, ref armor); break; case CodeService.CodeFlag.Elephants: + case CodeService.CodeFlag.Dolphins: case CodeService.CodeFlag.World when actor.Index != 0: KeepOldArmor(actor, slot, ref armor); break; @@ -168,6 +169,10 @@ public unsafe class FunModule : IDisposable SetElephant(EquipSlot.Body, ref armor[1], stainId); SetElephant(EquipSlot.Head, ref armor[0], stainId); break; + case CodeService.CodeFlag.Dolphins: + SetDolphin(EquipSlot.Body, ref armor[1]); + SetDolphin(EquipSlot.Head, ref armor[0]); + break; case CodeService.CodeFlag.World when actor.Index != 0: _worldSets.Apply(actor, _rng, armor); break; @@ -227,6 +232,32 @@ public unsafe class FunModule : IDisposable 7, // Rose Pink ]; + private static IReadOnlyList DolphinBodies + => + [ + new CharacterArmor(6089, 1, 4), // Toad + new CharacterArmor(6089, 1, 4), // Toad + new CharacterArmor(6089, 1, 4), // Toad + new CharacterArmor(6023, 1, 4), // Swine + new CharacterArmor(6023, 1, 4), // Swine + new CharacterArmor(6023, 1, 4), // Swine + new CharacterArmor(6133, 1, 4), // Gaja + new CharacterArmor(6182, 1, 3), // Imp + new CharacterArmor(6182, 1, 3), // Imp + new CharacterArmor(6182, 1, 4), // Imp + new CharacterArmor(6182, 1, 4), // Imp + ]; + + private void SetDolphin(EquipSlot slot, ref CharacterArmor armor) + { + armor = slot switch + { + EquipSlot.Body => DolphinBodies[_rng.Next(0, DolphinBodies.Count - 1)], + EquipSlot.Head => new CharacterArmor(5040, 1, 0), + _ => armor, + }; + } + private void SetElephant(EquipSlot slot, ref CharacterArmor armor, StainId stainId) { armor = slot switch From b5b9289dc2c1b19cbf9fec7a095ec35a6d230050 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 Feb 2024 16:42:43 +0100 Subject: [PATCH 229/786] Change mousewheel to ctrl, current material state. --- Glamourer/Designs/Design.cs | 2 + Glamourer/Designs/DesignBase.cs | 50 +++- Glamourer/Designs/DesignConverter.cs | 38 ++- Glamourer/Designs/DesignEditor.cs | 9 + Glamourer/GameData/CustomizeParameterValue.cs | 7 + .../CustomizationDrawer.Simple.cs | 2 +- .../Gui/Equipment/GlamourerColorCombo.cs | 2 +- Glamourer/Gui/Equipment/ItemCombo.cs | 2 +- Glamourer/Gui/Equipment/WeaponCombo.cs | 2 +- Glamourer/Gui/Materials/MaterialDrawer.cs | 149 ++++------- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 2 +- .../Gui/Tabs/DesignTab/DesignColorCombo.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 20 ++ Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 12 +- Glamourer/Interop/Material/MaterialManager.cs | 104 ++++++-- .../Interop/Material/MaterialValueIndex.cs | 148 ++--------- .../Interop/Material/MaterialValueManager.cs | 237 +++++++++++++++++- Glamourer/State/InternalStateEditor.cs | 10 +- Glamourer/State/StateApplier.cs | 13 +- Glamourer/State/StateEditor.cs | 22 +- 20 files changed, 537 insertions(+), 296 deletions(-) diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 20d85e7..4e1e72e 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -72,6 +72,7 @@ public sealed class Design : DesignBase, ISavable ["Equipment"] = SerializeEquipment(), ["Customize"] = SerializeCustomize(), ["Parameters"] = SerializeParameters(), + ["Materials"] = SerializeMaterials(), ["Mods"] = SerializeMods(), ["Links"] = Links.Serialize(), }; @@ -136,6 +137,7 @@ public sealed class Design : DesignBase, ISavable LoadEquip(items, json["Equipment"], design, design.Name, true); LoadMods(json["Mods"], design); LoadParameters(json["Parameters"], design, design.Name); + LoadMaterials(json["Materials"], design, design.Name); LoadLinks(linkLoader, json["Links"], design); design.Color = json["Color"]?.ToObject() ?? string.Empty; return design; diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 9699bc2..8873339 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -14,7 +14,7 @@ public class DesignBase { public const int FileVersion = 1; - private DesignData _designData = new(); + private DesignData _designData = new(); private readonly DesignMaterialManager _materials = new(); /// For read-only information about custom material color changes. @@ -86,9 +86,9 @@ public class DesignBase internal CustomizeFlag ApplyCustomizeRaw => _applyCustomize; - internal EquipFlag ApplyEquip = EquipFlagExtensions.All; - internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; - internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState; + internal EquipFlag ApplyEquip = EquipFlagExtensions.All; + internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; + internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState; private bool _writeProtected; public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize) @@ -124,7 +124,6 @@ public class DesignBase _writeProtected = value; return true; - } public bool DoApplyEquip(EquipSlot slot) @@ -244,6 +243,7 @@ public class DesignBase ["Equipment"] = SerializeEquipment(), ["Customize"] = SerializeCustomize(), ["Parameters"] = SerializeParameters(), + ["Materials"] = SerializeMaterials(), }; return ret; } @@ -362,6 +362,45 @@ public class DesignBase return ret; } + protected JObject SerializeMaterials() + { + var ret = new JObject(); + foreach (var (key, value) in Materials) + ret[key.ToString("X16")] = JToken.FromObject(value); + return ret; + } + + protected static void LoadMaterials(JToken? materials, DesignBase design, string name) + { + if (materials is not JObject obj) + return; + + design.GetMaterialDataRef().Clear(); + foreach (var (key, value) in obj.Properties().Zip(obj.PropertyValues())) + { + try + { + var k = uint.Parse(key.Name, NumberStyles.HexNumber); + var v = value.ToObject(); + if (!MaterialValueIndex.FromKey(k, out var idx)) + { + Glamourer.Messager.NotificationMessage($"Invalid material value key {k} for design {name}, skipped.", + NotificationType.Warning); + continue; + } + + if (!design.GetMaterialDataRef().TryAddValue(MaterialValueIndex.FromKey(k), v)) + Glamourer.Messager.NotificationMessage($"Duplicate material value key {k} for design {name}, skipped.", + NotificationType.Warning); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Error parsing material value for design {name}, skipped", + NotificationType.Warning); + } + } + } + #endregion #region Deserialization @@ -382,6 +421,7 @@ public class DesignBase LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true); LoadEquip(items, json["Equipment"], ret, "Temporary Design", true); LoadParameters(json["Parameters"], ret, "Temporary Design"); + LoadMaterials(json["Materials"], ret, "Temporary Design"); return ret; } diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index be70672..9b9ebfc 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -1,4 +1,5 @@ using Glamourer.Designs.Links; +using Glamourer.Interop.Material; using Glamourer.Services; using Glamourer.State; using Glamourer.Utility; @@ -6,6 +7,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; +using Penumbra.GameData.Files; using Penumbra.GameData.Structs; namespace Glamourer.Designs; @@ -38,22 +40,23 @@ public class DesignConverter( => ShareBase64(ShareJObject(design)); public string ShareBase64(ActorState state, in ApplicationRules rules) - => ShareBase64(state.ModelData, rules); + => ShareBase64(state.ModelData, state.Materials, rules); - public string ShareBase64(in DesignData data, in ApplicationRules rules) + public string ShareBase64(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules) { - var design = Convert(data, rules); + var design = Convert(data, materials, rules); return ShareBase64(ShareJObject(design)); } public DesignBase Convert(ActorState state, in ApplicationRules rules) - => Convert(state.ModelData, rules); + => Convert(state.ModelData, state.Materials, rules); - public DesignBase Convert(in DesignData data, in ApplicationRules rules) + public DesignBase Convert(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules) { var design = _designs.CreateTemporary(); rules.Apply(design); design.SetDesignData(_customize, data); + ComputeMaterials(design.GetMaterialDataRef(), materials, rules.Equip); return design; } @@ -181,4 +184,29 @@ public class DesignConverter( yield return (EquipSlot.OffHand, oh, offhand.Stain); } + + private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials, + EquipFlag equipFlags = EquipFlagExtensions.All) + { + foreach (var (key, value) in materials.Values) + { + var idx = MaterialValueIndex.FromKey(key); + if (idx.RowIndex >= MtrlFile.ColorTable.NumRows) + continue; + if (idx.MaterialIndex >= MaterialService.MaterialsPerModel) + continue; + + var slot = idx.DrawObject switch + { + MaterialValueIndex.DrawObjectType.Human => idx.SlotIndex < 10 ? ((uint)idx.SlotIndex).ToEquipSlot() : EquipSlot.Unknown, + MaterialValueIndex.DrawObjectType.Mainhand when idx.SlotIndex == 0 => EquipSlot.MainHand, + MaterialValueIndex.DrawObjectType.Offhand when idx.SlotIndex == 0 => EquipSlot.OffHand, + _ => EquipSlot.Unknown, + }; + if (slot is EquipSlot.Unknown || (slot.ToBothFlags() & equipFlags) == 0) + continue; + + manager.AddOrUpdateValue(idx, value.Convert()); + } + } } diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index ab258d7..9640c71 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -1,6 +1,7 @@ using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; +using Glamourer.Interop.Material; using Glamourer.Services; using Glamourer.State; using Penumbra.GameData.Enums; @@ -250,6 +251,14 @@ public class DesignEditor( foreach (var parameter in CustomizeParameterExtensions.AllFlags.Where(other.DoApplyParameter)) ChangeCustomizeParameter(design, parameter, other.DesignData.Parameters[parameter]); + + foreach (var (key, value) in other.Materials) + { + if (!value.Enabled) + continue; + + design.GetMaterialDataRef().AddOrUpdateValue(MaterialValueIndex.FromKey(key), value); + } } /// Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. diff --git a/Glamourer/GameData/CustomizeParameterValue.cs b/Glamourer/GameData/CustomizeParameterValue.cs index 3bfdf99..87ab851 100644 --- a/Glamourer/GameData/CustomizeParameterValue.cs +++ b/Glamourer/GameData/CustomizeParameterValue.cs @@ -62,4 +62,11 @@ public static class VectorExtensions [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] public static bool NearEqual(this CustomizeParameterValue lhs, CustomizeParameterValue rhs, float eps = 1e-9f) => NearEqual(lhs.InternalQuadruple, rhs.InternalQuadruple, eps); + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + public static bool NearEqual(this float lhs, float rhs, float eps = 1e-5f) + { + var diff = lhs - rhs; + return diff < 0 ? diff > -eps : diff < eps; + } } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 3a8e021..e684554 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -207,7 +207,7 @@ public partial class CustomizationDrawer private static bool CaptureMouseWheel(ref int value, int offset, int cap) { - if (!ImGui.IsItemHovered()) + if (!ImGui.IsItemHovered() || !ImGui.GetIO().KeyCtrl) return false; ImGuiInternal.ItemSetUsingMouseWheel(); diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index 3a791d0..d3fde9f 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -10,7 +10,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, FavoriteManager _favorites) - : FilterComboColors(_comboWidth, MouseWheelType.Unmodified, CreateFunc(_stains, _favorites), Glamourer.Log) + : FilterComboColors(_comboWidth, MouseWheelType.Control, CreateFunc(_stains, _favorites), Glamourer.Log) { protected override bool DrawSelectable(int globalIdx, bool selected) { diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index a6f22b5..24ff582 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -24,7 +24,7 @@ public sealed class ItemCombo : FilterComboCache public Variant CustomVariant { get; private set; } public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites) - : base(() => GetItems(favorites, items, slot), MouseWheelType.Unmodified, log) + : base(() => GetItems(favorites, items, slot), MouseWheelType.Control, log) { _favorites = favorites; Label = GetLabel(gameData, slot); diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index b6c3218..17abb24 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -17,7 +17,7 @@ public sealed class WeaponCombo : FilterComboCache private float _innerWidth; public WeaponCombo(ItemManager items, FullEquipType type, Logger log) - : base(() => GetWeapons(items, type), MouseWheelType.Unmodified, log) + : base(() => GetWeapons(items, type), MouseWheelType.Control, log) { Label = GetLabel(type); SearchByParts = true; diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index e4a612d..fb6a4ac 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -1,18 +1,20 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Designs; using Glamourer.Interop.Material; using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; +using OtterGui; using OtterGui.Services; +using Penumbra.GameData.Enums; using Penumbra.GameData.Files; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Materials; -public unsafe class MaterialDrawer(StateManager _stateManager) : IService +public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _designManager) : IService { private static readonly IReadOnlyList Types = [ @@ -23,135 +25,94 @@ public unsafe class MaterialDrawer(StateManager _stateManager) : IService private ActorState? _state; - public void DrawPanel(Actor actor) + public void DrawActorPanel(Actor actor) { if (!actor.IsCharacter || !_stateManager.GetOrCreate(actor, out _state)) return; - foreach (var type in Types) - { - var index = new MaterialValueIndex(type, 0, 0, 0, 0); - if (index.TryGetModel(actor, out var model)) - DrawModelType(model, index); - } - } - - private void DrawModelType(Model model, MaterialValueIndex sourceIndex) - { - using var tree = ImRaii.TreeNode(sourceIndex.DrawObject.ToString()); - if (!tree) + var model = actor.Model; + if (!model.IsHuman) return; - var names = model.AsCharacterBase->GetModelType() is CharacterBase.ModelType.Human - ? SlotNamesHuman - : SlotNames; - for (byte i = 0; i < model.AsCharacterBase->SlotCount; ++i) - { - var index = sourceIndex with { SlotIndex = i }; - DrawSlot(model, names, index); - } - } - - private void DrawSlot(Model model, IReadOnlyList names, MaterialValueIndex sourceIndex) - { - using var tree = ImRaii.TreeNode(names[sourceIndex.SlotIndex]); - if (!tree) + if (model.AsCharacterBase->SlotCount < 10) return; - for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) + // Humans should have at least 10 slots for the equipment types. Technically more. + foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) { - var index = sourceIndex with { MaterialIndex = i }; - var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + i; + var item = model.GetArmor(slot).ToWeapon(0); + DrawSlotMaterials(model, slot.ToName(), item, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte) idx, 0, 0)); + } + + var (mainhand, offhand, mh, oh) = actor.Model.GetWeapons(actor); + if (mainhand.IsWeapon && mainhand.AsCharacterBase->SlotCount > 0) + DrawSlotMaterials(mainhand, EquipSlot.MainHand.ToName(), mh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, 0, 0)); + if (offhand.IsWeapon && offhand.AsCharacterBase->SlotCount > 0) + DrawSlotMaterials(offhand, EquipSlot.OffHand.ToName(), oh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, 0, 0)); + } + + + private void DrawSlotMaterials(Model model, string name, CharacterWeapon drawData, MaterialValueIndex index) + { + var drawnMaterial = 1; + for (byte materialIndex = 0; materialIndex < MaterialService.MaterialsPerModel; ++materialIndex) + { + var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + materialIndex; if (*texture == null) continue; if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table)) continue; - DrawMaterial(ref table, index); + using var tree = ImRaii.TreeNode($"{name} Material #{drawnMaterial++}###{name}{materialIndex}"); + if (!tree) + continue; + + DrawMaterial(ref table, drawData, index with { MaterialIndex = materialIndex} ); } } - private void DrawMaterial(ref MtrlFile.ColorTable table, MaterialValueIndex sourceIndex) + private void DrawMaterial(ref MtrlFile.ColorTable table, CharacterWeapon drawData, MaterialValueIndex sourceIndex) { - using var tree = ImRaii.TreeNode($"Material {sourceIndex.MaterialIndex + 1}"); - if (!tree) - return; - for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i) { var index = sourceIndex with { RowIndex = i }; ref var row = ref table[i]; - DrawRow(ref row, index); + DrawRow(ref row, drawData, index); } } - private void DrawRow(ref MtrlFile.ColorTable.Row row, MaterialValueIndex sourceIndex) + private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index) { - var r = _state!.Materials.GetValues( - MaterialValueIndex.Min(sourceIndex.DrawObject, sourceIndex.SlotIndex, sourceIndex.MaterialIndex, sourceIndex.RowIndex), - MaterialValueIndex.Max(sourceIndex.DrawObject, sourceIndex.SlotIndex, sourceIndex.MaterialIndex, sourceIndex.RowIndex)); - - var highlightColor = ColorId.FavoriteStarOn.Value(); - - using var id = ImRaii.PushId(sourceIndex.RowIndex); - var index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Diffuse }; - var (diffuse, diffuseGame, changed) = MaterialValueManager.GetSpecific(r, index, out var d) - ? (d.Model, d.Game, true) - : (row.Diffuse, row.Diffuse, false); - using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed)) + using var id = ImRaii.PushId(index.RowIndex); + var changed = _state!.Materials.TryGetValue(index, out var value); + if (!changed) { - if (ImGui.ColorEdit3("Diffuse", ref diffuse, ImGuiColorEditFlags.NoInputs)) - _stateManager.ChangeMaterialValue(_state!, index, diffuse, diffuseGame, ApplySettings.Manual); + var internalRow = new ColorRow(row); + value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual); } - - index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Specular }; - (var specular, var specularGame, changed) = MaterialValueManager.GetSpecific(r, index, out var s) - ? (s.Model, s.Game, true) - : (row.Specular, row.Specular, false); + var applied = ImGui.ColorEdit3("Diffuse", ref value.Model.Diffuse, ImGuiColorEditFlags.NoInputs); ImGui.SameLine(); - using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed)) - { - if (ImGui.ColorEdit3("Specular", ref specular, ImGuiColorEditFlags.NoInputs)) - _stateManager.ChangeMaterialValue(_state!, index, specular, specularGame, ApplySettings.Manual); - } - - index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.Emissive }; - (var emissive, var emissiveGame, changed) = MaterialValueManager.GetSpecific(r, index, out var e) - ? (e.Model, e.Game, true) - : (row.Emissive, row.Emissive, false); + applied |= ImGui.ColorEdit3("Specular", ref value.Model.Specular, ImGuiColorEditFlags.NoInputs); ImGui.SameLine(); - using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed)) - { - if (ImGui.ColorEdit3("Emissive", ref emissive, ImGuiColorEditFlags.NoInputs)) - _stateManager.ChangeMaterialValue(_state!, index, emissive, emissiveGame, ApplySettings.Manual); - } - - index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.GlossStrength }; - (var glossStrength, var glossStrengthGame, changed) = MaterialValueManager.GetSpecific(r, index, out var g) - ? (g.Model.X, g.Game.X, true) - : (row.GlossStrength, row.GlossStrength, false); + applied |= ImGui.ColorEdit3("Emissive", ref value.Model.Emissive, ImGuiColorEditFlags.NoInputs); ImGui.SameLine(); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed)) - { - if (ImGui.DragFloat("Gloss", ref glossStrength, 0.1f)) - _stateManager.ChangeMaterialValue(_state!, index, new Vector3(glossStrength), new Vector3(glossStrengthGame), - ApplySettings.Manual); - } - - index = sourceIndex with { DataIndex = MaterialValueIndex.ColorTableIndex.SpecularStrength }; - (var specularStrength, var specularStrengthGame, changed) = MaterialValueManager.GetSpecific(r, index, out var ss) - ? (ss.Model.X, ss.Game.X, true) - : (row.SpecularStrength, row.SpecularStrength, false); + applied |= ImGui.DragFloat("Gloss", ref value.Model.GlossStrength, 0.1f); ImGui.SameLine(); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - using (ImRaii.PushColor(ImGuiCol.Text, highlightColor, changed)) + applied |= ImGui.DragFloat("Specular Strength", ref value.Model.SpecularStrength, 0.1f); + if (applied) + _stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual); + if (changed) { - if (ImGui.DragFloat("Specular Strength", ref specularStrength, 0.1f)) - _stateManager.ChangeMaterialValue(_state!, index, new Vector3(specularStrength), new Vector3(specularStrengthGame), - ApplySettings.Manual); + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value()); + ImGui.TextUnformatted(FontAwesomeIcon.UserEdit.ToIconString()); + } } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index ff6b798..90ff4b7 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -119,7 +119,7 @@ public class ActorPanel( if (ImGui.CollapsingHeader("Material Shit")) - _materialDrawer.DrawPanel(_actor); + _materialDrawer.DrawActorPanel(_actor); using var disabled = ImRaii.Disabled(transformationId != 0); if (_state.ModelData.IsHuman) DrawHumanPanel(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs index 72d717c..e59be09 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -10,7 +10,7 @@ public sealed class DesignColorCombo(DesignColors _designColors, bool _skipAutom FilterComboCache(_skipAutomatic ? _designColors.Keys.OrderBy(k => k) : _designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName), - MouseWheelType.Shift, Glamourer.Log) + MouseWheelType.Control, Glamourer.Log) { protected override void OnMouseWheel(string preview, ref int current, int steps) { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index a957975..13cd293 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -174,6 +174,25 @@ public class DesignPanel( _parameterDrawer.Draw(_manager, _selector.Selected!); } + private void DrawMaterialValues() + { + if (!_config.UseAdvancedParameters) + return; + + using var h = ImRaii.CollapsingHeader("Advanced Dyes"); + if (!h) + return; + + foreach (var ((key, value), i) in _selector.Selected!.Materials.WithIndex()) + { + using var id = ImRaii.PushId(i); + ImGui.TextUnformatted($"{key:X16}"); + ImGui.SameLine(); + var enabled = value.Enabled; + ImGui.Checkbox("Enabled", ref enabled); + } + } + private void DrawCustomizeApplication() { using var id = ImRaii.PushId("Customizations"); @@ -365,6 +384,7 @@ public class DesignPanel( DrawCustomize(); DrawEquipment(); DrawCustomizeParameters(); + DrawMaterialValues(); _designDetails.Draw(); DrawApplicationRules(); _modAssociations.Draw(); diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 77dafa9..eb61580 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -85,7 +85,7 @@ public class NpcPanel( try { var data = ToDesignData(); - var text = _converter.ShareBase64(data, ApplicationRules.NpcFromModifiers()); + var text = _converter.ShareBase64(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); ImGui.SetClipboardText(text); } catch (Exception ex) @@ -101,7 +101,7 @@ public class NpcPanel( ImGui.OpenPopup("Save as Design"); _newName = _selector.Selection.Name; var data = ToDesignData(); - _newDesign = _converter.Convert(data, ApplicationRules.NpcFromModifiers()); + _newDesign = _converter.Convert(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); } private void SaveDesignDrawPopup() @@ -195,7 +195,7 @@ public class NpcPanel( if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var design = _converter.Convert(ToDesignData(), ApplicationRules.NpcFromModifiers()); + var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); _state.ApplyDesign(state, design, ApplySettings.Manual); } } @@ -213,7 +213,7 @@ public class NpcPanel( if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var design = _converter.Convert(ToDesignData(), ApplicationRules.NpcFromModifiers()); + var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); _state.ApplyDesign(state, design, ApplySettings.Manual); } } @@ -247,7 +247,9 @@ public class NpcPanel( var colorName = color.Length == 0 ? DesignColors.AutomaticName : color; ImGui.TableNextColumn(); if (_colorCombo.Draw("##colorCombo", colorName, - "Associate a color with this NPC appearance. Right-Click to revert to automatic coloring.", + "Associate a color with this NPC appearance.\n" + + "Right-Click to revert to automatic coloring.\n" + + "Hold Control and scroll the mousewheel to scroll.", width - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight()) && _colorCombo.CurrentSelection != null) { diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 8cbe0c5..f35ee8a 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -6,6 +6,8 @@ using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Services; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Files; using Penumbra.GameData.Structs; namespace Glamourer.Interop.Material; @@ -19,6 +21,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable private int _lastSlot; + private readonly ThreadLocal> _deleteList = new(() => []); + public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra) { _stateManager = stateManager; @@ -39,7 +43,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable var (slotId, materialId) = FindMaterial(characterBase, material); if (!validType - || slotId == byte.MaxValue + || slotId > 9 + || type is not MaterialValueIndex.DrawObjectType.Human && slotId > 0 || !actor.Identifier(_actors, out var identifier) || !_stateManager.TryGetValue(identifier, out var state)) return; @@ -53,38 +58,60 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable if (!PrepareColorSet.TryGetColorTable(characterBase, material, stain, out var baseColorSet)) return; - for (var i = 0; i < values.Length; ++i) + var drawData = type switch { - var idx = MaterialValueIndex.FromKey(values[i].key); - var (oldGame, model, source) = values[i].Value; - ref var row = ref baseColorSet[idx.RowIndex]; - if (!idx.DataIndex.TryGetValue(row, out var newGame)) - continue; - - if (newGame == oldGame) - { - idx.DataIndex.SetValue(ref row, model); - } - else - { - switch (source.Base()) - { - case StateSource.Manual: - _stateManager.ChangeMaterialValue(state, idx, Vector3.Zero, Vector3.Zero, ApplySettings.Game); - --i; - break; - case StateSource.Fixed: - idx.DataIndex.SetValue(ref row, model); - state.Materials.UpdateValue(idx, new MaterialValueState(newGame, model, source), out _); - break; - } - } - } + MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId), + _ => GetTempSlot((Weapon*)characterBase), + }; + UpdateMaterialValues(state, values, drawData, ref baseColorSet); if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture)) ret = (nint)texture; } + /// 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) + { + var deleteList = _deleteList.Value!; + deleteList.Clear(); + for (var i = 0; i < values.Length; ++i) + { + var idx = MaterialValueIndex.FromKey(values[i].Key); + var materialValue = values[i].Value; + ref var row = ref colorTable[idx.RowIndex]; + var newGame = new ColorRow(row); + if (materialValue.EqualGame(newGame, drawData)) + materialValue.Model.Apply(ref row); + else + switch (materialValue.Source) + { + case StateSource.Pending: + materialValue.Model.Apply(ref row); + state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.Manual), + out _); + break; + case StateSource.IpcManual: + case StateSource.Manual: + deleteList.Add(idx); + break; + case StateSource.Fixed: + case StateSource.IpcFixed: + materialValue.Model.Apply(ref row); + state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, materialValue.Source), + out _); + break; + } + } + + foreach (var idx in deleteList) + _stateManager.ChangeMaterialValue(state, idx, default, ApplySettings.Game); + } + + /// + /// Find the index of a material by searching through a draw objects pointers. + /// Tries to take shortcuts for consecutive searches like when a character is newly created. + /// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private (byte SlotId, byte MaterialId) FindMaterial(CharacterBase* characterBase, MaterialResourceHandle* material) { @@ -119,6 +146,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable return (byte.MaxValue, byte.MaxValue); } + /// Find the type of the given draw object by checking the actors pointers. private static bool FindType(CharacterBase* characterBase, Actor actor, out MaterialValueIndex.DrawObjectType type) { type = MaterialValueIndex.DrawObjectType.Human; @@ -145,4 +173,26 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable return false; } + + /// We need to get the temporary set, variant and stain that is currently being set if it is available. + private CharacterWeapon GetTempSlot(Human* human, byte slotId) + { + if (human->ChangedEquipData == null) + return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0); + + return ((CharacterArmor*)human->ChangedEquipData + slotId * 3)->ToWeapon(0); + } + + /// + /// We need to get the temporary set, variant and stain that is currently being set if it is available. + /// Weapons do not change in skeleton id without being reconstructed, so this is not changeable data. + /// + private CharacterWeapon GetTempSlot(Weapon* weapon) + { + var changedData = *(void**)((byte*)weapon + 0x918); + if (changedData == null) + return new CharacterWeapon(weapon->ModelSetId, weapon->SecondaryId, (Variant)weapon->Variant, (StainId)weapon->ModelUnknown); + + return new CharacterWeapon(weapon->ModelSetId, *(SecondaryId*)changedData, ((Variant*)changedData)[2], ((StainId*)changedData)[3]); + } } diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index d2d8db1..47ddb7a 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -11,14 +11,13 @@ public readonly record struct MaterialValueIndex( MaterialValueIndex.DrawObjectType DrawObject, byte SlotIndex, byte MaterialIndex, - byte RowIndex, - MaterialValueIndex.ColorTableIndex DataIndex) + byte RowIndex) { public uint Key - => ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex, DataIndex); + => ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex); public bool Valid - => Validate(DrawObject) && ValidateSlot(SlotIndex) && ValidateMaterial(MaterialIndex) && ValidateRow(RowIndex) && Validate(DataIndex); + => Validate(DrawObject) && ValidateSlot(SlotIndex) && ValidateMaterial(MaterialIndex) && ValidateRow(RowIndex); public static bool FromKey(uint key, out MaterialValueIndex index) { @@ -96,7 +95,7 @@ public readonly record struct MaterialValueIndex( public unsafe bool TryGetColorTable(Texture** texture, out MtrlFile.ColorTable table) => DirectXTextureHelper.TryGetColorTable(*texture, out table); - public unsafe bool TryGetColorRow(Actor actor, out MtrlFile.ColorTable.Row row) + public bool TryGetColorRow(Actor actor, out MtrlFile.ColorTable.Row row) { if (!TryGetColorTable(actor, out var table)) { @@ -108,40 +107,16 @@ public readonly record struct MaterialValueIndex( return true; } - public unsafe bool TryGetValue(Actor actor, out Vector3 value) - { - if (!TryGetColorRow(actor, out var row)) - { - value = Vector3.Zero; - return false; - } - - value = DataIndex switch - { - ColorTableIndex.Diffuse => row.Diffuse, - ColorTableIndex.Specular => row.Specular, - ColorTableIndex.SpecularStrength => new Vector3(row.SpecularStrength, 0, 0), - ColorTableIndex.Emissive => row.Emissive, - ColorTableIndex.GlossStrength => new Vector3(row.GlossStrength, 0, 0), - ColorTableIndex.TileSet => new Vector3(row.TileSet), - ColorTableIndex.MaterialRepeat => new Vector3(row.MaterialRepeat, 0), - ColorTableIndex.MaterialSkew => new Vector3(row.MaterialSkew, 0), - _ => new Vector3(float.NaN), - }; - return !float.IsNaN(value.X); - } public static MaterialValueIndex FromKey(uint key) => new(key); - public static MaterialValueIndex Min(DrawObjectType drawObject = 0, byte slotIndex = 0, byte materialIndex = 0, byte rowIndex = 0, - ColorTableIndex dataIndex = 0) - => new(drawObject, slotIndex, materialIndex, rowIndex, dataIndex); + public static MaterialValueIndex Min(DrawObjectType drawObject = 0, byte slotIndex = 0, byte materialIndex = 0, byte rowIndex = 0) + => new(drawObject, slotIndex, materialIndex, rowIndex); public static MaterialValueIndex Max(DrawObjectType drawObject = (DrawObjectType)byte.MaxValue, byte slotIndex = byte.MaxValue, - byte materialIndex = byte.MaxValue, byte rowIndex = byte.MaxValue, - ColorTableIndex dataIndex = (ColorTableIndex)byte.MaxValue) - => new(drawObject, slotIndex, materialIndex, rowIndex, dataIndex); + byte materialIndex = byte.MaxValue, byte rowIndex = byte.MaxValue) + => new(drawObject, slotIndex, materialIndex, rowIndex); public enum DrawObjectType : byte { @@ -150,18 +125,6 @@ public readonly record struct MaterialValueIndex( Offhand, }; - public enum ColorTableIndex : byte - { - Diffuse, - Specular, - SpecularStrength, - Emissive, - GlossStrength, - TileSet, - MaterialRepeat, - MaterialSkew, - } - public static bool Validate(DrawObjectType type) => Enum.IsDefined(type); @@ -174,22 +137,17 @@ public readonly record struct MaterialValueIndex( public static bool ValidateRow(byte rowIndex) => rowIndex < MtrlFile.ColorTable.NumRows; - public static bool Validate(ColorTableIndex dataIndex) - => Enum.IsDefined(dataIndex); - - private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex, ColorTableIndex index) + private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex) { - var result = (uint)index & 0xFF; - result |= (uint)(rowIndex & 0xFF) << 8; - result |= (uint)(materialIndex & 0xF) << 16; - result |= (uint)(slotIndex & 0xFF) << 20; - result |= (uint)((byte)type & 0xF) << 28; + var result = (uint)rowIndex; + result |= (uint)materialIndex << 8; + result |= (uint)slotIndex << 16; + result |= (uint)((byte)type << 24); return result; } private MaterialValueIndex(uint key) - : this((DrawObjectType)((key >> 28) & 0xF), (byte)(key >> 20), (byte)((key >> 16) & 0xF), (byte)(key >> 8), - (ColorTableIndex)(key & 0xFF)) + : this((DrawObjectType)(key >> 24), (byte)(key >> 16), (byte)(key >> 8), (byte)key) { } private class Converter : JsonConverter @@ -202,81 +160,3 @@ public readonly record struct MaterialValueIndex( => FromKey(serializer.Deserialize(reader), out var value) ? value : throw new Exception($"Invalid material key {value.Key}."); } } - -public static class MaterialExtensions -{ - public static bool TryGetValue(this MaterialValueIndex.ColorTableIndex index, in MtrlFile.ColorTable.Row row, out Vector3 value) - { - value = index switch - { - MaterialValueIndex.ColorTableIndex.Diffuse => row.Diffuse, - MaterialValueIndex.ColorTableIndex.Specular => row.Specular, - MaterialValueIndex.ColorTableIndex.SpecularStrength => new Vector3(row.SpecularStrength, 0, 0), - MaterialValueIndex.ColorTableIndex.Emissive => row.Emissive, - MaterialValueIndex.ColorTableIndex.GlossStrength => new Vector3(row.GlossStrength, 0, 0), - MaterialValueIndex.ColorTableIndex.TileSet => new Vector3(row.TileSet), - MaterialValueIndex.ColorTableIndex.MaterialRepeat => new Vector3(row.MaterialRepeat, 0), - MaterialValueIndex.ColorTableIndex.MaterialSkew => new Vector3(row.MaterialSkew, 0), - _ => new Vector3(float.NaN), - }; - return !float.IsNaN(value.X); - } - - public static bool SetValue(this MaterialValueIndex.ColorTableIndex index, ref MtrlFile.ColorTable.Row row, in Vector3 value) - { - switch (index) - { - case MaterialValueIndex.ColorTableIndex.Diffuse: - if (value == row.Diffuse) - return false; - - row.Diffuse = value; - return true; - - case MaterialValueIndex.ColorTableIndex.Specular: - if (value == row.Specular) - return false; - - row.Specular = value; - return true; - case MaterialValueIndex.ColorTableIndex.SpecularStrength: - if (value.X == row.SpecularStrength) - return false; - - row.SpecularStrength = value.X; - return true; - case MaterialValueIndex.ColorTableIndex.Emissive: - if (value == row.Emissive) - return false; - - row.Emissive = value; - return true; - case MaterialValueIndex.ColorTableIndex.GlossStrength: - if (value.X == row.GlossStrength) - return false; - - row.GlossStrength = value.X; - return true; - case MaterialValueIndex.ColorTableIndex.TileSet: - var @ushort = (ushort)(value.X + 0.5f); - if (@ushort == row.TileSet) - return false; - - row.TileSet = @ushort; - return true; - case MaterialValueIndex.ColorTableIndex.MaterialRepeat: - if (value.X == row.MaterialRepeat.X && value.Y == row.MaterialRepeat.Y) - return false; - - row.MaterialRepeat = new Vector2(value.X, value.Y); - return true; - case MaterialValueIndex.ColorTableIndex.MaterialSkew: - if (value.X == row.MaterialSkew.X && value.Y == row.MaterialSkew.Y) - return false; - - row.MaterialSkew = new Vector2(value.X, value.Y); - return true; - default: return false; - } - } -} diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 5f7c2dc..c735182 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -1,12 +1,242 @@ global using StateMaterialManager = Glamourer.Interop.Material.MaterialValueManager; 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.Structs; namespace Glamourer.Interop.Material; -public record struct MaterialValueDesign(Vector3 Value, bool Enabled); -public record struct MaterialValueState(Vector3 Game, Vector3 Model, StateSource Source); +[JsonConverter(typeof(Converter))] +public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength) +{ + public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 0, 0); + + public Vector3 Diffuse = diffuse; + public Vector3 Specular = specular; + public Vector3 Emissive = emissive; + public float SpecularStrength = specularStrength; + public float GlossStrength = glossStrength; + + public ColorRow(in MtrlFile.ColorTable.Row row) + : this(row.Diffuse, row.Specular, row.Emissive, row.SpecularStrength, row.GlossStrength) + { } + + public readonly bool NearEqual(in ColorRow rhs) + => Diffuse.NearEqual(rhs.Diffuse) + && Specular.NearEqual(rhs.Specular) + && Emissive.NearEqual(rhs.Emissive) + && SpecularStrength.NearEqual(rhs.SpecularStrength) + && GlossStrength.NearEqual(rhs.GlossStrength); + + public readonly bool Apply(ref MtrlFile.ColorTable.Row row) + { + var ret = false; + if (!row.Diffuse.NearEqual(Diffuse)) + { + row.Diffuse = Diffuse; + ret = true; + } + + if (!row.Specular.NearEqual(Specular)) + { + row.Specular = Specular; + ret = true; + } + + if (!row.Emissive.NearEqual(Emissive)) + { + row.Emissive = Emissive; + ret = true; + } + + if (!row.SpecularStrength.NearEqual(SpecularStrength)) + { + row.SpecularStrength = SpecularStrength; + ret = true; + } + + if (!row.GlossStrength.NearEqual(GlossStrength)) + { + row.GlossStrength = GlossStrength; + ret = true; + } + + return ret; + } + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, ColorRow value, JsonSerializer serializer) + { + writer.WriteStartObject(); + writer.WritePropertyName("DiffuseR"); + writer.WriteValue(value.Diffuse.X); + writer.WritePropertyName("DiffuseG"); + writer.WriteValue(value.Diffuse.Y); + writer.WritePropertyName("DiffuseB"); + writer.WriteValue(value.Diffuse.Z); + writer.WritePropertyName("SpecularR"); + writer.WriteValue(value.Specular.X); + writer.WritePropertyName("SpecularG"); + writer.WriteValue(value.Specular.Y); + writer.WritePropertyName("SpecularB"); + writer.WriteValue(value.Specular.Z); + writer.WritePropertyName("SpecularA"); + writer.WriteValue(value.SpecularStrength); + writer.WritePropertyName("EmissiveR"); + writer.WriteValue(value.Emissive.X); + writer.WritePropertyName("EmissiveG"); + writer.WriteValue(value.Emissive.Y); + writer.WritePropertyName("EmissiveB"); + writer.WriteValue(value.Emissive.Z); + writer.WritePropertyName("Gloss"); + writer.WriteValue(value.GlossStrength); + writer.WriteEndObject(); + } + + public override ColorRow ReadJson(JsonReader reader, Type objectType, ColorRow existingValue, bool hasExistingValue, + JsonSerializer serializer) + { + var obj = JObject.Load(reader); + Set(ref existingValue.Diffuse.X, obj["DiffuseR"]?.Value()); + Set(ref existingValue.Diffuse.Y, obj["DiffuseG"]?.Value()); + Set(ref existingValue.Diffuse.Z, obj["DiffuseB"]?.Value()); + Set(ref existingValue.Specular.X, obj["SpecularR"]?.Value()); + Set(ref existingValue.Specular.Y, obj["SpecularG"]?.Value()); + Set(ref existingValue.Specular.Z, obj["SpecularB"]?.Value()); + Set(ref existingValue.SpecularStrength, obj["SpecularA"]?.Value()); + Set(ref existingValue.Emissive.X, obj["EmissiveR"]?.Value()); + Set(ref existingValue.Emissive.Y, obj["EmissiveG"]?.Value()); + Set(ref existingValue.Emissive.Z, obj["EmissiveB"]?.Value()); + Set(ref existingValue.GlossStrength, obj["Gloss"]?.Value()); + return existingValue; + + static void Set(ref T target, T? value) + where T : struct + { + if (value.HasValue) + target = value.Value; + } + } + } +} + +[JsonConverter(typeof(Converter))] +public struct MaterialValueDesign(ColorRow value, bool enabled) +{ + public ColorRow Value = value; + public bool Enabled = enabled; + + public readonly bool Apply(ref MaterialValueState state) + { + if (!Enabled) + return false; + + if (state.Model.NearEqual(Value)) + return false; + + state.Model = Value; + return true; + } + + private class Converter : JsonConverter + { + public override void WriteJson(JsonWriter writer, MaterialValueDesign value, JsonSerializer serializer) + { + writer.WriteStartObject(); + writer.WritePropertyName("DiffuseR"); + writer.WriteValue(value.Value.Diffuse.X); + writer.WritePropertyName("DiffuseG"); + writer.WriteValue(value.Value.Diffuse.Y); + writer.WritePropertyName("DiffuseB"); + writer.WriteValue(value.Value.Diffuse.Z); + writer.WritePropertyName("SpecularR"); + writer.WriteValue(value.Value.Specular.X); + writer.WritePropertyName("SpecularG"); + writer.WriteValue(value.Value.Specular.Y); + writer.WritePropertyName("SpecularB"); + writer.WriteValue(value.Value.Specular.Z); + writer.WritePropertyName("SpecularA"); + writer.WriteValue(value.Value.SpecularStrength); + writer.WritePropertyName("EmissiveR"); + writer.WriteValue(value.Value.Emissive.X); + writer.WritePropertyName("EmissiveG"); + writer.WriteValue(value.Value.Emissive.Y); + writer.WritePropertyName("EmissiveB"); + writer.WriteValue(value.Value.Emissive.Z); + writer.WritePropertyName("Gloss"); + writer.WriteValue(value.Value.GlossStrength); + writer.WritePropertyName("Enabled"); + writer.WriteValue(value.Enabled); + writer.WriteEndObject(); + } + + public override MaterialValueDesign ReadJson(JsonReader reader, Type objectType, MaterialValueDesign existingValue, + bool hasExistingValue, + JsonSerializer serializer) + { + var obj = JObject.Load(reader); + Set(ref existingValue.Value.Diffuse.X, obj["DiffuseR"]?.Value()); + Set(ref existingValue.Value.Diffuse.Y, obj["DiffuseG"]?.Value()); + Set(ref existingValue.Value.Diffuse.Z, obj["DiffuseB"]?.Value()); + Set(ref existingValue.Value.Specular.X, obj["SpecularR"]?.Value()); + Set(ref existingValue.Value.Specular.Y, obj["SpecularG"]?.Value()); + Set(ref existingValue.Value.Specular.Z, obj["SpecularB"]?.Value()); + Set(ref existingValue.Value.SpecularStrength, obj["SpecularA"]?.Value()); + Set(ref existingValue.Value.Emissive.X, obj["EmissiveR"]?.Value()); + Set(ref existingValue.Value.Emissive.Y, obj["EmissiveG"]?.Value()); + Set(ref existingValue.Value.Emissive.Z, obj["EmissiveB"]?.Value()); + Set(ref existingValue.Value.GlossStrength, obj["Gloss"]?.Value()); + existingValue.Enabled = obj["Enabled"]?.Value() ?? false; + return existingValue; + + static void Set(ref T target, T? value) + where T : struct + { + if (value.HasValue) + target = value.Value; + } + } + } +} + +[StructLayout(LayoutKind.Explicit)] +public struct MaterialValueState( + in ColorRow game, + in ColorRow model, + CharacterWeapon drawData, + StateSource source) +{ + public MaterialValueState(in ColorRow gameRow, in ColorRow modelRow, CharacterArmor armor, StateSource source) + : this(gameRow, modelRow, armor.ToWeapon(0), source) + { } + + [FieldOffset(0)] + public ColorRow Game = game; + + [FieldOffset(44)] + public ColorRow Model = model; + + [FieldOffset(88)] + public readonly CharacterWeapon DrawData = drawData; + + [FieldOffset(95)] + public readonly StateSource Source = source; + + public readonly bool EqualGame(in ColorRow rhsRow, CharacterWeapon rhsData) + => DrawData.Skeleton == rhsData.Skeleton + && DrawData.Weapon == rhsData.Weapon + && DrawData.Variant == rhsData.Variant + && DrawData.Stain == rhsData.Stain + && Game.NearEqual(rhsRow); + + public readonly MaterialValueDesign Convert() + => new(Model, true); +} public readonly struct MaterialValueManager { @@ -113,7 +343,7 @@ public readonly struct MaterialValueManager return count; } - public ReadOnlySpan<(uint key, T Value)> GetValues(MaterialValueIndex min, MaterialValueIndex max) + public ReadOnlySpan<(uint Key, T Value)> GetValues(MaterialValueIndex min, MaterialValueIndex max) => MaterialValueManager.Filter(CollectionsMarshal.AsSpan(_values), min, max); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -187,6 +417,7 @@ public static class MaterialValueManager maxIdx = ~maxIdx + idx; return maxIdx > minIdx ? (minIdx, maxIdx - 1) : (-1, -1); } + maxIdx += idx; while (maxIdx < values.Length - 1 && values[maxIdx + 1].Key <= maxKey) diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs index 75cee46..35587fe 100644 --- a/Glamourer/State/InternalStateEditor.cs +++ b/Glamourer/State/InternalStateEditor.cs @@ -222,7 +222,7 @@ public class InternalStateEditor( } /// Change the value of a single material color table entry. - public bool ChangeMaterialValue(ActorState state, MaterialValueIndex index, Vector3 value, Vector3 gameValue, StateSource source, out Vector3 oldValue, + public bool ChangeMaterialValue(ActorState state, MaterialValueIndex index, in MaterialValueState newValue, StateSource source, out ColorRow? oldValue, uint key = 0) { // We already have an existing value. @@ -240,18 +240,18 @@ public class InternalStateEditor( } // Update if edited. - state.Materials.UpdateValue(index, new MaterialValueState(gameValue, value, source), out _); + state.Materials.UpdateValue(index, newValue, out _); return true; } // We do not have an existing value. - oldValue = gameValue; + oldValue = null; // Do not do anything if locked or if the game value updates, because then we do not need to add an entry. if (!state.CanUnlock(key) || source is StateSource.Game) return false; - // Only add an entry if it is sufficiently different from the game value. - return !value.NearEqual(gameValue) && state.Materials.TryAddValue(index, new MaterialValueState(gameValue, value, source)); + // Only add an entry if it is different from the game value. + return state.Materials.TryAddValue(index, newValue); } public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue, diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 19c1f3e..7e38726 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -276,7 +276,7 @@ public class StateApplier( return data; } - public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, Vector3? value, bool force) + public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, ColorRow? value, bool force) { if (!force && !_config.UseAdvancedParameters) return; @@ -289,14 +289,11 @@ public class StateApplier( if (!index.TryGetColorTable(texture, out var table)) continue; - Vector3 actualValue; if (value.HasValue) - actualValue = value.Value; - else if (!PrepareColorSet.TryGetColorTable(actor, index, out var baseTable) - || !index.DataIndex.TryGetValue(baseTable[index.RowIndex], out actualValue)) - continue; - - if (!index.DataIndex.SetValue(ref table[index.RowIndex], actualValue)) + value.Value.Apply(ref table[index.RowIndex]); + else if (PrepareColorSet.TryGetColorTable(actor, index, out var baseTable)) + table[index.RowIndex] = baseTable[index.RowIndex]; + else continue; MaterialService.ReplaceColorTable(texture, table); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index e747448..7d0c203 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -165,15 +165,15 @@ public class StateEditor( StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag)); } - public void ChangeMaterialValue(object data, MaterialValueIndex index, Vector3 value, Vector3 gameValue, ApplySettings settings) + public void ChangeMaterialValue(object data, MaterialValueIndex index, in MaterialValueState newValue, ApplySettings settings) { var state = (ActorState)data; - if (!Editor.ChangeMaterialValue(state, index, value, gameValue, settings.Source, out var oldValue, settings.Key)) + if (!Editor.ChangeMaterialValue(state, index, newValue, settings.Source, out var oldValue, settings.Key)) return; var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange()); - Glamourer.Log.Verbose($"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, value, index)); + Glamourer.Log.Verbose($"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index)); } /// @@ -282,6 +282,20 @@ public class StateEditor( if (!settings.RespectManual || !state.Sources[meta].IsManual()) Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key); } + + foreach (var (key, value) in mergedDesign.Design.Materials) + { + if (!value.Enabled) + continue; + + var idx = MaterialValueIndex.FromKey(key); + // TODO + //if (state.Materials.TryGetValue(idx, out var materialState)) + //{ + // if (!settings.RespectManual || materialState.Source.IsManual()) + // Editor.ChangeMaterialValue(state, idx, new MaterialValueState(materialState.Game, value.Value, materialState.DrawData)); + //} + } } var actors = settings.Source.RequiresChange() From 99181d2fdba92d18b2e08cc0a7ef0d1e33e8d05f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 Feb 2024 16:51:12 +0100 Subject: [PATCH 230/786] Disable Material stuff for now. --- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 6 +++--- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- Glamourer/Interop/Material/MaterialManager.cs | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 90ff4b7..fc69095 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -117,9 +117,9 @@ public class ActorPanel( RevertButtons(); - - if (ImGui.CollapsingHeader("Material Shit")) - _materialDrawer.DrawActorPanel(_actor); + // TODO Materials + //if (ImGui.CollapsingHeader("Material Shit")) + // _materialDrawer.DrawActorPanel(_actor); using var disabled = ImRaii.Disabled(transformationId != 0); if (_state.ModelData.IsHuman) DrawHumanPanel(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 13cd293..4fa302e 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -384,7 +384,7 @@ public class DesignPanel( DrawCustomize(); DrawEquipment(); DrawCustomizeParameters(); - DrawMaterialValues(); + //DrawMaterialValues(); TODO Materials _designDetails.Draw(); DrawApplicationRules(); _modAssociations.Draw(); diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index f35ee8a..b8c33c8 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -30,7 +30,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable _penumbra = penumbra; _event = prepareColorSet; - _event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager); + // TODO Material + //_event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager); } public void Dispose() From 346e4890b0f6d214bc351cfff31880a279976339 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 Feb 2024 17:37:26 +0100 Subject: [PATCH 231/786] Improve link UI a bit. --- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index 7b996e8..32800f5 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -10,7 +10,7 @@ using OtterGui.Services; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, LinkDesignCombo _combo) : IUiService +public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, LinkDesignCombo _combo, DesignColors _colorManager) : IUiService { private int _dragDropIndex = -1; private LinkOrder _dragDropOrder = LinkOrder.None; @@ -20,6 +20,10 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe public void Draw() { using var header = ImRaii.CollapsingHeader("Design Links"); + ImGuiUtil.HoverTooltip( + "Design links are links to other designs that will be applied to characters or during automation according to the rules set.\n" + + "They apply from top to bottom just like automated design sets, so anything set by an earlier design will not not be set again by later designs, and order is important.\n" + + "If a linked design links to other designs, they will also be applied, so circular links are prohibited. "); if (!header) return; @@ -82,11 +86,30 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe { using var id = ImRaii.PushId((int)LinkOrder.Self); ImGui.TableNextColumn(); + var color = _colorManager.GetColor(_selector.Selected!); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var c = ImRaii.PushColor(ImGuiCol.Text, color); + ImGui.AlignTextToFramePadding(); + ImGuiUtil.RightAlign(FontAwesomeIcon.ArrowRightLong.ToIconString()); + } + ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.Selectable(_selector.IncognitoMode ? _selector.Selected!.Incognito : _selector.Selected!.Name.Text); + using (ImRaii.PushColor(ImGuiCol.Text, color)) + { + ImGui.AlignTextToFramePadding(); + ImGui.Selectable(_selector.IncognitoMode ? _selector.Selected!.Incognito : _selector.Selected!.Name.Text); + } + + ImGuiUtil.HoverTooltip("Current Design"); DrawDragDrop(_selector.Selected!, LinkOrder.Self, 0); ImGui.TableNextColumn(); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var c = ImRaii.PushColor(ImGuiCol.Text, color); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(FontAwesomeIcon.ArrowLeftLong.ToIconString()); + } } private void DrawSubList(IReadOnlyList list, LinkOrder order) @@ -103,8 +126,12 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe var (design, flags) = list[i]; ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.Selectable(_selector.IncognitoMode ? design.Incognito : design.Name.Text); + using (ImRaii.PushColor(ImGuiCol.Text, _colorManager.GetColor(design))) + { + ImGui.AlignTextToFramePadding(); + ImGui.Selectable(_selector.IncognitoMode ? design.Incognito : design.Name.Text); + } + DrawDragDrop(design, order, i); ImGui.TableNextColumn(); From fc52d44c9ce9151005a255f5611583b76ed48500 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 Feb 2024 18:01:05 +0100 Subject: [PATCH 232/786] Improve some mousewheel stuff and add some tooltips. --- .../Customization/CustomizationDrawer.Simple.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 15 ++++++++------- Glamourer/Gui/Equipment/GlamourerColorCombo.cs | 3 --- OtterGui | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index e684554..3e2b453 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -36,7 +36,7 @@ public partial class CustomizationDrawer var tmp = (int)_currentByte.Value; ImGui.SetNextItemWidth(_comboSelectorSize); if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp) - || CaptureMouseWheel(ref tmp, 0, _currentCount - 1)) + || CaptureMouseWheel(ref tmp, 0, _currentCount)) UpdateValue((CustomizeValue)tmp); } diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 8dfa50c..c56e56e 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -7,6 +7,7 @@ using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Widgets; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -139,7 +140,7 @@ public class EquipmentDrawer public bool DrawAllStain(out StainId ret, bool locked) { using var disabled = ImRaii.Disabled(locked); - var change = _stainCombo.Draw("Dye All Slots", Stain.None.RgbaColor, string.Empty, false, false); + var change = _stainCombo.Draw("Dye All Slots", Stain.None.RgbaColor, string.Empty, false, false, MouseWheelType.None); ret = Stain.None.RowIndex; if (change) if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out var stain)) @@ -464,12 +465,12 @@ public class EquipmentDrawer (var tt, item, var valid) = (allowRevert && !revertItem.Equals(currentItem), allowClear && !clearItem.Equals(currentItem), ImGui.GetIO().KeyCtrl) switch { - (true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.", revertItem, true), - (true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.", clearItem, true), - (true, false, true) => ("Control and Right-Click to revert to game.", revertItem, true), - (true, false, false) => ("Control and Right-Click to revert to game.", default, false), - (false, true, _) => ("Right-click to clear.", clearItem, true), - (false, false, _) => (string.Empty, default, false), + (true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", revertItem, true), + (true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", clearItem, true), + (true, false, true) => ("Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", revertItem, true), + (true, false, false) => ("Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", default, false), + (false, true, _) => ("Right-click to clear.\nControl and mouse wheel to scroll.", clearItem, true), + (false, false, _) => ("Control and mouse wheel to scroll.", default, false), }; ImGuiUtil.HoverTooltip(tt); diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index d3fde9f..527dbb5 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -36,9 +36,6 @@ public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, Fa return base.DrawSelectable(globalIdx, selected); } - public override bool Draw(string label, uint color, string name, bool found, bool gloss, float previewWidth) - => base.Draw(label, color, name, found, gloss, previewWidth); - private static Func>> CreateFunc(DictStain stains, FavoriteManager favorites) => () => stains.Select(kvp => (kvp, favorites.Contains(kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp) diff --git a/OtterGui b/OtterGui index 2d8a03e..1a187f7 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 2d8a03eebd80e19c6936a28ab2e3a8c164cc17f3 +Subproject commit 1a187f756f2e8823197bd43db1c3383231f5eaff From dd5463071ef2194454b064e2147cc56c14e446bd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 Feb 2024 18:05:07 +0100 Subject: [PATCH 233/786] Add changelog. --- Glamourer/Gui/GlamourerChangelog.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index fcbebd6..ad35c5e 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -28,6 +28,7 @@ public class GlamourerChangelog Add1_1_0_0(Changelog); Add1_1_0_2(Changelog); Add1_1_0_4(Changelog); + Add1_1_0_5(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -48,6 +49,25 @@ public class GlamourerChangelog } } + private static void Add1_1_0_5(Changelog log) + => log.NextVersion("Version 1.1.0.5") + .RegisterHighlight("Added the option to link to other designs in a design, causing all of them to be applied at once.") + .RegisterEntry("This required reworking the handling for applying multiple designs at once (i.e.merging them).", 1) + .RegisterEntry("This was a considerable backend change on both automation sets and design application. I may have messed up and introduced bugs. Please let me know if something does not work right anymore.", 1) + .RegisterHighlight("Added an option so that manual application of a mainhand weapon will also automatically apply its associated offhand (and gloves, for certain fist weapons). This is off by default.") + .RegisterHighlight("Added an option that always tries to apply associated mod settings for designs to the Penumbra collection associated with the character the design is applied to.") + .RegisterEntry("This is off by default and I strongly recommend AGAINST using it, since Glamourer has no way to revert such changes. You are responsible for keeping your collection in order.", 1) + .RegisterHighlight("Added mouse wheel scrolling to many selectors, e.g. for equipment, dyes or customizations. You need to hold Control while ") + .RegisterEntry("Improved handling for highlights with advanced customization colors and normal customization settings.") + .RegisterEntry("Added copy/paste buttons for advanced customization colors.") + .RegisterEntry("Added alpha preview to advanced customization colors.") + .RegisterEntry("Updated a few fun module things. Now there are Pink elephants on parade!") + .RegisterEntry("Split up the IPC source state so IPC consumers can apply designs without them sticking around.") + .RegisterEntry("Fixed an issue with weapon loading being dependant on the order of loading Penumbra and Glamourer.") + .RegisterEntry("Fixed an issue with buttons sharing state and switching from design duplication to creating new ones caused errors.") + .RegisterEntry("Fixed an issue where actors leaving during cutscenes or GPose caused Glamourer to throw a fit.") + .RegisterEntry("Fixed an issue with NPC designs applying advanced customizations to targets and coloring them entirely black."); + private static void Add1_1_0_4(Changelog log) => log.NextVersion("Version 1.1.0.4") .RegisterEntry("Added a check and warning for a lingering Palette+ installation.") From 28ce293d49a1135a863badc84e71408e09109d0c Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 6 Feb 2024 17:07:21 +0000 Subject: [PATCH 234/786] [CI] Updating repo.json for testing_1.1.0.5 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 35510d7..33c4c7b 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.4", + "TestingAssemblyVersion": "1.1.0.5", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 836e3e6603ff331da8f58038a207657f7bbf36f2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 Feb 2024 19:49:06 +0100 Subject: [PATCH 235/786] Fix dumb bug. --- Glamourer/Configuration.cs | 1 - Glamourer/Gui/ToggleDrawData.cs | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 29b0646..1d24bf3 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -3,7 +3,6 @@ using Dalamud.Game.ClientState.Keys; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Gui; -using Glamourer.Gui.Customization; using Glamourer.Services; using Newtonsoft.Json; using OtterGui; diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index 55b1b7a..75edc72 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -27,8 +27,8 @@ public struct ToggleDrawData { switch (_index.GetFlag()) { - case MetaIndex index: - _editor.ChangeMetaState(_data, index, value, ApplySettings.Manual); + case MetaFlag flag: + _editor.ChangeMetaState(_data, flag.ToIndex(), value, ApplySettings.Manual); break; case CrestFlag flag: _editor.ChangeCrest(_data, flag, value, ApplySettings.Manual); @@ -42,8 +42,8 @@ public struct ToggleDrawData var design = (Design)_data; switch (_index.GetFlag()) { - case MetaIndex index: - manager.ChangeApplyMeta(design, index, value); + case MetaFlag flag: + manager.ChangeApplyMeta(design, flag.ToIndex(), value); break; case CrestFlag flag: manager.ChangeApplyCrest(design, flag, value); From e967c17d84fabe1f717296903f39c31a325a52dc Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 6 Feb 2024 18:51:39 +0000 Subject: [PATCH 236/786] [CI] Updating repo.json for testing_1.1.0.6 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 33c4c7b..18f9d99 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.5", + "TestingAssemblyVersion": "1.1.0.6", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.6/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 0ea6e6dac55977d1ec6d833a73a1f9cf5eb062ac Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 7 Feb 2024 01:31:35 +0100 Subject: [PATCH 237/786] Fix crest application rules not copying on duplicates. --- Glamourer/Designs/DesignBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 8873339..4910793 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -57,6 +57,7 @@ public class DesignBase ApplyCustomize = clone.ApplyCustomizeRaw; ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; ApplyParameters = clone.ApplyParameters & CustomizeParameterExtensions.All; + ApplyCrest = clone.ApplyCrest & CrestExtensions.All; ApplyMeta = clone.ApplyMeta & MetaExtensions.All; } From 7653fd22c0df8f9b45db0e2748c0470a45eb8736 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 7 Feb 2024 01:31:47 +0100 Subject: [PATCH 238/786] Fix meta state updating logic. --- Glamourer/State/StateListener.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 23e0f3c..626c64d 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -564,7 +564,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (!state.Sources[MetaIndex.VisorState].IsFixed()) + if (state.Sources[MetaIndex.VisorState].IsFixed()) value = state.ModelData.IsVisorToggled(); else _manager.ChangeMetaState(state, MetaIndex.VisorState, value, ApplySettings.Game); @@ -597,7 +597,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (!state.Sources[MetaIndex.HatState].IsFixed()) + if (state.Sources[MetaIndex.HatState].IsFixed()) value = state.ModelData.IsHatVisible(); else _manager.ChangeMetaState(state, MetaIndex.HatState, value, ApplySettings.Game); @@ -630,7 +630,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (!state.Sources[MetaIndex.WeaponState].IsFixed()) + if (state.Sources[MetaIndex.WeaponState].IsFixed()) value = state.ModelData.IsWeaponVisible(); else _manager.ChangeMetaState(state, MetaIndex.WeaponState, value, ApplySettings.Game); From b228658414aa8527bfb8ee970665219a09d43ae2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 7 Feb 2024 01:39:26 +0100 Subject: [PATCH 239/786] Fix weapon state updating logic. --- Glamourer/State/StateEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 7d0c203..254c67e 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -259,7 +259,7 @@ public class StateEditor( if (!mergedDesign.Design.DoApplyEquip(weaponSlot)) continue; - if (settings.RespectManual && !state.Sources[weaponSlot, false].IsManual()) + if (settings.RespectManual && state.Sources[weaponSlot, false].IsManual()) continue; var currentType = state.ModelData.Item(weaponSlot).Type; From 088066f68a85ca6e50f1dccb58b99b9441c19335 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 7 Feb 2024 00:41:38 +0000 Subject: [PATCH 240/786] [CI] Updating repo.json for testing_1.1.0.7 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 18f9d99..0315b3a 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.6", + "TestingAssemblyVersion": "1.1.0.7", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.6/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.7/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From ec0f7a2d1e7e1124656db3ae7daf25119c096573 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 7 Feb 2024 02:56:06 +0100 Subject: [PATCH 241/786] Fix revert design base state. --- Glamourer/Automation/AutoDesignApplier.cs | 6 +++--- Glamourer/State/StateEditor.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 65116db..28e1111 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -79,7 +79,7 @@ public sealed class AutoDesignApplier : IDisposable { Glamourer.Log.Verbose( $"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); - _state.ChangeItem(_jobChangeState, EquipSlot.MainHand, data.Item1, new ApplySettings(Source: data.Item2)); + _state.ChangeItem(state, EquipSlot.MainHand, data.Item1, new ApplySettings(Source: data.Item2)); weapon = state.ModelData.Weapon(EquipSlot.MainHand); } @@ -91,7 +91,7 @@ public sealed class AutoDesignApplier : IDisposable { Glamourer.Log.Verbose( $"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); - _state.ChangeItem(_jobChangeState, EquipSlot.OffHand, data.Item1, new ApplySettings(Source: data.Item2)); + _state.ChangeItem(state, EquipSlot.OffHand, data.Item1, new ApplySettings(Source: data.Item2)); weapon = state.ModelData.Weapon(EquipSlot.OffHand); } @@ -264,7 +264,7 @@ public sealed class AutoDesignApplier : IDisposable var mergedDesign = _designMerger.Merge( set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks.Select(l => (l.Design, l.Flags & d.Type)) ?? [(d.Design, d.Type)]), - state.ModelData, true, _config.AlwaysApplyAssociatedMods); + state.BaseData, true, _config.AlwaysApplyAssociatedMods); _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false)); } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 254c67e..219feab 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -321,7 +321,7 @@ public class StateEditor( public void ApplyDesign(object data, DesignBase design, ApplySettings settings) { var merged = settings.MergeLinks && design is Design d - ? merger.Merge(d.AllLinks, ((ActorState)data).ModelData, false, Config.AlwaysApplyAssociatedMods) + ? merger.Merge(d.AllLinks, ((ActorState)data).BaseData, false, Config.AlwaysApplyAssociatedMods) : new MergedDesign(design); ApplyDesign(data, merged, settings with From 7a33daf95456d1a09a1e8f37dd852ed7005e8a3f Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 7 Feb 2024 01:58:03 +0000 Subject: [PATCH 242/786] [CI] Updating repo.json for testing_1.1.0.8 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 0315b3a..92df653 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.7", + "TestingAssemblyVersion": "1.1.0.8", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.7/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.8/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 7dfc20ce23aa16be60fcb3d1639880298bc38e63 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 8 Feb 2024 20:35:38 +0100 Subject: [PATCH 243/786] tmp --- Glamourer/Designs/DesignBase.cs | 8 +++--- Glamourer/Designs/DesignConverter.cs | 3 ++- Glamourer/Designs/Links/DesignMerger.cs | 4 +-- Glamourer/Gui/DesignCombo.cs | 35 ++++++++++++++----------- Glamourer/State/StateEditor.cs | 2 +- 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 4910793..86c849e 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -92,13 +92,15 @@ public class DesignBase internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState; private bool _writeProtected; - public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize) + public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize, CustomizeSet? set = null) { if (customize.Equals(_designData.Customize)) return false; - _designData.Customize = customize; - CustomizeSet = customizeService.Manager.GetSet(customize.Clan, customize.Gender); + set ??= customizeService.Manager.GetSet(customize.Clan, customize.Gender); + Debug.Assert(set.Clan == customize.Clan && set.Gender == customize.Gender, "Invalid CustomizeSet provided."); + _designData.Customize = customize; + CustomizeSet = set; return true; } diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 9b9ebfc..b68afa2 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -124,7 +124,8 @@ public class DesignConverter( } ret.SetApplyMeta(MetaIndex.Wetness, customize); - ret.ApplyCustomize = customize ? CustomizeFlagExtensions.AllRelevant : 0; + if (!customize) + ret.ApplyCustomize = 0; if (!equip) { diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 438c0f4..4bb51a2 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -227,7 +227,7 @@ public class DesignMerger( ret.Sources[CustomizeIndex.Face] = source; } - var set = _customize.Manager.GetSet(customize.Clan, customize.Gender); + var set = _customize.Manager.GetSet(ret.Design.DesignData.Customize.Clan, ret.Design.DesignData.Customize.Gender); var face = customize.Face; foreach (var index in Enum.GetValues()) { @@ -248,7 +248,7 @@ public class DesignMerger( fixFlags &= ~flag; } - ret.Design.SetCustomize(_customize, customize); + ret.Design.SetCustomize(_customize, customize, set); } private static void ApplyFixFlags(MergedDesign ret, CustomizeFlag fixFlags) diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index f5e3272..d25ce66 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -24,7 +24,7 @@ public abstract class DesignComboBase : FilterComboCache>, protected DesignComboBase(Func>> generator, Logger log, DesignChanged designChanged, TabSelected tabSelected, EphemeralConfig config, DesignColors designColors) - : base(generator, MouseWheelType.Unmodified, log) + : base(generator, MouseWheelType.Control, log) { _designChanged = designChanged; TabSelected = tabSelected; @@ -169,21 +169,24 @@ public abstract class DesignCombo : DesignComboBase => Draw(Design, (Incognito ? Design?.Incognito : Design?.Name.Text) ?? string.Empty, width); } -public sealed class QuickDesignCombo( - DesignManager designs, - DesignFileSystem fileSystem, - Logger log, - DesignChanged designChanged, - TabSelected tabSelected, - EphemeralConfig config, - DesignColors designColors) - : DesignCombo(log, designChanged, tabSelected, config, designColors, () => - [ - .. designs.Designs - .Where(d => d.QuickDesign) - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) - .OrderBy(d => d.Item2), - ]); +public sealed class QuickDesignCombo : DesignCombo +{ + public QuickDesignCombo(DesignManager designs, + DesignFileSystem fileSystem, + Logger log, + DesignChanged designChanged, + TabSelected tabSelected, + EphemeralConfig config, + DesignColors designColors) + : base(log, designChanged, tabSelected, config, designColors, () => + [ + .. designs.Designs + .Where(d => d.QuickDesign) + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .OrderBy(d => d.Item2), + ]) + => AllowMouseWheel = MouseWheelType.Unmodified; +} public sealed class LinkDesignCombo( DesignManager designs, diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 219feab..d4c9314 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -277,7 +277,7 @@ public class StateEditor( (m.Item1, settings.UseSingleSource ? settings.Source : m.Item2 is StateSource.Game ? StateSource.Game : m.Item2))); - foreach (var meta in MetaExtensions.AllRelevant) + foreach (var meta in MetaExtensions.AllRelevant.Where(mergedDesign.Design.DoApplyMeta)) { if (!settings.RespectManual || !state.Sources[meta].IsManual()) Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key); From b4cd5110f2f7fbae16d442bc984f1052ece15457 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 12 Feb 2024 19:55:55 +0100 Subject: [PATCH 244/786] Set Application rules to All for GetCustomization. --- Glamourer/Api/GlamourerIpc.GetCustomization.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Api/GlamourerIpc.GetCustomization.cs b/Glamourer/Api/GlamourerIpc.GetCustomization.cs index 4f35a2f..0bcfedd 100644 --- a/Glamourer/Api/GlamourerIpc.GetCustomization.cs +++ b/Glamourer/Api/GlamourerIpc.GetCustomization.cs @@ -41,6 +41,6 @@ public partial class GlamourerIpc return null; } - return _designConverter.ShareBase64(state, ApplicationRules.AllButParameters(state)); + return _designConverter.ShareBase64(state, ApplicationRules.All); } } From 488bea0e784653ae77bf5fb107d27a09429e9632 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 12 Feb 2024 19:56:55 +0100 Subject: [PATCH 245/786] Make ManualWithLinks the default. --- Glamourer/Api/GlamourerIpc.Apply.cs | 2 +- Glamourer/Designs/IDesignEditor.cs | 10 ++++++++++ Glamourer/Gui/DesignQuickBar.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 4 ++-- Glamourer/Services/CommandService.cs | 4 ++-- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs index f09d491..fcff35b 100644 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ b/Glamourer/Api/GlamourerIpc.Apply.cs @@ -163,7 +163,7 @@ public partial class GlamourerIpc if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode)) { _stateManager.ApplyDesign(state, design, - new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: lockCode)); + new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: lockCode, MergeLinks: true)); state.Lock(lockCode); } } diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index b655327..d882310 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -24,6 +24,16 @@ public readonly record struct ApplySettings( MergeLinks = false, }; + public static readonly ApplySettings ManualWithLinks = new() + { + Key = 0, + Source = StateSource.Manual, + FromJobChange = false, + RespectManual = false, + UseSingleSource = false, + MergeLinks = true, + }; + public static readonly ApplySettings Game = new() { Key = 0, diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 1ad28b0..a1a748f 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -163,7 +163,7 @@ public sealed class DesignQuickBar : Window, IDisposable var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); - _stateManager.ApplyDesign(state, design, ApplySettings.Manual); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); } public void DrawRevertButton(Vector2 buttonSize) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index fc69095..968b88c 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -344,7 +344,7 @@ public class ActorPanel( var text = ImGui.GetClipboardText(); var design = _converter.FromBase64(text, applyCustomize, applyGear, out _) ?? throw new Exception("The clipboard did not contain valid data."); - _stateManager.ApplyDesign(_state!, design, ApplySettings.Manual with { MergeLinks = true }); + _stateManager.ApplyDesign(_state!, design, ApplySettings.ManualWithLinks); } catch (Exception ex) { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 4fa302e..3cf46fe 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -458,7 +458,7 @@ public class DesignPanel( { var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual with { MergeLinks = true }); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks); } } @@ -477,7 +477,7 @@ public class DesignPanel( { var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual with {MergeLinks = true}); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks); } } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 2f3e938..20cd1e6 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -417,7 +417,7 @@ public class CommandService : IDisposable if (!_objects.TryGetValue(identifier, out var actors)) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ApplyDesign(state, design, ApplySettings.Manual with { MergeLinks = true }); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); } else { @@ -426,7 +426,7 @@ public class CommandService : IDisposable if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) { ApplyModSettings(design, actor, applyMods); - _stateManager.ApplyDesign(state, design, ApplySettings.Manual with { MergeLinks = true }); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); } } } From c40853e5b209e9c1bd12d6e25e0315e26daec0e3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 12 Feb 2024 19:59:41 +0100 Subject: [PATCH 246/786] Fix customize check to use correct gender/clan. --- Glamourer/Automation/AutoDesignApplier.cs | 2 +- Glamourer/Designs/DesignBase.cs | 8 +++----- Glamourer/Designs/Links/DesignMerger.cs | 14 +++++++++----- .../Gui/Tabs/DebugTab/DesignConverterPanel.cs | 2 ++ Glamourer/State/StateEditor.cs | 2 +- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 28e1111..adbd355 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -264,7 +264,7 @@ public sealed class AutoDesignApplier : IDisposable var mergedDesign = _designMerger.Merge( set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks.Select(l => (l.Design, l.Flags & d.Type)) ?? [(d.Design, d.Type)]), - state.BaseData, true, _config.AlwaysApplyAssociatedMods); + state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods); _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false)); } diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 86c849e..4910793 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -92,15 +92,13 @@ public class DesignBase internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState; private bool _writeProtected; - public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize, CustomizeSet? set = null) + public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize) { if (customize.Equals(_designData.Customize)) return false; - set ??= customizeService.Manager.GetSet(customize.Clan, customize.Gender); - Debug.Assert(set.Clan == customize.Clan && set.Gender == customize.Gender, "Invalid CustomizeSet provided."); - _designData.Customize = customize; - CustomizeSet = set; + _designData.Customize = customize; + CustomizeSet = customizeService.Manager.GetSet(customize.Clan, customize.Gender); return true; } diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 4bb51a2..4b29582 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -5,6 +5,7 @@ using Glamourer.State; using Glamourer.Unlocks; using OtterGui.Services; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Designs.Links; @@ -15,13 +16,14 @@ public class DesignMerger( ItemUnlockManager _itemUnlocks, CustomizeUnlockManager _customizeUnlocks) : IService { - public MergedDesign Merge(LinkContainer designs, in DesignData baseRef, bool respectOwnership, bool modAssociations) - => Merge(designs.Select(d => ((DesignBase?) d.Link, d.Type)), baseRef, respectOwnership, modAssociations); + public MergedDesign Merge(LinkContainer designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership, bool modAssociations) + => Merge(designs.Select(d => ((DesignBase?) d.Link, d.Type)), currentCustomize, baseRef, respectOwnership, modAssociations); - public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef, bool respectOwnership, + public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership, bool modAssociations) { var ret = new MergedDesign(designManager); + ret.Design.SetCustomize(_customize, currentCustomize); CustomizeFlag fixFlags = 0; respectOwnership &= _config.UnlockedItemMode; foreach (var (design, type) in designs) @@ -63,6 +65,8 @@ public class DesignMerger( private static void ReduceMeta(in DesignData design, MetaFlag applyMeta, MergedDesign ret, StateSource source) { applyMeta &= ~ret.Design.ApplyMeta; + if (applyMeta == 0) + return; foreach (var index in MetaExtensions.AllRelevant) { @@ -227,7 +231,7 @@ public class DesignMerger( ret.Sources[CustomizeIndex.Face] = source; } - var set = _customize.Manager.GetSet(ret.Design.DesignData.Customize.Clan, ret.Design.DesignData.Customize.Gender); + var set = _customize.Manager.GetSet(customize.Clan, customize.Gender); var face = customize.Face; foreach (var index in Enum.GetValues()) { @@ -248,7 +252,7 @@ public class DesignMerger( fixFlags &= ~flag; } - ret.Design.SetCustomize(_customize, customize, set); + ret.Design.SetCustomize(_customize, customize); } private static void ApplyFixFlags(MergedDesign ret, CustomizeFlag fixFlags) diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs index 042dfd3..2345abc 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs @@ -78,6 +78,8 @@ public class DesignConverterPanel(DesignConverter _designConverter) : IGameDataD { using var f = ImRaii.PushFont(UiBuilder.MonoFont); ImGuiUtil.TextWrapped(_textUncompressed); + if (ImGui.IsItemClicked()) + ImGui.SetClipboardText(_textUncompressed); } if (_json != null) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index d4c9314..3705d86 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -321,7 +321,7 @@ public class StateEditor( public void ApplyDesign(object data, DesignBase design, ApplySettings settings) { var merged = settings.MergeLinks && design is Design d - ? merger.Merge(d.AllLinks, ((ActorState)data).BaseData, false, Config.AlwaysApplyAssociatedMods) + ? merger.Merge(d.AllLinks, ((ActorState)data).ModelData.Customize, ((ActorState)data).BaseData, false, Config.AlwaysApplyAssociatedMods) : new MergedDesign(design); ApplyDesign(data, merged, settings with From 72f1663e28794a590f0a8e19459e0e8384794f3e Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 12 Feb 2024 19:02:13 +0000 Subject: [PATCH 247/786] [CI] Updating repo.json for testing_1.1.0.9 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 92df653..231dabe 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.8", + "TestingAssemblyVersion": "1.1.0.9", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.8/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.9/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From f5666680ff71f12fadbc40c92422488747f73de3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 13 Feb 2024 00:24:40 +0100 Subject: [PATCH 248/786] Make customizations right-clickable in penumbra. --- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 61 +++++++++++++++++---- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index 4cb11d0..b4db251 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Dalamud; +using Glamourer.Designs; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; @@ -13,10 +14,11 @@ namespace Glamourer.Gui; public sealed class PenumbraChangedItemTooltip : IDisposable { - private readonly PenumbraService _penumbra; - private readonly StateManager _stateManager; - private readonly ItemManager _items; - private readonly ObjectManager _objects; + private readonly PenumbraService _penumbra; + private readonly StateManager _stateManager; + private readonly ItemManager _items; + private readonly ObjectManager _objects; + private readonly CustomizeService _customize; private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2]; @@ -27,12 +29,14 @@ public sealed class PenumbraChangedItemTooltip : IDisposable public DateTime LastTooltip { get; private set; } = DateTime.MinValue; public DateTime LastClick { get; private set; } = DateTime.MinValue; - public PenumbraChangedItemTooltip(PenumbraService penumbra, StateManager stateManager, ItemManager items, ObjectManager objects) + public PenumbraChangedItemTooltip(PenumbraService penumbra, StateManager stateManager, ItemManager items, ObjectManager objects, + CustomizeService customize) { _penumbra = penumbra; _stateManager = stateManager; _items = items; _objects = objects; + _customize = customize; _penumbra.Tooltip += OnPenumbraTooltip; _penumbra.Click += OnPenumbraClick; } @@ -166,6 +170,16 @@ public sealed class PenumbraChangedItemTooltip : IDisposable CreateTooltip(item, "[Glamourer] ", false); return; + case ChangedItemType.Customization: + var (race, gender, index, value) = ChangedItemExtensions.Split(id); + if (!_objects.Player.Model.IsHuman) + return; + + var customize = _objects.Player.Model.GetCustomize(); + if (CheckGenderRace(customize, race, gender) && VerifyValue(customize, index, value)) + ImGui.TextUnformatted("[Glamourer] Right-Click to apply to current actor."); + + return; } } @@ -182,21 +196,27 @@ public sealed class PenumbraChangedItemTooltip : IDisposable private void OnPenumbraClick(MouseButton button, ChangedItemType type, uint id) { LastClick = DateTime.UtcNow; + if (button is not MouseButton.Right) + return; + + if (!Player(out var state)) + return; + switch (type) { case ChangedItemType.Item: case ChangedItemType.ItemOffhand: - if (button is not MouseButton.Right) - return; - - if (!Player(out var state)) - return; - if (!_items.ItemData.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return; ApplyItem(state, item); return; + case ChangedItemType.Customization: + var (race, gender, index, value) = ChangedItemExtensions.Split(id); + var customize = state.ModelData.Customize; + if (CheckGenderRace(customize, race, gender) && VerifyValue(customize, index, value)) + _stateManager.ChangeCustomize(state, index, value, ApplySettings.Manual); + return; } } @@ -214,4 +234,21 @@ public sealed class PenumbraChangedItemTooltip : IDisposable _lastItems[slot.ToIndex()] = oldItem; } } + + private static bool CheckGenderRace(in CustomizeArray customize, ModelRace race, Gender gender) + { + if (race is ModelRace.Unknown && gender is Gender.Unknown) + return true; + if (gender != customize.Gender) + return false; + if (race.ToRace() != customize.Race) + return false; + if (race is ModelRace.Highlander && customize.Clan is not SubRace.Highlander) + return false; + + return true; + } + + private bool VerifyValue(in CustomizeArray customize, CustomizeIndex index, CustomizeValue value) + => _customize.IsCustomizationValid(customize.Clan, customize.Gender, customize.Face, index, value); } diff --git a/Penumbra.Api b/Penumbra.Api index cfc5171..a28219a 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit cfc51714f74cae93608bc507775a9580cd1801de +Subproject commit a28219ac57b53c3be6ca8c252ceb9f76ae0b6c21 diff --git a/Penumbra.GameData b/Penumbra.GameData index fb18c80..3a7f6d8 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit fb18c80551203a1cf6cd01ec2b0850fbc8e44240 +Subproject commit 3a7f6d86c9975a4892f58be3c629b7664e6c3733 From 3537406eaa396f67da27d87b9325ad66f90bf6ee Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 13 Feb 2024 00:52:30 +0100 Subject: [PATCH 249/786] Fix IPC Labels. --- Glamourer/Api/GlamourerIpc.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 550b7e2..5eaf6fe 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -46,10 +46,10 @@ public sealed partial class GlamourerIpc : IDisposable _getAllCustomizationFromCharacterProvider = new FuncProvider(pi, LabelGetAllCustomizationFromCharacter, GetAllCustomizationFromCharacter); - _applyAllProvider = new ActionProvider(pi, LabelApplyAll, ApplyAll); - _applyAllOnceProvider = new ActionProvider(pi, LabelApplyAll, ApplyAllOnce); - _applyAllToCharacterProvider = new ActionProvider(pi, LabelApplyAllToCharacter, ApplyAllToCharacter); - _applyAllOnceToCharacterProvider = new ActionProvider(pi, LabelApplyAllToCharacter, ApplyAllOnceToCharacter); + _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); @@ -89,10 +89,10 @@ public sealed partial class GlamourerIpc : IDisposable _setItemProvider = new FuncProvider(pi, LabelSetItem, (idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, false)); - _setItemOnceProvider = new FuncProvider(pi, LabelSetItem, + _setItemOnceProvider = new FuncProvider(pi, LabelSetItemOnce, (idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, true)); - _setItemByActorNameProvider = new FuncProvider(pi, LabelSetItemOnceByActorName, + _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)); From 02dff90dd00f779d0e4e3c88aec528827587b621 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 13 Feb 2024 12:42:38 +0000 Subject: [PATCH 250/786] [CI] Updating repo.json for testing_1.1.0.10 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 231dabe..296a87d 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.9", + "TestingAssemblyVersion": "1.1.0.10", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.9/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.10/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a194f88903c40113558a2acff9d87930eadced95 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 14 Feb 2024 15:42:08 +0100 Subject: [PATCH 251/786] Add overrides to associated collections. --- Glamourer/Gui/MainWindow.cs | 1 + .../SettingsTab/CollectionOverrideDrawer.cs | 122 +++++++++++++ .../Gui/Tabs/{ => SettingsTab}/SettingsTab.cs | 10 +- .../Interop/Penumbra/ModSettingApplier.cs | 17 +- .../Services/CollectionOverrideService.cs | 160 ++++++++++++++++++ Glamourer/Services/CommandService.cs | 4 +- Glamourer/Services/FilenameService.cs | 26 +-- Glamourer/Services/ServiceManager.cs | 1 + Glamourer/Services/TextureService.cs | 9 +- Penumbra.GameData | 2 +- 10 files changed, 322 insertions(+), 30 deletions(-) create mode 100644 Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs rename Glamourer/Gui/Tabs/{ => SettingsTab}/SettingsTab.cs (95%) create mode 100644 Glamourer/Services/CollectionOverrideService.cs diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 6f09d04..cb21a91 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -9,6 +9,7 @@ using Glamourer.Gui.Tabs.AutomationTab; using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.NpcTab; +using Glamourer.Gui.Tabs.SettingsTab; using Glamourer.Gui.Tabs.UnlocksTab; using ImGuiNET; using OtterGui.Custom; diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs new file mode 100644 index 0000000..2ddabda --- /dev/null +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -0,0 +1,122 @@ +using Dalamud.Interface; +using Dalamud.Interface.Components; +using Dalamud.Interface.Style; +using Glamourer.Interop; +using Glamourer.Services; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; +using Penumbra.GameData.Actors; + +namespace Glamourer.Gui.Tabs.SettingsTab; + +public class CollectionOverrideDrawer( + CollectionOverrideService collectionOverrides, + Configuration config, + ObjectManager objects, + ActorManager actors) : IService +{ + private string _newIdentifier = string.Empty; + private ActorIdentifier[] _identifiers = []; + private int _dragDropIndex = -1; + private Exception? _exception; + private string _collection = string.Empty; + + public void Draw() + { + using var header = ImRaii.CollapsingHeader("Collection Overrides"); + ImGuiUtil.HoverTooltip( + "Here you can set up overrides for Penumbra collections that should have their settings changed when automatically applying mod settings from a design.\n" + + "Instead of the collection associated with the overridden character, the overridden collection will be manipulated."); + if (!header) + return; + + using var table = ImRaii.Table("table", 3, 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("collections", ImGuiTableColumnFlags.WidthStretch, 0.4f); + + for (var i = 0; i < collectionOverrides.Overrides.Count; ++i) + { + 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--); + + 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); + } + + 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"); + + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemInnerSpacing.X - buttonSize.X * 2); + if (ImGui.InputTextWithHint("##newActor", "New Identifier...", ref _newIdentifier, 80)) + try + { + _identifiers = actors.FromUserString(_newIdentifier, false); + } + catch (ActorIdentifierFactory.IdentifierParseError e) + { + _exception = e; + _identifiers = []; + } + + var tt = _identifiers.Any(i => i.IsValid) + ? $"Add a new override for {_identifiers.First(i => i.IsValid)}." + : _newIdentifier.Length == 0 + ? "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.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled)); + ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString()); + } + + 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/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs similarity index 95% rename from Glamourer/Gui/Tabs/SettingsTab.cs rename to Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 11490df..5d0fcca 100644 --- a/Glamourer/Gui/Tabs/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -15,7 +15,7 @@ using OtterGui; using OtterGui.Raii; using OtterGui.Widgets; -namespace Glamourer.Gui.Tabs; +namespace Glamourer.Gui.Tabs.SettingsTab; public class SettingsTab( Configuration config, @@ -29,7 +29,8 @@ public class SettingsTab( IKeyState keys, DesignColorUi designColorUi, PaletteImport paletteImport, - PalettePlusChecker paletteChecker) + PalettePlusChecker paletteChecker, + CollectionOverrideDrawer overrides) : ITab { private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); @@ -57,6 +58,7 @@ public class SettingsTab( DrawBehaviorSettings(); DrawInterfaceSettings(); DrawColorSettings(); + overrides.Draw(); DrawCodes(); } @@ -90,6 +92,9 @@ public class SettingsTab( "Enable the display and editing of advanced customization options like arbitrary colors.", config.UseAdvancedParameters, paletteChecker.SetAdvancedParameters); PaletteImportButton(); + Checkbox("Enable Advanced Dye Options", + "Enable the display and editing of advanced dyes (color sets) for all equipment", + config.UseAdvancedDyes, v => config.UseAdvancedDyes = v); Checkbox("Always Apply Associated Mods", "Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n" + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n" @@ -189,6 +194,7 @@ public class SettingsTab( ImGui.NewLine(); } + private void PaletteImportButton() { if (!config.UseAdvancedParameters || !config.ShowPalettePlusImport) diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 01ea4d7..198c6ad 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -1,11 +1,13 @@ using Glamourer.Designs.Links; using Glamourer.Interop.Structs; +using Glamourer.Services; using Glamourer.State; using OtterGui.Services; namespace Glamourer.Interop.Penumbra; -public class ModSettingApplier(PenumbraService penumbra, Configuration config, ObjectManager objects) : IService +public class ModSettingApplier(PenumbraService penumbra, Configuration config, ObjectManager objects, CollectionOverrideService overrides) + : IService { public void HandleStateApplication(ActorState state, MergedDesign design) { @@ -24,7 +26,7 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O foreach (var actor in data.Objects) { - var collection = penumbra.GetActorCollection(actor); + 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}."); @@ -40,16 +42,17 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O if (message.Length > 0) Glamourer.Log.Verbose($"[Mod Applier] Error applying mod settings: {message}"); else - Glamourer.Log.Verbose($"[Mod Applier] Set mod settings for {mod.DirectoryName} in {collection}."); + Glamourer.Log.Verbose( + $"[Mod Applier] Set mod settings for {mod.DirectoryName} in {collection}{(overridden ? " (overridden by settings)" : string.Empty)}."); } } } - public (List Messages, int Applied, string Collection) ApplyModSettings(IReadOnlyDictionary settings, Actor actor) + public (List Messages, int Applied, string Collection, bool Overridden) ApplyModSettings(IReadOnlyDictionary settings, Actor actor) { - var collection = penumbra.GetActorCollection(actor); + var (collection, overridden) = overrides.GetCollection(actor); if (collection.Length <= 0) - return ([$"Could not obtain associated collection for {actor.Utf8Name}."], 0, string.Empty); + return ([$"Could not obtain associated collection for {actor.Utf8Name}."], 0, string.Empty, false); var messages = new List(); var appliedMods = 0; @@ -62,6 +65,6 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O ++appliedMods; } - return (messages, appliedMods, collection); + return (messages, appliedMods, collection, overridden); } } diff --git a/Glamourer/Services/CollectionOverrideService.cs b/Glamourer/Services/CollectionOverrideService.cs new file mode 100644 index 0000000..7cdbc47 --- /dev/null +++ b/Glamourer/Services/CollectionOverrideService.cs @@ -0,0 +1,160 @@ +using Glamourer.Interop.Penumbra; +using Glamourer.Interop.Structs; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OtterGui; +using OtterGui.Filesystem; +using OtterGui.Services; +using Penumbra.GameData.Actors; + +namespace Glamourer.Services; + +public sealed class CollectionOverrideService : IService, ISavable +{ + public const int Version = 1; + private readonly SaveService _saveService; + private readonly ActorManager _actors; + private readonly PenumbraService _penumbra; + + public CollectionOverrideService(SaveService saveService, ActorManager actors, PenumbraService penumbra) + { + _saveService = saveService; + _actors = actors; + _penumbra = penumbra; + Load(); + } + + public unsafe (string Collection, 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); + } + + private readonly List<(ActorIdentifier Actor, string Collection)> _overrides = []; + + public IReadOnlyList<(ActorIdentifier Actor, string Collection)> Overrides + => _overrides; + + public string ToFilename(FilenameService fileNames) + => fileNames.CollectionOverrideFile; + + public void AddOverride(IEnumerable identifiers, string collection) + { + if (collection.Length == 0) + return; + + foreach (var id in identifiers.Where(i => i.IsValid)) + { + _overrides.Add((id, collection)); + Glamourer.Log.Debug($"Added collection override {id.Incognito(null)} -> {collection}."); + _saveService.QueueSave(this); + } + } + + public void ChangeOverride(int idx, string newCollection) + { + if (idx < 0 || idx >= _overrides.Count || newCollection.Length == 0) + return; + + var current = _overrides[idx]; + if (current.Collection == newCollection) + return; + + _overrides[idx] = current with { Collection = newCollection }; + Glamourer.Log.Debug($"Changed collection override {idx + 1} from {current.Collection} to {newCollection}."); + _saveService.QueueSave(this); + } + + public void DeleteOverride(int idx) + { + if (idx < 0 || idx >= _overrides.Count) + return; + + _overrides.RemoveAt(idx); + Glamourer.Log.Debug($"Removed collection override {idx + 1}."); + _saveService.QueueSave(this); + } + + public void MoveOverride(int idxFrom, int idxTo) + { + if (!_overrides.Move(idxFrom, idxTo)) + return; + + Glamourer.Log.Debug($"Moved collection override {idxFrom + 1} to {idxTo + 1}."); + _saveService.QueueSave(this); + } + + private void Load() + { + var file = _saveService.FileNames.CollectionOverrideFile; + if (!File.Exists(file)) + return; + + try + { + var text = File.ReadAllText(file); + var jObj = JObject.Parse(text); + var version = jObj["Version"]?.ToObject() ?? 0; + switch (version) + { + case 1: + if (jObj["Overrides"] is not JArray array) + { + Glamourer.Log.Error($"Invalid format of collection override file, ignored."); + return; + } + + foreach (var token in array.OfType()) + { + var collection = token["Collection"]?.ToObject() ?? string.Empty; + var identifier = _actors.FromJson(token); + 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)); + } + + break; + default: + Glamourer.Log.Error($"Invalid version {version} of collection override file, ignored."); + return; + } + } + catch (Exception ex) + { + Glamourer.Log.Error($"Error loading collection override file:\n{ex}"); + } + } + + public void Save(StreamWriter writer) + { + var jObj = new JObject() + { + ["Version"] = Version, + ["Overrides"] = SerializeOverrides(), + }; + using var j = new JsonTextWriter(writer); + j.Formatting = Formatting.Indented; + jObj.WriteTo(j); + return; + + JArray SerializeOverrides() + { + var jArray = new JArray(); + foreach (var (actor, collection) in _overrides) + { + var obj = actor.ToJson(); + obj["Collection"] = collection; + jArray.Add(obj); + } + + return jArray; + } + } +} diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 20cd1e6..74364e2 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -440,13 +440,13 @@ public class CommandService : IDisposable if (!applyMods || design is not Design d) return; - var (messages, appliedMods, collection) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); + var (messages, appliedMods, collection, 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}."); + Glamourer.Messager.Chat.Print($"Applied {appliedMods} mod settings to {collection}{(overridden ? " (overridden by settings)" : string.Empty)}."); } private bool Delete(string argument) diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index 244c972..e19e289 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -17,21 +17,23 @@ public class FilenameService public readonly string DesignColorFile; public readonly string EphemeralConfigFile; public readonly string NpcAppearanceFile; + public readonly string CollectionOverrideFile; public FilenameService(DalamudPluginInterface pi) { - ConfigDirectory = pi.ConfigDirectory.FullName; - ConfigFile = pi.ConfigFile.FullName; - AutomationFile = Path.Combine(ConfigDirectory, "automation.json"); - DesignFileSystem = Path.Combine(ConfigDirectory, "sort_order.json"); - MigrationDesignFile = Path.Combine(ConfigDirectory, "Designs.json"); - UnlockFileCustomize = Path.Combine(ConfigDirectory, "unlocks_customize.json"); - UnlockFileItems = Path.Combine(ConfigDirectory, "unlocks_items.json"); - DesignDirectory = Path.Combine(ConfigDirectory, "designs"); - FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json"); - DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json"); - EphemeralConfigFile = Path.Combine(ConfigDirectory, "ephemeral_config.json"); - NpcAppearanceFile = Path.Combine(ConfigDirectory, "npc_appearance_data.json"); + ConfigDirectory = pi.ConfigDirectory.FullName; + ConfigFile = pi.ConfigFile.FullName; + AutomationFile = Path.Combine(ConfigDirectory, "automation.json"); + DesignFileSystem = Path.Combine(ConfigDirectory, "sort_order.json"); + MigrationDesignFile = Path.Combine(ConfigDirectory, "Designs.json"); + UnlockFileCustomize = Path.Combine(ConfigDirectory, "unlocks_customize.json"); + UnlockFileItems = Path.Combine(ConfigDirectory, "unlocks_items.json"); + DesignDirectory = Path.Combine(ConfigDirectory, "designs"); + FavoriteFile = Path.Combine(ConfigDirectory, "favorites.json"); + DesignColorFile = Path.Combine(ConfigDirectory, "design_colors.json"); + EphemeralConfigFile = Path.Combine(ConfigDirectory, "ephemeral_config.json"); + NpcAppearanceFile = Path.Combine(ConfigDirectory, "npc_appearance_data.json"); + CollectionOverrideFile = Path.Combine(ConfigDirectory, "collection_overrides.json"); } public IEnumerable Designs() diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index a5cc0ee..aca333e 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -12,6 +12,7 @@ using Glamourer.Gui.Tabs.AutomationTab; using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.NpcTab; +using Glamourer.Gui.Tabs.SettingsTab; using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Interop; using Glamourer.Interop.Penumbra; diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index 6539c7b..0619279 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -7,13 +7,10 @@ using Penumbra.GameData.Structs; namespace Glamourer.Services; -public sealed class TextureService : TextureCache, IDisposable +public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider) + : TextureCache(dataManager, textureProvider), IDisposable { - public TextureService(UiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider) - : base(dataManager, textureProvider) - => _slotIcons = CreateSlotIcons(uiBuilder); - - private readonly IDalamudTextureWrap?[] _slotIcons; + private readonly IDalamudTextureWrap?[] _slotIcons = CreateSlotIcons(uiBuilder); public (nint, Vector2, bool) GetIcon(EquipItem item, EquipSlot slot) { diff --git a/Penumbra.GameData b/Penumbra.GameData index 3a7f6d8..5825baf 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 3a7f6d86c9975a4892f58be3c629b7664e6c3733 +Subproject commit 5825bafb0602cf8a252581f21291c0d27e5561f0 From 10962cac6c2a1e53f0bc9e939e6a0b0c98ace8e8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 14 Feb 2024 18:51:48 +0100 Subject: [PATCH 252/786] Improve advanced dye stuff. --- Glamourer/Configuration.cs | 1 + Glamourer/Designs/DesignEditor.cs | 69 ++++- Glamourer/Designs/Links/DesignMerger.cs | 11 + Glamourer/Events/DesignChanged.cs | 11 +- .../CustomizationDrawer.Color.cs | 2 +- Glamourer/Gui/DesignQuickBar.cs | 4 +- Glamourer/Gui/Materials/MaterialDrawer.cs | 244 ++++++++++++------ Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 5 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 17 +- Glamourer/Interop/Material/MaterialManager.cs | 16 +- .../Interop/Material/MaterialValueIndex.cs | 11 + .../Interop/Material/MaterialValueManager.cs | 17 +- Glamourer/State/StateApplier.cs | 2 +- Glamourer/State/StateEditor.cs | 32 ++- Glamourer/State/StateManager.cs | 11 +- OtterGui | 2 +- 16 files changed, 342 insertions(+), 113 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 1d24bf3..676cacc 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -35,6 +35,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool ShowQuickBarInTabs { get; set; } = true; public bool OpenWindowAtStart { get; set; } = false; public bool UseAdvancedParameters { get; set; } = true; + public bool UseAdvancedDyes { get; set; } = false; public bool ShowRevertAdvancedParametersButton { get; set; } = true; public bool ShowPalettePlusImport { get; set; } = true; public bool UseFloatForColors { get; set; } = true; diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index 9640c71..91050ec 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -33,7 +33,7 @@ public class DesignEditor( /// public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings _ = default) { - var design = (Design)data; + var design = (Design)data; var oldValue = design.DesignData.Customize[idx]; switch (idx) { @@ -96,7 +96,7 @@ public class DesignEditor( public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings _ = default) { var design = (Design)data; - var old = design.DesignData.Parameters[flag]; + var old = design.DesignData.Parameters[flag]; if (!design.GetDesignDataRef().Parameters.Set(flag, value)) return; @@ -219,6 +219,68 @@ public class DesignEditor( DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value)); } + public void ChangeMaterialRevert(Design design, MaterialValueIndex index, bool revert) + { + var materials = design.GetMaterialDataRef(); + if (!materials.TryGetValue(index, out var oldValue)) + return; + + materials.AddOrUpdateValue(index, oldValue with { Revert = revert }); + Glamourer.Log.Debug($"Changed advanced dye value for {index} to {(revert ? "Revert." : "no longer Revert.")}"); + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.MaterialRevert, design, index); + } + + public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row) + { + var materials = design.GetMaterialDataRef(); + if (materials.TryGetValue(index, out var oldValue)) + { + if (!row.HasValue) + { + materials.RemoveValue(index); + Glamourer.Log.Debug($"Removed advanced dye value for {index}."); + } + else if (!row.Value.NearEqual(oldValue.Value)) + { + materials.UpdateValue(index, new MaterialValueDesign(row.Value, oldValue.Enabled, oldValue.Revert), out _); + Glamourer.Log.Debug($"Updated advanced dye value for {index} to new value."); + } + else + { + return; + } + } + else + { + if (!row.HasValue) + return; + if (!materials.TryAddValue(index, new MaterialValueDesign(row.Value, true, false))) + return; + + Glamourer.Log.Debug($"Added new advanced dye value for {index}."); + } + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.DelaySave(design); + DesignChanged.Invoke(DesignChanged.Type.Material, design, (oldValue, row, index)); + } + + public void ChangeApplyMaterialValue(Design design, MaterialValueIndex index, bool value) + { + var materials = design.GetMaterialDataRef(); + if (!materials.TryGetValue(index, out var oldValue) || oldValue.Enabled == value) + return; + + materials.AddOrUpdateValue(index, oldValue with { Enabled = value }); + Glamourer.Log.Debug($"Changed application of advanced dye for {index} to {value}."); + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.ApplyMaterial, design, index); + } + + /// public void ApplyDesign(object data, MergedDesign other, ApplySettings _ = default) => ApplyDesign(data, other.Design); @@ -262,7 +324,8 @@ public class DesignEditor( } /// Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. - private bool ChangeMainhandPeriphery(DesignBase design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, + private bool ChangeMainhandPeriphery(DesignBase design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, + out EquipItem? newOff, out EquipItem? newGauntlets) { newOff = null; diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 4b29582..7aa0fdf 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -1,5 +1,6 @@ using Glamourer.Automation; using Glamourer.GameData; +using Glamourer.Interop.Material; using Glamourer.Services; using Glamourer.State; using Glamourer.Unlocks; @@ -46,6 +47,7 @@ public class DesignMerger( ReduceCrests(data, crestFlags, ret, source); ReduceParameters(data, parameterFlags, ret, source); ReduceMods(design as Design, ret, modAssociations); + ReduceMaterials(design, ret); } ApplyFixFlags(ret, fixFlags); @@ -53,6 +55,15 @@ public class DesignMerger( } + private static void ReduceMaterials(DesignBase? design, MergedDesign ret) + { + if (design == null) + return; + var materials = ret.Design.GetMaterialDataRef(); + foreach (var (key, value) in design.Materials.Where(p => p.Item2.Enabled)) + materials.TryAddValue(MaterialValueIndex.FromKey(key), value); + } + private static void ReduceMods(Design? design, MergedDesign ret, bool modAssociations) { if (design == null || !modAssociations) diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 81d56e4..70e9aa6 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -74,10 +74,16 @@ public sealed class DesignChanged() /// An existing design had a customize parameter changed. Data is the old value, the new value and the flag [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. Parameter, + /// An existing design had an advanced dye row added, changed, or deleted. Data is the old value, the new value and the index [(ColorRow?, ColorRow?, MaterialValueIndex)]. + Material, + + /// An existing design had an advanced dye rows Revert state changed. Data is the index [MaterialValueIndex]. + MaterialRevert, + /// An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. ApplyCustomize, - /// An existing design changed whether a specific equipment is applied. Data is the slot of the equipment [EquipSlot]. + /// An existing design changed whether a specific equipment piece is applied. Data is the slot of the equipment [EquipSlot]. ApplyEquip, /// An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. @@ -89,6 +95,9 @@ public sealed class DesignChanged() /// An existing design changed whether a specific customize parameter is applied. Data is the flag for the parameter [CustomizeParameterFlag]. ApplyParameter, + /// An existing design changed whether an advanced dye row is applied. Data is the index [MaterialValueIndex]. + ApplyMaterial, + /// An existing design changed its write protection status. Data is the new value [bool]. WriteProtection, diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index ff6e0c5..fb50f55 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -80,7 +80,7 @@ public partial class CustomizationDrawer { var size = ImGui.GetItemRectSize(); ImGui.GetWindowDrawList() - .AddCircleFilled(ImGui.GetItemRectMin() + size / 2, size.X / 4, ImGuiUtil.ContrastColorBW(custom.Color)); + .AddCircleFilled(ImGui.GetItemRectMin() + size / 2, size.X / 4, ImGuiUtil.ContrastColorBw(custom.Color)); } if (i % 8 != 7) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index a1a748f..2521765 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -240,7 +240,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Revert the advanced customizations of the player character to their game state."; + tooltip = "Left-Click: Revert the advanced customizations and dyes of the player character to their game state."; } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) @@ -248,7 +248,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (available != 0) tooltip += '\n'; available |= 2; - tooltip += $"Right-Click: Revert the advanced customizations of {_targetIdentifier} to their game state."; + tooltip += $"Right-Click: Revert the advanced customizations and dyes of {_targetIdentifier} to their game state."; } if (available == 0) diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index fb6a4ac..2840dc5 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -10,20 +10,135 @@ using OtterGui; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; +using Penumbra.GameData.Gui; using Penumbra.GameData.Structs; +using Vortice.DXGI; namespace Glamourer.Gui.Materials; -public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _designManager) : IService +public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _designManager, Configuration _config) : IService { - private static readonly IReadOnlyList Types = - [ - MaterialValueIndex.DrawObjectType.Human, - MaterialValueIndex.DrawObjectType.Mainhand, - MaterialValueIndex.DrawObjectType.Offhand, - ]; + private ActorState? _state; + private EquipSlot _newSlot = EquipSlot.Head; + private int _newMaterialIdx; + private int _newRowIdx; + private MaterialValueIndex _newKey = MaterialValueIndex.Min(); - private ActorState? _state; + public void DrawDesignPanel(Design design) + { + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + using (var table = ImRaii.Table("table", 5, ImGuiTableFlags.RowBg)) + { + if (!table) + return; + + + ImGui.TableSetupColumn("button", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); + ImGui.TableSetupColumn("enabled", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); + ImGui.TableSetupColumn("values", ImGuiTableColumnFlags.WidthFixed, ImGui.GetStyle().ItemInnerSpacing.X * 4 + 3 * buttonSize.X + 220 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X); + ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch); + + + + for (var i = 0; i < design.Materials.Count; ++i) + { + var (idx, value) = design.Materials[i]; + var key = MaterialValueIndex.FromKey(idx); + var name = key.DrawObject switch + { + MaterialValueIndex.DrawObjectType.Human => ((uint)key.SlotIndex).ToEquipSlot().ToName(), + MaterialValueIndex.DrawObjectType.Mainhand => EquipSlot.MainHand.ToName(), + MaterialValueIndex.DrawObjectType.Offhand => EquipSlot.OffHand.ToName(), + _ => string.Empty, + }; + if (name.Length == 0) + continue; + + name = $"{name} Material #{key.MaterialIndex + 1} Row #{key.RowIndex + 1}"; + using var id = ImRaii.PushId((int)idx); + var deleteEnabled = _config.DeleteDesignModifier.IsActive(); + ImGui.TableNextColumn(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, + $"Delete this color row.{(deleteEnabled ? string.Empty : $"\nHold {_config.DeleteDesignModifier} to delete.")}", + !deleteEnabled, true)) + { + _designManager.ChangeMaterialValue(design, key, null); + --i; + } + + ImGui.TableNextColumn(); + var enabled = value.Enabled; + if (ImGui.Checkbox("Enabled", ref enabled)) + _designManager.ChangeApplyMaterialValue(design, key, enabled); + + ImGui.TableNextColumn(); + var revert = value.Revert; + using (ImRaii.Disabled(revert)) + { + var row = value.Value; + DrawRow(design, key, row); + } + + ImGui.TableNextColumn(); + + if (ImGui.Checkbox("Revert", ref revert)) + _designManager.ChangeMaterialRevert(design, key, revert); + ImGuiUtil.HoverTooltip( + "If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."); + + ImGui.TableNextColumn(); + ImGui.TextUnformatted(name); + } + } + + var exists = design.GetMaterialDataRef().TryGetValue(_newKey, out _); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, + exists ? "The selected advanced dye row already exists." : "Add the selected advanced dye row.", exists, true)) + _designManager.ChangeMaterialValue(design, _newKey, ColorRow.Empty); + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + if (EquipSlotCombo.Draw("##slot", "Choose a slot for an advanced dye row.", ref _newSlot)) + _newKey = _newSlot switch + { + EquipSlot.MainHand => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, (byte)_newMaterialIdx, + (byte)_newRowIdx), + EquipSlot.OffHand => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, (byte)_newMaterialIdx, + (byte)_newRowIdx), + _ => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte)_newSlot.ToIndex(), (byte)_newMaterialIdx, + (byte)_newRowIdx), + }; + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + DrawMaterialIdxDrag(); + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + DrawRowIdxDrag(); + } + + private void DrawMaterialIdxDrag() + { + _newMaterialIdx += 1; + ImGui.SetNextItemWidth(ImGui.CalcTextSize("Material #000").X); + if (ImGui.DragInt("##Material", ref _newMaterialIdx, 0.01f, 1, MaterialService.MaterialsPerModel, "Material #%i")) + { + _newMaterialIdx = Math.Clamp(_newMaterialIdx, 1, MaterialService.MaterialsPerModel); + _newKey = _newKey with { MaterialIndex = (byte)(_newMaterialIdx - 1) }; + } + + _newMaterialIdx -= 1; + } + + private void DrawRowIdxDrag() + { + _newRowIdx += 1; + ImGui.SetNextItemWidth(ImGui.CalcTextSize("Row #0000").X); + if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, MtrlFile.ColorTable.NumRows, "Row #%i")) + { + _newRowIdx = Math.Clamp(_newRowIdx, 1, MtrlFile.ColorTable.NumRows); + _newKey = _newKey with { RowIndex = (byte)(_newRowIdx - 1) }; + } + + _newRowIdx -= 1; + } public void DrawActorPanel(Actor actor) { @@ -41,20 +156,21 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) { var item = model.GetArmor(slot).ToWeapon(0); - DrawSlotMaterials(model, slot.ToName(), item, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte) idx, 0, 0)); + DrawSlotMaterials(model, slot.ToName(), item, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte)idx, 0, 0)); } var (mainhand, offhand, mh, oh) = actor.Model.GetWeapons(actor); if (mainhand.IsWeapon && mainhand.AsCharacterBase->SlotCount > 0) - DrawSlotMaterials(mainhand, EquipSlot.MainHand.ToName(), mh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, 0, 0)); + DrawSlotMaterials(mainhand, EquipSlot.MainHand.ToName(), mh, + new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, 0, 0)); if (offhand.IsWeapon && offhand.AsCharacterBase->SlotCount > 0) - DrawSlotMaterials(offhand, EquipSlot.OffHand.ToName(), oh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, 0, 0)); + DrawSlotMaterials(offhand, EquipSlot.OffHand.ToName(), oh, + new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, 0, 0)); } private void DrawSlotMaterials(Model model, string name, CharacterWeapon drawData, MaterialValueIndex index) { - var drawnMaterial = 1; for (byte materialIndex = 0; materialIndex < MaterialService.MaterialsPerModel; ++materialIndex) { var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + materialIndex; @@ -64,11 +180,11 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table)) continue; - using var tree = ImRaii.TreeNode($"{name} Material #{drawnMaterial++}###{name}{materialIndex}"); + using var tree = ImRaii.TreeNode($"{name} Material #{materialIndex + 1}###{name}{materialIndex}"); if (!tree) continue; - DrawMaterial(ref table, drawData, index with { MaterialIndex = materialIndex} ); + DrawMaterial(ref table, drawData, index with { MaterialIndex = materialIndex }); } } @@ -82,6 +198,27 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de } } + private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row) + { + var spacing = ImGui.GetStyle().ItemInnerSpacing; + var tmp = row; + var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", row.Diffuse, v => tmp.Diffuse = v, "D"); + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", row.Specular, v => tmp.Specular = v, "S"); + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", row.Emissive, v => tmp.Emissive = v, "E"); + ImGui.SameLine(0, spacing.X); + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + applied |= ImGui.DragFloat("##Gloss", ref tmp.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G"); + ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + ImGui.SameLine(0, spacing.X); + ImGui.SetNextItemWidth(120 * ImGuiHelpers.GlobalScale); + applied |= ImGui.DragFloat("##Specular Strength", ref tmp.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS"); + ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + if (applied) + _designManager.ChangeMaterialValue(design, index, tmp); + } + private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index) { using var id = ImRaii.PushId(index.RowIndex); @@ -92,22 +229,33 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual); } - var applied = ImGui.ColorEdit3("Diffuse", ref value.Model.Diffuse, ImGuiColorEditFlags.NoInputs); - ImGui.SameLine(); - applied |= ImGui.ColorEdit3("Specular", ref value.Model.Specular, ImGuiColorEditFlags.NoInputs); - ImGui.SameLine(); - applied |= ImGui.ColorEdit3("Emissive", ref value.Model.Emissive, ImGuiColorEditFlags.NoInputs); - ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImGui.TextUnformatted($"Row {index.RowIndex + 1:D2}"); + } + + ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); + var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, v => value.Model.Diffuse = v, "D"); + + var spacing = ImGui.GetStyle().ItemInnerSpacing; + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular, v => value.Model.Specular = v, "S"); + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive, v => value.Model.Emissive = v, "E"); + ImGui.SameLine(0, spacing.X); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("Gloss", ref value.Model.GlossStrength, 0.1f); - ImGui.SameLine(); + applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") && value.Model.GlossStrength > 0; + ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + ImGui.SameLine(0, spacing.X); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("Specular Strength", ref value.Model.SpecularStrength, 0.1f); + applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS"); + ImGuiUtil.HoverTooltip("Change the specular strength for this row."); if (applied) _stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual); if (changed) { - ImGui.SameLine(); + ImGui.SameLine(0, spacing.X); using (ImRaii.PushFont(UiBuilder.IconFont)) { using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value()); @@ -115,52 +263,4 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de } } } - - private static readonly IReadOnlyList SlotNames = - [ - "Slot 1", - "Slot 2", - "Slot 3", - "Slot 4", - "Slot 5", - "Slot 6", - "Slot 7", - "Slot 8", - "Slot 9", - "Slot 10", - "Slot 11", - "Slot 12", - "Slot 13", - "Slot 14", - "Slot 15", - "Slot 16", - "Slot 17", - "Slot 18", - "Slot 19", - "Slot 20", - ]; - - private static readonly IReadOnlyList SlotNamesHuman = - [ - "Head", - "Body", - "Hands", - "Legs", - "Feet", - "Earrings", - "Neck", - "Wrists", - "Right Finger", - "Left Finger", - "Slot 11", - "Slot 12", - "Slot 13", - "Slot 14", - "Slot 15", - "Slot 16", - "Slot 17", - "Slot 18", - "Slot 19", - "Slot 20", - ]; } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 968b88c..e1c8356 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -117,9 +117,8 @@ public class ActorPanel( RevertButtons(); - // TODO Materials - //if (ImGui.CollapsingHeader("Material Shit")) - // _materialDrawer.DrawActorPanel(_actor); + if (_config.UseAdvancedDyes && ImGui.CollapsingHeader("Material Shit")) + _materialDrawer.DrawActorPanel(_actor); using var disabled = ImRaii.Disabled(transformationId != 0); if (_state.ModelData.IsHuman) DrawHumanPanel(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 3cf46fe..168f994 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -7,6 +7,7 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; +using Glamourer.Gui.Materials; using Glamourer.Interop; using Glamourer.State; using ImGuiNET; @@ -31,7 +32,8 @@ public class DesignPanel( ImportService _importService, MultiDesignPanel _multiDesignPanel, CustomizeParameterDrawer _parameterDrawer, - DesignLinkDrawer _designLinkDrawer) + DesignLinkDrawer _designLinkDrawer, + MaterialDrawer _materials) { private readonly FileDialogManager _fileDialog = new(); @@ -176,21 +178,14 @@ public class DesignPanel( private void DrawMaterialValues() { - if (!_config.UseAdvancedParameters) + if (!_config.UseAdvancedDyes) return; using var h = ImRaii.CollapsingHeader("Advanced Dyes"); if (!h) return; - foreach (var ((key, value), i) in _selector.Selected!.Materials.WithIndex()) - { - using var id = ImRaii.PushId(i); - ImGui.TextUnformatted($"{key:X16}"); - ImGui.SameLine(); - var enabled = value.Enabled; - ImGui.Checkbox("Enabled", ref enabled); - } + _materials.DrawDesignPanel(_selector.Selected!); } private void DrawCustomizeApplication() @@ -384,7 +379,7 @@ public class DesignPanel( DrawCustomize(); DrawEquipment(); DrawCustomizeParameters(); - //DrawMaterialValues(); TODO Materials + DrawMaterialValues(); _designDetails.Draw(); DrawApplicationRules(); _modAssociations.Draw(); diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index b8c33c8..38e45fe 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -18,20 +18,21 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable private readonly StateManager _stateManager; private readonly PenumbraService _penumbra; private readonly ActorManager _actors; + private readonly Configuration _config; private int _lastSlot; private readonly ThreadLocal> _deleteList = new(() => []); - public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra) + public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra, + Configuration config) { _stateManager = stateManager; _actors = actors; _penumbra = penumbra; + _config = config; _event = prepareColorSet; - - // TODO Material - //_event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager); + _event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager); } public void Dispose() @@ -39,6 +40,9 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainId stain, ref nint ret) { + if (!_config.UseAdvancedDyes) + return; + var actor = _penumbra.GameObjectFromDrawObject(characterBase); var validType = FindType(characterBase, actor, out var type); var (slotId, materialId) = FindMaterial(characterBase, material); @@ -176,7 +180,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable } /// We need to get the temporary set, variant and stain that is currently being set if it is available. - private CharacterWeapon GetTempSlot(Human* human, byte slotId) + private static CharacterWeapon GetTempSlot(Human* human, byte slotId) { if (human->ChangedEquipData == null) return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0); @@ -188,7 +192,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// We need to get the temporary set, variant and stain that is currently being set if it is available. /// Weapons do not change in skeleton id without being reconstructed, so this is not changeable data. /// - private CharacterWeapon GetTempSlot(Weapon* weapon) + private static CharacterWeapon GetTempSlot(Weapon* weapon) { var changedData = *(void**)((byte*)weapon + 0x918); if (changedData == null) diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 47ddb7a..0facba4 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -2,6 +2,7 @@ using FFXIVClientStructs.Interop; using Glamourer.Interop.Structs; using Newtonsoft.Json; +using Penumbra.GameData.Enums; using Penumbra.GameData.Files; namespace Glamourer.Interop.Material; @@ -150,6 +151,16 @@ public readonly record struct MaterialValueIndex( : this((DrawObjectType)(key >> 24), (byte)(key >> 16), (byte)(key >> 8), (byte)key) { } + public override string ToString() + => DrawObject switch + { + DrawObjectType.Human when SlotIndex < 10 => + $"{((uint)SlotIndex).ToEquipSlot().ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Mainhand when SlotIndex == 0 => $"{EquipSlot.MainHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Offhand when SlotIndex == 0 => $"{EquipSlot.OffHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + _ => $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + }; + private class Converter : JsonConverter { public override void WriteJson(JsonWriter writer, MaterialValueIndex value, JsonSerializer serializer) diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index c735182..35b61fc 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -126,16 +126,26 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa } [JsonConverter(typeof(Converter))] -public struct MaterialValueDesign(ColorRow value, bool enabled) +public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert) { public ColorRow Value = value; public bool Enabled = enabled; + public bool Revert = revert; public readonly bool Apply(ref MaterialValueState state) { if (!Enabled) return false; + if (revert) + { + if (state.Model.NearEqual(state.Game)) + return false; + + state.Model = state.Game; + return true; + } + if (state.Model.NearEqual(Value)) return false; @@ -148,6 +158,8 @@ public struct MaterialValueDesign(ColorRow value, bool enabled) public override void WriteJson(JsonWriter writer, MaterialValueDesign value, JsonSerializer serializer) { writer.WriteStartObject(); + writer.WritePropertyName("Revert"); + writer.WriteValue(value.Revert); writer.WritePropertyName("DiffuseR"); writer.WriteValue(value.Value.Diffuse.X); writer.WritePropertyName("DiffuseG"); @@ -180,6 +192,7 @@ public struct MaterialValueDesign(ColorRow value, bool enabled) JsonSerializer serializer) { var obj = JObject.Load(reader); + Set(ref existingValue.Revert, obj["Revert"]?.Value()); Set(ref existingValue.Value.Diffuse.X, obj["DiffuseR"]?.Value()); Set(ref existingValue.Value.Diffuse.Y, obj["DiffuseG"]?.Value()); Set(ref existingValue.Value.Diffuse.Z, obj["DiffuseB"]?.Value()); @@ -235,7 +248,7 @@ public struct MaterialValueState( && Game.NearEqual(rhsRow); public readonly MaterialValueDesign Convert() - => new(Model, true); + => new(Model, true, false); } public readonly struct MaterialValueManager diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 7e38726..1b9c69a 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -278,7 +278,7 @@ public class StateApplier( public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, ColorRow? value, bool force) { - if (!force && !_config.UseAdvancedParameters) + if (!force && !_config.UseAdvancedDyes) return; foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 3705d86..f77e75a 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -172,7 +172,8 @@ public class StateEditor( return; var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange()); - Glamourer.Log.Verbose($"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index)); } @@ -288,13 +289,25 @@ public class StateEditor( if (!value.Enabled) continue; - var idx = MaterialValueIndex.FromKey(key); - // TODO - //if (state.Materials.TryGetValue(idx, out var materialState)) - //{ - // if (!settings.RespectManual || materialState.Source.IsManual()) - // Editor.ChangeMaterialValue(state, idx, new MaterialValueState(materialState.Game, value.Value, materialState.DrawData)); - //} + var idx = MaterialValueIndex.FromKey(key); + var source = settings.Source.SetPending(); + if (state.Materials.TryGetValue(idx, out var materialState)) + { + if (settings.RespectManual && !materialState.Source.IsManual()) + continue; + + if (value.Revert) + Editor.ChangeMaterialValue(state, idx, default, StateSource.Game, out _, settings.Key); + else + Editor.ChangeMaterialValue(state, idx, + new MaterialValueState(materialState.Game, value.Value, materialState.DrawData, source), settings.Source, out _, + settings.Key); + } + else if (!value.Revert) + { + Editor.ChangeMaterialValue(state, idx, new MaterialValueState(ColorRow.Empty, value.Value, CharacterWeapon.Empty, source), + settings.Source, out _, settings.Key); + } } } @@ -321,7 +334,8 @@ public class StateEditor( public void ApplyDesign(object data, DesignBase design, ApplySettings settings) { var merged = settings.MergeLinks && design is Design d - ? merger.Merge(d.AllLinks, ((ActorState)data).ModelData.Customize, ((ActorState)data).BaseData, false, Config.AlwaysApplyAssociatedMods) + ? merger.Merge(d.AllLinks, ((ActorState)data).ModelData.Customize, ((ActorState)data).BaseData, false, + Config.AlwaysApplyAssociatedMods) : new MergedDesign(design); ApplyDesign(data, merged, settings with diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 32d1237..d731a66 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -4,6 +4,7 @@ using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; +using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; @@ -11,6 +12,7 @@ using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using System; namespace Glamourer.State; @@ -265,9 +267,16 @@ public sealed class StateManager( var actors = ActorData.Invalid; if (source is not StateSource.Game) + { actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); + foreach (var (idx, mat) in state.Materials.Values) + Applier.ChangeMaterialValue(actors, MaterialValueIndex.FromKey(idx), mat.Game, true); + } + + state.Materials.Clear(); + Glamourer.Log.Verbose( - $"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null); } diff --git a/OtterGui b/OtterGui index 1a187f7..75c5a7b 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 1a187f756f2e8823197bd43db1c3383231f5eaff +Subproject commit 75c5a7b220e7f799f85741288e3de4a20af9bcf4 From fb7a92b72a1afc279494cf97caae7d415541ad5b Mon Sep 17 00:00:00 2001 From: Caraxi Date: Thu, 15 Feb 2024 19:05:27 +1030 Subject: [PATCH 253/786] Add ability to update configuration of associated mod --- Glamourer/Designs/DesignManager.cs | 10 ++++ .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 50 +++++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index da6ed90..e880feb 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -273,6 +273,16 @@ 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; + design.AssociatedMods[mod] = settings; + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Updated associated mod {mod.DirectoryName} from design {design.Identifier}."); + DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings)); + } + /// Set the write protection status of a design. public void SetWriteProtection(Design design, bool value) { diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index e70cbec..dd0d82e 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -66,11 +66,12 @@ public class ModAssociationsTab private void DrawTable() { - using var table = ImRaii.Table("Mods", 6, ImGuiTableFlags.RowBg); + using var table = ImRaii.Table("Mods", 7, ImGuiTableFlags.RowBg); if (!table) return; ImGui.TableSetupColumn("##Delete", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight()); + ImGui.TableSetupColumn("##Update", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight()); ImGui.TableSetupColumn("Mod Name", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("Directory Name", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("State", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("State").X); @@ -79,28 +80,70 @@ public class ModAssociationsTab ImGui.TableHeadersRow(); Mod? removedMod = null; + (Mod mod, ModSettings settings)? updatedMod = null; foreach (var ((mod, settings), idx) in _selector.Selected!.AssociatedMods.WithIndex()) { using var id = ImRaii.PushId(idx); - DrawAssociatedModRow(mod, settings, out var removedModTmp); + DrawAssociatedModRow(mod, settings, out var removedModTmp, out var updatedModTmp); if (removedModTmp.HasValue) removedMod = removedModTmp; + if (updatedModTmp.HasValue) + updatedMod = updatedModTmp; } DrawNewModRow(); if (removedMod.HasValue) _manager.RemoveMod(_selector.Selected!, removedMod.Value); + + if (updatedMod.HasValue) + _manager.UpdateMod(_selector.Selected!, updatedMod.Value.mod, updatedMod.Value.settings); } - private void DrawAssociatedModRow(Mod mod, ModSettings settings, out Mod? removedMod) + private void DrawAssociatedModRow(Mod mod, ModSettings settings, out Mod? removedMod, out (Mod, ModSettings)? updatedMod) { removedMod = null; + updatedMod = null; ImGui.TableNextColumn(); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "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); + + if (ImGui.IsItemHovered()) + { + var (_, newSettings) = _penumbra.GetMods().FirstOrDefault(m => m.Mod == mod); + if (ImGui.IsItemClicked()) + updatedMod = (mod, newSettings); + + using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale); + using var tt = ImRaii.Tooltip(); + ImGui.Separator(); + var namesDifferent = mod.Name != mod.DirectoryName; + ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0)); + using (var group = ImRaii.Group()) + { + if (namesDifferent) + ImGui.TextUnformatted("Directory Name"); + ImGui.TextUnformatted("Enabled"); + ImGui.TextUnformatted("Priority"); + ModCombo.DrawSettingsLeft(newSettings); + } + + ImGui.SameLine(Math.Max(ImGui.GetItemRectSize().X + 3 * ImGui.GetStyle().ItemSpacing.X, 150 * ImGuiHelpers.GlobalScale)); + using (var group = ImRaii.Group()) + { + if (namesDifferent) + ImGui.TextUnformatted(mod.DirectoryName); + ImGui.TextUnformatted(newSettings.Enabled.ToString()); + ImGui.TextUnformatted(newSettings.Priority.ToString()); + ModCombo.DrawSettingsRight(newSettings); + } + } + ImGui.TableNextColumn(); var selected = ImGui.Selectable($"{mod.Name}##name"); var hovered = ImGui.IsItemHovered(); @@ -156,6 +199,7 @@ public class ModAssociationsTab { 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 0f7d7caba86c4709e5300893a5766a1c9b9c9a00 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 16 Feb 2024 00:13:02 +0100 Subject: [PATCH 254/786] Fix squared values. --- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 6 +- .../Interop/Material/MaterialValueManager.cs | 89 +++++-------------- 2 files changed, 27 insertions(+), 68 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index dd0d82e..ae2a76c 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -124,7 +124,7 @@ public class ModAssociationsTab ImGui.Separator(); var namesDifferent = mod.Name != mod.DirectoryName; ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0)); - using (var group = ImRaii.Group()) + using (ImRaii.Group()) { if (namesDifferent) ImGui.TextUnformatted("Directory Name"); @@ -134,7 +134,7 @@ public class ModAssociationsTab } ImGui.SameLine(Math.Max(ImGui.GetItemRectSize().X + 3 * ImGui.GetStyle().ItemSpacing.X, 150 * ImGuiHelpers.GlobalScale)); - using (var group = ImRaii.Group()) + using (ImRaii.Group()) { if (namesDifferent) ImGui.TextUnformatted(mod.DirectoryName); @@ -211,6 +211,6 @@ public class ModAssociationsTab _manager.AddMod(_selector.Selected!, _modCombo.CurrentSelection.Mod, _modCombo.CurrentSelection.Settings); ImGui.TableNextColumn(); _modCombo.Draw("##new", currentName.IsNullOrEmpty() ? "Select new Mod..." : currentName, string.Empty, - 200 * ImGuiHelpers.GlobalScale, ImGui.GetTextLineHeight()); + ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight()); } } diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 35b61fc..6714e96 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -10,7 +10,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Material; -[JsonConverter(typeof(Converter))] +/// Values are not squared. public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength) { public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 0, 0); @@ -22,7 +22,7 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa public float GlossStrength = glossStrength; public ColorRow(in MtrlFile.ColorTable.Row row) - : this(row.Diffuse, row.Specular, row.Emissive, row.SpecularStrength, row.GlossStrength) + : this(Root(row.Diffuse), Root(row.Specular), Root(row.Emissive), row.SpecularStrength, row.GlossStrength) { } public readonly bool NearEqual(in ColorRow rhs) @@ -32,24 +32,39 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa && SpecularStrength.NearEqual(rhs.SpecularStrength) && GlossStrength.NearEqual(rhs.GlossStrength); + private static Vector3 Square(Vector3 value) + => new(Square(value.X), Square(value.Y), Square(value.Z)); + + private static float Square(float value) + => value < 0 ? -value * value : value * value; + + private static Vector3 Root(Vector3 value) + => new(Root(value.X), Root(value.Y), Root(value.Z)); + + private static float Root(float value) + => value < 0 ? MathF.Sqrt(-value) : MathF.Sqrt(value); + public readonly bool Apply(ref MtrlFile.ColorTable.Row row) { var ret = false; - if (!row.Diffuse.NearEqual(Diffuse)) + var d = Square(Diffuse); + if (!row.Diffuse.NearEqual(d)) { - row.Diffuse = Diffuse; + row.Diffuse = d; ret = true; } - if (!row.Specular.NearEqual(Specular)) + var s = Square(Specular); + if (!row.Specular.NearEqual(s)) { - row.Specular = Specular; + row.Specular = s; ret = true; } - if (!row.Emissive.NearEqual(Emissive)) + var e = Square(Emissive); + if (!row.Emissive.NearEqual(e)) { - row.Emissive = Emissive; + row.Emissive = e; ret = true; } @@ -67,62 +82,6 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa return ret; } - - private class Converter : JsonConverter - { - public override void WriteJson(JsonWriter writer, ColorRow value, JsonSerializer serializer) - { - writer.WriteStartObject(); - writer.WritePropertyName("DiffuseR"); - writer.WriteValue(value.Diffuse.X); - writer.WritePropertyName("DiffuseG"); - writer.WriteValue(value.Diffuse.Y); - writer.WritePropertyName("DiffuseB"); - writer.WriteValue(value.Diffuse.Z); - writer.WritePropertyName("SpecularR"); - writer.WriteValue(value.Specular.X); - writer.WritePropertyName("SpecularG"); - writer.WriteValue(value.Specular.Y); - writer.WritePropertyName("SpecularB"); - writer.WriteValue(value.Specular.Z); - writer.WritePropertyName("SpecularA"); - writer.WriteValue(value.SpecularStrength); - writer.WritePropertyName("EmissiveR"); - writer.WriteValue(value.Emissive.X); - writer.WritePropertyName("EmissiveG"); - writer.WriteValue(value.Emissive.Y); - writer.WritePropertyName("EmissiveB"); - writer.WriteValue(value.Emissive.Z); - writer.WritePropertyName("Gloss"); - writer.WriteValue(value.GlossStrength); - writer.WriteEndObject(); - } - - public override ColorRow ReadJson(JsonReader reader, Type objectType, ColorRow existingValue, bool hasExistingValue, - JsonSerializer serializer) - { - var obj = JObject.Load(reader); - Set(ref existingValue.Diffuse.X, obj["DiffuseR"]?.Value()); - Set(ref existingValue.Diffuse.Y, obj["DiffuseG"]?.Value()); - Set(ref existingValue.Diffuse.Z, obj["DiffuseB"]?.Value()); - Set(ref existingValue.Specular.X, obj["SpecularR"]?.Value()); - Set(ref existingValue.Specular.Y, obj["SpecularG"]?.Value()); - Set(ref existingValue.Specular.Z, obj["SpecularB"]?.Value()); - Set(ref existingValue.SpecularStrength, obj["SpecularA"]?.Value()); - Set(ref existingValue.Emissive.X, obj["EmissiveR"]?.Value()); - Set(ref existingValue.Emissive.Y, obj["EmissiveG"]?.Value()); - Set(ref existingValue.Emissive.Z, obj["EmissiveB"]?.Value()); - Set(ref existingValue.GlossStrength, obj["Gloss"]?.Value()); - return existingValue; - - static void Set(ref T target, T? value) - where T : struct - { - if (value.HasValue) - target = value.Value; - } - } - } } [JsonConverter(typeof(Converter))] @@ -137,7 +96,7 @@ public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert) if (!Enabled) return false; - if (revert) + if (Revert) { if (state.Model.NearEqual(state.Game)) return false; From 5f74f4b4d9c3f459ade0747a43a8cdb8e1d83681 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 16 Feb 2024 15:11:59 +0100 Subject: [PATCH 255/786] Add preview stuff. --- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 25 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 224 ++++++++++++++++++ Glamourer/Gui/Materials/MaterialDrawer.cs | 3 - Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 34 +-- Glamourer/Gui/Tabs/ActorTab/ActorTab.cs | 15 +- .../Material/LiveColorTablePreviewer.cs | 115 +++++++++ .../Interop/Material/MaterialValueIndex.cs | 4 +- 7 files changed, 370 insertions(+), 50 deletions(-) create mode 100644 Glamourer/Gui/Materials/AdvancedDyePopup.cs create mode 100644 Glamourer/Interop/Material/LiveColorTablePreviewer.cs diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index c56e56e..25732e0 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -34,15 +34,16 @@ public class EquipmentDrawer public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures, Configuration config, GPoseService gPose) { - _items = items; - _codes = codes; - _textures = textures; - _config = config; - _gPose = gPose; - _stainData = items.Stains; - _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); - _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); - _weaponCombo = new Dictionary(FullEquipTypeExtensions.WeaponTypes.Count * 2); + _items = items; + _codes = codes; + _textures = textures; + _config = config; + _gPose = gPose; + _advancedDyes = advancedDyes; + _stainData = items.Stains; + _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); + _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); + _weaponCombo = new Dictionary(FullEquipTypeExtensions.WeaponTypes.Count * 2); foreach (var type in Enum.GetValues()) { if (type.ToSlot() is EquipSlot.MainHand) @@ -465,8 +466,10 @@ public class EquipmentDrawer (var tt, item, var valid) = (allowRevert && !revertItem.Equals(currentItem), allowClear && !clearItem.Equals(currentItem), ImGui.GetIO().KeyCtrl) switch { - (true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", revertItem, true), - (true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", clearItem, true), + (true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", + revertItem, true), + (true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", + clearItem, true), (true, false, true) => ("Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", revertItem, true), (true, false, false) => ("Control and Right-Click to revert to game.\nControl and mouse wheel to scroll.", default, false), (false, true, _) => ("Right-click to clear.\nControl and mouse wheel to scroll.", clearItem, true), diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs new file mode 100644 index 0000000..4f4c76f --- /dev/null +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -0,0 +1,224 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Glamourer.Designs; +using Glamourer.Gui.Tabs.ActorTab; +using Glamourer.Interop.Material; +using Glamourer.Interop.Structs; +using Glamourer.State; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Files; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Materials; + +public sealed unsafe class AdvancedDyePopup( + MainWindowPosition mainPosition, + Configuration config, + StateManager stateManager, + ActorSelector actorSelector, + MaterialDrawer materials, + LiveColorTablePreviewer preview) : IService +{ + private MaterialValueIndex? _drawIndex; + private ActorIdentifier _identifier; + private ActorState? _state; + private Actor _actor; + private byte _selectedMaterial = byte.MaxValue; + + private bool ShouldBeDrawn() + { + if (!mainPosition.IsOpen) + return false; + + if (!config.UseAdvancedDyes) + return false; + + if (config.Ephemeral.SelectedTab is not MainWindow.TabType.Actors) + return false; + + if (!_drawIndex.HasValue) + return false; + + if (actorSelector.Selection.Identifier != _identifier || !_identifier.IsValid) + return false; + + if (_state == null) + return false; + + _actor = actorSelector.Selection.Data.Valid ? actorSelector.Selection.Data.Objects[0] : Actor.Null; + if (!_actor.Valid || !_actor.Model.IsCharacterBase) + return false; + + return true; + } + + public void DrawButton(ActorIdentifier identifier, ActorState state, MaterialValueIndex index) + { + using var id = ImRaii.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); + var isOpen = identifier == _identifier && state == _state && index == _drawIndex; + using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen)) + { + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Palette.ToIconString(), new Vector2(ImGui.GetFrameHeight()), + "Open advanced dyes for this slot.", false, true)) + { + if (isOpen) + { + _drawIndex = null; + _identifier = ActorIdentifier.Invalid; + _state = null; + _selectedMaterial = byte.MaxValue; + } + else + { + _drawIndex = index; + _identifier = identifier; + _state = state; + _selectedMaterial = byte.MaxValue; + } + } + } + } + + public unsafe void Draw() + { + if (!ShouldBeDrawn()) + return; + + var position = mainPosition.Position; + position.X += mainPosition.Size.X; + position.Y += ImGui.GetFrameHeightWithSpacing() * 3; + var size = new Vector2(3 * ImGui.GetFrameHeight() + 300 * ImGuiHelpers.GlobalScale, 18.5f * ImGui.GetFrameHeightWithSpacing()); + ImGui.SetNextWindowPos(position); + ImGui.SetNextWindowSize(size); + var window = ImGui.Begin("###Glamourer Advanced Dyes", + ImGuiWindowFlags.NoFocusOnAppearing + | ImGuiWindowFlags.NoCollapse + | ImGuiWindowFlags.NoDecoration + | ImGuiWindowFlags.NoMove + | ImGuiWindowFlags.NoResize); + try + { + if (!window) + return; + + using var bar = ImRaii.TabBar("tabs"); + if (!bar) + return; + + var model = _actor.Model.AsCharacterBase; + var firstAvailable = true; + Span label = stackalloc byte[12]; + label[0] = (byte)'M'; + label[1] = (byte)'a'; + label[2] = (byte)'t'; + label[3] = (byte)'e'; + label[4] = (byte)'r'; + label[5] = (byte)'i'; + label[6] = (byte)'a'; + label[7] = (byte)'l'; + label[8] = (byte)' '; + label[9] = (byte)'#'; + label[11] = 0; + + for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) + { + var texture = model->ColorTableTextures + _drawIndex!.Value.SlotIndex * MaterialService.MaterialsPerModel + i; + var index = _drawIndex!.Value with { MaterialIndex = i }; + var available = *texture != null && DirectXTextureHelper.TryGetColorTable(*texture, out var table); + if (index == preview.LastValueIndex with {RowIndex = 0}) + table = preview.LastOriginalColorTable; + + using var disable = ImRaii.Disabled(!available); + label[10] = (byte)('1' + i); + var select = available && (_selectedMaterial == i || firstAvailable && _selectedMaterial == byte.MaxValue) + ? ImGuiTabItemFlags.SetSelected + : ImGuiTabItemFlags.None; + + if (available) + firstAvailable = false; + if (select is ImGuiTabItemFlags.SetSelected) + _selectedMaterial = i; + + + fixed (byte* labelPtr = label) + { + using var tab = ImRaii.TabItem(labelPtr, select); + if (tab.Success && available) + DrawTable(index, table); + } + } + } + finally + { + ImGui.End(); + } + } + + private void DrawTable(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table) + { + for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i) + { + var index = materialIndex with { RowIndex = i }; + ref var row = ref table[i]; + DrawRow(ref row, CharacterWeapon.Empty, index, table); + } + } + + private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index, in MtrlFile.ColorTable table) + { + using var id = ImRaii.PushId(index.RowIndex); + var changed = _state!.Materials.TryGetValue(index, out var value); + if (!changed) + { + var internalRow = new ColorRow(row); + value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual); + } + + ImGui.AlignTextToFramePadding(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImGui.TextUnformatted($"Row {index.RowIndex + 1:D2}"); + } + + ImGui.SameLine(); + ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Locate", false, true); + if (ImGui.IsItemHovered()) + preview.OnHover(index, _actor.Index, table); + + ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); + var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, + v => value.Model.Diffuse = v, "D"); + + var spacing = ImGui.GetStyle().ItemInnerSpacing; + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular, + v => value.Model.Specular = v, "S"); + ImGui.SameLine(0, spacing.X); + applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive, + v => value.Model.Emissive = v, "E"); + ImGui.SameLine(0, spacing.X); + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") + && value.Model.GlossStrength > 0; + ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + ImGui.SameLine(0, spacing.X); + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS"); + ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + if (applied) + stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual); + if (changed) + { + ImGui.SameLine(0, spacing.X); + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value()); + ImGui.TextUnformatted(FontAwesomeIcon.UserEdit.ToIconString()); + } + } + } +} diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 2840dc5..c3efa1f 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -32,15 +32,12 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de if (!table) return; - ImGui.TableSetupColumn("button", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); ImGui.TableSetupColumn("enabled", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); ImGui.TableSetupColumn("values", ImGuiTableColumnFlags.WidthFixed, ImGui.GetStyle().ItemInnerSpacing.X * 4 + 3 * buttonSize.X + 220 * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X); ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch); - - for (var i = 0; i < design.Materials.Count; ++i) { var (idx, value) = design.Materials[i]; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 8bace46..3c23fbc 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -1,7 +1,6 @@ using Dalamud.Interface; using Glamourer.Interop; using Glamourer.Interop.Structs; -using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -10,28 +9,17 @@ using Penumbra.GameData.Actors; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorSelector +public class ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config) { - private readonly EphemeralConfig _config; - private readonly ObjectManager _objects; - private readonly ActorManager _actors; - private ActorIdentifier _identifier = ActorIdentifier.Invalid; - public ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config) - { - _objects = objects; - _actors = actors; - _config = config; - } - public bool IncognitoMode { - get => _config.IncognitoMode; + get => config.IncognitoMode; set { - _config.IncognitoMode = value; - _config.Save(); + config.IncognitoMode = value; + config.Save(); } } @@ -40,7 +28,7 @@ public class ActorSelector private float _width; public (ActorIdentifier Identifier, ActorData Data) Selection - => _objects.TryGetValue(_identifier, out var data) ? (_identifier, data) : (_identifier, ActorData.Invalid); + => objects.TryGetValue(_identifier, out var data) ? (_identifier, data) : (_identifier, ActorData.Invalid); public bool HasSelection => _identifier.IsValid; @@ -65,10 +53,10 @@ public class ActorSelector if (!child) return; - _objects.Update(); + objects.Update(); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); - var remainder = ImGuiClip.FilteredClippedDraw(_objects, skips, CheckFilter, DrawSelectable); + var remainder = ImGuiClip.FilteredClippedDraw(objects, skips, CheckFilter, DrawSelectable); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } @@ -89,14 +77,14 @@ public class ActorSelector var buttonWidth = new Vector2(_width / 2, 0); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth - , "Select the local player character.", !_objects.Player, true)) - _identifier = _objects.Player.GetIdentifier(_actors); + , "Select the local player character.", !objects.Player, true)) + _identifier = objects.Player.GetIdentifier(actors); ImGui.SameLine(); - var (id, data) = _objects.TargetData; + var (id, data) = objects.TargetData; var tt = data.Valid ? $"Select the current target {id} in the list." : id.IsValid ? $"The target {id} is not in the list." : "No target selected."; - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth, tt, _objects.IsInGPose || !data.Valid, true)) + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth, tt, objects.IsInGPose || !data.Valid, true)) _identifier = id; } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs index 0d4584d..4e5e15c 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs @@ -4,24 +4,15 @@ using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorTab : ITab +public class ActorTab(ActorSelector selector, ActorPanel panel) : ITab { - private readonly ActorSelector _selector; - private readonly ActorPanel _panel; - public ReadOnlySpan Label => "Actors"u8; public void DrawContent() { - _selector.Draw(200 * ImGuiHelpers.GlobalScale); + selector.Draw(200 * ImGuiHelpers.GlobalScale); ImGui.SameLine(); - _panel.Draw(); - } - - public ActorTab(ActorSelector selector, ActorPanel panel) - { - _selector = selector; - _panel = panel; + panel.Draw(); } } diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs new file mode 100644 index 0000000..752101a --- /dev/null +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -0,0 +1,115 @@ +using Dalamud.Plugin.Services; +using Glamourer.Interop.Structs; +using ImGuiNET; +using OtterGui.Services; +using Penumbra.GameData.Files; +using Penumbra.GameData.Structs; + +namespace Glamourer.Interop.Material; + +public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable +{ + private readonly IObjectTable _objects; + private readonly IFramework _framework; + + 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 LiveColorTablePreviewer(IObjectTable objects, IFramework framework) + { + _objects = objects; + _framework = framework; + _framework.Update += OnFramework; + } + + private void Reset() + { + if (!LastValueIndex.Valid || _lastObjectIndex == ObjectIndex.AnyIndex) + return; + + var actor = (Actor)_objects.GetObjectAddress(_lastObjectIndex.Index); + if (actor.IsCharacter && LastValueIndex.TryGetTexture(actor, out var texture)) + MaterialService.ReplaceColorTable(texture, LastOriginalColorTable); + + Glamourer.Log.Information($"Reset {_lastObjectIndex} {LastValueIndex}"); + LastValueIndex = MaterialValueIndex.Invalid; + _lastObjectIndex = ObjectIndex.AnyIndex; + } + + private void OnFramework(IFramework _) + { + if (!_valueIndex.Valid || _objectIndex == ObjectIndex.AnyIndex) + { + Reset(); + _valueIndex = MaterialValueIndex.Invalid; + _objectIndex = ObjectIndex.AnyIndex; + return; + } + + var actor = (Actor)_objects.GetObjectAddress(_objectIndex.Index); + if (!actor.IsCharacter) + { + _valueIndex = MaterialValueIndex.Invalid; + _objectIndex = ObjectIndex.AnyIndex; + return; + } + + if (_valueIndex != LastValueIndex || _lastObjectIndex != _objectIndex) + { + Reset(); + LastValueIndex = _valueIndex; + _lastObjectIndex = _objectIndex; + LastOriginalColorTable = _originalColorTable; + } + + if (_valueIndex.TryGetTexture(actor, out var texture)) + { + Glamourer.Log.Information($"Set {_objectIndex} {_valueIndex}"); + var diffuse = CalculateDiffuse(); + var table = LastOriginalColorTable; + table[_valueIndex.RowIndex].Diffuse = diffuse; + table[_valueIndex.RowIndex].Emissive = diffuse / 8; + MaterialService.ReplaceColorTable(texture, table); + } + + _valueIndex = MaterialValueIndex.Invalid; + _objectIndex = ObjectIndex.AnyIndex; + } + + public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, MtrlFile.ColorTable table) + { + if (_valueIndex.Valid) + return; + + _valueIndex = index; + _objectIndex = objectIndex; + if (!LastValueIndex.Valid + || _lastObjectIndex == ObjectIndex.AnyIndex + || LastValueIndex.MaterialIndex != _valueIndex.MaterialIndex + || LastValueIndex.DrawObject != _valueIndex.DrawObject + || LastValueIndex.SlotIndex != _valueIndex.SlotIndex) + _originalColorTable = table; + } + + private static Vector3 CalculateDiffuse() + { + const int frameLength = 1; + const int steps = 64; + var frame = ImGui.GetFrameCount(); + var hueByte = frame % (steps * frameLength) / frameLength; + var hue = (float)hueByte / steps; + ImGui.ColorConvertHSVtoRGB(hue, 1, 1, out var r, out var g, out var b); + return new Vector3(r, g, b); + } + + public void Dispose() + { + Reset(); + _framework.Update -= OnFramework; + } +} diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 0facba4..13c7577 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -14,6 +14,7 @@ public readonly record struct MaterialValueIndex( byte MaterialIndex, byte RowIndex) { + public static readonly MaterialValueIndex Invalid = new(DrawObjectType.Invalid, 0, 0, 0); public uint Key => ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex); @@ -121,13 +122,14 @@ public readonly record struct MaterialValueIndex( public enum DrawObjectType : byte { + Invalid, Human, Mainhand, Offhand, }; public static bool Validate(DrawObjectType type) - => Enum.IsDefined(type); + => type is not DrawObjectType.Invalid && Enum.IsDefined(type); public static bool ValidateSlot(byte slotIndex) => slotIndex < 10; From 59529476ebd1322304741607f8e30c328616ba3c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 16 Feb 2024 17:48:40 +0100 Subject: [PATCH 256/786] Further improvements. --- Glamourer/Configuration.cs | 1 + Glamourer/Events/StateChanged.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 28 +- Glamourer/Gui/GenericPopupWindow.cs | 14 +- Glamourer/Gui/MainWindow.cs | 28 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 303 +++++++++++------- Glamourer/Gui/Materials/ColorRowClipboard.cs | 20 ++ Glamourer/Gui/Materials/MaterialDrawer.cs | 34 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 8 +- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 5 + .../Material/LiveColorTablePreviewer.cs | 1 - Glamourer/Interop/Material/MaterialManager.cs | 2 +- .../Interop/Material/MaterialValueIndex.cs | 38 ++- Glamourer/State/InternalStateEditor.cs | 7 +- Glamourer/State/StateEditor.cs | 12 + 15 files changed, 342 insertions(+), 161 deletions(-) create mode 100644 Glamourer/Gui/Materials/ColorRowClipboard.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 676cacc..a0d5ff2 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -36,6 +36,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool OpenWindowAtStart { get; set; } = false; public bool UseAdvancedParameters { get; set; } = true; public bool UseAdvancedDyes { get; set; } = false; + public bool KeepAdvancedDyesAttached { get; set; } = true; public bool ShowRevertAdvancedParametersButton { get; set; } = true; public bool ShowPalettePlusImport { get; set; } = true; public bool UseFloatForColors { get; set; } = true; diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index e5e0cb5..ad2ab7b 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -42,7 +42,7 @@ namespace Glamourer.Events /// A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. Parameter, - /// A characters saved state had a material color table value changed. Data is the old value, the new value and the index [(Vector3, Vector3, MaterialValueIndex)]. + /// A characters saved state had a material color table value changed. Data is the old value, the new value and the index [(Vector3, Vector3, MaterialValueIndex)] or just the index for resets. MaterialValue, /// A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 25732e0..0e47839 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -2,6 +2,7 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; using Glamourer.Events; +using Glamourer.Gui.Materials; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; @@ -27,12 +28,13 @@ public class EquipmentDrawer private readonly TextureService _textures; private readonly Configuration _config; private readonly GPoseService _gPose; + private readonly AdvancedDyePopup _advancedDyes; private float _requiredComboWidthUnscaled; private float _requiredComboWidth; public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures, - Configuration config, GPoseService gPose) + Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes) { _items = items; _codes = codes; @@ -283,6 +285,10 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(equipDrawData); } + else + { + _advancedDyes.DrawButton(equipDrawData.Slot); + } if (VerifyRestrictedGear(equipDrawData)) label += " (Restricted)"; @@ -303,6 +309,10 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(mainhand); } + else + { + _advancedDyes.DrawButton(EquipSlot.MainHand); + } if (allWeapons) mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})"; @@ -321,6 +331,10 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(offhand); } + else + { + _advancedDyes.DrawButton(EquipSlot.OffHand); + } WeaponHelpMarker(offhandLabel); } @@ -351,6 +365,10 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(equipDrawData); } + else + { + _advancedDyes.DrawButton(equipDrawData.Slot); + } if (VerifyRestrictedGear(equipDrawData)) { @@ -384,6 +402,10 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(mainhand); } + else + { + _advancedDyes.DrawButton(EquipSlot.MainHand); + } } if (offhand.CurrentItem.Type is FullEquipType.Unknown) @@ -410,6 +432,10 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(offhand); } + else + { + _advancedDyes.DrawButton(EquipSlot.OffHand); + } } } diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index 77257b5..502af14 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -2,6 +2,7 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; +using Glamourer.Gui.Materials; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -10,12 +11,13 @@ namespace Glamourer.Gui; public class GenericPopupWindow : Window { - private readonly Configuration _config; - private readonly ICondition _condition; - private readonly IClientState _state; - public bool OpenFestivalPopup { get; internal set; } = false; + private readonly Configuration _config; + private readonly AdvancedDyePopup _advancedDye; + private readonly ICondition _condition; + private readonly IClientState _state; + public bool OpenFestivalPopup { get; internal set; } = false; - public GenericPopupWindow(Configuration config, IClientState state, ICondition condition) + public GenericPopupWindow(Configuration config, IClientState state, ICondition condition, AdvancedDyePopup advancedDye) : base("Glamourer Popups", ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoDecoration @@ -29,6 +31,7 @@ public class GenericPopupWindow : Window _config = config; _state = state; _condition = condition; + _advancedDye = advancedDye; DisableWindowSounds = true; IsOpen = true; } @@ -42,6 +45,7 @@ public class GenericPopupWindow : Window } DrawFestivalPopup(); + //_advancedDye.Draw(); } private bool CheckFestivalPopupConditions() diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index cb21a91..d9a6e1f 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -13,10 +13,18 @@ using Glamourer.Gui.Tabs.SettingsTab; using Glamourer.Gui.Tabs.UnlocksTab; using ImGuiNET; using OtterGui.Custom; +using OtterGui.Services; using OtterGui.Widgets; namespace Glamourer.Gui; +public class MainWindowPosition : IService +{ + public bool IsOpen { get; set; } + public Vector2 Position { get; set; } + public Vector2 Size { get; set; } +} + public class MainWindow : Window, IDisposable { public enum TabType @@ -32,10 +40,11 @@ public class MainWindow : Window, IDisposable Npcs = 7, } - private readonly Configuration _config; - private readonly DesignQuickBar _quickBar; - private readonly TabSelected _event; - private readonly ITab[] _tabs; + private readonly Configuration _config; + private readonly DesignQuickBar _quickBar; + private readonly TabSelected _event; + private readonly MainWindowPosition _position; + private readonly ITab[] _tabs; public readonly SettingsTab Settings; public readonly ActorTab Actors; @@ -50,7 +59,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) + NpcTab npcs, MainWindowPosition position) : base(GetLabel()) { pi.UiBuilder.DisableGposeUiHide = true; @@ -69,6 +78,7 @@ public class MainWindow : Window, IDisposable Messages = messages; _quickBar = quickBar; Npcs = npcs; + _position = position; _config = config; _tabs = [ @@ -90,6 +100,7 @@ public class MainWindow : Window, IDisposable Flags = _config.Ephemeral.LockMainWindow ? Flags | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize : Flags & ~(ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); + _position.IsOpen = IsOpen; } public void Dispose() @@ -98,9 +109,14 @@ public class MainWindow : Window, IDisposable public override void Draw() { 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) { - SelectTab = TabType.None; _config.Ephemeral.SelectedTab = FromLabel(currentTab); _config.Ephemeral.Save(); } diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 4f4c76f..eadf321 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -1,7 +1,9 @@ -using Dalamud.Interface; +using System.Reflection.Metadata.Ecma335; +using Dalamud.Interface; using Dalamud.Interface.Utility; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.Interop; using Glamourer.Designs; -using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Interop.Material; using Glamourer.Interop.Structs; using Glamourer.State; @@ -9,148 +11,145 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; -using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; using Penumbra.GameData.Files; -using Penumbra.GameData.Structs; namespace Glamourer.Gui.Materials; public sealed unsafe class AdvancedDyePopup( - MainWindowPosition mainPosition, Configuration config, StateManager stateManager, - ActorSelector actorSelector, - MaterialDrawer materials, LiveColorTablePreviewer preview) : IService { private MaterialValueIndex? _drawIndex; - private ActorIdentifier _identifier; - private ActorState? _state; + private ActorState _state = null!; private Actor _actor; private byte _selectedMaterial = byte.MaxValue; private bool ShouldBeDrawn() { - if (!mainPosition.IsOpen) - return false; - if (!config.UseAdvancedDyes) return false; - if (config.Ephemeral.SelectedTab is not MainWindow.TabType.Actors) + if (_drawIndex is not { Valid: true }) return false; - if (!_drawIndex.HasValue) - return false; - - if (actorSelector.Selection.Identifier != _identifier || !_identifier.IsValid) - return false; - - if (_state == null) - return false; - - _actor = actorSelector.Selection.Data.Valid ? actorSelector.Selection.Data.Objects[0] : Actor.Null; - if (!_actor.Valid || !_actor.Model.IsCharacterBase) + if (!_actor.IsCharacter || !_state.ModelData.IsHuman || !_actor.Model.IsHuman) return false; return true; } - public void DrawButton(ActorIdentifier identifier, ActorState state, MaterialValueIndex index) - { - using var id = ImRaii.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); - var isOpen = identifier == _identifier && state == _state && index == _drawIndex; - using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen)) - { - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Palette.ToIconString(), new Vector2(ImGui.GetFrameHeight()), - "Open advanced dyes for this slot.", false, true)) - { - if (isOpen) - { - _drawIndex = null; - _identifier = ActorIdentifier.Invalid; - _state = null; - _selectedMaterial = byte.MaxValue; - } - else - { - _drawIndex = index; - _identifier = identifier; - _state = state; - _selectedMaterial = byte.MaxValue; - } - } - } - } + public void DrawButton(EquipSlot slot) + => DrawButton(MaterialValueIndex.FromSlot(slot)); - public unsafe void Draw() + private void DrawButton(MaterialValueIndex index) { - if (!ShouldBeDrawn()) + if (!config.UseAdvancedDyes) return; - var position = mainPosition.Position; - position.X += mainPosition.Size.X; - position.Y += ImGui.GetFrameHeightWithSpacing() * 3; - var size = new Vector2(3 * ImGui.GetFrameHeight() + 300 * ImGuiHelpers.GlobalScale, 18.5f * ImGui.GetFrameHeightWithSpacing()); - ImGui.SetNextWindowPos(position); - ImGui.SetNextWindowSize(size); - var window = ImGui.Begin("###Glamourer Advanced Dyes", - ImGuiWindowFlags.NoFocusOnAppearing + ImGui.SameLine(); + using var id = ImRaii.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); + var isOpen = index == _drawIndex; + + using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen) + .Push(ImGuiCol.Text, ColorId.HeaderButtons.Value(), isOpen) + .Push(ImGuiCol.Border, ColorId.HeaderButtons.Value(), isOpen)) + { + using var frame = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, isOpen); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Palette.ToIconString(), new Vector2(ImGui.GetFrameHeight()), + string.Empty, false, true)) + { + _selectedMaterial = byte.MaxValue; + _drawIndex = isOpen ? null : index; + } + } + + ImGuiUtil.HoverTooltip("Open advanced dyes for this slot."); + } + + + private void DrawTabBar(ReadOnlySpan> textures, ref bool firstAvailable) + { + using var bar = ImRaii.TabBar("tabs"); + if (!bar) + return; + + for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) + { + var index = _drawIndex!.Value with { MaterialIndex = i }; + var available = index.TryGetTexture(textures, out var texture) && index.TryGetColorTable(texture, out var table); + if (index == preview.LastValueIndex with { RowIndex = 0 }) + table = preview.LastOriginalColorTable; + + using var disable = ImRaii.Disabled(!available); + var select = available && firstAvailable && _selectedMaterial == byte.MaxValue + ? ImGuiTabItemFlags.SetSelected + : ImGuiTabItemFlags.None; + + if (available) + firstAvailable = false; + + using var tab = _label.TabItem(i, select); + if (!available) + { + using var disabled = ImRaii.Enabled(); + ImGuiUtil.HoverTooltip("This material does not exist or does not have an associated color set.", + ImGuiHoveredFlags.AllowWhenDisabled); + } + + if ((tab.Success || select is ImGuiTabItemFlags.SetSelected) && available) + { + _selectedMaterial = i; + DrawTable(index, table); + } + } + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + if (ImGui.TabItemButton($"{FontAwesomeIcon.Times.ToIconString()} ", ImGuiTabItemFlags.NoTooltip)) + _drawIndex = null; + } + + ImGuiUtil.HoverTooltip("Close the advanced dye window."); + } + + private void DrawContent(ReadOnlySpan> textures) + { + var firstAvailable = true; + DrawTabBar(textures, ref firstAvailable); + + if (firstAvailable) + ImGui.TextUnformatted("No Editable Materials available."); + } + + private void DrawWindow(ReadOnlySpan> textures) + { + var flags = ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDecoration - | ImGuiWindowFlags.NoMove - | ImGuiWindowFlags.NoResize); + | ImGuiWindowFlags.NoResize; + + // Set position to the right of the main window when attached + // The downwards offset is implicit through child position. + if (config.KeepAdvancedDyesAttached) + { + var position = ImGui.GetWindowPos(); + position.X += ImGui.GetWindowSize().X; + ImGui.SetNextWindowPos(position); + flags |= ImGuiWindowFlags.NoMove; + } + + var size = new Vector2(7 * ImGui.GetFrameHeight() + 3 * ImGui.GetStyle().ItemInnerSpacing.X + 300 * ImGuiHelpers.GlobalScale, + 17f * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y); + ImGui.SetNextWindowSize(size); + + + var window = ImGui.Begin("###Glamourer Advanced Dyes", flags); try { - if (!window) - return; - - using var bar = ImRaii.TabBar("tabs"); - if (!bar) - return; - - var model = _actor.Model.AsCharacterBase; - var firstAvailable = true; - Span label = stackalloc byte[12]; - label[0] = (byte)'M'; - label[1] = (byte)'a'; - label[2] = (byte)'t'; - label[3] = (byte)'e'; - label[4] = (byte)'r'; - label[5] = (byte)'i'; - label[6] = (byte)'a'; - label[7] = (byte)'l'; - label[8] = (byte)' '; - label[9] = (byte)'#'; - label[11] = 0; - - for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) - { - var texture = model->ColorTableTextures + _drawIndex!.Value.SlotIndex * MaterialService.MaterialsPerModel + i; - var index = _drawIndex!.Value with { MaterialIndex = i }; - var available = *texture != null && DirectXTextureHelper.TryGetColorTable(*texture, out var table); - if (index == preview.LastValueIndex with {RowIndex = 0}) - table = preview.LastOriginalColorTable; - - using var disable = ImRaii.Disabled(!available); - label[10] = (byte)('1' + i); - var select = available && (_selectedMaterial == i || firstAvailable && _selectedMaterial == byte.MaxValue) - ? ImGuiTabItemFlags.SetSelected - : ImGuiTabItemFlags.None; - - if (available) - firstAvailable = false; - if (select is ImGuiTabItemFlags.SetSelected) - _selectedMaterial = i; - - - fixed (byte* labelPtr = label) - { - using var tab = ImRaii.TabItem(labelPtr, select); - if (tab.Success && available) - DrawTable(index, table); - } - } + if (window) + DrawContent(textures); } finally { @@ -158,37 +157,55 @@ public sealed unsafe class AdvancedDyePopup( } } + public unsafe void Draw(Actor actor, ActorState state) + { + _actor = actor; + _state = state; + if (!ShouldBeDrawn()) + return; + + if (_drawIndex!.Value.TryGetTextures(actor, out var textures)) + DrawWindow(textures); + } + private void DrawTable(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table) { + using var disabled = ImRaii.Disabled(_state.IsLocked); for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i) { var index = materialIndex with { RowIndex = i }; ref var row = ref table[i]; - DrawRow(ref row, CharacterWeapon.Empty, index, table); + DrawRow(ref row, index, table); } } - private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index, in MtrlFile.ColorTable table) + private void DrawRow(ref MtrlFile.ColorTable.Row row, MaterialValueIndex index, in MtrlFile.ColorTable table) { using var id = ImRaii.PushId(index.RowIndex); var changed = _state!.Materials.TryGetValue(index, out var value); if (!changed) { var internalRow = new ColorRow(row); - value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual); + var slot = index.ToSlot(); + var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand + ? _state.ModelData.Weapon(slot) + : _state.ModelData.Armor(slot).ToWeapon(0); + value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual); } + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), buttonSize, "Highlight the affected colors on the character.", + false, true); + if (ImGui.IsItemHovered()) + preview.OnHover(index, _actor.Index, table); + + ImGui.SameLine(); ImGui.AlignTextToFramePadding(); using (ImRaii.PushFont(UiBuilder.MonoFont)) { ImGui.TextUnformatted($"Row {index.RowIndex + 1:D2}"); } - ImGui.SameLine(); - ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Locate", false, true); - if (ImGui.IsItemHovered()) - preview.OnHover(index, _actor.Index, table); - ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, v => value.Model.Diffuse = v, "D"); @@ -209,16 +226,54 @@ public sealed unsafe class AdvancedDyePopup( ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS"); ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + ImGui.SameLine(0, spacing.X); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", false, + true)) + ColorRowClipboard.Row = value.Model; + ImGui.SameLine(0, spacing.X); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize, + "Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet, true)) + { + value.Model = ColorRowClipboard.Row; + applied = true; + } + + ImGui.SameLine(0, spacing.X); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this row to game state.", !changed, true)) + stateManager.ResetMaterialValue(_state, index, ApplySettings.Game); + if (applied) stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual); - if (changed) + } + + private LabelStruct _label = new(); + + private struct LabelStruct + { + private fixed byte _label[12]; + + public ImRaii.IEndObject TabItem(byte materialIndex, ImGuiTabItemFlags flags) { - ImGui.SameLine(0, spacing.X); - using (ImRaii.PushFont(UiBuilder.IconFont)) + _label[10] = (byte)('1' + materialIndex); + fixed (byte* ptr = _label) { - using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value()); - ImGui.TextUnformatted(FontAwesomeIcon.UserEdit.ToIconString()); + return ImRaii.TabItem(ptr, flags | ImGuiTabItemFlags.NoTooltip); } } + + public LabelStruct() + { + _label[0] = (byte)'M'; + _label[1] = (byte)'a'; + _label[2] = (byte)'t'; + _label[3] = (byte)'e'; + _label[4] = (byte)'r'; + _label[5] = (byte)'i'; + _label[6] = (byte)'a'; + _label[7] = (byte)'l'; + _label[8] = (byte)' '; + _label[9] = (byte)'#'; + _label[11] = 0; + } } } diff --git a/Glamourer/Gui/Materials/ColorRowClipboard.cs b/Glamourer/Gui/Materials/ColorRowClipboard.cs new file mode 100644 index 0000000..74c1c68 --- /dev/null +++ b/Glamourer/Gui/Materials/ColorRowClipboard.cs @@ -0,0 +1,20 @@ +using Glamourer.Interop.Material; + +namespace Glamourer.Gui.Materials; + +public static class ColorRowClipboard +{ + private static ColorRow _row; + + public static bool IsSet { get; private set; } + + public static ColorRow Row + { + get => _row; + set + { + IsSet = true; + _row = value; + } + } +} diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index c3efa1f..b5f9d58 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -32,12 +32,13 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de if (!table) return; - ImGui.TableSetupColumn("button", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); + ImGui.TableSetupColumn("buttons", ImGuiTableColumnFlags.WidthFixed, buttonSize.X * 3 + 2 * ImGui.GetStyle().ItemInnerSpacing.X); ImGui.TableSetupColumn("enabled", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); - ImGui.TableSetupColumn("values", ImGuiTableColumnFlags.WidthFixed, ImGui.GetStyle().ItemInnerSpacing.X * 4 + 3 * buttonSize.X + 220 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X); - ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch); - + ImGui.TableSetupColumn("values", ImGuiTableColumnFlags.WidthFixed, + ImGui.GetStyle().ItemInnerSpacing.X * 4 + 3 * buttonSize.X + 220 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X); + ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch); + for (var i = 0; i < design.Materials.Count; ++i) { var (idx, value) = design.Materials[i]; @@ -64,6 +65,17 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de --i; } + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", + false, + true)) + ColorRowClipboard.Row = value.Value; + + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize, + "Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet, true)) + _designManager.ChangeMaterialValue(design, key, ColorRowClipboard.Row); + ImGui.TableNextColumn(); var enabled = value.Enabled; if (ImGui.Checkbox("Enabled", ref enabled)) @@ -233,16 +245,20 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de } ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); - var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, v => value.Model.Diffuse = v, "D"); + var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, + v => value.Model.Diffuse = v, "D"); var spacing = ImGui.GetStyle().ItemInnerSpacing; ImGui.SameLine(0, spacing.X); - applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular, v => value.Model.Specular = v, "S"); + applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular, + v => value.Model.Specular = v, "S"); ImGui.SameLine(0, spacing.X); - applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive, v => value.Model.Emissive = v, "E"); + applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive, + v => value.Model.Emissive = v, "E"); ImGui.SameLine(0, spacing.X); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") && value.Model.GlossStrength > 0; + applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") + && value.Model.GlossStrength > 0; ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); ImGui.SameLine(0, spacing.X); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index e1c8356..25b71b3 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -36,7 +36,8 @@ public class ActorPanel( ICondition _conditions, DictModelChara _modelChara, CustomizeParameterDrawer _parameterDrawer, - MaterialDrawer _materialDrawer) + MaterialDrawer _materialDrawer, + AdvancedDyePopup _advancedDyes) { private ActorIdentifier _identifier; private string _actorName = string.Empty; @@ -117,13 +118,12 @@ public class ActorPanel( RevertButtons(); - if (_config.UseAdvancedDyes && ImGui.CollapsingHeader("Material Shit")) - _materialDrawer.DrawActorPanel(_actor); using var disabled = ImRaii.Disabled(transformationId != 0); if (_state.ModelData.IsHuman) DrawHumanPanel(); else DrawMonsterPanel(); + _advancedDyes.Draw(_actor, _state); } private void DrawHumanPanel() @@ -320,7 +320,7 @@ public class ActorPanel( private void SaveDesignOpen() { ImGui.OpenPopup("Save as Design"); - _newName = _state!.Identifier.ToName(); + _newName = _state!.Identifier.ToName(); _newDesign = _converter.Convert(_state, ApplicationRules.FromModifiers(_state)); } diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 5d0fcca..f56a988 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -189,6 +189,11 @@ public class SettingsTab( PaletteImportButton(); } + if (config.UseAdvancedDyes) + Checkbox("Keep Advanced Dye Window Attached", + "Keeps the advanced dye window expansion attached to the main window, or makes it freely movable.", + config.KeepAdvancedDyesAttached, v => config.KeepAdvancedDyesAttached = v); + Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general.", config.DebugMode, v => config.DebugMode = v); ImGui.NewLine(); diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index 752101a..7b10829 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -69,7 +69,6 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable if (_valueIndex.TryGetTexture(actor, out var texture)) { - Glamourer.Log.Information($"Set {_objectIndex} {_valueIndex}"); var diffuse = CalculateDiffuse(); var table = LastOriginalColorTable; table[_valueIndex.RowIndex].Diffuse = diffuse; diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 38e45fe..40af5a2 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -110,7 +110,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable } foreach (var idx in deleteList) - _stateManager.ChangeMaterialValue(state, idx, default, ApplySettings.Game); + _stateManager.ResetMaterialValue(state, idx, ApplySettings.Game); } /// diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 13c7577..a53e162 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -15,6 +15,7 @@ public readonly record struct MaterialValueIndex( byte RowIndex) { public static readonly MaterialValueIndex Invalid = new(DrawObjectType.Invalid, 0, 0, 0); + public uint Key => ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex); @@ -27,6 +28,29 @@ public readonly record struct MaterialValueIndex( return index.Valid; } + public static MaterialValueIndex FromSlot(EquipSlot slot) + { + if (slot is EquipSlot.MainHand) + return new MaterialValueIndex(DrawObjectType.Mainhand, 0, 0, 0); + if (slot is EquipSlot.OffHand) + return new MaterialValueIndex(DrawObjectType.Offhand, 0, 0, 0); + + var idx = slot.ToIndex(); + if (idx < 10) + return new MaterialValueIndex(DrawObjectType.Human, (byte)idx, 0, 0); + + return Invalid; + } + + public EquipSlot ToSlot() + => DrawObject switch + { + DrawObjectType.Human when SlotIndex < 10 => ((uint)SlotIndex).ToEquipSlot(), + DrawObjectType.Mainhand when SlotIndex == 0 => EquipSlot.MainHand, + DrawObjectType.Offhand when SlotIndex == 0 => EquipSlot.OffHand, + _ => EquipSlot.Unknown, + }; + public unsafe bool TryGetModel(Actor actor, out Model model) { if (!actor.Valid) @@ -154,14 +178,12 @@ public readonly record struct MaterialValueIndex( { } public override string ToString() - => DrawObject switch - { - DrawObjectType.Human when SlotIndex < 10 => - $"{((uint)SlotIndex).ToEquipSlot().ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - DrawObjectType.Mainhand when SlotIndex == 0 => $"{EquipSlot.MainHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - DrawObjectType.Offhand when SlotIndex == 0 => $"{EquipSlot.OffHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - _ => $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - }; + { + var slot = ToSlot(); + return slot is EquipSlot.Unknown + ? $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}" + : $"{slot.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}"; + } private class Converter : JsonConverter { diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs index 35587fe..045c0df 100644 --- a/Glamourer/State/InternalStateEditor.cs +++ b/Glamourer/State/InternalStateEditor.cs @@ -222,7 +222,8 @@ public class InternalStateEditor( } /// Change the value of a single material color table entry. - public bool ChangeMaterialValue(ActorState state, MaterialValueIndex index, in MaterialValueState newValue, StateSource source, out ColorRow? oldValue, + public bool ChangeMaterialValue(ActorState state, MaterialValueIndex index, in MaterialValueState newValue, StateSource source, + out ColorRow? oldValue, uint key = 0) { // We already have an existing value. @@ -254,6 +255,10 @@ public class InternalStateEditor( return state.Materials.TryAddValue(index, newValue); } + /// Reset the value of a single material color table entry. + public bool ResetMaterialValue(ActorState state, MaterialValueIndex index, uint key = 0) + => state.CanUnlock(key) && state.Materials.RemoveValue(index); + public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue, uint key = 0) { diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index f77e75a..3d1b27c 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -177,6 +177,18 @@ public class StateEditor( StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index)); } + public void ResetMaterialValue(object data, MaterialValueIndex index, ApplySettings settings) + { + var state = (ActorState)data; + if (!Editor.ResetMaterialValue(state, index, settings.Key)) + return; + + var actors = Applier.ChangeMaterialValue(state, index, true); + Glamourer.Log.Verbose( + $"Reset material value in state {state.Identifier.Incognito(null)} to game value. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, index); + } + /// public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings) { From a5509e8ba036ca632e29501e1f163e72c6ed2961 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 17 Feb 2024 13:49:08 +0100 Subject: [PATCH 257/786] Finish advanced dye UI for now. --- Glamourer/Gui/Materials/MaterialDrawer.cs | 342 +++++++----------- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 2 - .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 2 - Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- OtterGui | 2 +- Penumbra.GameData | 2 +- 6 files changed, 138 insertions(+), 214 deletions(-) diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index b5f9d58..aeb4a9b 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -3,124 +3,159 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Glamourer.Designs; using Glamourer.Interop.Material; -using Glamourer.Interop.Structs; -using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; using Penumbra.GameData.Gui; -using Penumbra.GameData.Structs; -using Vortice.DXGI; namespace Glamourer.Gui.Materials; -public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _designManager, Configuration _config) : IService +public class MaterialDrawer(DesignManager _designManager, Configuration _config) : IService { - private ActorState? _state; + public const float GlossWidth = 100; + public const float SpecularStrengthWidth = 125; + private EquipSlot _newSlot = EquipSlot.Head; private int _newMaterialIdx; private int _newRowIdx; - private MaterialValueIndex _newKey = MaterialValueIndex.Min(); + private MaterialValueIndex _newKey = MaterialValueIndex.FromSlot(EquipSlot.Head); - public void DrawDesignPanel(Design design) + private Vector2 _buttonSize; + private float _spacing; + + public void Draw(Design design) { - var buttonSize = new Vector2(ImGui.GetFrameHeight()); - using (var table = ImRaii.Table("table", 5, ImGuiTableFlags.RowBg)) + var available = ImGui.GetContentRegionAvail().X; + _spacing = ImGui.GetStyle().ItemInnerSpacing.X; + _buttonSize = new Vector2(ImGui.GetFrameHeight()); + var colorWidth = 4 * _buttonSize.X + + (GlossWidth + SpecularStrengthWidth) * ImGuiHelpers.GlobalScale + + 6 * _spacing + + ImGui.CalcTextSize("Revert").X; + if (available > 1.95 * colorWidth) + DrawSingleRow(design); + else + DrawTwoRow(design); + DrawNew(design); + } + + private void DrawName(MaterialValueIndex index) + { + using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale).Push(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); + using var color = ImRaii.PushColor(ImGuiCol.Border, ImGui.GetColorU32(ImGuiCol.Text)); + ImGuiUtil.DrawTextButton(index.ToString(), new Vector2((GlossWidth + SpecularStrengthWidth) * ImGuiHelpers.GlobalScale + _spacing, 0), 0); + } + + private void DrawSingleRow(Design design) + { + for (var i = 0; i < design.Materials.Count; ++i) { - if (!table) - return; + using var id = ImRaii.PushId(i); + var (idx, value) = design.Materials[i]; + var key = MaterialValueIndex.FromKey(idx); - ImGui.TableSetupColumn("buttons", ImGuiTableColumnFlags.WidthFixed, buttonSize.X * 3 + 2 * ImGui.GetStyle().ItemInnerSpacing.X); - ImGui.TableSetupColumn("enabled", ImGuiTableColumnFlags.WidthFixed, buttonSize.X); - ImGui.TableSetupColumn("values", ImGuiTableColumnFlags.WidthFixed, - ImGui.GetStyle().ItemInnerSpacing.X * 4 + 3 * buttonSize.X + 220 * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X); - ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch); - - for (var i = 0; i < design.Materials.Count; ++i) - { - var (idx, value) = design.Materials[i]; - var key = MaterialValueIndex.FromKey(idx); - var name = key.DrawObject switch - { - MaterialValueIndex.DrawObjectType.Human => ((uint)key.SlotIndex).ToEquipSlot().ToName(), - MaterialValueIndex.DrawObjectType.Mainhand => EquipSlot.MainHand.ToName(), - MaterialValueIndex.DrawObjectType.Offhand => EquipSlot.OffHand.ToName(), - _ => string.Empty, - }; - if (name.Length == 0) - continue; - - name = $"{name} Material #{key.MaterialIndex + 1} Row #{key.RowIndex + 1}"; - using var id = ImRaii.PushId((int)idx); - var deleteEnabled = _config.DeleteDesignModifier.IsActive(); - ImGui.TableNextColumn(); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, - $"Delete this color row.{(deleteEnabled ? string.Empty : $"\nHold {_config.DeleteDesignModifier} to delete.")}", - !deleteEnabled, true)) - { - _designManager.ChangeMaterialValue(design, key, null); - --i; - } - - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", - false, - true)) - ColorRowClipboard.Row = value.Value; - - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize, - "Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet, true)) - _designManager.ChangeMaterialValue(design, key, ColorRowClipboard.Row); - - ImGui.TableNextColumn(); - var enabled = value.Enabled; - if (ImGui.Checkbox("Enabled", ref enabled)) - _designManager.ChangeApplyMaterialValue(design, key, enabled); - - ImGui.TableNextColumn(); - var revert = value.Revert; - using (ImRaii.Disabled(revert)) - { - var row = value.Value; - DrawRow(design, key, row); - } - - ImGui.TableNextColumn(); - - if (ImGui.Checkbox("Revert", ref revert)) - _designManager.ChangeMaterialRevert(design, key, revert); - ImGuiUtil.HoverTooltip( - "If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."); - - ImGui.TableNextColumn(); - ImGui.TextUnformatted(name); - } + DrawName(key); + ImGui.SameLine(0, _spacing); + DeleteButton(design, key, ref i); + ImGui.SameLine(0, _spacing); + CopyButton(value.Value); + ImGui.SameLine(0, _spacing); + PasteButton(design, key); + ImGui.SameLine(0, _spacing); + EnabledToggle(design, key, value.Enabled); + ImGui.SameLine(0, _spacing); + DrawRow(design, key, value.Value, value.Revert); + ImGui.SameLine(0, _spacing); + RevertToggle(design, key, value.Revert); } + } - var exists = design.GetMaterialDataRef().TryGetValue(_newKey, out _); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, - exists ? "The selected advanced dye row already exists." : "Add the selected advanced dye row.", exists, true)) - _designManager.ChangeMaterialValue(design, _newKey, ColorRow.Empty); + private void DrawTwoRow(Design design) + { + for (var i = 0; i < design.Materials.Count; ++i) + { + using var id = ImRaii.PushId(i); + var (idx, value) = design.Materials[i]; + var key = MaterialValueIndex.FromKey(idx); - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + DrawName(key); + ImGui.SameLine(0, _spacing); + DeleteButton(design, key, ref i); + ImGui.SameLine(0, _spacing); + CopyButton(value.Value); + ImGui.SameLine(0, _spacing); + PasteButton(design, key); + ImGui.SameLine(0, _spacing); + EnabledToggle(design, key, value.Enabled); + + + DrawRow(design, key, value.Value, value.Revert); + ImGui.SameLine(0, _spacing); + RevertToggle(design, key, value.Revert); + ImGui.Separator(); + } + } + + private void DeleteButton(Design design, MaterialValueIndex index, ref int idx) + { + var deleteEnabled = _config.DeleteDesignModifier.IsActive(); + if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), _buttonSize, + $"Delete this color row.{(deleteEnabled ? string.Empty : $"\nHold {_config.DeleteDesignModifier} to delete.")}", + !deleteEnabled, true)) + return; + + _designManager.ChangeMaterialValue(design, index, null); + --idx; + } + + private void CopyButton(in ColorRow row) + { + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), _buttonSize, "Export this row to your clipboard.", + false, + true)) + ColorRowClipboard.Row = row; + } + + private void PasteButton(Design design, MaterialValueIndex index) + { + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), _buttonSize, + "Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet, true)) + _designManager.ChangeMaterialValue(design, index, ColorRowClipboard.Row); + } + + private void EnabledToggle(Design design, MaterialValueIndex index, bool enabled) + { + if (ImGui.Checkbox("Enabled", ref enabled)) + _designManager.ChangeApplyMaterialValue(design, index, enabled); + } + + private void RevertToggle(Design design, MaterialValueIndex index, bool revert) + { + if (ImGui.Checkbox("Revert", ref revert)) + _designManager.ChangeMaterialRevert(design, index, revert); + ImGuiUtil.HoverTooltip( + "If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."); + } + + public void DrawNew(Design design) + { if (EquipSlotCombo.Draw("##slot", "Choose a slot for an advanced dye row.", ref _newSlot)) - _newKey = _newSlot switch + _newKey = MaterialValueIndex.FromSlot(_newSlot) with { - EquipSlot.MainHand => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, (byte)_newMaterialIdx, - (byte)_newRowIdx), - EquipSlot.OffHand => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, (byte)_newMaterialIdx, - (byte)_newRowIdx), - _ => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte)_newSlot.ToIndex(), (byte)_newMaterialIdx, - (byte)_newRowIdx), + MaterialIndex = (byte)_newMaterialIdx, + RowIndex = (byte)_newRowIdx, }; ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); DrawMaterialIdxDrag(); ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); DrawRowIdxDrag(); + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + var exists = design.GetMaterialDataRef().TryGetValue(_newKey, out _); + if (ImGuiUtil.DrawDisabledButton("Add New Row", Vector2.Zero, + exists ? "The selected advanced dye row already exists." : "Add the selected advanced dye row.", exists, false)) + _designManager.ChangeMaterialValue(design, _newKey, ColorRow.Empty); } private void DrawMaterialIdxDrag() @@ -149,131 +184,24 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de _newRowIdx -= 1; } - public void DrawActorPanel(Actor actor) + private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row, bool disabled) { - if (!actor.IsCharacter || !_stateManager.GetOrCreate(actor, out _state)) - return; - - var model = actor.Model; - if (!model.IsHuman) - return; - - if (model.AsCharacterBase->SlotCount < 10) - return; - - // Humans should have at least 10 slots for the equipment types. Technically more. - foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) - { - var item = model.GetArmor(slot).ToWeapon(0); - DrawSlotMaterials(model, slot.ToName(), item, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte)idx, 0, 0)); - } - - var (mainhand, offhand, mh, oh) = actor.Model.GetWeapons(actor); - if (mainhand.IsWeapon && mainhand.AsCharacterBase->SlotCount > 0) - DrawSlotMaterials(mainhand, EquipSlot.MainHand.ToName(), mh, - new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, 0, 0)); - if (offhand.IsWeapon && offhand.AsCharacterBase->SlotCount > 0) - DrawSlotMaterials(offhand, EquipSlot.OffHand.ToName(), oh, - new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, 0, 0)); - } - - - private void DrawSlotMaterials(Model model, string name, CharacterWeapon drawData, MaterialValueIndex index) - { - for (byte materialIndex = 0; materialIndex < MaterialService.MaterialsPerModel; ++materialIndex) - { - var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + materialIndex; - if (*texture == null) - continue; - - if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table)) - continue; - - using var tree = ImRaii.TreeNode($"{name} Material #{materialIndex + 1}###{name}{materialIndex}"); - if (!tree) - continue; - - DrawMaterial(ref table, drawData, index with { MaterialIndex = materialIndex }); - } - } - - private void DrawMaterial(ref MtrlFile.ColorTable table, CharacterWeapon drawData, MaterialValueIndex sourceIndex) - { - for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i) - { - var index = sourceIndex with { RowIndex = i }; - ref var row = ref table[i]; - DrawRow(ref row, drawData, index); - } - } - - private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row) - { - var spacing = ImGui.GetStyle().ItemInnerSpacing; - var tmp = row; + var tmp = row; + using var _ = ImRaii.Disabled(disabled); var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", row.Diffuse, v => tmp.Diffuse = v, "D"); - ImGui.SameLine(0, spacing.X); + ImGui.SameLine(0, _spacing); applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", row.Specular, v => tmp.Specular = v, "S"); - ImGui.SameLine(0, spacing.X); + ImGui.SameLine(0, _spacing); applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", row.Emissive, v => tmp.Emissive = v, "E"); - ImGui.SameLine(0, spacing.X); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + ImGui.SameLine(0, _spacing); + ImGui.SetNextItemWidth(GlossWidth * ImGuiHelpers.GlobalScale); applied |= ImGui.DragFloat("##Gloss", ref tmp.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G"); ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); - ImGui.SameLine(0, spacing.X); - ImGui.SetNextItemWidth(120 * ImGuiHelpers.GlobalScale); + ImGui.SameLine(0, _spacing); + ImGui.SetNextItemWidth(SpecularStrengthWidth * ImGuiHelpers.GlobalScale); applied |= ImGui.DragFloat("##Specular Strength", ref tmp.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS"); ImGuiUtil.HoverTooltip("Change the specular strength for this row."); if (applied) _designManager.ChangeMaterialValue(design, index, tmp); } - - private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index) - { - using var id = ImRaii.PushId(index.RowIndex); - var changed = _state!.Materials.TryGetValue(index, out var value); - if (!changed) - { - var internalRow = new ColorRow(row); - value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual); - } - - ImGui.AlignTextToFramePadding(); - using (ImRaii.PushFont(UiBuilder.MonoFont)) - { - ImGui.TextUnformatted($"Row {index.RowIndex + 1:D2}"); - } - - ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); - var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, - v => value.Model.Diffuse = v, "D"); - - var spacing = ImGui.GetStyle().ItemInnerSpacing; - ImGui.SameLine(0, spacing.X); - applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular, - v => value.Model.Specular = v, "S"); - ImGui.SameLine(0, spacing.X); - applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive, - v => value.Model.Emissive = v, "E"); - ImGui.SameLine(0, spacing.X); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") - && value.Model.GlossStrength > 0; - ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); - ImGui.SameLine(0, spacing.X); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS"); - ImGuiUtil.HoverTooltip("Change the specular strength for this row."); - if (applied) - _stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual); - if (changed) - { - ImGui.SameLine(0, spacing.X); - using (ImRaii.PushFont(UiBuilder.IconFont)) - { - using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value()); - ImGui.TextUnformatted(FontAwesomeIcon.UserEdit.ToIconString()); - } - } - } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 25b71b3..dc97acb 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -9,7 +9,6 @@ using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Materials; using Glamourer.Interop; -using Glamourer.Interop.Material; using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; @@ -36,7 +35,6 @@ public class ActorPanel( ICondition _conditions, DictModelChara _modelChara, CustomizeParameterDrawer _parameterDrawer, - MaterialDrawer _materialDrawer, AdvancedDyePopup _advancedDyes) { private ActorIdentifier _identifier; diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index 8e93877..81c9766 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -53,8 +53,6 @@ public unsafe class ModelEvaluationPanel( ImGui.TextUnformatted($"Transformation Id: {actor.AsCharacter->CharacterData.TransformationId}"); if (actor.AsCharacter->CharacterData.ModelCharaId_2 != -1) ImGui.TextUnformatted($"ModelChara2 {actor.AsCharacter->CharacterData.ModelCharaId_2}"); - if (actor.AsCharacter->CharacterData.StatusEffectVFXId != 0) - ImGui.TextUnformatted($"Status Id: {actor.AsCharacter->CharacterData.StatusEffectVFXId}"); } ImGuiUtil.DrawTableColumn("Mainhand"); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 168f994..1e3e468 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -185,7 +185,7 @@ public class DesignPanel( if (!h) return; - _materials.DrawDesignPanel(_selector.Selected!); + _materials.Draw(_selector.Selected!); } private void DrawCustomizeApplication() diff --git a/OtterGui b/OtterGui index 75c5a7b..9d68a48 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 75c5a7b220e7f799f85741288e3de4a20af9bcf4 +Subproject commit 9d68a487610266058fbec853efed9a35c9df6fbe diff --git a/Penumbra.GameData b/Penumbra.GameData index 5825baf..44021b9 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 5825bafb0602cf8a252581f21291c0d27e5561f0 +Subproject commit 44021b93e6901c84b739bbf4d1c6350f4486cdbf From e8f6b9361026c04f077cae5876c4fcecb1760505 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 17 Feb 2024 13:49:18 +0100 Subject: [PATCH 258/786] Add changelog. --- Glamourer/Configuration.cs | 2 +- Glamourer/Gui/GlamourerChangelog.cs | 66 ++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index a0d5ff2..36e12f5 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -35,7 +35,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool ShowQuickBarInTabs { get; set; } = true; public bool OpenWindowAtStart { get; set; } = false; public bool UseAdvancedParameters { get; set; } = true; - public bool UseAdvancedDyes { get; set; } = false; + public bool UseAdvancedDyes { get; set; } = true; public bool KeepAdvancedDyesAttached { get; set; } = true; public bool ShowRevertAdvancedParametersButton { get; set; } = true; public bool ShowPalettePlusImport { get; set; } = true; diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index ad35c5e..26723de 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -28,7 +28,8 @@ public class GlamourerChangelog Add1_1_0_0(Changelog); Add1_1_0_2(Changelog); Add1_1_0_4(Changelog); - Add1_1_0_5(Changelog); + AddDummy(Changelog); + Add1_2_0_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -49,29 +50,55 @@ public class GlamourerChangelog } } - private static void Add1_1_0_5(Changelog log) - => log.NextVersion("Version 1.1.0.5") + private static void Add1_2_0_0(Changelog log) + => log.NextVersion("Version 1.2.0.0") .RegisterHighlight("Added the option to link to other designs in a design, causing all of them to be applied at once.") .RegisterEntry("This required reworking the handling for applying multiple designs at once (i.e.merging them).", 1) - .RegisterEntry("This was a considerable backend change on both automation sets and design application. I may have messed up and introduced bugs. Please let me know if something does not work right anymore.", 1) - .RegisterHighlight("Added an option so that manual application of a mainhand weapon will also automatically apply its associated offhand (and gloves, for certain fist weapons). This is off by default.") - .RegisterHighlight("Added an option that always tries to apply associated mod settings for designs to the Penumbra collection associated with the character the design is applied to.") - .RegisterEntry("This is off by default and I strongly recommend AGAINST using it, since Glamourer has no way to revert such changes. You are responsible for keeping your collection in order.", 1) - .RegisterHighlight("Added mouse wheel scrolling to many selectors, e.g. for equipment, dyes or customizations. You need to hold Control while ") + .RegisterEntry( + "This was a considerable backend change on both automation sets and design application. I may have messed up and introduced bugs. Please let me know if something does not work right anymore.", + 1) + .RegisterHighlight("Added advanced dye options for equipment. You can now runtime-edit the color sets of your gear.") + .RegisterEntry( + "The logic for this is very complicated and may interfere with other options or not update correctly, it will need a lot of testing.", + 1) + .RegisterEntry("Like Advanced Customization options, this can be turned off in the behaviour settings.", 1) + .RegisterEntry( + "To access the options, click the palette buttons in the Equipment Panel - the popup can also be detached from the main window in the settings.", + 1) + .RegisterEntry("In designs, only actually changed rows will be stored. You can manually add rows, too.", 1) + .RegisterHighlight( + "Added an option so that manual application of a mainhand weapon will also automatically apply its associated offhand (and gloves, for certain fist weapons). This is off by default.") + .RegisterHighlight( + "Added an option that always tries to apply associated mod settings for designs to the Penumbra collection associated with the character the design is applied to.") + .RegisterEntry( + "This is off by default and I strongly recommend AGAINST using it, since Glamourer has no way to revert such changes. You are responsible for keeping your collection in order.", + 1) + .RegisterHighlight( + "Added mouse wheel scrolling to many selectors, e.g. for equipment, dyes or customizations. You need to hold Control while ") .RegisterEntry("Improved handling for highlights with advanced customization colors and normal customization settings.") + .RegisterHighlight( + "Changed Item Customizations in Penumbra can now be right-clicked to preview them on your character, if you have the correct Gender/Race combo on them.") + .RegisterHighlight( + "Add the option to override associated collections for characters, so that automatically applied mod associations affect the overriden collection.") .RegisterEntry("Added copy/paste buttons for advanced customization colors.") .RegisterEntry("Added alpha preview to advanced customization colors.") + .RegisterEntry("Added a button to update the settings for an associated mod from their current settings.") .RegisterEntry("Updated a few fun module things. Now there are Pink elephants on parade!") .RegisterEntry("Split up the IPC source state so IPC consumers can apply designs without them sticking around.") .RegisterEntry("Fixed an issue with weapon loading being dependant on the order of loading Penumbra and Glamourer.") - .RegisterEntry("Fixed an issue with buttons sharing state and switching from design duplication to creating new ones caused errors.") + .RegisterEntry( + "Fixed an issue with buttons sharing state and switching from design duplication to creating new ones caused errors.") .RegisterEntry("Fixed an issue where actors leaving during cutscenes or GPose caused Glamourer to throw a fit.") .RegisterEntry("Fixed an issue with NPC designs applying advanced customizations to targets and coloring them entirely black."); + private static void AddDummy(Changelog log) + => log.NextVersion(string.Empty); + private static void Add1_1_0_4(Changelog log) => log.NextVersion("Version 1.1.0.4") .RegisterEntry("Added a check and warning for a lingering Palette+ installation.") - .RegisterHighlight("Added a button to only revert advanced customizations to game state to the quick design bar. This can be toggled off in the interface settings.") + .RegisterHighlight( + "Added a button to only revert advanced customizations to game state to the quick design bar. This can be toggled off in the interface settings.") .RegisterEntry("Added visible configuration options for color display for the advanced customizations.") .RegisterEntry("Updated Battle NPC data from Gubal for 6.55.") .RegisterEntry("Fixed issues with advanced customizations not resetting correctly with Use Game State as Base.") @@ -86,12 +113,13 @@ public class GlamourerChangelog .RegisterEntry("Added design colors in the preview of combos (in the quick bar and the automation panel).") .RegisterHighlight("Improved Palette+ import options: Instead of entering a name, you can now select from available palettes.") .RegisterHighlight("In the settings tab, there is also a button to import ALL palettes from Palette+ as separate designs.", 1) - .RegisterEntry("Added a tooltip that you can enter numeric values to drag sliders by control-clicking for the muscle slider, also used slightly more useful caps.") + .RegisterEntry( + "Added a tooltip that you can enter numeric values to drag sliders by control-clicking for the muscle slider, also used slightly more useful caps.") .RegisterEntry("Fixed issues with monk weapons, again.") .RegisterEntry("Fixed an issue with the favourites file not loading.") .RegisterEntry("Fixed the name of the advanced parameters in the application panel.") .RegisterEntry("Fixed design clones not respecting advanced parameter application rules."); - + private static void Add1_1_0_0(Changelog log) => log.NextVersion("Version 1.1.0.0") @@ -99,12 +127,18 @@ public class GlamourerChangelog .RegisterHighlight("A characters body type can now be changed when copying state or saving designs from certain NPCs.") .RegisterHighlight("Added support for picking advanced colors for your characters customizations.") .RegisterEntry("The display and application of those can be toggled off in Glamourers behaviour settings.", 1) - .RegisterEntry("This provides the same functionality as Palette+, and Palette+ will probably be discontinued soonish (in accordance with Chirp).", 1) - .RegisterEntry("An option to import existing palettes from Palette+ by name is provided for designs, and can be toggled off in the settings.", 1) - .RegisterHighlight("Advanced colors, equipment and dyes can now be reset to their game state separately by Control-Rightclicking them.") + .RegisterEntry( + "This provides the same functionality as Palette+, and Palette+ will probably be discontinued soonish (in accordance with Chirp).", + 1) + .RegisterEntry( + "An option to import existing palettes from Palette+ by name is provided for designs, and can be toggled off in the settings.", + 1) + .RegisterHighlight( + "Advanced colors, equipment and dyes can now be reset to their game state separately by Control-Rightclicking them.") .RegisterHighlight("Hairstyles and face paints can now be made favourites.") .RegisterEntry("Added a new command '/glamour delete' to delete saved designs by name or identifier.") - .RegisterEntry("Added an optional parameter to the '/glamour apply' command that makes it apply the associated mod settings for a design to the collection associated with the identified character.") + .RegisterEntry( + "Added an optional parameter to the '/glamour apply' command that makes it apply the associated mod settings for a design to the collection associated with the identified character.") .RegisterEntry("Fixed changing weapons in Designs not working correctly.") .RegisterEntry("Fixed restricted gear protection breaking outfits for Mare pairs.") .RegisterEntry("Improved the handling of some cheat codes and added new ones.") From ef2d9ba2070db0fd003df235974463798deb37a6 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 17 Feb 2024 12:54:55 +0000 Subject: [PATCH 259/786] [CI] Updating repo.json for testing_1.1.0.11 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 296a87d..a44ccbe 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.10", + "TestingAssemblyVersion": "1.1.0.11", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.10/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.11/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 0bc9fc872e8470a2f1c2d7b4edc90704e32f3814 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 17 Feb 2024 15:01:26 +0100 Subject: [PATCH 260/786] Add some valid options for material values. --- Glamourer/Gui/GlamourerChangelog.cs | 2 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 35 +++++++++++++++---- .../Interop/Material/MaterialValueIndex.cs | 33 +++++++++++------ 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 26723de..7c1d188 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -57,7 +57,7 @@ public class GlamourerChangelog .RegisterEntry( "This was a considerable backend change on both automation sets and design application. I may have messed up and introduced bugs. Please let me know if something does not work right anymore.", 1) - .RegisterHighlight("Added advanced dye options for equipment. You can now runtime-edit the color sets of your gear.") + .RegisterHighlight("Added advanced dye options for equipment. You can now live-edit the color sets of your gear.") .RegisterEntry( "The logic for this is very complicated and may interfere with other options or not update correctly, it will need a lot of testing.", 1) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index eadf321..43036e8 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -1,7 +1,7 @@ -using System.Reflection.Metadata.Ecma335; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using Glamourer.Designs; using Glamourer.Interop.Material; @@ -13,6 +13,7 @@ using OtterGui.Raii; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; +using Penumbra.String; namespace Glamourer.Gui.Materials; @@ -68,6 +69,20 @@ public sealed unsafe class AdvancedDyePopup( ImGuiUtil.HoverTooltip("Open advanced dyes for this slot."); } + private (string Path, string GamePath) ResourceName(MaterialValueIndex index) + { + var materialHandle = (MaterialResourceHandle*)_actor.Model.AsCharacterBase->MaterialsSpan[ + index.MaterialIndex + index.SlotIndex * MaterialService.MaterialsPerModel].Value; + var model = _actor.Model.AsCharacterBase->ModelsSpan[index.SlotIndex].Value; + var modelHandle = model == null ? null : model->ModelResourceHandle; + var path = materialHandle == null + ? string.Empty + : ByteString.FromSpanUnsafe(materialHandle->ResourceHandle.FileName.AsSpan(), true).ToString(); + var gamePath = modelHandle == null + ? string.Empty + : modelHandle->GetMaterialFileNameBySlotAsString(index.MaterialIndex); + return (path, gamePath); + } private void DrawTabBar(ReadOnlySpan> textures, ref bool firstAvailable) { @@ -79,6 +94,7 @@ public sealed unsafe class AdvancedDyePopup( { var index = _drawIndex!.Value with { MaterialIndex = i }; var available = index.TryGetTexture(textures, out var texture) && index.TryGetColorTable(texture, out var table); + if (index == preview.LastValueIndex with { RowIndex = 0 }) table = preview.LastOriginalColorTable; @@ -91,11 +107,16 @@ public sealed unsafe class AdvancedDyePopup( firstAvailable = false; using var tab = _label.TabItem(i, select); - if (!available) + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { - using var disabled = ImRaii.Enabled(); - ImGuiUtil.HoverTooltip("This material does not exist or does not have an associated color set.", - ImGuiHoveredFlags.AllowWhenDisabled); + using var enabled = ImRaii.Enabled(); + var (path, gamePath) = ResourceName(index); + if (gamePath.Length == 0 || path.Length == 0) + ImGui.SetTooltip("This material does not exist."); + else if (!available) + ImGui.SetTooltip($"This material does not have an associated color set.\n\n{gamePath}\n{path}"); + else + ImGui.SetTooltip($"{gamePath}\n{path}"); } if ((tab.Success || select is ImGuiTabItemFlags.SetSelected) && available) @@ -186,7 +207,7 @@ public sealed unsafe class AdvancedDyePopup( if (!changed) { var internalRow = new ColorRow(row); - var slot = index.ToSlot(); + var slot = index.ToEquipSlot(); var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand ? _state.ModelData.Weapon(slot) : _state.ModelData.Armor(slot).ToWeapon(0); diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index a53e162..1229cd7 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -20,7 +20,7 @@ public readonly record struct MaterialValueIndex( => ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex); public bool Valid - => Validate(DrawObject) && ValidateSlot(SlotIndex) && ValidateMaterial(MaterialIndex) && ValidateRow(RowIndex); + => Validate(DrawObject) && ValidateSlot(DrawObject, SlotIndex) && ValidateMaterial(MaterialIndex) && ValidateRow(RowIndex); public static bool FromKey(uint key, out MaterialValueIndex index) { @@ -42,7 +42,7 @@ public readonly record struct MaterialValueIndex( return Invalid; } - public EquipSlot ToSlot() + public EquipSlot ToEquipSlot() => DrawObject switch { DrawObjectType.Human when SlotIndex < 10 => ((uint)SlotIndex).ToEquipSlot(), @@ -155,8 +155,14 @@ public readonly record struct MaterialValueIndex( public static bool Validate(DrawObjectType type) => type is not DrawObjectType.Invalid && Enum.IsDefined(type); - public static bool ValidateSlot(byte slotIndex) - => slotIndex < 10; + public static bool ValidateSlot(DrawObjectType type, byte slotIndex) + => type switch + { + DrawObjectType.Human => slotIndex < 14, + DrawObjectType.Mainhand => slotIndex == 0, + DrawObjectType.Offhand => slotIndex == 0, + _ => false, + }; public static bool ValidateMaterial(byte materialIndex) => materialIndex < MaterialService.MaterialsPerModel; @@ -178,12 +184,19 @@ public readonly record struct MaterialValueIndex( { } public override string ToString() - { - var slot = ToSlot(); - return slot is EquipSlot.Unknown - ? $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}" - : $"{slot.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}"; - } + => DrawObject switch + { + DrawObjectType.Invalid => "Invalid", + DrawObjectType.Human when SlotIndex < 10 => + $"{((uint)SlotIndex).ToEquipSlot().ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Human when SlotIndex == 10 => $"BodySlot.Hair.ToString() Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Human when SlotIndex == 11 => $"BodySlot.Face.ToString() Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Human when SlotIndex == 12 => $"{BodySlot.Tail} / {BodySlot.Ear} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Human when SlotIndex == 13 => $"Connectors Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Mainhand when SlotIndex == 0 => $"{EquipSlot.MainHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Offhand when SlotIndex == 0 => $"{EquipSlot.OffHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + _ => $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + }; private class Converter : JsonConverter { From 22a8ba3f35b16d67244b26bdd7ab6cca941b3a92 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 18 Feb 2024 13:05:04 +0100 Subject: [PATCH 261/786] Make a reapply event. --- Glamourer/Api/GlamourerIpc.Revert.cs | 2 +- Glamourer/Automation/AutoDesignApplier.cs | 8 ++++---- Glamourer/Events/StateChanged.cs | 3 +++ Glamourer/Gui/DesignQuickBar.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 4 ++-- Glamourer/Interop/CharaFile/CmaFile.cs | 5 +++-- Glamourer/Services/CommandService.cs | 4 ++-- Glamourer/State/StateApplier.cs | 2 ++ Glamourer/State/StateManager.cs | 8 ++++---- 9 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.Revert.cs b/Glamourer/Api/GlamourerIpc.Revert.cs index c5ca3b3..c5e1005 100644 --- a/Glamourer/Api/GlamourerIpc.Revert.cs +++ b/Glamourer/Api/GlamourerIpc.Revert.cs @@ -110,7 +110,7 @@ public partial class GlamourerIpc foreach (var obj in data.Objects) { _autoDesignApplier.ReapplyAutomation(obj, state.Identifier, state); - _stateManager.ReapplyState(obj); + _stateManager.ReapplyState(obj, StateSource.IpcManual); } } } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index adbd355..380de33 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -151,7 +151,7 @@ public sealed class AutoDesignApplier : IDisposable { Reduce(data.Objects[0], state, newSet, false, false); foreach (var actor in data.Objects) - _state.ReapplyState(actor); + _state.ReapplyState(actor, StateSource.Fixed); } } else if (_objects.TryGetValueAllWorld(id, out data) || _objects.TryGetValueNonOwned(id, out data)) @@ -162,7 +162,7 @@ public sealed class AutoDesignApplier : IDisposable if (_state.GetOrCreate(specificId, actor, out var state)) { Reduce(actor, state, newSet, false, false); - _state.ReapplyState(actor); + _state.ReapplyState(actor, StateSource.Fixed); } } } @@ -210,7 +210,7 @@ public sealed class AutoDesignApplier : IDisposable var respectManual = state.LastJob == newJob.Id; state.LastJob = actor.Job; Reduce(actor, state, set, respectManual, true); - _state.ReapplyState(actor); + _state.ReapplyState(actor, StateSource.Fixed); } public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state) @@ -310,7 +310,7 @@ public sealed class AutoDesignApplier : IDisposable Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob); NewGearsetId = -1; foreach (var actor in data.Objects) - _state.ReapplyState(actor); + _state.ReapplyState(actor, StateSource.Fixed); } public static unsafe bool CheckGearset(short check) diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index ad2ab7b..0cffc35 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -53,6 +53,9 @@ namespace Glamourer.Events /// A characters saved state had a meta toggle changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. Other, + + /// A characters state was reapplied. Data is null. + Reapply, } public enum Priority diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 2521765..58bdb24 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -225,7 +225,7 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { _autoDesignApplier.ReapplyAutomation(actor, id, state!); - _stateManager.ReapplyState(actor); + _stateManager.ReapplyState(actor, StateSource.Manual); } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index dc97acb..b205127 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -373,7 +373,7 @@ public class ActorPanel( ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton("Reapply State", Vector2.Zero, "Try to reapply the configured state if something went wrong.", _state!.IsLocked)) - _stateManager.ReapplyState(_actor); + _stateManager.ReapplyState(_actor, StateSource.Manual); ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton("Reapply Automation", Vector2.Zero, @@ -381,7 +381,7 @@ public class ActorPanel( !_config.EnableAutoDesigns || _state!.IsLocked)) { _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!); - _stateManager.ReapplyState(_actor); + _stateManager.ReapplyState(_actor, StateSource.Manual); } } diff --git a/Glamourer/Interop/CharaFile/CmaFile.cs b/Glamourer/Interop/CharaFile/CmaFile.cs index bf67a59..dab91ac 100644 --- a/Glamourer/Interop/CharaFile/CmaFile.cs +++ b/Glamourer/Interop/CharaFile/CmaFile.cs @@ -1,4 +1,4 @@ -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Services; using Newtonsoft.Json.Linq; using Penumbra.GameData.Enums; @@ -54,9 +54,10 @@ public sealed class CmaFile { foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var idx = slot.ToIndex(); + var idx = slot.ToIndex(); if (idx * 4 + 3 >= byteData.Length) continue; + var armor = ((CharacterArmor*)ptr)[idx]; var item = items.Identify(slot, armor.Set, armor.Variant); data.SetItem(slot, item); diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 74364e2..3ca1e76 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -308,7 +308,7 @@ public class CommandService : IDisposable if (_stateManager.GetOrCreate(identifier, actor, out var state)) { _autoDesignApplier.ReapplyAutomation(actor, identifier, state); - _stateManager.ReapplyState(actor); + _stateManager.ReapplyState(actor, StateSource.Manual); } } } @@ -357,7 +357,7 @@ public class CommandService : IDisposable return true; foreach (var actor in data.Objects) - _stateManager.ReapplyState(actor); + _stateManager.ReapplyState(actor, StateSource.Manual); } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 1b9c69a..a025cb5 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -341,6 +341,8 @@ public class StateApplier( ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); ChangeCrests(actors, state.ModelData.CrestVisibility); ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); + foreach (var material in state.Materials.Values) + ChangeMaterialValue(actors, MaterialValueIndex.FromKey(material.Key), material.Value.Model, state.IsLocked); } return actors; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index d731a66..b58bab9 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -334,14 +334,14 @@ public sealed class StateManager( } } - public void ReapplyState(Actor actor) + public void ReapplyState(Actor actor, StateSource source) { if (!GetOrCreate(actor, out var state)) return; - Applier.ApplyAll(state, - !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), - false); + var data = Applier.ApplyAll(state, + !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); + StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null); } public void DeleteState(ActorIdentifier identifier) From c85598acf47fe90a213eae1a444247b7c5f05105 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 18 Feb 2024 13:06:19 +0100 Subject: [PATCH 262/786] Listen to temporary mod changes. --- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 3 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 50 ++++--------------- Penumbra.Api | 2 +- 3 files changed, 11 insertions(+), 44 deletions(-) diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index f56a988..fab2085 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -21,7 +21,6 @@ public class SettingsTab( Configuration config, DesignFileSystemSelector selector, CodeService codeService, - PenumbraAutoRedraw autoRedraw, ContextMenuService contextMenuService, UiBuilder uiBuilder, GlamourerChangelog changelog, @@ -84,7 +83,7 @@ public class SettingsTab( config.DisableFestivals == 0, v => config.DisableFestivals = v ? (byte)0 : (byte)2); Checkbox("Auto-Reload Gear", "Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection.", - config.AutoRedrawEquipOnChanges, autoRedraw.SetState); + config.AutoRedrawEquipOnChanges, v => config.AutoRedrawEquipOnChanges = v); Checkbox("Revert Manual Changes on Zone Change", "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone.", config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index f702e84..bf8fb4b 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -9,58 +9,26 @@ public class PenumbraAutoRedraw : IDisposable private readonly PenumbraService _penumbra; private readonly StateManager _state; private readonly ObjectManager _objects; - private bool _enabled; public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects) { - _penumbra = penumbra; - _config = config; - _state = state; - _objects = objects; - if (_config.AutoRedrawEquipOnChanges) - Enable(); - } - - public void SetState(bool value) - { - if (value == _config.AutoRedrawEquipOnChanges) - return; - - _config.AutoRedrawEquipOnChanges = value; - _config.Save(); - if (value) - Enable(); - else - Disable(); - } - - public void Enable() - { - if (_enabled) - return; - + _penumbra = penumbra; + _config = config; + _state = state; + _objects = objects; _penumbra.ModSettingChanged += OnModSettingChange; - _enabled = true; - } - - public void Disable() - { - if (!_enabled) - return; - - _penumbra.ModSettingChanged -= OnModSettingChange; - _enabled = false; } public void Dispose() - { - Disable(); - } + => _penumbra.ModSettingChanged -= OnModSettingChange; private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited) { + if (!_config.AutoRedrawEquipOnChanges && type is not ModSettingChange.TemporaryMod) + return; + var playerName = _penumbra.GetCurrentPlayerCollection(); if (playerName == name) - _state.ReapplyState(_objects.Player); + _state.ReapplyState(_objects.Player, StateSource.IpcManual); } } diff --git a/Penumbra.Api b/Penumbra.Api index a28219a..2b6bcf3 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit a28219ac57b53c3be6ca8c252ceb9f76ae0b6c21 +Subproject commit 2b6bcf338794b34bcba2730c70dcbb73ce97311b From cdaabc05e99de1cbdd13df8094bbd973410cb907 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 18 Feb 2024 14:45:34 +0100 Subject: [PATCH 263/786] Fix bug with weapon colorsets, add table buttons. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 52 ++++++++++++++++++- Glamourer/Gui/Materials/ColorRowClipboard.cs | 16 +++++- .../Material/LiveColorTablePreviewer.cs | 31 +++++++---- Glamourer/Interop/Material/PrepareColorSet.cs | 7 +-- 4 files changed, 92 insertions(+), 14 deletions(-) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 43036e8..f91f352 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -26,6 +26,7 @@ public sealed unsafe class AdvancedDyePopup( private ActorState _state = null!; private Actor _actor; private byte _selectedMaterial = byte.MaxValue; + private bool _anyChanged = false; private bool ShouldBeDrawn() { @@ -162,7 +163,7 @@ public sealed unsafe class AdvancedDyePopup( } var size = new Vector2(7 * ImGui.GetFrameHeight() + 3 * ImGui.GetStyle().ItemInnerSpacing.X + 300 * ImGuiHelpers.GlobalScale, - 17f * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y); + 18 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + ImGui.GetStyle().ItemSpacing.Y); ImGui.SetNextWindowSize(size); @@ -192,12 +193,57 @@ public sealed unsafe class AdvancedDyePopup( private void DrawTable(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table) { using var disabled = ImRaii.Disabled(_state.IsLocked); + _anyChanged = false; for (byte i = 0; i < MtrlFile.ColorTable.NumRows; ++i) { var index = materialIndex with { RowIndex = i }; ref var row = ref table[i]; DrawRow(ref row, index, table); } + + ImGui.Separator(); + DrawAllRow(materialIndex, table); + } + + private void DrawAllRow(MaterialValueIndex materialIndex, in MtrlFile.ColorTable table) + { + using var id = ImRaii.PushId(100); + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), buttonSize, "Highlight all affected colors on the character.", + false, true); + if (ImGui.IsItemHovered()) + preview.OnHover(materialIndex with { RowIndex = byte.MaxValue }, _actor.Index, table); + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImGui.TextUnformatted("All Color Rows"); + } + + var spacing = ImGui.GetStyle().ItemInnerSpacing.X; + ImGui.SameLine(ImGui.GetWindowSize().X - 3 * buttonSize.X - 3 * spacing); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this table to your clipboard.", false, + true)) + ColorRowClipboard.Table = table; + ImGui.SameLine(0, spacing); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize, + "Import an exported table from your clipboard onto this table.", !ColorRowClipboard.IsTableSet, true)) + foreach (var (row, idx) in ColorRowClipboard.Table.WithIndex()) + { + var internalRow = new ColorRow(row); + var slot = materialIndex.ToEquipSlot(); + var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand + ? _state.ModelData.Weapon(slot) + : _state.ModelData.Armor(slot).ToWeapon(0); + var value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual); + stateManager.ChangeMaterialValue(_state!, materialIndex with { RowIndex = (byte)idx }, value, ApplySettings.Manual); + } + + 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) + stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = (byte)i }, ApplySettings.Game); } private void DrawRow(ref MtrlFile.ColorTable.Row row, MaterialValueIndex index, in MtrlFile.ColorTable table) @@ -213,6 +259,10 @@ public sealed unsafe class AdvancedDyePopup( : _state.ModelData.Armor(slot).ToWeapon(0); value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual); } + else + { + _anyChanged = true; + } var buttonSize = new Vector2(ImGui.GetFrameHeight()); ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), buttonSize, "Highlight the affected colors on the character.", diff --git a/Glamourer/Gui/Materials/ColorRowClipboard.cs b/Glamourer/Gui/Materials/ColorRowClipboard.cs index 74c1c68..f8310c3 100644 --- a/Glamourer/Gui/Materials/ColorRowClipboard.cs +++ b/Glamourer/Gui/Materials/ColorRowClipboard.cs @@ -1,13 +1,27 @@ using Glamourer.Interop.Material; +using Penumbra.GameData.Files; namespace Glamourer.Gui.Materials; public static class ColorRowClipboard { - private static ColorRow _row; + private static ColorRow _row; + private static MtrlFile.ColorTable _table; public static bool IsSet { get; private set; } + public static bool IsTableSet { get; private set; } + + public static MtrlFile.ColorTable Table + { + get => _table; + set + { + IsTableSet = true; + _table = value; + } + } + public static ColorRow Row { get => _row; diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index 7b10829..8cd2b78 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -29,21 +29,20 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable private void Reset() { - if (!LastValueIndex.Valid || _lastObjectIndex == ObjectIndex.AnyIndex) + if (LastValueIndex.DrawObject is MaterialValueIndex.DrawObjectType.Invalid || _lastObjectIndex == ObjectIndex.AnyIndex) return; var actor = (Actor)_objects.GetObjectAddress(_lastObjectIndex.Index); if (actor.IsCharacter && LastValueIndex.TryGetTexture(actor, out var texture)) MaterialService.ReplaceColorTable(texture, LastOriginalColorTable); - Glamourer.Log.Information($"Reset {_lastObjectIndex} {LastValueIndex}"); LastValueIndex = MaterialValueIndex.Invalid; _lastObjectIndex = ObjectIndex.AnyIndex; } private void OnFramework(IFramework _) { - if (!_valueIndex.Valid || _objectIndex == ObjectIndex.AnyIndex) + if (_valueIndex.DrawObject is MaterialValueIndex.DrawObjectType.Invalid || _objectIndex == ObjectIndex.AnyIndex) { Reset(); _valueIndex = MaterialValueIndex.Invalid; @@ -69,10 +68,24 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable if (_valueIndex.TryGetTexture(actor, out var texture)) { - var diffuse = CalculateDiffuse(); - var table = LastOriginalColorTable; - table[_valueIndex.RowIndex].Diffuse = diffuse; - table[_valueIndex.RowIndex].Emissive = diffuse / 8; + var diffuse = CalculateDiffuse(); + var emissive = diffuse / 8; + var table = LastOriginalColorTable; + if (_valueIndex.RowIndex != byte.MaxValue) + { + table[_valueIndex.RowIndex].Diffuse = diffuse; + table[_valueIndex.RowIndex].Emissive = emissive; + } + else + { + + for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i) + { + table[i].Diffuse = diffuse; + table[i].Emissive = emissive; + } + } + MaterialService.ReplaceColorTable(texture, table); } @@ -82,12 +95,12 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, MtrlFile.ColorTable table) { - if (_valueIndex.Valid) + if (_valueIndex.DrawObject is not MaterialValueIndex.DrawObjectType.Invalid) return; _valueIndex = index; _objectIndex = objectIndex; - if (!LastValueIndex.Valid + if (LastValueIndex.DrawObject is MaterialValueIndex.DrawObjectType.Invalid || _lastObjectIndex == ObjectIndex.AnyIndex || LastValueIndex.MaterialIndex != _valueIndex.MaterialIndex || LastValueIndex.DrawObject != _valueIndex.DrawObject diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 1fc2f68..0f5be0d 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -74,15 +74,16 @@ public sealed unsafe class PrepareColorSet public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out MtrlFile.ColorTable table) { var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex; - var model = actor.Model.AsCharacterBase; - var handle = (MaterialResourceHandle*)model->Materials[idx]; + if (!index.TryGetModel(actor, out var model)) + return false; + var handle = (MaterialResourceHandle*)model.AsCharacterBase->Materials[idx]; if (handle == null) { table = default; return false; } - return TryGetColorTable(model, handle, GetStain(), out table); + return TryGetColorTable(model.AsCharacterBase, handle, GetStain(), out table); StainId GetStain() { From d6575e6e6866c92ad404120aca9c94a0f49cfcda Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 18 Feb 2024 14:45:54 +0100 Subject: [PATCH 264/786] Handle state reapplication on temporary mod changes. --- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 31 +++++++++++++++---- Glamourer/State/StateManager.cs | 7 +++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index bf8fb4b..cb78c47 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -1,9 +1,10 @@ using Glamourer.State; +using OtterGui.Services; using Penumbra.Api.Enums; namespace Glamourer.Interop.Penumbra; -public class PenumbraAutoRedraw : IDisposable +public class PenumbraAutoRedraw : IDisposable, IRequiredService { private readonly Configuration _config; private readonly PenumbraService _penumbra; @@ -24,11 +25,29 @@ public class PenumbraAutoRedraw : IDisposable private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited) { - if (!_config.AutoRedrawEquipOnChanges && type is not ModSettingChange.TemporaryMod) - return; + if (type is ModSettingChange.TemporaryMod) + { + _objects.Update(); + foreach (var (id, state) in _state) + { + if (!_objects.TryGetValue(id, out var actors) || !actors.Valid) + continue; - var playerName = _penumbra.GetCurrentPlayerCollection(); - if (playerName == name) - _state.ReapplyState(_objects.Player, StateSource.IpcManual); + var collection = _penumbra.GetActorCollection(actors.Objects[0]); + if (collection != name) + continue; + + foreach (var actor in actors.Objects) + _state.ReapplyState(actor, state, StateSource.IpcManual); + Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}."); + } + } + else if (_config.AutoRedrawEquipOnChanges) + { + var playerName = _penumbra.GetCurrentPlayerCollection(); + if (playerName == name) + _state.ReapplyState(_objects.Player, StateSource.IpcManual); + Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player)."); + } } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index b58bab9..2c0b2d2 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -344,6 +344,13 @@ public sealed class StateManager( StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null); } + public void ReapplyState(Actor actor, ActorState state, StateSource source) + { + var data = Applier.ApplyAll(state, + !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); + StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null); + } + public void DeleteState(ActorIdentifier identifier) => _states.Remove(identifier); } From 62c1730a71c1829f51c9e73e219709608725de82 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 18 Feb 2024 13:47:39 +0000 Subject: [PATCH 265/786] [CI] Updating repo.json for testing_1.1.0.12 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index a44ccbe..ab3bf4d 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.11", + "TestingAssemblyVersion": "1.1.0.12", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.11/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.12/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 5f6e24c34f5fb2ddbe20984e2e0a6f6a1279e983 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 20 Feb 2024 17:41:52 +0100 Subject: [PATCH 266/786] Add chat command to apply items. --- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 5 ++ Glamourer/Interop/ScalingService.cs | 1 - Glamourer/Services/CommandService.cs | 85 ++++++++++++++++++- 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index 81c9766..e5f691d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -77,6 +77,11 @@ public unsafe class ModelEvaluationPanel( DrawCustomize(actor, model); DrawCrests(actor, model); DrawParameters(actor, model); + + 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"); } private void DrawParameters(Actor actor, Model model) diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 585b13b..c7b80cb 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -48,7 +48,6 @@ public unsafe class ScalingService : IDisposable [Signature("48 89 5C 24 ?? 55 57 41 57 48 8D 6C 24", DetourName = nameof(PlaceMinionDetour))] private readonly Hook _placeMinionHook = null!; - private void SetupMountDetour(Character.MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4) { var (race, clan, gender) = GetScaleRelevantCustomize(&container->OwnerObject->Character); diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 3ca1e76..287fca0 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -13,6 +13,7 @@ using OtterGui; using OtterGui.Classes; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Services; @@ -34,10 +35,12 @@ public class CommandService : IDisposable private readonly DesignFileSystem _designFileSystem; private readonly Configuration _config; private readonly ModSettingApplier _modApplier; + private readonly ItemManager _items; 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) + DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier, + ItemManager items) { _commands = commands; _mainWindow = mainWindow; @@ -52,6 +55,7 @@ public class CommandService : IDisposable _autoDesignManager = autoDesignManager; _config = config; _modApplier = modApplier; + _items = items; _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); _commands.AddHandler(ApplyCommandString, @@ -115,6 +119,7 @@ public class CommandService : IDisposable "copy" => CopyState(argument), "save" => SaveState(argument), "delete" => Delete(argument), + "applyitem" => ApplyItem(argument), _ => PrintHelp(argumentList[0]), }; } @@ -141,6 +146,8 @@ public class CommandService : IDisposable .AddCommand("save", "Save the current state of a character to a named design. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder() .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); return true; } @@ -364,6 +371,78 @@ public class CommandService : IDisposable return true; } + private bool ApplyItem(string arguments) + { + var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (split.Length is not 2) + { + _chat.Print(new SeStringBuilder().AddText("Use with /glamour applyitem ").AddYellow("[Item ID or Item Name]") + .AddText(" | ") + .AddGreen("[Character Identifier]") + .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; + } + + var items = new EquipItem[3]; + if (uint.TryParse(split[0], out var id)) + { + if (_items.ItemData.Primary.TryGetValue(id, out var main)) + items[0] = main; + } + else if (_items.ItemData.Primary.FindFirst(pair => string.Equals(pair.Value.Name, split[0], StringComparison.OrdinalIgnoreCase), out var i)) + { + items[0] = i.Value; + } + + if (!items[0].Valid) + { + _chat.Print(new SeStringBuilder().AddText("The item ").AddYellow(split[0], true) + .AddText(" could not be identified as a valid item.").BuiltString); + return false; + } + + if (_items.ItemData.Secondary.TryGetValue(items[0].ItemId, out var off)) + { + items[1] = off; + if (_items.ItemData.Tertiary.TryGetValue(items[0].ItemId, out var gauntlet)) + items[2] = gauntlet; + } + + 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)) + continue; + + foreach (var item in items.Where(i => i.Valid)) + _stateManager.ChangeItem(state, item.Type.ToSlot(), item, ApplySettings.Manual); + } + else + { + foreach (var actor in actors.Objects) + { + if (!_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) + continue; + + foreach (var item in items.Where(i => i.Valid)) + _stateManager.ChangeItem(state, item.Type.ToSlot(), item, ApplySettings.Manual); + } + } + } + + return true; + } + private bool Apply(string arguments) { var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); @@ -394,6 +473,7 @@ public class CommandService : IDisposable _chat.Print(new SeStringBuilder().AddText("If ").AddBlue("true") .AddText(", it will try to apply mod associations to the collection assigned to the identified character.").BuiltString); PlayerIdentifierHelp(false, true); + return true; } var split2 = split[1].Split(';', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); @@ -446,7 +526,8 @@ public class CommandService : IDisposable 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)}."); + Glamourer.Messager.Chat.Print( + $"Applied {appliedMods} mod settings to {collection}{(overridden ? " (overridden by settings)" : string.Empty)}."); } private bool Delete(string argument) From e5f62d3ea9f683a609b5a862c279152eb274b008 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 22 Feb 2024 18:06:26 +0100 Subject: [PATCH 267/786] Materials only when gear customization is on. --- Glamourer/Designs/Links/DesignMerger.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 7aa0fdf..6e0d7ba 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -47,7 +47,8 @@ public class DesignMerger( ReduceCrests(data, crestFlags, ret, source); ReduceParameters(data, parameterFlags, ret, source); ReduceMods(design as Design, ret, modAssociations); - ReduceMaterials(design, ret); + if (type.HasFlag(ApplicationType.GearCustomization)) + ReduceMaterials(design, ret); } ApplyFixFlags(ret, fixFlags); From d8ce81cdc4fede559be5a1c1277c1cf49cb95c98 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 22 Feb 2024 18:10:45 +0100 Subject: [PATCH 268/786] Run auto redraw on framework, add some locks, handle material value application differently for ApplyAll. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 17 +- Glamourer/Interop/Material/DirectXService.cs | 187 ++++++++++++++++++ .../Interop/Material/DirectXTextureHelper.cs | 116 ----------- .../Material/LiveColorTablePreviewer.cs | 16 +- Glamourer/Interop/Material/MaterialService.cs | 28 --- .../Interop/Material/MaterialValueIndex.cs | 25 --- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 48 +++-- Glamourer/State/StateApplier.cs | 63 ++++-- Glamourer/State/StateEditor.cs | 2 + Glamourer/State/StateManager.cs | 9 +- 10 files changed, 287 insertions(+), 224 deletions(-) create mode 100644 Glamourer/Interop/Material/DirectXService.cs delete mode 100644 Glamourer/Interop/Material/DirectXTextureHelper.cs diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index f91f352..f863e1f 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -20,13 +20,14 @@ namespace Glamourer.Gui.Materials; public sealed unsafe class AdvancedDyePopup( Configuration config, StateManager stateManager, - LiveColorTablePreviewer preview) : IService + LiveColorTablePreviewer preview, + DirectXService directX) : IService { private MaterialValueIndex? _drawIndex; private ActorState _state = null!; private Actor _actor; private byte _selectedMaterial = byte.MaxValue; - private bool _anyChanged = false; + private bool _anyChanged; private bool ShouldBeDrawn() { @@ -94,7 +95,7 @@ public sealed unsafe class AdvancedDyePopup( for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) { var index = _drawIndex!.Value with { MaterialIndex = i }; - var available = index.TryGetTexture(textures, out var texture) && index.TryGetColorTable(texture, out var table); + var available = index.TryGetTexture(textures, out var texture) && directX.TryGetColorTable(*texture, out var table); if (index == preview.LastValueIndex with { RowIndex = 0 }) table = preview.LastOriginalColorTable; @@ -179,7 +180,7 @@ public sealed unsafe class AdvancedDyePopup( } } - public unsafe void Draw(Actor actor, ActorState state) + public void Draw(Actor actor, ActorState state) { _actor = actor; _state = state; @@ -236,20 +237,20 @@ public sealed unsafe class AdvancedDyePopup( ? _state.ModelData.Weapon(slot) : _state.ModelData.Armor(slot).ToWeapon(0); var value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual); - stateManager.ChangeMaterialValue(_state!, materialIndex with { RowIndex = (byte)idx }, value, ApplySettings.Manual); + stateManager.ChangeMaterialValue(_state, materialIndex with { RowIndex = (byte)idx }, value, ApplySettings.Manual); } 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) - stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = (byte)i }, ApplySettings.Game); + stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = i }, ApplySettings.Game); } private void DrawRow(ref MtrlFile.ColorTable.Row row, MaterialValueIndex index, in MtrlFile.ColorTable table) { using var id = ImRaii.PushId(index.RowIndex); - var changed = _state!.Materials.TryGetValue(index, out var value); + var changed = _state.Materials.TryGetValue(index, out var value); if (!changed) { var internalRow = new ColorRow(row); @@ -314,7 +315,7 @@ public sealed unsafe class AdvancedDyePopup( stateManager.ResetMaterialValue(_state, index, ApplySettings.Game); if (applied) - stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual); + stateManager.ChangeMaterialValue(_state, index, value, ApplySettings.Manual); } private LabelStruct _label = new(); diff --git a/Glamourer/Interop/Material/DirectXService.cs b/Glamourer/Interop/Material/DirectXService.cs new file mode 100644 index 0000000..b204dd6 --- /dev/null +++ b/Glamourer/Interop/Material/DirectXService.cs @@ -0,0 +1,187 @@ +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using Lumina.Data.Files; +using OtterGui.Services; +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; + +namespace Glamourer.Interop.Material; + +public unsafe class DirectXService(IFramework framework) : IService +{ + 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) + { + if (original == null) + return false; + + var textureSize = stackalloc int[2]; + textureSize[0] = MaterialService.TextureWidth; + textureSize[1] = MaterialService.TextureHeight; + + lock (_lock) + { + using var texture = new SafeTextureHandle(Device.Instance()->CreateTexture2D(textureSize, 1, + (uint)TexFile.TextureFormat.R16G16B16A16F, + (uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7), false); + if (texture.IsInvalid) + return false; + + fixed (ColorTable* ptr = &colorTable) + { + if (!texture.Texture->InitializeContents(ptr)) + return false; + } + + Glamourer.Log.Verbose($"[{Thread.CurrentThread.ManagedThreadId}] Replaced texture {(ulong)*original:X} with new ColorTable."); + texture.Exchange(ref *(nint*)original); + } + + return true; + } + + public bool TryGetColorTable(Texture* texture, out ColorTable table) + { + if (_textures.TryGetValue((nint)texture, out var p) && framework.LastUpdateUTC == p.Update) + { + table = p.Table; + return true; + } + + lock (_lock) + { + if (!TextureColorTable(texture, out table)) + return false; + } + + _textures[(nint)texture] = (framework.LastUpdateUTC, table); + return true; + } + + /// Try to turn a color table GPU-loaded texture (R16G16B16A16Float, 4 Width, 16 Height) into an actual color table. + /// 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) + { + if (texture == null) + { + table = default; + return false; + } + + try + { + // Create direct x resource and ensure that it is kept alive. + using var tex = new ID3D11Texture2D1((nint)texture->D3D11Texture2D); + tex.AddRef(); + + table = GetResourceData(tex, CreateStagedClone, GetTextureData); + return true; + } + catch + { + return false; + } + } + + /// Create a staging clone of the existing texture handle for stability reasons. + private static ID3D11Texture2D1 CreateStagedClone(ID3D11Texture2D1 resource) + { + var desc = resource.Description1 with + { + Usage = ResourceUsage.Staging, + BindFlags = 0, + CPUAccessFlags = CpuAccessFlags.Read, + MiscFlags = 0, + }; + + var ret = resource.Device.As().CreateTexture2D1(desc); + Glamourer.Log.Excessive( + $"[{Thread.CurrentThread.ManagedThreadId}] Cloning resource {resource.NativePointer:X} to {ret.NativePointer:X}"); + return ret; + } + + /// Turn a mapped texture into a color table. + private static ColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map) + { + var desc = resource.Description1; + + if (desc.Format is not Format.R16G16B16A16_Float + || desc.Width != MaterialService.TextureWidth + || desc.Height != MaterialService.TextureHeight + || map.DepthPitch != map.RowPitch * desc.Height) + throw new InvalidDataException("The texture was not a valid color table texture."); + + return ReadTexture(map.DataPointer, map.DepthPitch, desc.Height, map.RowPitch); + } + + /// Transform the GPU data into the color table. + /// The pointer to the raw texture data. + /// The size of the raw texture data. + /// 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) + { + // 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) + return default; + + var ret = new ColorTable(); + var target = (byte*)&ret; + // If the stride is the same as in the table, just copy. + if (pitch == MaterialService.TextureWidth) + MemoryUtility.MemCpyUnchecked(target, (void*)data, length); + // Otherwise, adapt the stride. + else + + for (var y = 0; y < height; ++y) + { + MemoryUtility.MemCpyUnchecked(target + y * MaterialService.TextureWidth * sizeof(Half) * 4, (byte*)data + y * pitch, + MaterialService.TextureWidth * sizeof(Half) * 4); + } + + return ret; + } + + /// Get resources of a texture. + private static TRet GetResourceData(T res, Func cloneResource, Func getData) + where T : ID3D11Resource + { + using var stagingRes = cloneResource(res); + + res.Device.ImmediateContext.CopyResource(stagingRes, res); + Glamourer.Log.Excessive( + $"[{Thread.CurrentThread.ManagedThreadId}] Copied resource data {res.NativePointer:X} to {stagingRes.NativePointer:X}"); + stagingRes.Device.ImmediateContext.Map(stagingRes, 0, MapMode.Read, MapFlags.None, out var mapInfo).CheckError(); + Glamourer.Log.Excessive( + $"[{Thread.CurrentThread.ManagedThreadId}] Mapped resource data for {stagingRes.NativePointer:X} to {mapInfo.DataPointer:X}"); + + try + { + return getData(stagingRes, mapInfo); + } + finally + { + Glamourer.Log.Excessive($"[{Thread.CurrentThread.ManagedThreadId}] Obtained resource data."); + stagingRes.Device.ImmediateContext.Unmap(stagingRes, 0); + Glamourer.Log.Excessive($"[{Thread.CurrentThread.ManagedThreadId}] Unmapped resource data for {stagingRes.NativePointer:X}"); + } + } + + private static readonly Result WasStillDrawing = new(0x887A000A); +} diff --git a/Glamourer/Interop/Material/DirectXTextureHelper.cs b/Glamourer/Interop/Material/DirectXTextureHelper.cs deleted file mode 100644 index 9932abc..0000000 --- a/Glamourer/Interop/Material/DirectXTextureHelper.cs +++ /dev/null @@ -1,116 +0,0 @@ -using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using Penumbra.GameData.Files; -using Penumbra.String.Functions; -using SharpGen.Runtime; -using Vortice.Direct3D11; -using Vortice.DXGI; -using MapFlags = Vortice.Direct3D11.MapFlags; - -namespace Glamourer.Interop.Material; - -public static unsafe class DirectXTextureHelper -{ - /// Try to turn a color table GPU-loaded texture (R16G16B16A16Float, 4 Width, 16 Height) into an actual color table. - /// A pointer to the internal texture struct containing the GPU handle. - /// The returned color table. - /// Whether the table could be fetched. - public static bool TryGetColorTable(Texture* texture, out MtrlFile.ColorTable table) - { - if (texture == null) - { - table = default; - return false; - } - - try - { - // Create direct x resource and ensure that it is kept alive. - using var tex = new ID3D11Texture2D1((nint)texture->D3D11Texture2D); - tex.AddRef(); - - table = GetResourceData(tex, CreateStagedClone, GetTextureData); - return true; - } - catch - { - return false; - } - } - - /// Create a staging clone of the existing texture handle for stability reasons. - private static ID3D11Texture2D1 CreateStagedClone(ID3D11Texture2D1 resource) - { - var desc = resource.Description1 with - { - Usage = ResourceUsage.Staging, - BindFlags = 0, - CPUAccessFlags = CpuAccessFlags.Read, - MiscFlags = 0, - }; - - return resource.Device.As().CreateTexture2D1(desc); - } - - /// Turn a mapped texture into a color table. - private static MtrlFile.ColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map) - { - var desc = resource.Description1; - - if (desc.Format is not Format.R16G16B16A16_Float - || desc.Width != MaterialService.TextureWidth - || desc.Height != MaterialService.TextureHeight - || map.DepthPitch != map.RowPitch * desc.Height) - throw new InvalidDataException("The texture was not a valid color table texture."); - - return ReadTexture(map.DataPointer, map.DepthPitch, desc.Height, map.RowPitch); - } - - /// Transform the GPU data into the color table. - /// The pointer to the raw texture data. - /// The size of the raw texture data. - /// The height of the texture. (Needs to be 16). - /// The stride in the texture data. - /// - private static MtrlFile.ColorTable 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(MtrlFile.ColorTable) != expectedSize || height != MaterialService.TextureHeight) - return default; - - var ret = new MtrlFile.ColorTable(); - var target = (byte*)&ret; - // If the stride is the same as in the table, just copy. - if (pitch == MaterialService.TextureWidth) - MemoryUtility.MemCpyUnchecked(target, (void*)data, length); - // Otherwise, adapt the stride. - else - - for (var y = 0; y < height; ++y) - { - MemoryUtility.MemCpyUnchecked(target + y * MaterialService.TextureWidth * sizeof(Half) * 4, (byte*)data + y * pitch, - MaterialService.TextureWidth * sizeof(Half) * 4); - } - - return ret; - } - - /// Get resources of a texture. - private static TRet GetResourceData(T res, Func cloneResource, Func getData) - where T : ID3D11Resource - { - using var stagingRes = cloneResource(res); - - res.Device.ImmediateContext.CopyResource(stagingRes, res); - stagingRes.Device.ImmediateContext.Map(stagingRes, 0, MapMode.Read, MapFlags.None, out var mapInfo).CheckError(); - - try - { - return getData(stagingRes, mapInfo); - } - finally - { - stagingRes.Device.ImmediateContext.Unmap(stagingRes, 0); - } - } -} diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index 8cd2b78..6ec3496 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -9,8 +9,9 @@ namespace Glamourer.Interop.Material; public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable { - private readonly IObjectTable _objects; - private readonly IFramework _framework; + private readonly IObjectTable _objects; + private readonly IFramework _framework; + private readonly DirectXService _directXService; public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid; public MtrlFile.ColorTable LastOriginalColorTable { get; private set; } @@ -19,11 +20,11 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable private ObjectIndex _objectIndex = ObjectIndex.AnyIndex; private MtrlFile.ColorTable _originalColorTable; - - public LiveColorTablePreviewer(IObjectTable objects, IFramework framework) + public LiveColorTablePreviewer(IObjectTable objects, IFramework framework, DirectXService directXService) { _objects = objects; _framework = framework; + _directXService = directXService; _framework.Update += OnFramework; } @@ -34,7 +35,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable var actor = (Actor)_objects.GetObjectAddress(_lastObjectIndex.Index); if (actor.IsCharacter && LastValueIndex.TryGetTexture(actor, out var texture)) - MaterialService.ReplaceColorTable(texture, LastOriginalColorTable); + _directXService.ReplaceColorTable(texture, LastOriginalColorTable); LastValueIndex = MaterialValueIndex.Invalid; _lastObjectIndex = ObjectIndex.AnyIndex; @@ -78,15 +79,14 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable } else { - for (var i = 0; i < MtrlFile.ColorTable.NumRows; ++i) { - table[i].Diffuse = diffuse; + table[i].Diffuse = diffuse; table[i].Emissive = emissive; } } - MaterialService.ReplaceColorTable(texture, table); + _directXService.ReplaceColorTable(texture, table); } _valueIndex = MaterialValueIndex.Invalid; diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index 6b49d3d..48b5fe7 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -13,34 +13,6 @@ public static unsafe class MaterialService public const int TextureHeight = ColorTable.NumRows; public const int MaterialsPerModel = 4; - /// 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 static bool ReplaceColorTable(Texture** original, in ColorTable colorTable) - { - if (original == null) - return false; - - var textureSize = stackalloc int[2]; - textureSize[0] = TextureWidth; - textureSize[1] = TextureHeight; - - using var texture = new SafeTextureHandle(Device.Instance()->CreateTexture2D(textureSize, 1, (uint)TexFile.TextureFormat.R16G16B16A16F, - (uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7), false); - if (texture.IsInvalid) - return false; - - fixed (ColorTable* ptr = &colorTable) - { - if (!texture.Texture->InitializeContents(ptr)) - return false; - } - - texture.Exchange(ref *(nint*)original); - return true; - } - public static bool GenerateNewColorTable(in ColorTable colorTable, out Texture* texture) { var textureSize = stackalloc int[2]; diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 1229cd7..f461637 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -109,31 +109,6 @@ public readonly record struct MaterialValueIndex( return true; } - public unsafe bool TryGetColorTable(Actor actor, out MtrlFile.ColorTable table) - { - if (TryGetTexture(actor, out var texture)) - return TryGetColorTable(texture, out table); - - table = default; - return false; - } - - public unsafe bool TryGetColorTable(Texture** texture, out MtrlFile.ColorTable table) - => DirectXTextureHelper.TryGetColorTable(*texture, out table); - - public bool TryGetColorRow(Actor actor, out MtrlFile.ColorTable.Row row) - { - if (!TryGetColorTable(actor, out var table)) - { - row = default; - return false; - } - - row = table[RowIndex]; - return true; - } - - public static MaterialValueIndex FromKey(uint key) => new(key); diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index cb78c47..5901899 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -1,4 +1,5 @@ -using Glamourer.State; +using Dalamud.Plugin.Services; +using Glamourer.State; using OtterGui.Services; using Penumbra.Api.Enums; @@ -10,13 +11,15 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService private readonly PenumbraService _penumbra; private readonly StateManager _state; private readonly ObjectManager _objects; + private readonly IFramework _framework; - public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects) + public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects, IFramework framework) { _penumbra = penumbra; _config = config; _state = state; _objects = objects; + _framework = framework; _penumbra.ModSettingChanged += OnModSettingChange; } @@ -26,28 +29,31 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited) { if (type is ModSettingChange.TemporaryMod) - { - _objects.Update(); - foreach (var (id, state) in _state) + _framework.RunOnFrameworkThread(() => { - if (!_objects.TryGetValue(id, out var actors) || !actors.Valid) - continue; + _objects.Update(); + foreach (var (id, state) in _state) + { + if (!_objects.TryGetValue(id, out var actors) || !actors.Valid) + continue; - var collection = _penumbra.GetActorCollection(actors.Objects[0]); - if (collection != name) - continue; + var collection = _penumbra.GetActorCollection(actors.Objects[0]); + if (collection != name) + continue; - foreach (var actor in actors.Objects) - _state.ReapplyState(actor, state, StateSource.IpcManual); - Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}."); - } - } + foreach (var actor in actors.Objects) + _state.ReapplyState(actor, state, StateSource.IpcManual); + Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}."); + } + }); else if (_config.AutoRedrawEquipOnChanges) - { - var playerName = _penumbra.GetCurrentPlayerCollection(); - if (playerName == name) - _state.ReapplyState(_objects.Player, StateSource.IpcManual); - Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player)."); - } + _framework.RunOnFrameworkThread(() => + { + var playerName = _penumbra.GetCurrentPlayerCollection(); + if (playerName == name) + _state.ReapplyState(_objects.Player, StateSource.IpcManual); + Glamourer.Log.Debug( + $"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player)."); + }); } } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index a025cb5..52996ea 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -25,7 +25,8 @@ public class StateApplier( MetaService _metaService, ObjectManager _objects, CrestService _crests, - Configuration _config) + Configuration _config, + DirectXService _directX) { /// Simply force a redraw regardless of conditions. public void ForceRedraw(ActorData data) @@ -286,7 +287,7 @@ public class StateApplier( if (!index.TryGetTexture(actor, out var texture)) continue; - if (!index.TryGetColorTable(texture, out var table)) + if (!_directX.TryGetColorTable(*texture, out var table)) continue; if (value.HasValue) @@ -296,7 +297,43 @@ public class StateApplier( else continue; - MaterialService.ReplaceColorTable(texture, table); + _directX.ReplaceColorTable(texture, table); + } + } + + public ActorData ChangeMaterialValues(ActorState state, bool apply) + { + var data = GetData(state); + if (apply) + ChangeMaterialValues(data, state.Materials, state.IsLocked); + return data; + } + + public unsafe void ChangeMaterialValues(ActorData data, in StateMaterialManager materials, bool force) + { + if (!force && !_config.UseAdvancedDyes) + return; + + var groupedMaterialValues = materials.Values.Select(p => (MaterialValueIndex.FromKey(p.Key), p.Value)) + .GroupBy(p => (p.Item1.DrawObject, p.Item1.SlotIndex, p.Item1.MaterialIndex)); + + foreach (var group in groupedMaterialValues) + { + var values = group.ToList(); + var mainKey = values[0].Item1; + foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) + { + if (!mainKey.TryGetTexture(actor, out var texture)) + continue; + + if (!_directX.TryGetColorTable(*texture, out var table)) + continue; + + foreach (var (key, value) in values) + value.Model.Apply(ref table[key.RowIndex]); + + _directX.ReplaceColorTable(texture, table); + } } } @@ -332,17 +369,17 @@ public class StateApplier( ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors; ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand)); - } - if (state.ModelData.IsHuman) - { - ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible()); - ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); - ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); - ChangeCrests(actors, state.ModelData.CrestVisibility); - ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); - foreach (var material in state.Materials.Values) - ChangeMaterialValue(actors, MaterialValueIndex.FromKey(material.Key), material.Value.Model, state.IsLocked); + if (state.ModelData.IsHuman) + { + ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible()); + ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); + ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); + ChangeCrests(actors, state.ModelData.CrestVisibility); + ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); + // This should never be applied when caused through IPC, then redraw should be true. + ChangeMaterialValues(actors, state.Materials, state.IsLocked); + } } return actors; diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 3d1b27c..915aa2c 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -321,6 +321,8 @@ public class StateEditor( settings.Source, out _, settings.Key); } } + + requiresRedraw |= mergedDesign.Design.Materials.Count > 0 && settings.Source.IsIpc(); } var actors = settings.Source.RequiresChange() diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 2c0b2d2..e4772aa 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -224,7 +224,8 @@ public sealed class StateManager( || !state.ModelData.IsHuman || CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw(); - state.ModelData = state.BaseData; + redraw |= state.Materials.Values.Count > 0 && source.IsIpc(); + state.ModelData = state.BaseData; state.ModelData.SetIsWet(false); foreach (var index in Enum.GetValues()) state.Sources[index] = StateSource.Game; @@ -339,15 +340,13 @@ public sealed class StateManager( if (!GetOrCreate(actor, out var state)) return; - var data = Applier.ApplyAll(state, - !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); - StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null); + ReapplyState(actor, state, source); } public void ReapplyState(Actor actor, ActorState state, StateSource source) { var data = Applier.ApplyAll(state, - !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); + !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw() || state.Materials.Values.Count > 0 && source.IsIpc(), false); StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null); } From 53c2a7495fd3178b0989d31780d7904f9d64ff3a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 22 Feb 2024 18:32:40 +0100 Subject: [PATCH 269/786] Make AutoRedraw for IPC wait a bit before applying. --- Glamourer/Events/StateChanged.cs | 1 + .../Interop/Penumbra/PenumbraAutoRedraw.cs | 53 +++++++++++++++++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 0cffc35..4d878e8 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -61,6 +61,7 @@ namespace Glamourer.Events public enum Priority { GlamourerIpc = int.MinValue, + PenumbraAutoRedraw = 0, } } } diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index 5901899..665d243 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -1,5 +1,8 @@ using Dalamud.Plugin.Services; +using Glamourer.Events; +using Glamourer.Interop.Structs; using Glamourer.State; +using OtterGui.Classes; using OtterGui.Services; using Penumbra.Api.Enums; @@ -7,24 +10,61 @@ namespace Glamourer.Interop.Penumbra; public class PenumbraAutoRedraw : IDisposable, IRequiredService { + private const int WaitFrames = 5; private readonly Configuration _config; private readonly PenumbraService _penumbra; private readonly StateManager _state; private readonly ObjectManager _objects; private readonly IFramework _framework; + private readonly StateChanged _stateChanged; - public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects, IFramework framework) + public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects, IFramework framework, + StateChanged stateChanged) { _penumbra = penumbra; _config = config; _state = state; _objects = objects; _framework = framework; + _stateChanged = stateChanged; _penumbra.ModSettingChanged += OnModSettingChange; + _framework.Update += OnFramework; + _stateChanged.Subscribe(OnStateChange, StateChanged.Priority.PenumbraAutoRedraw); } public void Dispose() - => _penumbra.ModSettingChanged -= OnModSettingChange; + { + _penumbra.ModSettingChanged -= OnModSettingChange; + _framework.Update -= OnFramework; + _stateChanged.Unsubscribe(OnStateChange); + } + + private readonly ConcurrentQueue<(ActorState, Action, int)> _actions = []; + private readonly ConcurrentSet _skips = []; + + private void OnStateChange(StateChanged.Type type, StateSource source, ActorState state, ActorData _1, object? _2) + { + if (type is StateChanged.Type.Design && source.IsIpc()) + _skips.TryAdd(state); + } + + private void OnFramework(IFramework _) + { + var count = _actions.Count; + while (_actions.TryDequeue(out var tuple) && count-- > 0) + { + if (_skips.ContainsKey(tuple.Item1)) + { + _skips.TryRemove(tuple.Item1); + continue; + } + + if (tuple.Item3 > 0) + _actions.Enqueue((tuple.Item1, tuple.Item2, tuple.Item3 - 1)); + else + tuple.Item2(); + } + } private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited) { @@ -41,9 +81,12 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService if (collection != name) continue; - foreach (var actor in actors.Objects) - _state.ReapplyState(actor, state, StateSource.IpcManual); - Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}."); + _actions.Enqueue((state, () => + { + foreach (var actor in actors.Objects) + _state.ReapplyState(actor, state, StateSource.IpcManual); + Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}."); + }, WaitFrames)); } }); else if (_config.AutoRedrawEquipOnChanges) From d36e4f891b50b79c627aee92b626aaf66e88c55a Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 22 Feb 2024 17:34:29 +0000 Subject: [PATCH 270/786] [CI] Updating repo.json for testing_1.1.0.13 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index ab3bf4d..67a5999 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.12", + "TestingAssemblyVersion": "1.1.0.13", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.12/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.13/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 80a6e89aa5addca2bb811c5f7de59e3457621856 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 22 Feb 2024 22:50:26 +0100 Subject: [PATCH 271/786] Fix problem with mare colors not resetting, reduce redraws again, use material texture instead of GPU. --- Glamourer/Api/GlamourerIpc.Apply.cs | 2 +- Glamourer/Automation/AutoDesignApplier.cs | 4 ++-- Glamourer/Designs/IDesignEditor.cs | 6 +++++- Glamourer/Interop/Material/DirectXService.cs | 1 - .../Interop/Material/LiveColorTablePreviewer.cs | 10 +++++----- Glamourer/State/StateApplier.cs | 15 +++------------ Glamourer/State/StateEditor.cs | 5 +++-- Glamourer/State/StateManager.cs | 3 +-- 8 files changed, 20 insertions(+), 26 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs index fcff35b..94896f0 100644 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ b/Glamourer/Api/GlamourerIpc.Apply.cs @@ -163,7 +163,7 @@ public partial class GlamourerIpc 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)); + new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: lockCode, MergeLinks: true, ResetMaterials: !once && lockCode != 0)); state.Lock(lockCode); } } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 380de33..b815376 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -254,7 +254,7 @@ public sealed class AutoDesignApplier : IDisposable private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange) { - if (set.BaseState == AutoDesignSet.Base.Game) + if (set.BaseState is AutoDesignSet.Base.Game) _state.ResetStateFixed(state, respectManual); else if (!respectManual) state.Sources.RemoveFixedDesignSources(); @@ -265,7 +265,7 @@ public sealed class AutoDesignApplier : IDisposable var mergedDesign = _designMerger.Merge( set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks.Select(l => (l.Design, l.Flags & d.Type)) ?? [(d.Design, d.Type)]), state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods); - _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false)); + _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, set.BaseState is AutoDesignSet.Base.Game)); } /// Get world-specific first and all-world afterward. diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index d882310..a0aab84 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -12,7 +12,8 @@ public readonly record struct ApplySettings( bool RespectManual = false, bool FromJobChange = false, bool UseSingleSource = false, - bool MergeLinks = false) + bool MergeLinks = false, + bool ResetMaterials = false) { public static readonly ApplySettings Manual = new() { @@ -22,6 +23,7 @@ public readonly record struct ApplySettings( RespectManual = false, UseSingleSource = false, MergeLinks = false, + ResetMaterials = false, }; public static readonly ApplySettings ManualWithLinks = new() @@ -32,6 +34,7 @@ public readonly record struct ApplySettings( RespectManual = false, UseSingleSource = false, MergeLinks = true, + ResetMaterials = false, }; public static readonly ApplySettings Game = new() @@ -42,6 +45,7 @@ public readonly record struct ApplySettings( RespectManual = false, UseSingleSource = false, MergeLinks = false, + ResetMaterials = true, }; } diff --git a/Glamourer/Interop/Material/DirectXService.cs b/Glamourer/Interop/Material/DirectXService.cs index b204dd6..d3fa3db 100644 --- a/Glamourer/Interop/Material/DirectXService.cs +++ b/Glamourer/Interop/Material/DirectXService.cs @@ -15,7 +15,6 @@ namespace Glamourer.Interop.Material; public unsafe class DirectXService(IFramework framework) : IService { 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. diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index 6ec3496..5e1fa06 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -110,11 +110,11 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable private static Vector3 CalculateDiffuse() { - const int frameLength = 1; - const int steps = 64; - var frame = ImGui.GetFrameCount(); - var hueByte = frame % (steps * frameLength) / frameLength; - var hue = (float)hueByte / steps; + const long frameLength = TimeSpan.TicksPerMillisecond * 5; + const long steps = 2000; + var frame = DateTimeOffset.UtcNow.UtcTicks; + var hueByte = frame % (steps * frameLength) / frameLength; + var hue = (float)hueByte / steps; ImGui.ColorConvertHSVtoRGB(hue, 1, 1, out var r, out var g, out var b); return new Vector3(r, g, b); } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 52996ea..9c06ce5 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -301,11 +301,11 @@ public class StateApplier( } } - public ActorData ChangeMaterialValues(ActorState state, bool apply) + public ActorData ChangeMaterialValue(ActorState state, MaterialValueIndex index, bool apply) { var data = GetData(state); if (apply) - ChangeMaterialValues(data, state.Materials, state.IsLocked); + ChangeMaterialValue(data, index, state.Materials.TryGetValue(index, out var v) ? v.Model : null, state.IsLocked); return data; } @@ -326,7 +326,7 @@ public class StateApplier( if (!mainKey.TryGetTexture(actor, out var texture)) continue; - if (!_directX.TryGetColorTable(*texture, out var table)) + if (!PrepareColorSet.TryGetColorTable(actor, mainKey, out var table)) continue; foreach (var (key, value) in values) @@ -337,14 +337,6 @@ public class StateApplier( } } - public ActorData ChangeMaterialValue(ActorState state, MaterialValueIndex index, bool apply) - { - var data = GetData(state); - if (apply) - ChangeMaterialValue(data, index, state.Materials.TryGetValue(index, out var v) ? v.Model : null, state.IsLocked); - return data; - } - /// Apply the entire state of an actor to all relevant actors, either via immediate redraw or piecewise. /// The state to apply. /// Whether a redraw should be forced. @@ -377,7 +369,6 @@ public class StateApplier( ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); ChangeCrests(actors, state.ModelData.CrestVisibility); ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); - // This should never be applied when caused through IPC, then redraw should be true. ChangeMaterialValues(actors, state.Materials, state.IsLocked); } } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 915aa2c..2ddacf6 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -296,6 +296,9 @@ public class StateEditor( Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key); } + if (settings.ResetMaterials) + state.Materials.Clear(); + foreach (var (key, value) in mergedDesign.Design.Materials) { if (!value.Enabled) @@ -321,8 +324,6 @@ public class StateEditor( settings.Source, out _, settings.Key); } } - - requiresRedraw |= mergedDesign.Design.Materials.Count > 0 && settings.Source.IsIpc(); } var actors = settings.Source.RequiresChange() diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index e4772aa..46cd856 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -224,7 +224,6 @@ public sealed class StateManager( || !state.ModelData.IsHuman || CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw(); - redraw |= state.Materials.Values.Count > 0 && source.IsIpc(); state.ModelData = state.BaseData; state.ModelData.SetIsWet(false); foreach (var index in Enum.GetValues()) @@ -346,7 +345,7 @@ public sealed class StateManager( public void ReapplyState(Actor actor, ActorState state, StateSource source) { var data = Applier.ApplyAll(state, - !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw() || state.Materials.Values.Count > 0 && source.IsIpc(), false); + !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null); } From fdee4c4ca87c626c586011603e4c1dcd76566d26 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 22 Feb 2024 21:53:32 +0000 Subject: [PATCH 272/786] [CI] Updating repo.json for testing_1.1.0.14 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 67a5999..5b6233c 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.13", + "TestingAssemblyVersion": "1.1.0.14", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.13/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.14/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From ecf6008b716585b48a5e27df89bc45f79c8c4ca0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 23 Feb 2024 16:49:07 +0100 Subject: [PATCH 273/786] Fix applying designs via weapons in gpose. --- Glamourer/State/StateEditor.cs | 28 ++++++++++++++++++++++------ Glamourer/State/StateManager.cs | 5 +++-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 2ddacf6..faaee58 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -19,7 +19,8 @@ public class StateEditor( Configuration config, ItemManager items, DesignMerger merger, - ModSettingApplier modApplier) : IDesignEditor + ModSettingApplier modApplier, + GPoseService gPose) : IDesignEditor { protected readonly InternalStateEditor Editor = editor; protected readonly StateApplier Applier = applier; @@ -276,12 +277,27 @@ public class StateEditor( continue; var currentType = state.ModelData.Item(weaponSlot).Type; - if (!settings.FromJobChange && mergedDesign.Weapons.TryGetValue(currentType, out var weapon)) + if (!settings.FromJobChange) { - var source = settings.UseSingleSource ? settings.Source : - weapon.Item2 is StateSource.Game ? StateSource.Game : weapon.Item2; - Editor.ChangeItem(state, weaponSlot, weapon.Item1, source, out _, - settings.Key); + if (gPose.InGPose) + { + Editor.ChangeItem(state, weaponSlot, mergedDesign.Design.DesignData.Item(weaponSlot), + settings.UseSingleSource ? settings.Source : mergedDesign.Sources[weaponSlot, false], out var old, settings.Key); + var oldSource = state.Sources[weaponSlot, false]; + gPose.AddActionOnLeave(() => + { + if (old.Type == state.BaseData.Item(weaponSlot).Type) + Editor.ChangeItem(state, weaponSlot, old, oldSource, out _, settings.Key); + }); + } + + if (mergedDesign.Weapons.TryGetValue(currentType, out var weapon)) + { + var source = settings.UseSingleSource ? settings.Source : + weapon.Item2 is StateSource.Game ? StateSource.Game : weapon.Item2; + Editor.ChangeItem(state, weaponSlot, weapon.Item1, source, out _, + settings.Key); + } } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 46cd856..3637729 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -27,8 +27,9 @@ public sealed class StateManager( Configuration config, JobChangeState jobChange, DesignMerger merger, - ModSettingApplier modApplier) - : StateEditor(editor, applier, @event, jobChange, config, items, merger, modApplier), IReadOnlyDictionary + ModSettingApplier modApplier, + GPoseService gPose) + : StateEditor(editor, applier, @event, jobChange, config, items, merger, modApplier, gPose), IReadOnlyDictionary { private readonly Dictionary _states = []; From b8dc64eb1d3e79c3c7fab4e16e4f249667e0c27f Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 23 Feb 2024 15:52:08 +0000 Subject: [PATCH 274/786] [CI] Updating repo.json for testing_1.1.0.15 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 5b6233c..478d60b 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.14", + "TestingAssemblyVersion": "1.1.0.15", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.14/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.15/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From e4324bc4ceca11b362d9880fcbc3cc1922633b77 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 24 Feb 2024 12:07:42 +0100 Subject: [PATCH 275/786] Update API. --- Penumbra.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.Api b/Penumbra.Api index 2b6bcf3..79ffdd6 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 2b6bcf338794b34bcba2730c70dcbb73ce97311b +Subproject commit 79ffdd69a28141a1ac93daa24d76573b2fa0d71e From 3e9edf36172d7319039cbf153ddf48432bdec0f7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 24 Feb 2024 14:03:51 +0100 Subject: [PATCH 276/786] Make the player not update too often on changes. --- Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index 665d243..bf65a2f 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -41,6 +41,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService private readonly ConcurrentQueue<(ActorState, Action, int)> _actions = []; private readonly ConcurrentSet _skips = []; + private DateTime _frame; private void OnStateChange(StateChanged.Type type, StateSource source, ActorState state, ActorData _1, object? _2) { @@ -69,6 +70,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService private void OnModSettingChange(ModSettingChange type, string name, string mod, bool inherited) { if (type is ModSettingChange.TemporaryMod) + { _framework.RunOnFrameworkThread(() => { _objects.Update(); @@ -89,7 +91,15 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService }, WaitFrames)); } }); + } else if (_config.AutoRedrawEquipOnChanges) + { + // Only update once per frame. + var currentFrame = _framework.LastUpdateUTC; + if (currentFrame == _frame) + return; + + _frame = currentFrame; _framework.RunOnFrameworkThread(() => { var playerName = _penumbra.GetCurrentPlayerCollection(); @@ -98,5 +108,6 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService Glamourer.Log.Debug( $"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player)."); }); + } } } From f2951ca800009b661ec5fa7302dbc932b52ad1b1 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 24 Feb 2024 13:05:34 +0000 Subject: [PATCH 277/786] [CI] Updating repo.json for testing_1.1.0.16 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 478d60b..63c0d2a 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.15", + "TestingAssemblyVersion": "1.1.0.16", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.15/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.16/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 1cf0e2f70eb1932178f2d75a9dc006b871ccf6c3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 Feb 2024 15:48:24 +0100 Subject: [PATCH 278/786] Add some new things, rework Revert-handling. --- Glamourer/Automation/ApplicationType.cs | 10 +- Glamourer/Automation/AutoDesign.cs | 20 +-- Glamourer/Automation/AutoDesignApplier.cs | 2 +- Glamourer/Automation/AutoDesignManager.cs | 60 ++++---- Glamourer/Automation/AutoDesignSet.cs | 2 +- Glamourer/Designs/Design.cs | 36 ++++- Glamourer/Designs/DesignColors.cs | 5 +- Glamourer/Designs/IDesignStandIn.cs | 23 ++++ Glamourer/Designs/Links/DesignMerger.cs | 21 +-- Glamourer/Designs/RandomDesign.cs | 69 ++++++++++ Glamourer/Designs/RandomDesignGenerator.cs | 91 +++++++++++++ Glamourer/Designs/RevertDesign.cs | 41 ++++++ Glamourer/Events/DesignChanged.cs | 2 +- Glamourer/Gui/DesignCombo.cs | 128 ++++++++---------- Glamourer/Gui/DesignQuickBar.cs | 8 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 14 +- .../Gui/Tabs/DebugTab/AutoDesignPanel.cs | 2 +- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 19 +-- Glamourer/Services/CommandService.cs | 101 +++++++++----- Glamourer/Services/ServiceManager.cs | 2 +- Penumbra.GameData | 2 +- 21 files changed, 477 insertions(+), 181 deletions(-) create mode 100644 Glamourer/Designs/IDesignStandIn.cs create mode 100644 Glamourer/Designs/RandomDesign.cs create mode 100644 Glamourer/Designs/RandomDesignGenerator.cs create mode 100644 Glamourer/Designs/RevertDesign.cs diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs index 03e3a2d..12dac50 100644 --- a/Glamourer/Automation/ApplicationType.cs +++ b/Glamourer/Automation/ApplicationType.cs @@ -18,18 +18,18 @@ public enum ApplicationType : byte public static class ApplicationTypeExtensions { - public static readonly IReadOnlyList<(ApplicationType, string)> Types = new[] - { + public static readonly IReadOnlyList<(ApplicationType, string)> Types = + [ (ApplicationType.Customizations, "Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."), (ApplicationType.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."), (ApplicationType.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."), (ApplicationType.GearCustomization, "Apply all dye and crest changes that are enabled in this design."), (ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), - }; + ]; public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat( - this ApplicationType type, DesignBase? design) + this ApplicationType type, IDesignStandIn designStandIn) { var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0) | (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0) @@ -42,7 +42,7 @@ public static class ApplicationTypeExtensions | (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0) | (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0); - if (design == null) + if (designStandIn is not DesignBase design) return (equipFlags, customizeFlags, crestFlag, parameterFlags, metaFlag); return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest, diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 50704f4..2a0ab7e 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -1,7 +1,6 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop.Structs; -using Glamourer.State; using Newtonsoft.Json.Linq; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -12,20 +11,11 @@ public class AutoDesign { public const string RevertName = "Revert"; - public Design? Design; + public IDesignStandIn Design = new RevertDesign(); public JobGroup Jobs; public ApplicationType Type; public short GearsetIndex = -1; - public string Name(bool incognito) - => Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text; - - public ref readonly DesignData GetDesignData(ActorState state) - => ref Design == null ? ref state.BaseData : ref Design.DesignData; - - public bool Revert - => Design == null; - public AutoDesign Clone() => new() { @@ -50,12 +40,16 @@ public class AutoDesign } public JObject Serialize() - => new() + { + var ret = new JObject { - ["Design"] = Design?.Identifier.ToString(), + ["Design"] = Design.SerializeName(), ["Type"] = (uint)Type, ["Conditions"] = CreateConditionObject(), }; + Design.AddData(ret); + return ret; + } private JObject CreateConditionObject() { diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index b815376..332f9ee 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -263,7 +263,7 @@ public sealed class AutoDesignApplier : IDisposable return; var mergedDesign = _designMerger.Merge( - set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks.Select(l => (l.Design, l.Flags & d.Type)) ?? [(d.Design, d.Type)]), + set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design.AllLinks.Select(l => (l.Design, l.Flags & d.Type))), state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods); _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, set.BaseState is AutoDesignSet.Base.Game)); } diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index e45b8e0..710336e 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -21,11 +21,12 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos private readonly SaveService _saveService; - private readonly JobService _jobs; - private readonly DesignManager _designs; - private readonly ActorManager _actors; - private readonly AutomationChanged _event; - private readonly DesignChanged _designEvent; + private readonly JobService _jobs; + private readonly DesignManager _designs; + private readonly ActorManager _actors; + private readonly AutomationChanged _event; + private readonly DesignChanged _designEvent; + private readonly RandomDesignGenerator _randomDesigns; private readonly List _data = []; private readonly Dictionary _enabled = []; @@ -34,14 +35,15 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos => _enabled; public AutoDesignManager(JobService jobs, ActorManager actors, SaveService saveService, DesignManager designs, AutomationChanged @event, - FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent) + FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent, RandomDesignGenerator randomDesigns) { - _jobs = jobs; - _actors = actors; - _saveService = saveService; - _designs = designs; - _event = @event; - _designEvent = designEvent; + _jobs = jobs; + _actors = actors; + _saveService = saveService; + _designs = designs; + _event = @event; + _designEvent = designEvent; + _randomDesigns = randomDesigns; _designEvent.Subscribe(OnDesignChange, DesignChanged.Priority.AutoDesignManager); Load(); migrator.ConsumeMigratedData(_actors, fileSystem, this); @@ -227,7 +229,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _event.Invoke(AutomationChanged.Type.ChangedBase, set, (old, newBase)); } - public void AddDesign(AutoDesignSet set, Design? design) + public void AddDesign(AutoDesignSet set, IDesignStandIn design) { var newDesign = new AutoDesign() { @@ -238,7 +240,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos set.Designs.Add(newDesign); Save(); Glamourer.Log.Debug( - $"Added new associated design {design?.Identifier.ToString() ?? "Reverter"} as design {set.Designs.Count} to design set."); + $"Added new associated design {design.ResolveName(true)} as design {set.Designs.Count} to design set."); _event.Invoke(AutomationChanged.Type.AddedDesign, set, set.Designs.Count - 1); } @@ -278,20 +280,20 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _event.Invoke(AutomationChanged.Type.MovedDesign, set, (from, to)); } - public void ChangeDesign(AutoDesignSet set, int which, Design? newDesign) + public void ChangeDesign(AutoDesignSet set, int which, IDesignStandIn newDesign) { if (which >= set.Designs.Count || which < 0) return; var design = set.Designs[which]; - if (design.Design?.Identifier == newDesign?.Identifier) + if (design.Design.Equals(newDesign)) return; var old = design.Design; design.Design = newDesign; Save(); Glamourer.Log.Debug( - $"Changed linked design from {old?.Identifier.ToString() ?? "Reverter"} to {newDesign?.Identifier.ToString() ?? "Reverter"} for associated design {which + 1} in design set."); + $"Changed linked design from {old.ResolveName(true)} to {newDesign.ResolveName(true)} for associated design {which + 1} in design set."); _event.Invoke(AutomationChanged.Type.ChangedDesign, set, (which, old, newDesign)); } @@ -450,8 +452,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos continue; } - var design = ToDesignObject(set.Name, j); - if (design != null) + if (ToDesignObject(set.Name, j) is { } design) set.Designs.Add(design); } } @@ -459,10 +460,18 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos private AutoDesign? ToDesignObject(string setName, JObject jObj) { - var designIdentifier = jObj["Design"]?.ToObject(); - Design? design = null; - // designIdentifier == null means Revert-Design. - if (designIdentifier != null) + var designIdentifier = jObj["Design"]?.ToObject(); + IDesignStandIn? design; + // designIdentifier == null means Revert-Design for backwards compatibility + if (designIdentifier is null or RevertDesign.SerializedName) + { + design = new RevertDesign(); + } + else if (designIdentifier is RandomDesign.SerializedName) + { + design = new RandomDesign(_randomDesigns); + } + else { if (designIdentifier.Length == 0) { @@ -480,14 +489,17 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos return null; } - if (!_designs.Designs.TryGetValue(guid, out design)) + if (!_designs.Designs.TryGetValue(guid, out var d)) { Glamourer.Messager.NotificationMessage( $"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.", NotificationType.Warning); return null; } + + design = d; } + design.ParseData(jObj); // ApplicationType is a migration from an older property name. var applicationType = (ApplicationType)(jObj["Type"]?.ToObject() ?? jObj["ApplicationType"]?.ToObject() ?? 0); diff --git a/Glamourer/Automation/AutoDesignSet.cs b/Glamourer/Automation/AutoDesignSet.cs index 29a0e2e..adaa355 100644 --- a/Glamourer/Automation/AutoDesignSet.cs +++ b/Glamourer/Automation/AutoDesignSet.cs @@ -29,7 +29,7 @@ public class AutoDesignSet(string name, ActorIdentifier[] identifiers, List()) + : this(name, identifiers, []) { } public enum Base : byte diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 4e1e72e..e01c09e 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -1,15 +1,17 @@ using Dalamud.Interface.Internal.Notifications; using Glamourer.Automation; using Glamourer.Designs.Links; +using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Services; +using Glamourer.State; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui.Classes; namespace Glamourer.Designs; -public sealed class Design : DesignBase, ISavable +public sealed class Design : DesignBase, ISavable, IDesignStandIn { #region Data @@ -48,8 +50,36 @@ public sealed class Design : DesignBase, ISavable public string Incognito => Identifier.ToString()[..8]; - public IEnumerable<(DesignBase? Design, ApplicationType Flags)> AllLinks - => LinkContainer.GetAllLinks(this).Select(t => ((DesignBase?)t.Link.Link, t.Link.Type)); + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks + => LinkContainer.GetAllLinks(this).Select(t => ((IDesignStandIn)t.Link.Link, t.Link.Type)); + + #endregion + + #region IDesignStandIn + + public string ResolveName(bool incognito) + => incognito ? Incognito : Name.Text; + + public string SerializeName() + => Identifier.ToString(); + + public ref readonly DesignData GetDesignData(in DesignData baseData) + => ref GetDesignDataRef(); + + public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData() + => Materials; + + public bool Equals(IDesignStandIn? other) + => other is Design d && d.Identifier == Identifier; + + public StateSource AssociatedSource() + => StateSource.Manual; + + public void AddData(JObject _) + { } + + public void ParseData(JObject _) + { } #endregion diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index bd192be..8bc5539 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -149,8 +149,11 @@ public class DesignColors : ISavable, IReadOnlyDictionary Load(); } - public uint GetColor(Design design) + public uint GetColor(Design? design) { + if (design == null) + return ColorId.NormalDesign.Value(); + if (design.Color.Length == 0) return AutoColor(design); diff --git a/Glamourer/Designs/IDesignStandIn.cs b/Glamourer/Designs/IDesignStandIn.cs new file mode 100644 index 0000000..33122e2 --- /dev/null +++ b/Glamourer/Designs/IDesignStandIn.cs @@ -0,0 +1,23 @@ +using Glamourer.Automation; +using Glamourer.Interop.Material; +using Glamourer.State; +using Newtonsoft.Json.Linq; + +namespace Glamourer.Designs; + +public interface IDesignStandIn : IEquatable +{ + public string ResolveName(bool incognito); + public ref readonly DesignData GetDesignData(in DesignData baseRef); + + public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData(); + + public string SerializeName(); + public StateSource AssociatedSource(); + + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks { get; } + + public void AddData(JObject jObj); + + public void ParseData(JObject jObj); +} diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 6e0d7ba..ebe8aba 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -17,13 +17,15 @@ public class DesignMerger( ItemUnlockManager _itemUnlocks, CustomizeUnlockManager _customizeUnlocks) : IService { - public MergedDesign Merge(LinkContainer designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership, bool modAssociations) - => Merge(designs.Select(d => ((DesignBase?) d.Link, d.Type)), currentCustomize, baseRef, respectOwnership, modAssociations); + public MergedDesign Merge(LinkContainer designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership, + bool modAssociations) + => Merge(designs.Select(d => ((IDesignStandIn)d.Link, d.Type)), currentCustomize, baseRef, respectOwnership, modAssociations); - public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership, + public MergedDesign Merge(IEnumerable<(IDesignStandIn, ApplicationType)> designs, in CustomizeArray currentCustomize, in DesignData baseRef, + bool respectOwnership, bool modAssociations) { - var ret = new MergedDesign(designManager); + var ret = new MergedDesign(designManager); ret.Design.SetCustomize(_customize, currentCustomize); CustomizeFlag fixFlags = 0; respectOwnership &= _config.UnlockedItemMode; @@ -32,8 +34,8 @@ public class DesignMerger( if (type is 0) continue; - ref readonly var data = ref design == null ? ref baseRef : ref design.GetDesignDataRef(); - var source = design == null ? StateSource.Game : StateSource.Manual; + ref readonly var data = ref design.GetDesignData(baseRef); + var source = design.AssociatedSource(); if (!data.IsHuman) continue; @@ -56,10 +58,11 @@ public class DesignMerger( } - private static void ReduceMaterials(DesignBase? design, MergedDesign ret) + private static void ReduceMaterials(IDesignStandIn designStandIn, MergedDesign ret) { - if (design == null) + if (designStandIn is not DesignBase design) return; + var materials = ret.Design.GetMaterialDataRef(); foreach (var (key, value) in design.Materials.Where(p => p.Item2.Enabled)) materials.TryAddValue(MaterialValueIndex.FromKey(key), value); @@ -243,7 +246,7 @@ public class DesignMerger( ret.Sources[CustomizeIndex.Face] = source; } - var set = _customize.Manager.GetSet(customize.Clan, customize.Gender); + var set = _customize.Manager.GetSet(customize.Clan, customize.Gender); var face = customize.Face; foreach (var index in Enum.GetValues()) { diff --git a/Glamourer/Designs/RandomDesign.cs b/Glamourer/Designs/RandomDesign.cs new file mode 100644 index 0000000..3b9ba2a --- /dev/null +++ b/Glamourer/Designs/RandomDesign.cs @@ -0,0 +1,69 @@ +using Glamourer.Automation; +using Glamourer.Interop.Material; +using Glamourer.State; +using Newtonsoft.Json.Linq; + +namespace Glamourer.Designs; + +public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn +{ + public const string SerializedName = "//Random"; + public const string ResolvedName = "Random"; + private Design? _currentDesign; + + public string Restrictions { get; internal set; } = string.Empty; + + public string ResolveName(bool _) + => ResolvedName; + + public ref readonly DesignData GetDesignData(in DesignData baseRef) + { + _currentDesign ??= rng.Design(Restrictions); + if (_currentDesign == null) + return ref baseRef; + + return ref _currentDesign.GetDesignDataRef(); + } + + public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData() + { + _currentDesign ??= rng.Design(Restrictions); + if (_currentDesign == null) + return []; + + return _currentDesign.Materials; + } + + public string SerializeName() + => SerializedName; + + public bool Equals(IDesignStandIn? other) + => other is RandomDesign r && string.Equals(r.Restrictions, Restrictions, StringComparison.OrdinalIgnoreCase); + + public StateSource AssociatedSource() + => StateSource.Manual; + + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks + { + get + { + _currentDesign = rng.Design(Restrictions); + if (_currentDesign == null) + yield break; + + foreach (var (link, type) in _currentDesign.AllLinks) + yield return (link, type); + } + } + + public void AddData(JObject jObj) + { + jObj["Restrictions"] = Restrictions; + } + + public void ParseData(JObject jObj) + { + var restrictions = jObj["Restrictions"]?.ToObject() ?? string.Empty; + Restrictions = restrictions; + } +} diff --git a/Glamourer/Designs/RandomDesignGenerator.cs b/Glamourer/Designs/RandomDesignGenerator.cs new file mode 100644 index 0000000..4b8f13d --- /dev/null +++ b/Glamourer/Designs/RandomDesignGenerator.cs @@ -0,0 +1,91 @@ +using OtterGui; +using OtterGui.Services; +using System; + +namespace Glamourer.Designs; + +public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem) : IService +{ + private readonly Random _rng = new(); + + public Design? Design(IReadOnlyList localDesigns) + { + if (localDesigns.Count == 0) + return null; + + var idx = _rng.Next(0, localDesigns.Count - 1); + Glamourer.Log.Verbose($"[Random Design] Chose design {idx} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); + return localDesigns[idx]; + } + + public Design? Design() + => Design(designs); + + public Design? Design(string restrictions) + { + if (restrictions.Length == 0) + return Design(designs); + + List> predicates = []; + + switch (restrictions[0]) + { + case '{': + var end = restrictions.IndexOf('}'); + if (end == -1) + throw new ArgumentException($"The restriction group '{restrictions}' is not properly terminated."); + + restrictions = restrictions[1..end]; + var split = restrictions.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + foreach (var item in split.Distinct()) + predicates.Add(item[0] == '/' ? CreatePredicateSlash(item) : CreatePredicate(item)); + break; + case '/': + predicates.Add(CreatePredicateSlash(restrictions)); + break; + default: + predicates.Add(CreatePredicate(restrictions)); + break; + } + + if (predicates.Count == 1) + { + var p = predicates[0]; + return Design(designs.Select(Transform).Where(t => p(t.NameLower, t.Identifier, t.PathLower)).Select(t => t.Design).ToList()); + } + + return Design(designs.Select(Transform).Where(t => predicates.Any(p => p(t.NameLower, t.Identifier, t.PathLower))).Select(t => t.Design) + .ToList()); + + (Design Design, string NameLower, string Identifier, string PathLower) Transform(Design design) + { + var name = design.Name.Lower; + var identifier = design.Identifier.ToString(); + var path = fileSystem.FindLeaf(design, out var leaf) ? leaf.FullName().ToLowerInvariant() : string.Empty; + return (design, name, identifier, path); + } + + Func CreatePredicate(string input) + { + var value = input.ToLowerInvariant(); + return (string nameLower, string identifier, string pathLower) => + { + if (nameLower.Contains(value)) + return true; + if (identifier.Contains(value)) + return true; + if (pathLower.Contains(value)) + return true; + + return false; + }; + } + + Func CreatePredicateSlash(string input) + { + var value = input[1..].ToLowerInvariant(); + return (string nameLower, string identifier, string pathLower) + => pathLower.StartsWith(value); + } + } +} diff --git a/Glamourer/Designs/RevertDesign.cs b/Glamourer/Designs/RevertDesign.cs new file mode 100644 index 0000000..28a490e --- /dev/null +++ b/Glamourer/Designs/RevertDesign.cs @@ -0,0 +1,41 @@ +using Glamourer.Automation; +using Glamourer.Interop.Material; +using Glamourer.State; +using Newtonsoft.Json.Linq; + +namespace Glamourer.Designs; + +public class RevertDesign : IDesignStandIn +{ + public const string SerializedName = "//Revert"; + public const string ResolvedName = "Revert"; + + public string ResolveName(bool _) + => ResolvedName; + + public ref readonly DesignData GetDesignData(in DesignData baseRef) + => ref baseRef; + + public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData() + => []; + + public string SerializeName() + => SerializedName; + + public bool Equals(IDesignStandIn? other) + => other is RevertDesign; + + public StateSource AssociatedSource() + => StateSource.Game; + + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks + { + get { yield return (this, ApplicationType.All); } + } + + public void AddData(JObject jObj) + { } + + public void ParseData(JObject jObj) + { } +} diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 70e9aa6..cd51f6d 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -122,7 +122,7 @@ public sealed class DesignChanged() /// DesignFileSystemSelector = -1, - /// + /// DesignCombo = -2, } } diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index d25ce66..f497f79 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -3,26 +3,24 @@ using Dalamud.Interface.Utility.Raii; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Events; -using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Widgets; -using Penumbra.GameData.Enums; namespace Glamourer.Gui; -public abstract class DesignComboBase : FilterComboCache>, IDisposable +public abstract class DesignComboBase : FilterComboCache>, IDisposable { private readonly EphemeralConfig _config; private readonly DesignChanged _designChanged; private readonly DesignColors _designColors; protected readonly TabSelected TabSelected; protected float InnerWidth; - private Design? _currentDesign; + private IDesignStandIn? _currentDesign; - protected DesignComboBase(Func>> generator, Logger log, DesignChanged designChanged, + protected DesignComboBase(Func>> generator, Logger log, DesignChanged designChanged, TabSelected tabSelected, EphemeralConfig config, DesignColors designColors) : base(generator, MouseWheelType.Control, log) { @@ -46,28 +44,34 @@ public abstract class DesignComboBase : FilterComboCache>, { var (design, path) = Items[globalIdx]; bool ret; - using (var color = ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(design))) + if (design is Design realDesign) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(realDesign)); + ret = base.DrawSelectable(globalIdx, selected); + + if (path.Length > 0 && realDesign.Name != path) + { + var start = ImGui.GetItemRectMin(); + var pos = start.X + ImGui.CalcTextSize(realDesign.Name).X; + var maxSize = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X; + var remainingSpace = maxSize - pos; + var requiredSize = ImGui.CalcTextSize(path).X + ImGui.GetStyle().ItemInnerSpacing.X; + var offset = remainingSpace - requiredSize; + if (ImGui.GetScrollMaxY() == 0) + offset -= ImGui.GetStyle().ItemInnerSpacing.X; + + if (offset < ImGui.GetStyle().ItemSpacing.X) + ImGuiUtil.HoverTooltip(path); + else + ImGui.GetWindowDrawList().AddText(start with { X = pos + offset }, + ImGui.GetColorU32(ImGuiCol.TextDisabled), path); + } + } + else { ret = base.DrawSelectable(globalIdx, selected); } - if (path.Length > 0 && design.Name != path) - { - var start = ImGui.GetItemRectMin(); - var pos = start.X + ImGui.CalcTextSize(design.Name).X; - var maxSize = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X; - var remainingSpace = maxSize - pos; - var requiredSize = ImGui.CalcTextSize(path).X + ImGui.GetStyle().ItemInnerSpacing.X; - var offset = remainingSpace - requiredSize; - if (ImGui.GetScrollMaxY() == 0) - offset -= ImGui.GetStyle().ItemInnerSpacing.X; - - if (offset < ImGui.GetStyle().ItemSpacing.X) - ImGuiUtil.HoverTooltip(path); - else - ImGui.GetWindowDrawList().AddText(start with { X = pos + offset }, - ImGui.GetColorU32(ImGuiCol.TextDisabled), path); - } return ret; } @@ -79,22 +83,22 @@ public abstract class DesignComboBase : FilterComboCache>, return CurrentSelectionIdx; } - protected bool Draw(Design? currentDesign, string? label, float width) + protected bool Draw(IDesignStandIn? currentDesign, string? label, float width) { _currentDesign = currentDesign; InnerWidth = 400 * ImGuiHelpers.GlobalScale; var name = label ?? "Select Design Here..."; bool ret; - using (_ = currentDesign != null ? ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(currentDesign)) : null) + using (_ = currentDesign != null ? ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(currentDesign as Design)) : null) { ret = Draw("##design", name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()) && CurrentSelection != null; } - if (currentDesign != null) + if (currentDesign is Design design) { if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) - TabSelected.Invoke(MainWindow.TabType.Designs, currentDesign); + TabSelected.Invoke(MainWindow.TabType.Designs, design); ImGuiUtil.HoverTooltip("Control + Right-Click to move to design."); } @@ -102,8 +106,8 @@ public abstract class DesignComboBase : FilterComboCache>, return ret; } - protected override string ToString(Tuple obj) - => obj.Item1.Name.Text; + protected override string ToString(Tuple obj) + => obj.Item1.ResolveName(Incognito); protected override float GetFilterWidth() => InnerWidth - 2 * ImGui.GetStyle().FramePadding.X; @@ -111,7 +115,7 @@ public abstract class DesignComboBase : FilterComboCache>, protected override bool IsVisible(int globalIndex, LowerString filter) { var (design, path) = Items[globalIndex]; - return filter.IsContained(path) || design.Name.Lower.Contains(filter.Lower); + return filter.IsContained(path) || filter.IsContained(design.ResolveName(false)); } private void OnDesignChange(DesignChanged.Type type, Design design, object? data = null) @@ -151,7 +155,7 @@ public abstract class DesignComboBase : FilterComboCache>, public abstract class DesignCombo : DesignComboBase { protected DesignCombo(Logger log, DesignChanged designChanged, TabSelected tabSelected, - EphemeralConfig config, DesignColors designColors, Func>> generator) + EphemeralConfig config, DesignColors designColors, Func>> generator) : base(generator, log, designChanged, tabSelected, config, designColors) { if (Items.Count == 0) @@ -162,11 +166,11 @@ public abstract class DesignCombo : DesignComboBase base.Cleanup(); } - public Design? Design + public IDesignStandIn? Design => CurrentSelection?.Item1; public void Draw(float width) - => Draw(Design, (Incognito ? Design?.Incognito : Design?.Name.Text) ?? string.Empty, width); + => Draw(Design, Design?.ResolveName(Incognito) ?? string.Empty, width); } public sealed class QuickDesignCombo : DesignCombo @@ -182,7 +186,7 @@ public sealed class QuickDesignCombo : DesignCombo [ .. designs.Designs .Where(d => d.QuickDesign) - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) .OrderBy(d => d.Item2), ]) => AllowMouseWheel = MouseWheelType.Unmodified; @@ -199,51 +203,35 @@ public sealed class LinkDesignCombo( : DesignCombo(log, designChanged, tabSelected, config, designColors, () => [ .. designs.Designs - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) .OrderBy(d => d.Item2), ]); -public sealed class RevertDesignCombo : DesignComboBase +public sealed class SpecialDesignCombo( + DesignManager designs, + DesignFileSystem fileSystem, + TabSelected tabSelected, + DesignColors designColors, + Logger log, + DesignChanged designChanged, + AutoDesignManager autoDesignManager, + EphemeralConfig config, + RandomDesignGenerator rng) + : DesignComboBase(() => designs.Designs + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .OrderBy(d => d.Item2) + .Prepend(new Tuple(new RandomDesign(rng), string.Empty)) + .Prepend(new Tuple(new RevertDesign(), string.Empty)) + .ToList(), log, designChanged, tabSelected, config, designColors) { - public const int RevertDesignIndex = -1228; - public readonly Design RevertDesign; - private readonly AutoDesignManager _autoDesignManager; - - public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, - ItemManager items, CustomizeService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, - EphemeralConfig config) - : this(designs, fileSystem, tabSelected, designColors, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, - config) - { } - - private RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, - Design revertDesign, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, EphemeralConfig config) - : base(() => designs.Designs - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) - .OrderBy(d => d.Item2) - .Prepend(new Tuple(revertDesign, string.Empty)) - .ToList(), log, designChanged, tabSelected, config, designColors) - { - RevertDesign = revertDesign; - _autoDesignManager = autoDesignManager; - } - public void Draw(AutoDesignSet set, AutoDesign? design, int autoDesignIndex) { - if (!Draw(design?.Design, design?.Name(Incognito), ImGui.GetContentRegionAvail().X)) + if (!Draw(design?.Design, design?.Design.ResolveName(Incognito), ImGui.GetContentRegionAvail().X)) return; if (autoDesignIndex >= 0) - _autoDesignManager.ChangeDesign(set, autoDesignIndex, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1); + autoDesignManager.ChangeDesign(set, autoDesignIndex, CurrentSelection!.Item1); else - _autoDesignManager.AddDesign(set, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1); + autoDesignManager.AddDesign(set, CurrentSelection!.Item1); } - - private static Design CreateRevertDesign(CustomizeService customize, ItemManager items) - => new(customize, items) - { - Index = RevertDesignIndex, - Name = AutoDesign.RevertName, - ApplyCustomize = CustomizeFlagExtensions.AllRelevant, - }; } diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 58bdb24..b602ee8 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -123,7 +123,7 @@ public sealed class DesignQuickBar : Window, IDisposable private void DrawApplyButton(Vector2 size) { - var design = _designCombo.Design; + var design = _designCombo.Design as Design; var available = 0; var tooltip = string.Empty; if (design == null) @@ -135,7 +135,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (_playerIdentifier.IsValid && _playerData.Valid) { available |= 1; - tooltip = $"Left-Click: Apply {(_config.Ephemeral.IncognitoMode ? design.Incognito : design.Name)} to yourself."; + tooltip = $"Left-Click: Apply {design.ResolveName(_config.Ephemeral.IncognitoMode)} to yourself."; } if (_targetIdentifier.IsValid && _targetData.Valid) @@ -143,7 +143,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (available != 0) tooltip += '\n'; available |= 2; - tooltip += $"Right-Click: Apply {(_config.Ephemeral.IncognitoMode ? design.Incognito : design.Name)} to {_targetIdentifier}."; + tooltip += $"Right-Click: Apply {design.ResolveName(_config.Ephemeral.IncognitoMode)} to {_targetIdentifier}."; } if (available == 0) @@ -157,7 +157,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (state == null && !_stateManager.GetOrCreate(id, data.Objects[0], out state)) { - Glamourer.Messager.NotificationMessage($"Could not apply {design!.Incognito} to {id.Incognito(null)}: Failed to create state."); + Glamourer.Messager.NotificationMessage($"Could not apply {design!.ResolveName(true)} to {id.Incognito(null)}: Failed to create state."); return; } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 2f4e1d8..19d6fc5 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -1,6 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Automation; +using Glamourer.Designs; using Glamourer.Interop; using Glamourer.Services; using Glamourer.Unlocks; @@ -20,7 +21,7 @@ public class SetPanel( AutoDesignManager _manager, JobService _jobs, ItemUnlockManager _itemUnlocks, - RevertDesignCombo _designCombo, + SpecialDesignCombo _designCombo, CustomizeUnlockManager _customizeUnlocks, CustomizeService _customizations, IdentifierDrawer _identifierDrawer, @@ -258,21 +259,22 @@ public class SetPanel( private void DrawWarnings(AutoDesign design) { - if (design.Revert) + if (design.Design is not DesignBase) return; var size = new Vector2(ImGui.GetFrameHeight()); size.X += ImGuiHelpers.GlobalScale; var (equipFlags, customizeFlags, _, _, _) = design.ApplyWhat(); - var sb = new StringBuilder(); + var sb = new StringBuilder(); + var designData = design.Design.GetDesignData(default); foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)) { var flag = slot.ToFlag(); if (!equipFlags.HasFlag(flag)) continue; - var item = design.Design!.DesignData.Item(slot); + var item = designData.Item(slot); if (!_itemUnlocks.IsUnlocked(item.Id, out _)) sb.AppendLine($"{item.Name} in {slot.ToName()} slot is not unlocked. Consider obtaining it via gameplay means!"); } @@ -286,8 +288,8 @@ public class SetPanel( sb.Clear(); var sb2 = new StringBuilder(); - var customize = design.Design!.DesignData.Customize; - if (!design.Design.DesignData.IsHuman) + var customize = designData.Customize; + if (!designData.IsHuman) sb.AppendLine("The base model id can not be changed automatically to something non-human."); var set = _customizations.Manager.GetSet(customize.Clan, customize.Gender); diff --git a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs index 6dd7f6e..98b7d9e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs @@ -41,7 +41,7 @@ public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IGameDataDr foreach (var (design, designIdx) in set.Designs.WithIndex()) { - ImGuiUtil.DrawTableColumn($"{design.Name(false)} ({designIdx})"); + ImGuiUtil.DrawTableColumn($"{design.Design.ResolveName(false)} ({designIdx})"); ImGuiUtil.DrawTableColumn($"{design.Type} {design.Jobs.Name}"); } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index 32800f5..f9fcaef 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -152,32 +152,33 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe ImGui.TableNextColumn(); string ttBefore, ttAfter; bool canAddBefore, canAddAfter; - if (_combo.Design == null) + var design = _combo.Design as Design; + if (design == null) { ttAfter = ttBefore = "Select a design first."; canAddBefore = canAddAfter = false; } else { - canAddBefore = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, LinkOrder.Before, out var error); + canAddBefore = LinkContainer.CanAddLink(_selector.Selected!, design, LinkOrder.Before, out var error); ttBefore = canAddBefore - ? $"Add a link at the top of the list to {_combo.Design.Name}." - : $"Can not add a link to {_combo.Design.Name}:\n{error}"; - canAddAfter = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, LinkOrder.After, out error); + ? $"Add a link at the top of the list to {design.Name}." + : $"Can not add a link to {design.Name}:\n{error}"; + canAddAfter = LinkContainer.CanAddLink(_selector.Selected!, design, LinkOrder.After, out error); ttAfter = canAddAfter - ? $"Add a link at the bottom of the list to {_combo.Design.Name}." - : $"Can not add a link to {_combo.Design.Name}:\n{error}"; + ? $"Add a link at the bottom of the list to {design.Name}." + : $"Can not add a link to {design.Name}:\n{error}"; } if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowCircleUp.ToIconString(), buttonSize, ttBefore, !canAddBefore, true)) { - _linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, LinkOrder.Before); + _linkManager.AddDesignLink(_selector.Selected!, design!, LinkOrder.Before); _linkManager.MoveDesignLink(_selector.Selected!, _selector.Selected!.Links.Before.Count - 1, LinkOrder.Before, 0, LinkOrder.Before); } ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowCircleDown.ToIconString(), buttonSize, ttAfter, !canAddAfter, true)) - _linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, LinkOrder.After); + _linkManager.AddDesignLink(_selector.Selected!, design!, LinkOrder.After); } private void DrawDragDrop(Design design, LinkOrder order, int index) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 287fca0..3ca684e 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -19,28 +19,30 @@ namespace Glamourer.Services; public class CommandService : IDisposable { + private const string RandomString = "random"; private const string MainCommandString = "/glamourer"; private const string ApplyCommandString = "/glamour"; - private readonly ICommandManager _commands; - private readonly MainWindow _mainWindow; - private readonly IChatGui _chat; - private readonly ActorManager _actors; - private readonly ObjectManager _objects; - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly AutoDesignManager _autoDesignManager; - private readonly DesignManager _designManager; - private readonly DesignConverter _converter; - private readonly DesignFileSystem _designFileSystem; - private readonly Configuration _config; - private readonly ModSettingApplier _modApplier; - private readonly ItemManager _items; + private readonly ICommandManager _commands; + private readonly MainWindow _mainWindow; + private readonly IChatGui _chat; + private readonly ActorManager _actors; + private readonly ObjectManager _objects; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly AutoDesignManager _autoDesignManager; + private readonly DesignManager _designManager; + private readonly DesignConverter _converter; + private readonly DesignFileSystem _designFileSystem; + private readonly Configuration _config; + private readonly ModSettingApplier _modApplier; + private readonly ItemManager _items; + private readonly RandomDesignGenerator _randomDesign; 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) + ItemManager items, RandomDesignGenerator randomDesign) { _commands = commands; _mainWindow = mainWindow; @@ -56,6 +58,7 @@ public class CommandService : IDisposable _config = config; _modApplier = modApplier; _items = items; + _randomDesign = randomDesign; _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); _commands.AddHandler(ApplyCommandString, @@ -394,7 +397,8 @@ public class CommandService : IDisposable if (_items.ItemData.Primary.TryGetValue(id, out var main)) items[0] = main; } - else if (_items.ItemData.Primary.FindFirst(pair => string.Equals(pair.Value.Name, split[0], StringComparison.OrdinalIgnoreCase), out var i)) + else if (_items.ItemData.Primary.FindFirst(pair => string.Equals(pair.Value.Name, split[0], StringComparison.OrdinalIgnoreCase), + out var i)) { items[0] = i.Value; } @@ -448,7 +452,8 @@ public class CommandService : IDisposable var split = arguments.Split('|', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (split.Length is not 2) { - _chat.Print(new SeStringBuilder().AddText("Use with /glamour apply ").AddYellow("[Design Name, Path or Identifier, or Clipboard]") + _chat.Print(new SeStringBuilder().AddText("Use with /glamour apply ") + .AddYellow("[Design Name, Path or Identifier, Random, or Clipboard]") .AddText(" | ") .AddGreen("[Character Identifier]") .AddText("; ") @@ -467,6 +472,13 @@ public class CommandService : IDisposable .BuiltString); _chat.Print(new SeStringBuilder() .AddText(" 》 Clipboard as a single word will try to apply a design string currently in your clipboard.").BuiltString); + _chat.Print(new SeStringBuilder() + .AddText(" 》 ").AddYellow("Random").AddText(" supports the following restrictions:").BuiltString); + _chat.Print(new SeStringBuilder() + .AddText(" 》》》 ").AddYellow("Random").AddText(", choosing a random design out of all your designs.").BuiltString); + _chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:{List of [text] or /[text]}").AddText(", containing a list of restrictions within swirly braces, separated by semicolons.").BuiltString); + _chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:[text]").AddText(", choosing a random design where the path, name or identifier contains 'text' (no brackets).").BuiltString); + _chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:/[text]").AddText(", choosing a random design where the path starts with 'text' (no brackets).").BuiltString); _chat.Print(new SeStringBuilder() .AddText(" 》 ").AddBlue("").AddText(" is optional and can be omitted (together with the ;), ").AddBlue("true") .AddText(" or ").AddBlue("false").AddText(".").BuiltString); @@ -628,30 +640,57 @@ public class CommandService : IDisposable return false; } - private bool GetDesign(string argument, [NotNullWhen(true)] out DesignBase? design, bool allowClipboard) + private bool GetDesign(string argument, [NotNullWhen(true)] out DesignBase? design, bool allowSpecial) { design = null; if (argument.Length == 0) return false; - if (allowClipboard && string.Equals("clipboard", argument, StringComparison.OrdinalIgnoreCase)) + if (allowSpecial) { - try + if (string.Equals("clipboard", argument, StringComparison.OrdinalIgnoreCase)) { - var clipboardText = ImGui.GetClipboardText(); - if (clipboardText.Length > 0) - design = _converter.FromBase64(clipboardText, true, true, out _); - } - catch - { - // ignored + try + { + var clipboardText = ImGui.GetClipboardText(); + if (clipboardText.Length > 0) + design = _converter.FromBase64(clipboardText, true, true, out _); + } + catch + { + // ignored + } + + if (design != null) + return true; + + _chat.Print(new SeStringBuilder().AddText("Your current clipboard did not contain a valid design string.").BuiltString); + return false; } - if (design != null) + if (argument.StartsWith(RandomString, StringComparison.OrdinalIgnoreCase)) + { + try + { + if (argument.Length == RandomString.Length) + design = _randomDesign.Design(); + else if (argument[RandomString.Length] == ':') + design = _randomDesign.Design(argument[(RandomString.Length + 1)..]); + if (design == null) + { + _chat.Print(new SeStringBuilder().AddText("No design matched your restrictions.").BuiltString); + return false; + } + _chat.Print($"Chose random design {((Design)design).Name}."); + } + catch (Exception ex) + { + _chat.Print(new SeStringBuilder().AddText($"Error in the restriction string: {ex.Message}").BuiltString); + return false; + } + return true; - - _chat.Print(new SeStringBuilder().AddText("Your current clipboard did not contain a valid design string.").BuiltString); - return false; + } } if (Guid.TryParse(argument, out var guid)) diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index aca333e..2d3507d 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -149,7 +149,7 @@ public static class ServiceManagerA .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Penumbra.GameData b/Penumbra.GameData index 44021b9..d0db2f1 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 44021b93e6901c84b739bbf4d1c6350f4486cdbf +Subproject commit d0db2f1fbc3ce26d0756da5118157e5fc723c62f From 2a01b328e1f6024f484731073dd173fb81347c2e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 Feb 2024 12:23:27 +0100 Subject: [PATCH 279/786] Fix redraw. --- Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index bf65a2f..9e716c1 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -95,6 +95,10 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService else if (_config.AutoRedrawEquipOnChanges) { // Only update once per frame. + var playerName = _penumbra.GetCurrentPlayerCollection(); + if (playerName == name) + return; + var currentFrame = _framework.LastUpdateUTC; if (currentFrame == _frame) return; @@ -102,9 +106,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService _frame = currentFrame; _framework.RunOnFrameworkThread(() => { - var playerName = _penumbra.GetCurrentPlayerCollection(); - if (playerName == name) - _state.ReapplyState(_objects.Player, StateSource.IpcManual); + _state.ReapplyState(_objects.Player, StateSource.IpcManual); Glamourer.Log.Debug( $"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player)."); }); From 139508917b31356c648ca06e168b69d6c432702c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 Feb 2024 14:37:23 +0100 Subject: [PATCH 280/786] Add UI stuff to random designs. --- Glamourer/Automation/AutoDesign.cs | 3 +- Glamourer/Automation/AutoDesignApplier.cs | 1 + Glamourer/Automation/AutoDesignManager.cs | 16 + Glamourer/Designs/Design.cs | 3 + Glamourer/Designs/IDesignStandIn.cs | 2 + Glamourer/Designs/RandomDesignGenerator.cs | 91 ---- .../Designs/{ => Special}/RandomDesign.cs | 27 +- .../Designs/Special/RandomDesignGenerator.cs | 37 ++ Glamourer/Designs/Special/RandomPredicate.cs | 163 +++++++ .../Designs/{ => Special}/RevertDesign.cs | 5 +- Glamourer/Events/AutomationChanged.cs | 10 +- Glamourer/Gui/DesignCombo.cs | 37 ++ Glamourer/Gui/Materials/AdvancedDyePopup.cs | 6 +- .../AutomationTab/RandomRestrictionDrawer.cs | 432 ++++++++++++++++++ Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 17 +- .../Gui/Tabs/AutomationTab/SetSelector.cs | 2 +- Glamourer/Services/CommandService.cs | 8 +- Glamourer/Services/ServiceManager.cs | 1 + 18 files changed, 744 insertions(+), 117 deletions(-) delete mode 100644 Glamourer/Designs/RandomDesignGenerator.cs rename Glamourer/Designs/{ => Special}/RandomDesign.cs (61%) create mode 100644 Glamourer/Designs/Special/RandomDesignGenerator.cs create mode 100644 Glamourer/Designs/Special/RandomPredicate.cs rename Glamourer/Designs/{ => Special}/RevertDesign.cs (87%) create mode 100644 Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 2a0ab7e..7ceea6a 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -1,4 +1,5 @@ using Glamourer.Designs; +using Glamourer.Designs.Special; using Glamourer.GameData; using Glamourer.Interop.Structs; using Newtonsoft.Json.Linq; @@ -9,8 +10,6 @@ namespace Glamourer.Automation; public class AutoDesign { - public const string RevertName = "Revert"; - public IDesignStandIn Design = new RevertDesign(); public JobGroup Jobs; public ApplicationType Type; diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 332f9ee..5cd9cf5 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -131,6 +131,7 @@ public sealed class AutoDesignApplier : IDisposable case AutomationChanged.Type.ChangedDesign: case AutomationChanged.Type.ChangedConditions: case AutomationChanged.Type.ChangedType: + case AutomationChanged.Type.ChangedData: ApplyNew(set); break; } diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 710336e..c1e361d 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -1,6 +1,7 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; +using Glamourer.Designs.Special; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Services; @@ -347,6 +348,20 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, applicationType)); } + public void ChangeData(AutoDesignSet set, int which, object data) + { + if (which >= set.Designs.Count || which < 0) + return; + + var design = set.Designs[which]; + if (!design.Design.ChangeData(data)) + return; + + Save(); + Glamourer.Log.Debug($"Changed additional design data for associated design {which + 1} in design set."); + _event.Invoke(AutomationChanged.Type.ChangedData, set, (which, data)); + } + public string ToFilename(FilenameService fileNames) => fileNames.AutomationFile; @@ -499,6 +514,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos design = d; } + design.ParseData(jObj); // ApplicationType is a migration from an older property name. diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index e01c09e..123f15a 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -81,6 +81,9 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public void ParseData(JObject _) { } + public bool ChangeData(object data) + => false; + #endregion #region Serialization diff --git a/Glamourer/Designs/IDesignStandIn.cs b/Glamourer/Designs/IDesignStandIn.cs index 33122e2..a6ee702 100644 --- a/Glamourer/Designs/IDesignStandIn.cs +++ b/Glamourer/Designs/IDesignStandIn.cs @@ -20,4 +20,6 @@ public interface IDesignStandIn : IEquatable public void AddData(JObject jObj); public void ParseData(JObject jObj); + + public bool ChangeData(object data); } diff --git a/Glamourer/Designs/RandomDesignGenerator.cs b/Glamourer/Designs/RandomDesignGenerator.cs deleted file mode 100644 index 4b8f13d..0000000 --- a/Glamourer/Designs/RandomDesignGenerator.cs +++ /dev/null @@ -1,91 +0,0 @@ -using OtterGui; -using OtterGui.Services; -using System; - -namespace Glamourer.Designs; - -public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem) : IService -{ - private readonly Random _rng = new(); - - public Design? Design(IReadOnlyList localDesigns) - { - if (localDesigns.Count == 0) - return null; - - var idx = _rng.Next(0, localDesigns.Count - 1); - Glamourer.Log.Verbose($"[Random Design] Chose design {idx} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); - return localDesigns[idx]; - } - - public Design? Design() - => Design(designs); - - public Design? Design(string restrictions) - { - if (restrictions.Length == 0) - return Design(designs); - - List> predicates = []; - - switch (restrictions[0]) - { - case '{': - var end = restrictions.IndexOf('}'); - if (end == -1) - throw new ArgumentException($"The restriction group '{restrictions}' is not properly terminated."); - - restrictions = restrictions[1..end]; - var split = restrictions.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - foreach (var item in split.Distinct()) - predicates.Add(item[0] == '/' ? CreatePredicateSlash(item) : CreatePredicate(item)); - break; - case '/': - predicates.Add(CreatePredicateSlash(restrictions)); - break; - default: - predicates.Add(CreatePredicate(restrictions)); - break; - } - - if (predicates.Count == 1) - { - var p = predicates[0]; - return Design(designs.Select(Transform).Where(t => p(t.NameLower, t.Identifier, t.PathLower)).Select(t => t.Design).ToList()); - } - - return Design(designs.Select(Transform).Where(t => predicates.Any(p => p(t.NameLower, t.Identifier, t.PathLower))).Select(t => t.Design) - .ToList()); - - (Design Design, string NameLower, string Identifier, string PathLower) Transform(Design design) - { - var name = design.Name.Lower; - var identifier = design.Identifier.ToString(); - var path = fileSystem.FindLeaf(design, out var leaf) ? leaf.FullName().ToLowerInvariant() : string.Empty; - return (design, name, identifier, path); - } - - Func CreatePredicate(string input) - { - var value = input.ToLowerInvariant(); - return (string nameLower, string identifier, string pathLower) => - { - if (nameLower.Contains(value)) - return true; - if (identifier.Contains(value)) - return true; - if (pathLower.Contains(value)) - return true; - - return false; - }; - } - - Func CreatePredicateSlash(string input) - { - var value = input[1..].ToLowerInvariant(); - return (string nameLower, string identifier, string pathLower) - => pathLower.StartsWith(value); - } - } -} diff --git a/Glamourer/Designs/RandomDesign.cs b/Glamourer/Designs/Special/RandomDesign.cs similarity index 61% rename from Glamourer/Designs/RandomDesign.cs rename to Glamourer/Designs/Special/RandomDesign.cs index 3b9ba2a..b40ba92 100644 --- a/Glamourer/Designs/RandomDesign.cs +++ b/Glamourer/Designs/Special/RandomDesign.cs @@ -3,7 +3,7 @@ using Glamourer.Interop.Material; using Glamourer.State; using Newtonsoft.Json.Linq; -namespace Glamourer.Designs; +namespace Glamourer.Designs.Special; public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn { @@ -11,14 +11,14 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn public const string ResolvedName = "Random"; private Design? _currentDesign; - public string Restrictions { get; internal set; } = string.Empty; + public IReadOnlyList Predicates { get; private set; } = []; public string ResolveName(bool _) => ResolvedName; public ref readonly DesignData GetDesignData(in DesignData baseRef) { - _currentDesign ??= rng.Design(Restrictions); + _currentDesign ??= rng.Design(Predicates); if (_currentDesign == null) return ref baseRef; @@ -27,7 +27,7 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData() { - _currentDesign ??= rng.Design(Restrictions); + _currentDesign ??= rng.Design(Predicates); if (_currentDesign == null) return []; @@ -38,7 +38,9 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn => SerializedName; public bool Equals(IDesignStandIn? other) - => other is RandomDesign r && string.Equals(r.Restrictions, Restrictions, StringComparison.OrdinalIgnoreCase); + => other is RandomDesign r + && string.Equals(RandomPredicate.GeneratePredicateString(r.Predicates), RandomPredicate.GeneratePredicateString(Predicates), + StringComparison.OrdinalIgnoreCase); public StateSource AssociatedSource() => StateSource.Manual; @@ -47,7 +49,7 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn { get { - _currentDesign = rng.Design(Restrictions); + _currentDesign = rng.Design(Predicates); if (_currentDesign == null) yield break; @@ -58,12 +60,21 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn public void AddData(JObject jObj) { - jObj["Restrictions"] = Restrictions; + jObj["Restrictions"] = RandomPredicate.GeneratePredicateString(Predicates); } public void ParseData(JObject jObj) { var restrictions = jObj["Restrictions"]?.ToObject() ?? string.Empty; - Restrictions = restrictions; + Predicates = RandomPredicate.GeneratePredicates(restrictions); + } + + public bool ChangeData(object data) + { + if (data is not List predicates) + return false; + + Predicates = predicates; + return true; } } diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs new file mode 100644 index 0000000..8b2e050 --- /dev/null +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -0,0 +1,37 @@ +using OtterGui.Services; + +namespace Glamourer.Designs.Special; + +public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem) : IService +{ + private readonly Random _rng = new(); + + public Design? Design(IReadOnlyList localDesigns) + { + if (localDesigns.Count == 0) + return null; + + var idx = _rng.Next(0, localDesigns.Count - 1); + Glamourer.Log.Verbose($"[Random Design] Chose design {idx} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); + return localDesigns[idx]; + } + + public Design? Design() + => Design(designs); + + public Design? Design(IDesignPredicate predicate) + => Design(predicate.Get(designs, fileSystem).ToList()); + + public Design? Design(IReadOnlyList predicates) + { + if (predicates.Count == 0) + return Design(); + if (predicates.Count == 1) + return Design(predicates[0]); + + return Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList()); + } + + public Design? Design(string restrictions) + => Design(RandomPredicate.GeneratePredicates(restrictions)); +} diff --git a/Glamourer/Designs/Special/RandomPredicate.cs b/Glamourer/Designs/Special/RandomPredicate.cs new file mode 100644 index 0000000..efb3233 --- /dev/null +++ b/Glamourer/Designs/Special/RandomPredicate.cs @@ -0,0 +1,163 @@ +using OtterGui.Classes; + +namespace Glamourer.Designs.Special; + +public interface IDesignPredicate +{ + public bool Invoke(Design design, string lowerName, string identifier, string lowerPath); + + public bool Invoke((Design Design, string LowerName, string Identifier, string LowerPath) args) + => Invoke(args.Design, args.LowerName, args.Identifier, args.LowerPath); + + public IEnumerable Get(IEnumerable designs, DesignFileSystem fileSystem) + => designs.Select(d => Transform(d, fileSystem)) + .Where(Invoke) + .Select(t => t.Design); + + public static IEnumerable Get(IReadOnlyList predicates, IEnumerable designs, DesignFileSystem fileSystem) + => predicates.Count > 0 + ? designs.Select(d => Transform(d, fileSystem)) + .Where(t => predicates.Any(p => p.Invoke(t))) + .Select(t => t.Design) + : designs; + + private static (Design Design, string LowerName, string Identifier, string LowerPath) Transform(Design d, DesignFileSystem fs) + => (d, d.Name.Lower, d.Identifier.ToString(), fs.FindLeaf(d, out var l) ? l.FullName().ToLowerInvariant() : string.Empty); +} + +public static class RandomPredicate +{ + public readonly struct StartsWith(string value) : IDesignPredicate + { + public LowerString Value { get; } = value; + + public bool Invoke(Design design, string lowerName, string identifier, string lowerPath) + => lowerPath.StartsWith(Value.Lower); + + public override string ToString() + => $"/{Value.Text}"; + } + + public readonly struct Contains(string value) : IDesignPredicate + { + public LowerString Value { get; } = value; + + public bool Invoke(Design design, string lowerName, string identifier, string lowerPath) + { + if (lowerName.Contains(Value.Lower)) + return true; + if (identifier.Contains(Value.Lower)) + return true; + if (lowerPath.Contains(Value.Lower)) + return true; + + return false; + } + + public override string ToString() + => Value.Text; + } + + public readonly struct Exact(Exact.Type type, string value) : IDesignPredicate + { + public enum Type : byte + { + Name, + Path, + Identifier, + Tag, + Color, + } + + public Type Which { get; } = type; + public LowerString Value { get; } = value; + + public bool Invoke(Design design, string lowerName, string identifier, string lowerPath) + => Which switch + { + Type.Name => lowerName == Value.Lower, + Type.Path => lowerPath == Value.Lower, + Type.Identifier => identifier == Value.Lower, + Type.Tag => IsContained(Value, design.Tags), + Type.Color => design.Color == Value, + _ => false, + }; + + private static bool IsContained(LowerString value, IEnumerable data) + => data.Any(t => t == value); + + public override string ToString() + => $"\"{Which switch { Type.Name => 'n', Type.Identifier => 'i', Type.Path => 'p', Type.Tag => 't', Type.Color => 'c', _ => '?' }}?{Value.Text}\""; + } + + public static IDesignPredicate CreateSinglePredicate(string restriction) + { + switch (restriction[0]) + { + case '/': return new StartsWith(restriction[1..]); + case '"': + var end = restriction.IndexOf('"', 1); + if (end < 3) + return new Contains(restriction); + + switch (restriction[1], restriction[2]) + { + case ('n', '?'): + case ('N', '?'): + return new Exact(Exact.Type.Name, restriction[3..end]); + case ('p', '?'): + case ('P', '?'): + return new Exact(Exact.Type.Path, restriction[3..end]); + case ('i', '?'): + case ('I', '?'): + return new Exact(Exact.Type.Identifier, restriction[3..end]); + case ('t', '?'): + case ('T', '?'): + return new Exact(Exact.Type.Tag, restriction[3..end]); + case ('c', '?'): + case ('C', '?'): + return new Exact(Exact.Type.Color, restriction[3..end]); + default: return new Contains(restriction); + } + default: return new Contains(restriction); + } + } + + public static List GeneratePredicates(string restrictions) + { + if (restrictions.Length == 0) + return []; + + List predicates = new(1); + if (restrictions[0] is '{') + { + var end = restrictions.IndexOf('}'); + if (end == -1) + { + predicates.Add(CreateSinglePredicate(restrictions)); + } + else + { + restrictions = restrictions[1..end]; + var split = restrictions.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + predicates.AddRange(split.Distinct().Select(CreateSinglePredicate)); + } + } + else + { + predicates.Add(CreateSinglePredicate(restrictions)); + } + + return predicates; + } + + public static string GeneratePredicateString(IReadOnlyCollection predicates) + { + if (predicates.Count == 0) + return string.Empty; + if (predicates.Count == 1) + return predicates.First()!.ToString()!; + + return $"{{{string.Join("; ", predicates)}}}"; + } +} diff --git a/Glamourer/Designs/RevertDesign.cs b/Glamourer/Designs/Special/RevertDesign.cs similarity index 87% rename from Glamourer/Designs/RevertDesign.cs rename to Glamourer/Designs/Special/RevertDesign.cs index 28a490e..0f0207b 100644 --- a/Glamourer/Designs/RevertDesign.cs +++ b/Glamourer/Designs/Special/RevertDesign.cs @@ -3,7 +3,7 @@ using Glamourer.Interop.Material; using Glamourer.State; using Newtonsoft.Json.Linq; -namespace Glamourer.Designs; +namespace Glamourer.Designs.Special; public class RevertDesign : IDesignStandIn { @@ -38,4 +38,7 @@ public class RevertDesign : IDesignStandIn public void ParseData(JObject jObj) { } + + public bool ChangeData(object data) + => false; } diff --git a/Glamourer/Events/AutomationChanged.cs b/Glamourer/Events/AutomationChanged.cs index 1d45084..26f799a 100644 --- a/Glamourer/Events/AutomationChanged.cs +++ b/Glamourer/Events/AutomationChanged.cs @@ -46,7 +46,7 @@ public sealed class AutomationChanged() /// Move a given associated design in the list of a given set. Additional data is the index that got moved and the index it got moved to [(int, int)]. MovedDesign, - /// Change the linked design in an associated design for a given set. Additional data is the index of the changed associated design, the old linked design and the new linked design [(int, Design, Design)]. + /// Change the linked design in an associated design for a given set. Additional data is the index of the changed associated design, the old linked design and the new linked design [(int, IDesignStandIn, IDesignStandIn)]. ChangedDesign, /// Change the job condition in an associated design for a given set. Additional data is the index of the changed associated design, the old job group and the new job group [(int, JobGroup, JobGroup)]. @@ -54,6 +54,9 @@ public sealed class AutomationChanged() /// Change the application type in an associated design for a given set. Additional data is the index of the changed associated design, the old type and the new type. [(int, AutoDesign.Type, AutoDesign.Type)]. ChangedType, + + /// Change the additional data for a specific design type. Additional data is the index of the changed associated design and the new data. [(int, object)] + ChangedData, } public enum Priority @@ -62,6 +65,9 @@ public sealed class AutomationChanged() SetSelector = 0, /// - AutoDesignApplier, + AutoDesignApplier = 0, + + /// + RandomRestrictionDrawer = -1, } } diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index f497f79..a4bfadb 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -2,6 +2,7 @@ using Dalamud.Interface.Utility.Raii; using Glamourer.Automation; using Glamourer.Designs; +using Glamourer.Designs.Special; using Glamourer.Events; using ImGuiNET; using OtterGui; @@ -207,6 +208,42 @@ public sealed class LinkDesignCombo( .OrderBy(d => d.Item2), ]); +public sealed class RandomDesignCombo( + DesignManager designs, + DesignFileSystem fileSystem, + Logger log, + DesignChanged designChanged, + TabSelected tabSelected, + EphemeralConfig config, + DesignColors designColors) + : DesignCombo(log, designChanged, tabSelected, config, designColors, () => + [ + .. designs.Designs + .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .OrderBy(d => d.Item2), + ]) +{ + private Design? GetDesign(RandomPredicate.Exact exact) + { + return exact.Which switch + { + RandomPredicate.Exact.Type.Name => designs.Designs.FirstOrDefault(d => d.Name == exact.Value), + RandomPredicate.Exact.Type.Path => fileSystem.Find(exact.Value.Text, out var c) && c is DesignFileSystem.Leaf l ? l.Value : null, + RandomPredicate.Exact.Type.Identifier => designs.Designs.ByIdentifier(Guid.TryParse(exact.Value.Text, out var g) ? g : Guid.Empty), + _ => null, + }; + } + + public bool Draw(RandomPredicate.Exact exact, float width) + { + var design = GetDesign(exact); + return Draw(design, design?.ResolveName(Incognito) ?? $"Not Found [{exact.Value.Text}]", width); + } + + public bool Draw(IDesignStandIn? design, float width) + => Draw(design, design?.ResolveName(Incognito) ?? string.Empty, width); +} + public sealed class SpecialDesignCombo( DesignManager designs, DesignFileSystem fileSystem, diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index f863e1f..36e4320 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -158,16 +158,14 @@ public sealed unsafe class AdvancedDyePopup( if (config.KeepAdvancedDyesAttached) { var position = ImGui.GetWindowPos(); - position.X += ImGui.GetWindowSize().X; + position.X += ImGui.GetWindowSize().X + ImGui.GetStyle().WindowPadding.X; ImGui.SetNextWindowPos(position); flags |= ImGuiWindowFlags.NoMove; } - var size = new Vector2(7 * ImGui.GetFrameHeight() + 3 * ImGui.GetStyle().ItemInnerSpacing.X + 300 * ImGuiHelpers.GlobalScale, - 18 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + ImGui.GetStyle().ItemSpacing.Y); + 18 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + 2 * ImGui.GetStyle().ItemSpacing.Y); ImGui.SetNextWindowSize(size); - var window = ImGui.Begin("###Glamourer Advanced Dyes", flags); try { diff --git a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs new file mode 100644 index 0000000..f125f36 --- /dev/null +++ b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs @@ -0,0 +1,432 @@ +using Dalamud.Interface; +using Dalamud.Interface.Utility; +using Glamourer.Automation; +using Glamourer.Designs; +using Glamourer.Designs.Special; +using Glamourer.Events; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; + +namespace Glamourer.Gui.Tabs.AutomationTab; + +public sealed class RandomRestrictionDrawer : IService, IDisposable +{ + private AutoDesignSet? _set; + private int _designIndex = -1; + + private readonly AutomationChanged _automationChanged; + private readonly Configuration _config; + private readonly AutoDesignManager _autoDesignManager; + private readonly RandomDesignCombo _randomDesignCombo; + private readonly SetSelector _selector; + private readonly DesignStorage _designs; + private readonly DesignFileSystem _designFileSystem; + + private string _newText = string.Empty; + private string? _newDefinition; + private Design? _newDesign = null; + + public RandomRestrictionDrawer(AutomationChanged automationChanged, Configuration config, AutoDesignManager autoDesignManager, + RandomDesignCombo randomDesignCombo, SetSelector selector, DesignFileSystem designFileSystem, DesignStorage designs) + { + _automationChanged = automationChanged; + _config = config; + _autoDesignManager = autoDesignManager; + _randomDesignCombo = randomDesignCombo; + _selector = selector; + _designFileSystem = designFileSystem; + _designs = designs; + _automationChanged.Subscribe(OnAutomationChange, AutomationChanged.Priority.RandomRestrictionDrawer); + } + + public void Dispose() + { + _automationChanged.Unsubscribe(OnAutomationChange); + } + + public void DrawButton(AutoDesignSet set, int designIndex) + { + var isOpen = set == _set && designIndex == _designIndex; + using (var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen) + .Push(ImGuiCol.Text, ColorId.HeaderButtons.Value(), isOpen) + .Push(ImGuiCol.Border, ColorId.HeaderButtons.Value(), isOpen)) + { + using var frame = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, isOpen); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Edit.ToIconString(), new Vector2(ImGui.GetFrameHeight()), + string.Empty, false, true)) + { + if (isOpen) + Close(); + else + Open(set, designIndex); + } + } + + ImGuiUtil.HoverTooltip("Edit restrictions for this random design."); + } + + private void Open(AutoDesignSet set, int designIndex) + { + if (designIndex < 0 || designIndex >= set.Designs.Count) + return; + + var design = set.Designs[designIndex]; + if (design.Design is not RandomDesign) + return; + + _set = set; + _designIndex = designIndex; + } + + private void Close() + { + _set = null; + _designIndex = -1; + } + + public void Draw() + { + if (_set == null || _designIndex < 0 || _designIndex >= _set.Designs.Count) + return; + + if (_set != _selector.Selection) + { + Close(); + return; + } + + var design = _set.Designs[_designIndex]; + if (design.Design is not RandomDesign random) + return; + + DrawWindow(random); + } + + private void DrawWindow(RandomDesign random) + { + var flags = ImGuiWindowFlags.NoFocusOnAppearing + | ImGuiWindowFlags.NoCollapse + | ImGuiWindowFlags.NoResize; + + // Set position to the right of the main window when attached + // The downwards offset is implicit through child position. + if (_config.KeepAdvancedDyesAttached) + { + var position = ImGui.GetWindowPos(); + position.X += ImGui.GetWindowSize().X + ImGui.GetStyle().WindowPadding.X; + ImGui.SetNextWindowPos(position); + flags |= ImGuiWindowFlags.NoMove; + } + + using var color = ImRaii.PushColor(ImGuiCol.TitleBgActive, ImGui.GetColorU32(ImGuiCol.TitleBg)); + + var size = new Vector2(7 * ImGui.GetFrameHeight() + 3 * ImGui.GetStyle().ItemInnerSpacing.X + 300 * ImGuiHelpers.GlobalScale, + 18 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + ImGui.GetStyle().ItemSpacing.Y); + ImGui.SetNextWindowSize(size); + + var open = true; + var window = ImGui.Begin($"{_set!.Name} #{_designIndex + 1:D2}###Glamourer Random Design", ref open, flags); + try + { + if (window) + DrawContent(random); + } + finally + { + ImGui.End(); + } + + if (!open) + Close(); + } + + private void DrawTable(RandomDesign random, List list) + { + using var table = ImRaii.Table("##table", 3); + if (!table) + return; + + using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing); + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + var descWidth = ImGui.CalcTextSize("or that are set to the color").X; + ImGui.TableSetupColumn("desc", ImGuiTableColumnFlags.WidthFixed, descWidth); + ImGui.TableSetupColumn("input", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn("del", ImGuiTableColumnFlags.WidthFixed, buttonSize.X * 2 + ImGui.GetStyle().ItemInnerSpacing.X); + + var orSize = ImGui.CalcTextSize("or "); + for (var i = 0; i < random.Predicates.Count; ++i) + { + using var id = ImRaii.PushId(i); + var predicate = random.Predicates[i]; + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + if (i != 0) + ImGui.TextUnformatted("or "); + else + ImGui.Dummy(orSize); + ImGui.SameLine(0, 0); + ImGui.AlignTextToFramePadding(); + switch (predicate) + { + case RandomPredicate.Contains contains: + { + ImGui.TextUnformatted("that contain"); + ImGui.TableNextColumn(); + var data = contains.Value.Text; + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputTextWithHint("##match", "Name, Path, or Identifier Contains...", ref data, 128)) + { + if (data.Length == 0) + list.RemoveAt(i); + else + list[i] = new RandomPredicate.Contains(data); + _autoDesignManager.ChangeData(_set!, _designIndex, list); + } + + break; + } + case RandomPredicate.StartsWith startsWith: + { + ImGui.TextUnformatted("whose path starts with"); + ImGui.TableNextColumn(); + var data = startsWith.Value.Text; + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputTextWithHint("##startsWith", "Path Starts With...", ref data, 128)) + { + if (data.Length == 0) + list.RemoveAt(i); + else + list[i] = new RandomPredicate.StartsWith(data); + _autoDesignManager.ChangeData(_set!, _designIndex, list); + } + + break; + } + case RandomPredicate.Exact { Which: RandomPredicate.Exact.Type.Tag } exact: + { + ImGui.TextUnformatted("that contain the tag"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + var data = exact.Value.Text; + if (ImGui.InputTextWithHint("##color", "Contained tag...", ref data, 128)) + { + if (data.Length == 0) + list.RemoveAt(i); + else + list[i] = new RandomPredicate.Exact(RandomPredicate.Exact.Type.Tag, data); + _autoDesignManager.ChangeData(_set!, _designIndex, list); + } + + break; + } + case RandomPredicate.Exact { Which: RandomPredicate.Exact.Type.Color } exact: + { + ImGui.TextUnformatted("that are set to the color"); + ImGui.TableNextColumn(); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + var data = exact.Value.Text; + if (ImGui.InputTextWithHint("##color", "Assigned Color is...", ref data, 128)) + { + if (data.Length == 0) + list.RemoveAt(i); + else + list[i] = new RandomPredicate.Exact(RandomPredicate.Exact.Type.Color, data); + _autoDesignManager.ChangeData(_set!, _designIndex, list); + } + + break; + } + case RandomPredicate.Exact exact: + { + ImGui.TextUnformatted("that are exactly"); + ImGui.TableNextColumn(); + if (_randomDesignCombo.Draw(exact, ImGui.GetContentRegionAvail().X) && _randomDesignCombo.Design is Design d) + { + list[i] = new RandomPredicate.Exact(RandomPredicate.Exact.Type.Identifier, d.Identifier.ToString()); + _autoDesignManager.ChangeData(_set!, _designIndex, list); + } + + break; + } + } + + ImGui.TableNextColumn(); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this restriction.", false, true)) + { + list.RemoveAt(i); + _autoDesignManager.ChangeData(_set!, _designIndex, list); + } + + ImGui.SameLine(); + DrawLookup(predicate, buttonSize); + } + } + + private void DrawLookup(IDesignPredicate predicate, Vector2 buttonSize) + { + ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.MagnifyingGlassChart.ToIconString(), buttonSize, string.Empty, false, true); + if (!ImGui.IsItemHovered()) + return; + + var designs = predicate.Get(_designs, _designFileSystem); + LookupTooltip(designs); + } + + private void LookupTooltip(IEnumerable designs) + { + using var _ = ImRaii.Tooltip(); + var tt = string.Join('\n', designs.Select(d => _designFileSystem.FindLeaf(d, out var l) ? l.FullName() : d.Name.Text).OrderBy(t => t)); + ImGui.TextUnformatted(tt.Length == 0 + ? "Matches no currently existing designs." + : "Matches the following designs:"); + ImGui.Separator(); + ImGui.TextUnformatted(tt); + } + + private void DrawNewButtons(List list) + { + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + ImGui.InputTextWithHint("##newText", "Add New Restriction...", ref _newText, 128); + var spacing = ImGui.GetStyle().ItemInnerSpacing.X; + var invalid = _newText.Length == 0; + + var buttonSize = new Vector2((ImGui.GetContentRegionAvail().X - 3 * spacing) / 4, 0); + var changed = ImGuiUtil.DrawDisabledButton("Starts With", buttonSize, + "Add a new condition that design paths must start with the given text.", invalid) + && Add(new RandomPredicate.StartsWith(_newText)); + + ImGui.SameLine(0, spacing); + changed |= ImGuiUtil.DrawDisabledButton("Contains", buttonSize, + "Add a new condition that design paths, names or identifiers must contain the given text.", invalid) + && Add(new RandomPredicate.Contains(_newText)); + + ImGui.SameLine(0, spacing); + changed |= ImGuiUtil.DrawDisabledButton("Has Tag", buttonSize, + "Add a new condition that the design must contain the given tag.", invalid) + && Add(new RandomPredicate.Exact(RandomPredicate.Exact.Type.Tag, _newText)); + + ImGui.SameLine(0, spacing); + changed |= ImGuiUtil.DrawDisabledButton("Assigned Color", buttonSize, + "Add a new condition that the design must be assigned to the given color.", invalid) + && Add(new RandomPredicate.Exact(RandomPredicate.Exact.Type.Color, _newText)); + + if (_randomDesignCombo.Draw(_newDesign, ImGui.GetContentRegionAvail().X - spacing - buttonSize.X)) + _newDesign = _randomDesignCombo.CurrentSelection?.Item1 as Design; + ImGui.SameLine(0, spacing); + if (ImGuiUtil.DrawDisabledButton("Exact Design", buttonSize, "Add a single, specific design.", _newDesign == null)) + { + Add(new RandomPredicate.Exact(RandomPredicate.Exact.Type.Identifier, _newDesign!.Identifier.ToString())); + changed = true; + _newDesign = null; + } + + if (changed) + _autoDesignManager.ChangeData(_set!, _designIndex, list); + + return; + + bool Add(IDesignPredicate predicate) + { + list.Add(predicate); + return true; + } + } + + private void DrawManualInput(IReadOnlyList list) + { + ImGui.Dummy(Vector2.Zero); + ImGui.Separator(); + ImGui.Dummy(Vector2.Zero); + DrawTotalPreview(list); + var currentDefinition = RandomPredicate.GeneratePredicateString(list); + var definition = _newDefinition ?? currentDefinition; + definition = definition.Replace(";", ";\n\t").Replace("{", "{\n\t").Replace("}", "\n}"); + var lines = definition.Count(c => c is '\n'); + if (ImGui.InputTextMultiline("##definition", ref definition, 2000, + new Vector2(ImGui.GetContentRegionAvail().X, (lines + 1) * ImGui.GetTextLineHeight() + ImGui.GetFrameHeight()), + ImGuiInputTextFlags.CtrlEnterForNewLine)) + _newDefinition = definition; + if (ImGui.IsItemDeactivatedAfterEdit() && _newDefinition != null && _newDefinition != currentDefinition) + { + var predicates = RandomPredicate.GeneratePredicates(_newDefinition.Replace("\n", string.Empty).Replace("\t", string.Empty)); + _autoDesignManager.ChangeData(_set!, _designIndex, predicates); + _newDefinition = null; + } + + if (ImGui.Button("Copy to Clipboard Without Line Breaks", new Vector2(ImGui.GetContentRegionAvail().X, 0))) + { + try + { + ImGui.SetClipboardText(currentDefinition); + } + catch + { + // ignored + } + } + } + + private void DrawTotalPreview(IReadOnlyList list) + { + var designs = IDesignPredicate.Get(list, _designs, _designFileSystem).ToList(); + var button = designs.Count > 0 + ? $"All Restrictions Combined Match {designs.Count} Designs" + : "None of the Restrictions Matches Any Designs"; + ImGuiUtil.DrawDisabledButton(button, new Vector2(ImGui.GetContentRegionAvail().X, 0), + string.Empty, false, false); + if (ImGui.IsItemHovered()) + LookupTooltip(designs); + } + + private void DrawContent(RandomDesign random) + { + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ImGui.GetStyle().WindowPadding.Y + ImGuiHelpers.GlobalScale); + ImGui.Separator(); + ImGui.Dummy(Vector2.Zero); + + var list = random.Predicates.ToList(); + if (list.Count == 0) + { + ImGui.TextUnformatted("No Restrictions Set. Selects among all existing Designs."); + } + else + { + ImGui.TextUnformatted("Select among designs..."); + DrawTable(random, list); + } + + ImGui.Dummy(Vector2.Zero); + ImGui.Separator(); + ImGui.Dummy(Vector2.Zero); + + DrawNewButtons(list); + DrawManualInput(list); + } + + private void OnAutomationChange(AutomationChanged.Type type, AutoDesignSet? set, object? data) + { + if (set != _set || _set == null) + return; + + switch (type) + { + case AutomationChanged.Type.DeletedSet: + case AutomationChanged.Type.DeletedDesign when data is int index && _designIndex == index: + Close(); + break; + case AutomationChanged.Type.MovedDesign when data is (int from, int to): + if (_designIndex == from) + _designIndex = to; + else if (_designIndex < from && _designIndex > to) + _designIndex++; + else if (_designIndex > to && _designIndex < from) + _designIndex--; + break; + case AutomationChanged.Type.ChangedDesign when data is (int index, IDesignStandIn _, IDesignStandIn _) && index == _designIndex: + Close(); + break; + } + } +} diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 19d6fc5..bdf7861 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -2,6 +2,7 @@ using Dalamud.Interface.Utility; using Glamourer.Automation; using Glamourer.Designs; +using Glamourer.Designs.Special; using Glamourer.Interop; using Glamourer.Services; using Glamourer.Unlocks; @@ -25,7 +26,8 @@ public class SetPanel( CustomizeUnlockManager _customizeUnlocks, CustomizeService _customizations, IdentifierDrawer _identifierDrawer, - Configuration _config) + Configuration _config, + RandomRestrictionDrawer _randomDrawer) { private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log); @@ -115,6 +117,7 @@ public class SetPanel( ImGui.Separator(); ImGui.Dummy(Vector2.Zero); DrawDesignTable(); + _randomDrawer.Draw(); } @@ -190,6 +193,7 @@ public class SetPanel( ImGui.Selectable($"#{idx + 1:D2}"); DrawDragDrop(Selection, idx); ImGui.TableNextColumn(); + DrawRandomEditing(Selection, design, idx); _designCombo.Draw(Selection, design, idx); DrawDragDrop(Selection, idx); if (singleRow) @@ -257,6 +261,15 @@ public class SetPanel( } } + private void DrawRandomEditing(AutoDesignSet set, AutoDesign design, int designIdx) + { + if (design.Design is not RandomDesign) + return; + + _randomDrawer.DrawButton(set, designIdx); + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + } + private void DrawWarnings(AutoDesign design) { if (design.Design is not DesignBase) @@ -266,7 +279,7 @@ public class SetPanel( size.X += ImGuiHelpers.GlobalScale; var (equipFlags, customizeFlags, _, _, _) = design.ApplyWhat(); - var sb = new StringBuilder(); + var sb = new StringBuilder(); var designData = design.Design.GetDesignData(default); foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)) { diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 4e63361..950b735 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -290,7 +290,7 @@ public class SetSelector : IDisposable id = _actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, $"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true)) - _manager.AddDesignSet("New Design", id); + _manager.AddDesignSet("New Automation Set", id); } private void DuplicateSetButton(Vector2 size) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 3ca684e..f9f5636 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -3,6 +3,7 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Services; using Glamourer.Automation; using Glamourer.Designs; +using Glamourer.Designs.Special; using Glamourer.Gui; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -473,12 +474,7 @@ public class CommandService : IDisposable _chat.Print(new SeStringBuilder() .AddText(" 》 Clipboard as a single word will try to apply a design string currently in your clipboard.").BuiltString); _chat.Print(new SeStringBuilder() - .AddText(" 》 ").AddYellow("Random").AddText(" supports the following restrictions:").BuiltString); - _chat.Print(new SeStringBuilder() - .AddText(" 》》》 ").AddYellow("Random").AddText(", choosing a random design out of all your designs.").BuiltString); - _chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:{List of [text] or /[text]}").AddText(", containing a list of restrictions within swirly braces, separated by semicolons.").BuiltString); - _chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:[text]").AddText(", choosing a random design where the path, name or identifier contains 'text' (no brackets).").BuiltString); - _chat.Print(new SeStringBuilder().AddText(" 》》》 ").AddYellow("Random:/[text]").AddText(", choosing a random design where the path starts with 'text' (no brackets).").BuiltString); + .AddText(" 》 ").AddYellow("Random").AddText(" supports many restrictions, see the Restriction Builder when adding a Random design to Automations for valid strings.").BuiltString); _chat.Print(new SeStringBuilder() .AddText(" 》 ").AddBlue("").AddText(" is optional and can be omitted (together with the ;), ").AddBlue("true") .AddText(" or ").AddBlue("false").AddText(".").BuiltString); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 2d3507d..24a3902 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -149,6 +149,7 @@ public static class ServiceManagerA .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() From 7f04cb7c45e4e6bd83769188f2f06d89799fe9c6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 Feb 2024 16:39:23 +0100 Subject: [PATCH 281/786] Changelog. --- Glamourer/Gui/GlamourerChangelog.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 7c1d188..dc7d445 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -80,6 +80,7 @@ public class GlamourerChangelog "Changed Item Customizations in Penumbra can now be right-clicked to preview them on your character, if you have the correct Gender/Race combo on them.") .RegisterHighlight( "Add the option to override associated collections for characters, so that automatically applied mod associations affect the overriden collection.") + .RegisterHighlight("Added the option to apply random designs (with optional restrictions) to characters via slash commands and automation.") .RegisterEntry("Added copy/paste buttons for advanced customization colors.") .RegisterEntry("Added alpha preview to advanced customization colors.") .RegisterEntry("Added a button to update the settings for an associated mod from their current settings.") From 62aa9cabbe3f1fef4aea0ca27d589b96620b3b00 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 29 Feb 2024 15:57:24 +0000 Subject: [PATCH 282/786] [CI] Updating repo.json for testing_1.1.0.18 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 63c0d2a..1c55754 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.16", + "TestingAssemblyVersion": "1.1.0.18", "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.1.0.4/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.16/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.18/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c5211ab77969eb39a8a7679ddedadebeba498cf5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 Feb 2024 18:43:44 +0100 Subject: [PATCH 283/786] Fix inverted logic. --- Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index 9e716c1..c018113 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -96,7 +96,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService { // Only update once per frame. var playerName = _penumbra.GetCurrentPlayerCollection(); - if (playerName == name) + if (playerName != name) return; var currentFrame = _framework.LastUpdateUTC; From 8496f86b6b8998dd340f859a3219a7382b22d02b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Mar 2024 13:25:15 +0100 Subject: [PATCH 284/786] Fix gear set changes and item move events not being instantiated. --- Glamourer/Interop/InventoryService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 63abc9d..c5689fa 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -3,13 +3,14 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Events; +using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; namespace Glamourer.Interop; -public unsafe class InventoryService : IDisposable +public unsafe class InventoryService : IDisposable, IRequiredService { private readonly MovedEquipment _movedItemsEvent; private readonly EquippedGearset _gearsetEvent; From 436a975a44826e8ae0265f836ebe5ded3884ece2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Mar 2024 14:29:23 +0100 Subject: [PATCH 285/786] Fix advanced dye buttons appearing in design panel. --- Glamourer/Gui/Equipment/EquipDrawData.cs | 6 ++++++ Glamourer/Gui/Equipment/EquipmentDrawer.cs | 12 ++++++------ Glamourer/Interop/InventoryService.cs | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 67c6a9e..e6b5d0d 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -14,6 +14,12 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) public bool DisplayApplication; public bool AllowRevert; + public readonly bool IsDesign + => _object is Design; + + public readonly bool IsState + => _object is ActorState; + public readonly void SetItem(EquipItem item) => _editor.ChangeItem(_object, Slot, item, ApplySettings.Manual); diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 0e47839..53c3d3e 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -285,7 +285,7 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(equipDrawData); } - else + else if (equipDrawData.IsState) { _advancedDyes.DrawButton(equipDrawData.Slot); } @@ -309,7 +309,7 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(mainhand); } - else + else if (mainhand.IsState) { _advancedDyes.DrawButton(EquipSlot.MainHand); } @@ -331,7 +331,7 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(offhand); } - else + else if (offhand.IsState) { _advancedDyes.DrawButton(EquipSlot.OffHand); } @@ -365,7 +365,7 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(equipDrawData); } - else + else if (equipDrawData.IsState) { _advancedDyes.DrawButton(equipDrawData.Slot); } @@ -402,7 +402,7 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(mainhand); } - else + else if (mainhand.IsState) { _advancedDyes.DrawButton(EquipSlot.MainHand); } @@ -432,7 +432,7 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(offhand); } - else + else if (mainhand.IsState) { _advancedDyes.DrawButton(EquipSlot.OffHand); } diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index c5689fa..6871ed9 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -10,7 +10,7 @@ using Penumbra.String; namespace Glamourer.Interop; -public unsafe class InventoryService : IDisposable, IRequiredService +public sealed unsafe class InventoryService : IDisposable, IRequiredService { private readonly MovedEquipment _movedItemsEvent; private readonly EquippedGearset _gearsetEvent; From ed6f32f757b6f84ff375745214ce141d9daedea7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Mar 2024 14:29:36 +0100 Subject: [PATCH 286/786] Improve QDB. --- Glamourer/Configuration.cs | 85 +++++----- Glamourer/Gui/DesignQuickBar.cs | 151 +++++++++++++++--- Glamourer/Gui/GlamourerWindowSystem.cs | 1 + Glamourer/Gui/MainWindow.cs | 4 - Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 59 ++++++- Glamourer/Services/ConfigMigrationService.cs | 32 ++-- Glamourer/State/StateManager.cs | 54 ++++++- 7 files changed, 299 insertions(+), 87 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 36e12f5..d7b9ec1 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -18,35 +18,38 @@ 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 UseAdvancedParameters { get; set; } = true; - public bool UseAdvancedDyes { get; set; } = true; - public bool KeepAdvancedDyesAttached { get; set; } = true; - public bool ShowRevertAdvancedParametersButton { 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 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 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; [JsonConverter(typeof(SortModeConverter))] [JsonProperty(Order = int.MaxValue)] @@ -78,15 +81,8 @@ public class Configuration : IPluginConfiguration, ISavable public void Save() => _saveService.DelaySave(this); - public void Load(ConfigMigrationService migrator) + private void Load(ConfigMigrationService migrator) { - static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs) - { - Glamourer.Log.Error( - $"Error parsing Configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}"); - errorArgs.ErrorContext.Handled = true; - } - if (!File.Exists(_saveService.FileNames.ConfigFile)) return; @@ -107,6 +103,14 @@ public class Configuration : IPluginConfiguration, ISavable } migrator.Migrate(this); + return; + + static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs) + { + Glamourer.Log.Error( + $"Error parsing Configuration at {errorArgs.ErrorContext.Path}, using default or migrating:\n{errorArgs.ErrorContext.Error}"); + errorArgs.ErrorContext.Handled = true; + } } public string ToFilename(FilenameService fileNames) @@ -114,14 +118,15 @@ public class Configuration : IPluginConfiguration, ISavable public void Save(StreamWriter writer) { - using var jWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }; - var serializer = new JsonSerializer { Formatting = Formatting.Indented }; + using var jWriter = new JsonTextWriter(writer); + jWriter.Formatting = Formatting.Indented; + var serializer = new JsonSerializer { Formatting = Formatting.Indented }; serializer.Serialize(jWriter, this); } public static class Constants { - public const int CurrentVersion = 5; + public const int CurrentVersion = 6; public static readonly ISortMode[] ValidSortModes = { diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index b602ee8..35f23bb 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -16,6 +16,17 @@ using Penumbra.GameData.Actors; namespace Glamourer.Gui; +[Flags] +public enum QdbButtons +{ + ApplyDesign = 0x01, + RevertAll = 0x02, + RevertAutomation = 0x04, + RevertAdvanced = 0x08, + RevertEquip = 0x10, + RevertCustomize = 0x20, +} + public sealed class DesignQuickBar : Window, IDisposable { private ImGuiWindowFlags GetFlags @@ -55,7 +66,7 @@ public sealed class DesignQuickBar : Window, IDisposable public override void PreOpenCheck() { CheckHotkeys(); - IsOpen = _config.Ephemeral.ShowDesignQuickBar; + IsOpen = _config.Ephemeral.ShowDesignQuickBar && _config.QdbButtons != 0; } public override void PreDraw() @@ -93,15 +104,20 @@ public sealed class DesignQuickBar : Window, IDisposable var spacing = ImGui.GetStyle().ItemInnerSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); var buttonSize = new Vector2(ImGui.GetFrameHeight()); - var comboSize = width - _numButtons * (buttonSize.X + spacing.X); - _designCombo.Draw(comboSize); PrepareButtons(); - ImGui.SameLine(); - DrawApplyButton(buttonSize); - ImGui.SameLine(); + if (_config.QdbButtons.HasFlag(QdbButtons.ApplyDesign)) + { + var comboSize = width - _numButtons * (buttonSize.X + spacing.X); + _designCombo.Draw(comboSize); + ImGui.SameLine(); + DrawApplyButton(buttonSize); + } + DrawRevertButton(buttonSize); - DrawRevertAutomationButton(buttonSize); + DrawRevertEquipButton(buttonSize); + DrawRevertCustomizeButton(buttonSize); DrawRevertAdvancedCustomization(buttonSize); + DrawRevertAutomationButton(buttonSize); } private ActorIdentifier _playerIdentifier; @@ -152,12 +168,14 @@ public sealed class DesignQuickBar : Window, IDisposable var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.PlayCircle, size, tooltip, available); + ImGui.SameLine(); if (!clicked) return; if (state == null && !_stateManager.GetOrCreate(id, data.Objects[0], out state)) { - Glamourer.Messager.NotificationMessage($"Could not apply {design!.ResolveName(true)} to {id.Incognito(null)}: Failed to create state."); + Glamourer.Messager.NotificationMessage( + $"Could not apply {design!.ResolveName(true)} to {id.Incognito(null)}: Failed to create state."); return; } @@ -166,8 +184,11 @@ public sealed class DesignQuickBar : Window, IDisposable _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); } - public void DrawRevertButton(Vector2 buttonSize) + private void DrawRevertButton(Vector2 buttonSize) { + if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAll)) + return; + var available = 0; var tooltip = string.Empty; if (_playerIdentifier.IsValid && _playerState is { IsLocked: false }) @@ -188,15 +209,19 @@ public sealed class DesignQuickBar : Window, IDisposable tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); + ImGui.SameLine(); if (clicked) _stateManager.ResetState(state!, StateSource.Manual); } - public void DrawRevertAutomationButton(Vector2 buttonSize) + private void DrawRevertAutomationButton(Vector2 buttonSize) { if (!_config.EnableAutoDesigns) return; + if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAutomation)) + return; + var available = 0; var tooltip = string.Empty; @@ -217,8 +242,8 @@ public sealed class DesignQuickBar : Window, IDisposable if (available == 0) tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; - ImGui.SameLine(); var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, tooltip, available); + ImGui.SameLine(); if (!clicked) return; @@ -229,9 +254,12 @@ public sealed class DesignQuickBar : Window, IDisposable } } - public void DrawRevertAdvancedCustomization(Vector2 buttonSize) + private void DrawRevertAdvancedCustomization(Vector2 buttonSize) { - if (!_config.ShowRevertAdvancedParametersButton || !_config.UseAdvancedParameters) + if (!_config.UseAdvancedParameters) + return; + + if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) return; var available = 0; @@ -254,12 +282,74 @@ public sealed class DesignQuickBar : Window, IDisposable if (available == 0) tooltip = "Neither player character nor target are available or their state is locked."; - ImGui.SameLine(); var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, tooltip, available); + ImGui.SameLine(); if (clicked) _stateManager.ResetAdvancedState(state!, StateSource.Manual); } + private void DrawRevertCustomizeButton(Vector2 buttonSize) + { + if (!_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize)) + return; + + var available = 0; + var tooltip = string.Empty; + + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) + { + available |= 1; + tooltip = "Left-Click: Revert the customizations of the player character to their game state."; + } + + if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Revert the customizations of {_targetIdentifier} to their game state."; + } + + if (available == 0) + tooltip = "Neither player character nor target are available or their state is locked."; + + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.User, buttonSize, tooltip, available); + ImGui.SameLine(); + if (clicked) + _stateManager.ResetCustomize(state!, StateSource.Manual); + } + + private void DrawRevertEquipButton(Vector2 buttonSize) + { + if (!_config.QdbButtons.HasFlag(QdbButtons.RevertEquip)) + return; + + var available = 0; + var tooltip = string.Empty; + + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) + { + available |= 1; + tooltip = "Left-Click: Revert the equipment of the player character to its game state."; + } + + if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Revert the equipment of {_targetIdentifier} to its game state."; + } + + if (available == 0) + tooltip = "Neither player character nor target are available or their state is locked."; + + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Vest, buttonSize, tooltip, available); + ImGui.SameLine(); + if (clicked) + _stateManager.ResetEquip(state!, StateSource.Manual); + } + private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip, int available) { @@ -292,15 +382,32 @@ public sealed class DesignQuickBar : Window, IDisposable private float UpdateWidth() { - _numButtons = (_config.EnableAutoDesigns, _config is { ShowRevertAdvancedParametersButton: true, UseAdvancedParameters: true }) switch + _numButtons = 0; + if (_config.QdbButtons.HasFlag(QdbButtons.RevertAll)) + ++_numButtons; + if (_config.EnableAutoDesigns && _config.QdbButtons.HasFlag(QdbButtons.RevertAutomation)) + ++_numButtons; + if ((_config.UseAdvancedParameters || _config.UseAdvancedDyes) && _config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) + ++_numButtons; + if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize)) + ++_numButtons; + if (_config.QdbButtons.HasFlag(QdbButtons.RevertEquip)) + ++_numButtons; + if (_config.QdbButtons.HasFlag(QdbButtons.ApplyDesign)) { - (true, true) => 4, - (false, true) => 3, - (true, false) => 3, - (false, false) => 2, - }; - Size = new Vector2((7 + _numButtons) * ImGui.GetFrameHeight() + _numButtons * ImGui.GetStyle().ItemInnerSpacing.X, - ImGui.GetFrameHeight()); + ++_numButtons; + Size = new Vector2((7 + _numButtons) * ImGui.GetFrameHeight() + _numButtons * ImGui.GetStyle().ItemInnerSpacing.X, + ImGui.GetFrameHeight()); + } + else + { + Size = new Vector2( + _numButtons * ImGui.GetFrameHeight() + + (_numButtons - 1) * ImGui.GetStyle().ItemInnerSpacing.X + + ImGui.GetStyle().WindowPadding.X * 2, + ImGui.GetFrameHeight()); + } + return Size.Value.X; } } diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 59b237c..39f3c6d 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -25,6 +25,7 @@ public class GlamourerWindowSystem : IDisposable _uiBuilder.Draw += _windowSystem.Draw; _uiBuilder.OpenConfigUi += _ui.Toggle; _uiBuilder.DisableCutsceneUiHide = !config.HideWindowInCutscene; + _uiBuilder.DisableUserUiHide = config.ShowWindowWhenUiHidden; } public void Dispose() diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index d9a6e1f..71d3822 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -159,10 +159,6 @@ public class MainWindow : Window, IDisposable { var width = ImGui.CalcTextSize("Join Discord for Support").X + ImGui.GetStyle().FramePadding.X * 2; var xPos = ImGui.GetWindowWidth() - width; - // Respect the scroll bar width. - if (ImGui.GetScrollMaxY() > 0) - xPos -= ImGui.GetStyle().ScrollbarSize + ImGui.GetStyle().FramePadding.X; - ImGui.SetCursorPos(new Vector2(xPos, 0)); CustomGui.DrawDiscordButton(Glamourer.Messager, width); diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index fab2085..88f7b58 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -120,6 +120,7 @@ public class SettingsTab( Checkbox("Show Quick Design Bar in Main Window", "Show the quick design bar in the tab selection part of the main window, too.", config.ShowQuickBarInTabs, v => config.ShowQuickBarInTabs = v); + DrawQuickDesignBoxes(); ImGui.Dummy(Vector2.Zero); ImGui.Separator(); @@ -134,6 +135,12 @@ public class SettingsTab( else contextMenuService.Disable(); }); + Checkbox("Show Window when UI is Hidden", "Whether to show Glamourer windows even when the games UI is hidden.", + config.ShowWindowWhenUiHidden, v => + { + config.ShowWindowWhenUiHidden = v; + uiBuilder.DisableUserUiHide = v; + }); Checkbox("Hide Window in Cutscenes", "Whether the main Glamourer window should automatically be hidden when entering cutscenes or not.", config.HideWindowInCutscene, v => @@ -176,9 +183,9 @@ public class SettingsTab( config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v); if (config.UseAdvancedParameters) { - Checkbox("Show Revert Advanced Customizations Button in Quick Design Bar", - "Show a button to revert only advanced customizations on your character or a target in the quick design bar.", - config.ShowRevertAdvancedParametersButton, v => config.ShowRevertAdvancedParametersButton = v); + //Checkbox("Show Revert Advanced Customizations Button in Quick Design Bar", + // "Show a button to revert only advanced customizations on your character or a target in the quick design bar.", + // config.ShowRevertAdvancedParametersButton, v => config.ShowRevertAdvancedParametersButton = v); Checkbox("Show Color Display Config", "Show the Color Display configuration options in the Advanced Customization panels.", config.ShowColorConfig, v => config.ShowColorConfig = v); Checkbox("Show Palette+ Import Button", @@ -198,6 +205,52 @@ public class SettingsTab( ImGui.NewLine(); } + private void DrawQuickDesignBoxes() + { + var showAuto = config.EnableAutoDesigns; + var showAdvanced = config.UseAdvancedParameters || config.UseAdvancedDyes; + var numColumns = 6 - (showAuto ? 0 : 1) - (showAdvanced ? 0 : 1); + ImGui.NewLine(); + ImGui.TextUnformatted("Show the Following Buttons in the Quick Design Bar:"); + ImGui.Dummy(Vector2.Zero); + using var table = ImRaii.Table("##tableQdb", numColumns, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Borders | ImGuiTableFlags.NoHostExtendX); + if (!table) + return; + + var columns = new[] + { + (" Apply Design ", true, QdbButtons.ApplyDesign), + (" Revert All ", true, QdbButtons.RevertAll), + (" Revert to Auto ", showAuto, QdbButtons.RevertAutomation), + (" Revert Equip ", true, QdbButtons.RevertEquip), + (" Revert Customization ", true, QdbButtons.RevertCustomize), + (" Revert Advanced ", showAdvanced, QdbButtons.RevertAdvanced), + }; + + foreach (var (label, _, _) in columns.Where(t => t.Item2)) + { + ImGui.TableNextColumn(); + ImGui.TableHeader(label); + } + + foreach (var (_, _, flag) in columns.Where(t => t.Item2)) + { + using var id = ImRaii.PushId((int)flag); + ImGui.TableNextColumn(); + var offset = (ImGui.GetContentRegionAvail().X - ImGui.GetFrameHeight()) / 2; + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + offset); + var value = config.QdbButtons.HasFlag(flag); + if (!ImGui.Checkbox(string.Empty, ref value)) + continue; + + var buttons = value ? config.QdbButtons | flag : config.QdbButtons & ~flag; + if (buttons == config.QdbButtons) + continue; + + config.QdbButtons = buttons; + config.Save(); + } + } private void PaletteImportButton() { diff --git a/Glamourer/Services/ConfigMigrationService.cs b/Glamourer/Services/ConfigMigrationService.cs index 55b0664..88eaf69 100644 --- a/Glamourer/Services/ConfigMigrationService.cs +++ b/Glamourer/Services/ConfigMigrationService.cs @@ -4,38 +4,38 @@ using Newtonsoft.Json.Linq; namespace Glamourer.Services; -public class ConfigMigrationService +public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator, BackupService backupService) { - private readonly SaveService _saveService; - private readonly FixedDesignMigrator _fixedDesignMigrator; - private readonly BackupService _backupService; - private Configuration _config = null!; private JObject _data = null!; - public ConfigMigrationService(SaveService saveService, FixedDesignMigrator fixedDesignMigrator, BackupService backupService) - { - _saveService = saveService; - _fixedDesignMigrator = fixedDesignMigrator; - _backupService = backupService; - } - public void Migrate(Configuration config) { _config = config; - if (config.Version >= Configuration.Constants.CurrentVersion || !File.Exists(_saveService.FileNames.ConfigFile)) + if (config.Version >= Configuration.Constants.CurrentVersion || !File.Exists(saveService.FileNames.ConfigFile)) { AddColors(config, false); return; } - _data = JObject.Parse(File.ReadAllText(_saveService.FileNames.ConfigFile)); + _data = JObject.Parse(File.ReadAllText(saveService.FileNames.ConfigFile)); MigrateV1To2(); MigrateV2To4(); MigrateV4To5(); + MigrateV5To6(); AddColors(config, true); } + private void MigrateV5To6() + { + if (_config.Version > 5) + return; + + if (_data["ShowRevertAdvancedParametersButton"]?.ToObject() ?? true) + _config.QdbButtons |= QdbButtons.RevertAdvanced; + _config.Version = 6; + } + // Ephemeral Config. private void MigrateV4To5() { @@ -59,8 +59,8 @@ public class ConfigMigrationService if (_config.Version > 1) return; - _backupService.CreateMigrationBackup("pre_v1_to_v2_migration"); - _fixedDesignMigrator.Migrate(_data["FixedDesigns"]); + backupService.CreateMigrationBackup("pre_v1_to_v2_migration"); + fixedDesignMigrator.Migrate(_data["FixedDesigns"]); _config.Version = 2; var customizationColor = _data["CustomizationColor"]?.ToObject() ?? ColorId.CustomizationDesign.Data().DefaultColor; _config.Colors[ColorId.CustomizationDesign] = customizationColor; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 3637729..1b190b9 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -29,7 +29,8 @@ public sealed class StateManager( DesignMerger merger, ModSettingApplier modApplier, GPoseService gPose) - : StateEditor(editor, applier, @event, jobChange, config, items, merger, modApplier, gPose), IReadOnlyDictionary + : StateEditor(editor, applier, @event, jobChange, config, items, merger, modApplier, gPose), + IReadOnlyDictionary { private readonly Dictionary _states = []; @@ -225,7 +226,7 @@ public sealed class StateManager( || !state.ModelData.IsHuman || CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw(); - state.ModelData = state.BaseData; + state.ModelData = state.BaseData; state.ModelData.SetIsWet(false); foreach (var index in Enum.GetValues()) state.Sources[index] = StateSource.Game; @@ -281,6 +282,55 @@ public sealed class StateManager( StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null); } + public void ResetCustomize(ActorState state, StateSource source, uint key = 0) + { + if (!state.Unlock(key) || !state.ModelData.IsHuman) + return; + + foreach (var flag in CustomizationExtensions.All) + state.Sources[flag] = StateSource.Game; + + state.ModelData = state.BaseData; + var actors = ActorData.Invalid; + if (source is not StateSource.Game) + actors = Applier.ChangeCustomize(state, true); + Glamourer.Log.Verbose( + $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + } + + public void ResetEquip(ActorState state, StateSource source, uint key = 0) + { + if (!state.Unlock(key)) + return; + + foreach (var slot in EquipSlotExtensions.FullSlots) + { + state.Sources[slot, true] = StateSource.Game; + state.Sources[slot, false] = StateSource.Game; + if (source is not StateSource.Game) + { + state.ModelData.SetItem(slot, state.BaseData.Item(slot)); + state.ModelData.SetStain(slot, state.BaseData.Stain(slot)); + } + } + + var actors = ActorData.Invalid; + if (source is not StateSource.Game) + { + actors = Applier.ChangeArmor(state, EquipSlotExtensions.EqdpSlots[0], true); + foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1)) + Applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), + state.ModelData.IsHatVisible()); + + var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; + Applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); + var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors; + Applier.ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand)); + } + + Glamourer.Log.Verbose($"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + } + public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) { if (!state.Unlock(key)) From 00c5c06629652f9b92f5c85ea4b82b00e10935b0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Mar 2024 14:33:46 +0100 Subject: [PATCH 287/786] Update Changelog. --- Glamourer/Gui/GlamourerChangelog.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index dc7d445..694a68e 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -29,6 +29,7 @@ public class GlamourerChangelog Add1_1_0_2(Changelog); Add1_1_0_4(Changelog); AddDummy(Changelog); + AddDummy(Changelog); Add1_2_0_0(Changelog); } @@ -55,7 +56,9 @@ public class GlamourerChangelog .RegisterHighlight("Added the option to link to other designs in a design, causing all of them to be applied at once.") .RegisterEntry("This required reworking the handling for applying multiple designs at once (i.e.merging them).", 1) .RegisterEntry( - "This was a considerable backend change on both automation sets and design application. I may have messed up and introduced bugs. Please let me know if something does not work right anymore.", + "This was a considerable backend change on both automation sets and design application. I may have messed up and introduced bugs. " + + "The new version was on Testing for multiple weeks, but not many people use it. " + + "Please let me know if something does not work right anymore.", 1) .RegisterHighlight("Added advanced dye options for equipment. You can now live-edit the color sets of your gear.") .RegisterEntry( @@ -80,12 +83,16 @@ public class GlamourerChangelog "Changed Item Customizations in Penumbra can now be right-clicked to preview them on your character, if you have the correct Gender/Race combo on them.") .RegisterHighlight( "Add the option to override associated collections for characters, so that automatically applied mod associations affect the overriden collection.") - .RegisterHighlight("Added the option to apply random designs (with optional restrictions) to characters via slash commands and automation.") + .RegisterHighlight( + "Added the option to apply random designs (with optional restrictions) to characters via slash commands and automation.") .RegisterEntry("Added copy/paste buttons for advanced customization colors.") .RegisterEntry("Added alpha preview to advanced customization colors.") .RegisterEntry("Added a button to update the settings for an associated mod from their current settings.") + .RegisterHighlight("Added 'Revert Equipment' and 'Revert Customizations' buttons to the Quick Design Bar.") + .RegisterEntry("You can now toggle every functionality of the Quick Design Bar on or off separately.") .RegisterEntry("Updated a few fun module things. Now there are Pink elephants on parade!") .RegisterEntry("Split up the IPC source state so IPC consumers can apply designs without them sticking around.") + .RegisterEntry("Fixed an issue with gearset changes not registering in Glamourer for Automation.") .RegisterEntry("Fixed an issue with weapon loading being dependant on the order of loading Penumbra and Glamourer.") .RegisterEntry( "Fixed an issue with buttons sharing state and switching from design duplication to creating new ones caused errors.") From 22e7a71425771753e9774ba1f811badccd2fc621 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 1 Mar 2024 13:37:59 +0000 Subject: [PATCH 288/786] [CI] Updating repo.json for 1.2.0.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 1c55754..4926685 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.1.0.4", - "TestingAssemblyVersion": "1.1.0.18", + "AssemblyVersion": "1.2.0.0", + "TestingAssemblyVersion": "1.2.0.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.1.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.1.0.18/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a501d972520ce3f0a9118039e6ae0601c4c79f41 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Mar 2024 16:41:45 +0100 Subject: [PATCH 289/786] Fix design link application checkboxes. --- Glamourer/Gui/GlamourerChangelog.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 694a68e..473a64b 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -77,7 +77,7 @@ public class GlamourerChangelog "This is off by default and I strongly recommend AGAINST using it, since Glamourer has no way to revert such changes. You are responsible for keeping your collection in order.", 1) .RegisterHighlight( - "Added mouse wheel scrolling to many selectors, e.g. for equipment, dyes or customizations. You need to hold Control while ") + "Added mouse wheel scrolling to many selectors, e.g. for equipment, dyes or customizations. You need to hold Control while scrolling in most places.") .RegisterEntry("Improved handling for highlights with advanced customization colors and normal customization settings.") .RegisterHighlight( "Changed Item Customizations in Penumbra can now be right-clicked to preview them on your character, if you have the correct Gender/Race combo on them.") diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index f9fcaef..5a8c41c 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -230,13 +230,13 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe ImGui.SameLine(); Box(4); if (newType != current) - _linkManager.ChangeApplicationType(_selector.Selected!, idx, order, current); + _linkManager.ChangeApplicationType(_selector.Selected!, idx, order, newType); return; void Box(int i) { var (applicationType, description) = ApplicationTypeExtensions.Types[i]; - var value = applicationType.HasFlag(applicationType); + var value = current.HasFlag(applicationType); if (ImGui.Checkbox($"##{(byte)applicationType}", ref value)) newType = value ? newType | applicationType : newType & ~applicationType; ImGuiUtil.HoverTooltip(description); From 4bbb48b7b92afa0b0ea136a7a2a52ba4f01d612c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Mar 2024 23:38:28 +0100 Subject: [PATCH 290/786] Fix BodyType issues. --- Glamourer/Designs/Links/DesignMerger.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index ebe8aba..914ad96 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -22,8 +22,7 @@ public class DesignMerger( => Merge(designs.Select(d => ((IDesignStandIn)d.Link, d.Type)), currentCustomize, baseRef, respectOwnership, modAssociations); public MergedDesign Merge(IEnumerable<(IDesignStandIn, ApplicationType)> designs, in CustomizeArray currentCustomize, in DesignData baseRef, - bool respectOwnership, - bool modAssociations) + bool respectOwnership, bool modAssociations) { var ret = new MergedDesign(designManager); ret.Design.SetCustomize(_customize, currentCustomize); @@ -211,7 +210,10 @@ public class DesignMerger( private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret, StateSource source, bool respectOwnership) { - customizeFlags &= ~ret.Design.ApplyCustomizeRaw; + customizeFlags &= ~ret.Design.ApplyCustomizeExcludingBodyType; + if (ret.Design.DesignData.Customize.BodyType != 1) + customizeFlags &= ~CustomizeFlag.BodyType; + if (customizeFlags == 0) return; @@ -246,6 +248,13 @@ public class DesignMerger( ret.Sources[CustomizeIndex.Face] = source; } + if (customizeFlags.HasFlag(CustomizeFlag.BodyType)) + { + customize[CustomizeIndex.BodyType] = design.Customize.BodyType; + customizeFlags &= ~CustomizeFlag.BodyType; + ret.Sources[CustomizeIndex.BodyType] = source; + } + var set = _customize.Manager.GetSet(customize.Clan, customize.Gender); var face = customize.Face; foreach (var index in Enum.GetValues()) From f07717240fb7206078086191267b0768bcb670c3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Mar 2024 23:39:09 +0100 Subject: [PATCH 291/786] Fix turning non-humans human with merged designs. --- Glamourer/State/StateEditor.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index faaee58..92e6295 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -364,10 +364,13 @@ public class StateEditor( public void ApplyDesign(object data, DesignBase design, ApplySettings settings) { - var merged = settings.MergeLinks && design is Design d - ? merger.Merge(d.AllLinks, ((ActorState)data).ModelData.Customize, ((ActorState)data).BaseData, false, - Config.AlwaysApplyAssociatedMods) - : new MergedDesign(design); + var state = (ActorState)data; + MergedDesign merged; + if (!settings.MergeLinks || design is not Design d) + merged = new MergedDesign(design); + else + merged = merger.Merge(d.AllLinks, state.ModelData.IsHuman ? state.ModelData.Customize : CustomizeArray.Default, state.BaseData, + false, Config.AlwaysApplyAssociatedMods); ApplyDesign(data, merged, settings with { From 64c1f75ee021fe32a380257161c9decbb72a15e3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Mar 2024 23:40:11 +0100 Subject: [PATCH 292/786] Store last selected design, apply that design and last selected tab. --- Glamourer/EphemeralConfig.cs | 1 + Glamourer/Gui/MainWindow.cs | 1 + .../Tabs/DesignTab/DesignFileSystemSelector.cs | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/EphemeralConfig.cs index d106783..8497538 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/EphemeralConfig.cs @@ -16,6 +16,7 @@ public class EphemeralConfig : ISavable public bool LockDesignQuickBar { get; set; } = false; public bool LockMainWindow { get; set; } = false; public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; + public Guid SelectedDesign { get; set; } = Guid.Empty; public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 71d3822..e3186af 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -91,6 +91,7 @@ public class MainWindow : Window, IDisposable messages, debugTab, ]; + SelectTab = _config.Ephemeral.SelectedTab; _event.Subscribe(OnTabSelected, TabSelected.Priority.MainWindow); IsOpen = _config.OpenWindowAtStart; } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index cb401a1..6321482 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -61,6 +61,24 @@ public sealed class DesignFileSystemSelector : FileSystemSelector.Leaf? leaf, bool clear, in DesignState storage = default) + { + base.Select(leaf, clear, storage); + var id = SelectedLeaf?.Value.Identifier ?? Guid.Empty; + if (id != _config.Ephemeral.SelectedDesign) + { + _config.Ephemeral.SelectedDesign = id; + _config.Ephemeral.Save(); + } } protected override void DrawPopups() From bfe50f459d2a8341bf0706b40ebc12c90fbfc7df Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 1 Mar 2024 23:41:30 +0100 Subject: [PATCH 293/786] Add option to apply designs to player with doubl click. --- Glamourer/Configuration.cs | 1 + .../DesignTab/DesignFileSystemSelector.cs | 8 ++- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 15 ++--- Glamourer/Services/DesignApplier.cs | 61 +++++++++++++++++++ Glamourer/State/StateManager.cs | 3 + 5 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 Glamourer/Services/DesignApplier.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index d7b9ec1..4f6c435 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -44,6 +44,7 @@ public class Configuration : IPluginConfiguration, ISavable 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 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 6321482..cfc3877 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -3,6 +3,7 @@ using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin.Services; using Glamourer.Designs; using Glamourer.Events; +using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -21,6 +22,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector config.AllowDoubleClickToApply = v); Checkbox("Show all Application Rule Checkboxes for Automation", "Show multiple separate application rule checkboxes for automated designs, instead of a single box for enabling or disabling.", config.ShowAllAutomatedApplicationRules, v => config.ShowAllAutomatedApplicationRules = v); @@ -183,9 +186,6 @@ public class SettingsTab( config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v); if (config.UseAdvancedParameters) { - //Checkbox("Show Revert Advanced Customizations Button in Quick Design Bar", - // "Show a button to revert only advanced customizations on your character or a target in the quick design bar.", - // config.ShowRevertAdvancedParametersButton, v => config.ShowRevertAdvancedParametersButton = v); Checkbox("Show Color Display Config", "Show the Color Display configuration options in the Advanced Customization panels.", config.ShowColorConfig, v => config.ShowColorConfig = v); Checkbox("Show Palette+ Import Button", @@ -207,13 +207,14 @@ public class SettingsTab( private void DrawQuickDesignBoxes() { - var showAuto = config.EnableAutoDesigns; - var showAdvanced = config.UseAdvancedParameters || config.UseAdvancedDyes; - var numColumns = 6 - (showAuto ? 0 : 1) - (showAdvanced ? 0 : 1); + var showAuto = config.EnableAutoDesigns; + var showAdvanced = config.UseAdvancedParameters || config.UseAdvancedDyes; + var numColumns = 6 - (showAuto ? 0 : 1) - (showAdvanced ? 0 : 1); ImGui.NewLine(); ImGui.TextUnformatted("Show the Following Buttons in the Quick Design Bar:"); ImGui.Dummy(Vector2.Zero); - using var table = ImRaii.Table("##tableQdb", numColumns, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Borders | ImGuiTableFlags.NoHostExtendX); + using var table = ImRaii.Table("##tableQdb", numColumns, + ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Borders | ImGuiTableFlags.NoHostExtendX); if (!table) return; diff --git a/Glamourer/Services/DesignApplier.cs b/Glamourer/Services/DesignApplier.cs new file mode 100644 index 0000000..e0134d4 --- /dev/null +++ b/Glamourer/Services/DesignApplier.cs @@ -0,0 +1,61 @@ +using Glamourer.Designs; +using Glamourer.Interop; +using Glamourer.Interop.Structs; +using Glamourer.State; +using OtterGui.Services; +using Penumbra.GameData.Actors; + +namespace Glamourer.Services; + +public sealed class DesignApplier(StateManager stateManager, ObjectManager objects) : IService +{ + public void ApplyToPlayer(DesignBase design) + { + var (player, data) = objects.PlayerData; + if (!data.Valid) + return; + + if (!stateManager.GetOrCreate(player, data.Objects[0], out var state)) + return; + + stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); + } + + public void ApplyToTarget(DesignBase design) + { + var (player, data) = objects.TargetData; + if (!data.Valid) + return; + + if (!stateManager.GetOrCreate(player, data.Objects[0], out var state)) + return; + + stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); + } + + public void Apply(ActorIdentifier actor, DesignBase design) + { + objects.Update(); + Apply(actor, objects.TryGetValue(actor, out var d) ? d : ActorData.Invalid, design, ApplySettings.ManualWithLinks); + } + + public void Apply(ActorIdentifier actor, DesignBase design, ApplySettings settings) + { + objects.Update(); + Apply(actor, objects.TryGetValue(actor, out var d) ? d : ActorData.Invalid, design, settings); + } + + public void Apply(ActorIdentifier actor, ActorData data, DesignBase design) + => Apply(actor, data, design, ApplySettings.ManualWithLinks); + + public void Apply(ActorIdentifier actor, ActorData data, DesignBase design, ApplySettings settings) + { + if (!actor.IsValid || !data.Valid) + return; + + if (!stateManager.GetOrCreate(actor, data.Objects[0], out var state)) + return; + + stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); + } +} diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 1b190b9..6ea28d9 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -68,6 +68,9 @@ public sealed class StateManager( if (TryGetValue(identifier, out state)) return true; + if (!actor.Valid) + return false; + try { // Initial Creation, use the actors data for the base data, From 96fcf9e7278af9602aa94a19875ff0a87e46f82d Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 1 Mar 2024 22:43:26 +0000 Subject: [PATCH 294/786] [CI] Updating repo.json for 1.2.0.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 4926685..dccc47b 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.0.0", - "TestingAssemblyVersion": "1.2.0.0", + "AssemblyVersion": "1.2.0.1", + "TestingAssemblyVersion": "1.2.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From bade38f82e774727ffac67b886b66ae7268a3be4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 2 Mar 2024 12:08:17 +0100 Subject: [PATCH 295/786] Handle other direction for bodytype. --- Glamourer/Designs/Links/DesignMerger.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 914ad96..92bc568 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -26,7 +26,8 @@ public class DesignMerger( { var ret = new MergedDesign(designManager); ret.Design.SetCustomize(_customize, currentCustomize); - CustomizeFlag fixFlags = 0; + var startBodyType = currentCustomize.BodyType; + CustomizeFlag fixFlags = 0; respectOwnership &= _config.UnlockedItemMode; foreach (var (design, type) in designs) { @@ -41,7 +42,7 @@ public class DesignMerger( var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyMeta) = type.ApplyWhat(design); ReduceMeta(data, applyMeta, ret, source); - ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source, respectOwnership); + ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source, respectOwnership, startBodyType); ReduceEquip(data, equipFlags, ret, source, respectOwnership); ReduceMainhands(data, equipFlags, ret, source, respectOwnership); ReduceOffhands(data, equipFlags, ret, source, respectOwnership); @@ -208,10 +209,10 @@ public class DesignMerger( } private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret, - StateSource source, bool respectOwnership) + StateSource source, bool respectOwnership, CustomizeValue startBodyType) { customizeFlags &= ~ret.Design.ApplyCustomizeExcludingBodyType; - if (ret.Design.DesignData.Customize.BodyType != 1) + if (ret.Design.DesignData.Customize.BodyType != startBodyType) customizeFlags &= ~CustomizeFlag.BodyType; if (customizeFlags == 0) From 93ba44d2308eac6cadb224fc90126611bd6032f3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 2 Mar 2024 16:11:31 +0100 Subject: [PATCH 296/786] Change fixed state handling for advanced dyes. --- Glamourer/Automation/AutoDesignApplier.cs | 10 ++++++++- .../Interop/Material/MaterialValueManager.cs | 22 ++++++++++++++----- Glamourer/State/StateManager.cs | 16 +++++++++++++- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 5cd9cf5..d080ae7 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -4,6 +4,7 @@ using Glamourer.Designs; using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.Interop; +using Glamourer.Interop.Material; using Glamourer.Interop.Structs; using Glamourer.State; using Penumbra.GameData.Actors; @@ -256,9 +257,16 @@ public sealed class AutoDesignApplier : IDisposable private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange) { if (set.BaseState is AutoDesignSet.Base.Game) + { _state.ResetStateFixed(state, respectManual); + } else if (!respectManual) + { state.Sources.RemoveFixedDesignSources(); + foreach(var (key, value) in state.Materials.Values) + if (value.Source is StateSource.Fixed) + state.Materials.UpdateValue(key, new MaterialValueState(value.Game, value.Model, value.DrawData, StateSource.Manual), out _); + } if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) return; @@ -266,7 +274,7 @@ public sealed class AutoDesignApplier : IDisposable var mergedDesign = _designMerger.Merge( set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design.AllLinks.Select(l => (l.Design, l.Flags & d.Type))), state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods); - _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, set.BaseState is AutoDesignSet.Base.Game)); + _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false)); } /// Get world-specific first and all-world afterward. diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 6714e96..599d264 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -228,6 +228,9 @@ public readonly struct MaterialValueManager } public bool TryGetValue(MaterialValueIndex index, out T value) + => TryGetValue(index.Key, out value); + + public bool TryGetValue(uint key, out T value) { if (_values.Count == 0) { @@ -235,7 +238,7 @@ public readonly struct MaterialValueManager return false; } - var idx = Search(index.Key); + var idx = Search(key); if (idx >= 0) { value = _values[idx].Value; @@ -247,8 +250,10 @@ public readonly struct MaterialValueManager } public bool TryAddValue(MaterialValueIndex index, in T value) + => TryAddValue(index.Key, value); + + public bool TryAddValue(uint key, in T value) { - var key = index.Key; var idx = Search(key); if (idx >= 0) return false; @@ -258,11 +263,14 @@ public readonly struct MaterialValueManager } public bool RemoveValue(MaterialValueIndex index) + => RemoveValue(index.Key); + + public bool RemoveValue(uint key) { if (_values.Count == 0) return false; - var idx = Search(index.Key); + var idx = Search(key); if (idx < 0) return false; @@ -271,8 +279,10 @@ public readonly struct MaterialValueManager } public void AddOrUpdateValue(MaterialValueIndex index, in T value) + => AddOrUpdateValue(index.Key, value); + + public void AddOrUpdateValue(uint key, in T value) { - var key = index.Key; var idx = Search(key); if (idx < 0) _values.Insert(~idx, (key, value)); @@ -281,6 +291,9 @@ public readonly struct MaterialValueManager } public bool UpdateValue(MaterialValueIndex index, in T value, out T oldValue) + => UpdateValue(index.Key, value, out oldValue); + + public bool UpdateValue(uint key, in T value, out T oldValue) { if (_values.Count == 0) { @@ -288,7 +301,6 @@ public readonly struct MaterialValueManager return false; } - var key = index.Key; var idx = Search(key); if (idx < 0) { diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 6ea28d9..5519f8d 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -322,8 +322,10 @@ public sealed class StateManager( { actors = Applier.ChangeArmor(state, EquipSlotExtensions.EqdpSlots[0], true); foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1)) + { Applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), state.ModelData.IsHatVisible()); + } var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; Applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); @@ -331,7 +333,8 @@ public sealed class StateManager( Applier.ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand)); } - Glamourer.Log.Verbose($"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) @@ -386,6 +389,17 @@ public sealed class StateManager( state.Sources[meta] = StateSource.Game; state.ModelData.SetMeta(meta, state.BaseData.GetMeta(meta)); } + + foreach (var (index, value) in state.Materials.Values.ToList()) + { + switch (value.Source) + { + case StateSource.Fixed: + case StateSource.Manual when !respectManualPalettes: + state.Materials.RemoveValue(index); + break; + } + } } public void ReapplyState(Actor actor, StateSource source) From 62d89633bb654f43a6980caa1c845ca27be33f06 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 2 Mar 2024 15:13:24 +0000 Subject: [PATCH 297/786] [CI] Updating repo.json for 1.2.0.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index dccc47b..74967f7 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.0.1", - "TestingAssemblyVersion": "1.2.0.1", + "AssemblyVersion": "1.2.0.2", + "TestingAssemblyVersion": "1.2.0.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From e6bd91319b0619dab78ed80f96ffebbded6a3294 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 3 Mar 2024 00:52:53 +0100 Subject: [PATCH 298/786] Fix thrown exception by stupidity. --- Glamourer/Automation/AutoDesignApplier.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index d080ae7..40533f8 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -263,9 +263,13 @@ public sealed class AutoDesignApplier : IDisposable else if (!respectManual) { state.Sources.RemoveFixedDesignSources(); - foreach(var (key, value) in state.Materials.Values) + for (var i = 0; i < state.Materials.Values.Count; ++i) + { + var (key, value) = state.Materials.Values[i]; if (value.Source is StateSource.Fixed) - state.Materials.UpdateValue(key, new MaterialValueState(value.Game, value.Model, value.DrawData, StateSource.Manual), out _); + state.Materials.UpdateValue(key, new MaterialValueState(value.Game, value.Model, value.DrawData, StateSource.Manual), + out _); + } } if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) From 9f9a58af2a077231af6652d175b6437bafd4fadf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 3 Mar 2024 13:42:57 +0100 Subject: [PATCH 299/786] Add an option to respect manual changes on changing automation. --- Glamourer/Automation/AutoDesignApplier.cs | 4 ++-- Glamourer/Configuration.cs | 1 + Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 40533f8..4cd07e5 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -151,7 +151,7 @@ public sealed class AutoDesignApplier : IDisposable { if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - Reduce(data.Objects[0], state, newSet, false, false); + Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false); foreach (var actor in data.Objects) _state.ReapplyState(actor, StateSource.Fixed); } @@ -163,7 +163,7 @@ public sealed class AutoDesignApplier : IDisposable var specificId = actor.GetIdentifier(_actors); if (_state.GetOrCreate(specificId, actor, out var state)) { - Reduce(actor, state, newSet, false, false); + Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false); _state.ReapplyState(actor, StateSource.Fixed); } } diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 4f6c435..896b431 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -45,6 +45,7 @@ public class Configuration : IPluginConfiguration, ISavable 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 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/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 4ac8d80..e988d13 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -7,7 +7,6 @@ using Glamourer.Designs; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.PalettePlus; -using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; using ImGuiNET; @@ -78,6 +77,9 @@ public class SettingsTab( Checkbox("Do Not Apply Unobtained Items in Automation", "Enable this if you want automatically applied designs to only consider items and customizations you have actually unlocked once, and skip those you have not.", config.UnlockedItemMode, v => config.UnlockedItemMode = v); + Checkbox("Respect Manual Changes When Editing Automation", + "Whether changing any currently active automation group will respect manual changes to the character before re-applying the changed automation or not.", + config.RespectManualOnAutomationUpdate, v => config.RespectManualOnAutomationUpdate = v); Checkbox("Enable Festival Easter-Eggs", "Glamourer may do some fun things on specific dates. Disable this if you do not want your experience disrupted by this.", config.DisableFestivals == 0, v => config.DisableFestivals = v ? (byte)0 : (byte)2); From ce3197cb252b228ec36f6af56400566b16966f11 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 3 Mar 2024 12:46:42 +0000 Subject: [PATCH 300/786] [CI] Updating repo.json for 1.2.0.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 74967f7..a0f5206 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.0.2", - "TestingAssemblyVersion": "1.2.0.2", + "AssemblyVersion": "1.2.0.3", + "TestingAssemblyVersion": "1.2.0.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From f35c20ed4d61598d657db23a308500bac514b817 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 4 Mar 2024 15:08:21 +0100 Subject: [PATCH 301/786] Maybe fix listening for moved items. --- Glamourer/State/StateListener.cs | 40 +++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 626c64d..c16af33 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -235,27 +235,41 @@ public class StateListener : IDisposable foreach (var (slot, item, stain) in items) { var currentItem = state.BaseData.Item(slot); - var model = state.ModelData.Weapon(slot); + var model = slot is EquipSlot.MainHand or EquipSlot.OffHand ? state.ModelData.Weapon(slot) : state.ModelData.Armor(slot).ToWeapon(0); var current = currentItem.Weapon(state.BaseData.Stain(slot)); if (model.Value == current.Value || !_items.ItemData.TryGetValue(item, EquipSlot.MainHand, out var changedItem)) continue; var changed = changedItem.Weapon(stain); - if (current.Value == changed.Value && !state.Sources[slot, false].IsFixed()) + var itemChanged = current.Skeleton == changed.Skeleton + && current.Variant == changed.Variant + && current.Weapon == changed.Weapon + && !state.Sources[slot, false].IsFixed(); + + var stainChanged = current.Stain == changed.Stain && !state.Sources[slot, true].IsFixed(); + + switch ((itemChanged, stainChanged)) { - _manager.ChangeItem(state, slot, currentItem, ApplySettings.Game); - _manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game); - switch (slot) - { - case EquipSlot.MainHand: - case EquipSlot.OffHand: - _applier.ChangeWeapon(objects, slot, currentItem, stain); - break; - default: + case (true, true): + _manager.ChangeEquip(state, slot, currentItem, current.Stain, ApplySettings.Game); + if (slot is EquipSlot.MainHand or EquipSlot.OffHand) + _applier.ChangeWeapon(objects, slot, currentItem, current.Stain); + else _applier.ChangeArmor(objects, slot, current.ToArmor(), !state.Sources[slot, false].IsFixed(), state.ModelData.IsHatVisible()); - break; - } + break; + case (true, false): + _manager.ChangeItem(state, slot, currentItem, ApplySettings.Game); + if (slot is EquipSlot.MainHand or EquipSlot.OffHand) + _applier.ChangeWeapon(objects, slot, currentItem, model.Stain); + else + _applier.ChangeArmor(objects, slot, current.ToArmor(model.Stain), !state.Sources[slot, false].IsFixed(), + state.ModelData.IsHatVisible()); + break; + case (false, true): + _manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game); + _applier.ChangeStain(objects, slot, current.Stain); + break; } } } From 6696d539d371685607e7a35aa0703b1ef18b0b99 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 4 Mar 2024 15:08:48 +0100 Subject: [PATCH 302/786] Add quick selection design and saving last quick selected design in config. --- Glamourer/Automation/AutoDesignManager.cs | 23 ++- .../Designs/Special/QuickSelectedDesign.cs | 52 ++++++ Glamourer/EphemeralConfig.cs | 19 +- Glamourer/Gui/DesignCombo.cs | 166 +++++++++++++----- OtterGui | 2 +- 5 files changed, 203 insertions(+), 59 deletions(-) create mode 100644 Glamourer/Designs/Special/QuickSelectedDesign.cs diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index c1e361d..1b95f73 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -28,6 +28,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos private readonly AutomationChanged _event; private readonly DesignChanged _designEvent; private readonly RandomDesignGenerator _randomDesigns; + private readonly QuickSelectedDesign _quickSelectedDesign; private readonly List _data = []; private readonly Dictionary _enabled = []; @@ -36,15 +37,17 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos => _enabled; public AutoDesignManager(JobService jobs, ActorManager actors, SaveService saveService, DesignManager designs, AutomationChanged @event, - FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent, RandomDesignGenerator randomDesigns) + FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent, RandomDesignGenerator randomDesigns, + QuickSelectedDesign quickSelectedDesign) { - _jobs = jobs; - _actors = actors; - _saveService = saveService; - _designs = designs; - _event = @event; - _designEvent = designEvent; - _randomDesigns = randomDesigns; + _jobs = jobs; + _actors = actors; + _saveService = saveService; + _designs = designs; + _event = @event; + _designEvent = designEvent; + _randomDesigns = randomDesigns; + _quickSelectedDesign = quickSelectedDesign; _designEvent.Subscribe(OnDesignChange, DesignChanged.Priority.AutoDesignManager); Load(); migrator.ConsumeMigratedData(_actors, fileSystem, this); @@ -486,6 +489,10 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos { design = new RandomDesign(_randomDesigns); } + else if (designIdentifier is QuickSelectedDesign.SerializedName) + { + design = _quickSelectedDesign; + } else { if (designIdentifier.Length == 0) diff --git a/Glamourer/Designs/Special/QuickSelectedDesign.cs b/Glamourer/Designs/Special/QuickSelectedDesign.cs new file mode 100644 index 0000000..dd0f00f --- /dev/null +++ b/Glamourer/Designs/Special/QuickSelectedDesign.cs @@ -0,0 +1,52 @@ +using Glamourer.Automation; +using Glamourer.Gui; +using Glamourer.Interop.Material; +using Glamourer.State; +using Newtonsoft.Json.Linq; +using OtterGui.Services; + +namespace Glamourer.Designs.Special; + +public class QuickSelectedDesign(QuickDesignCombo combo) : IDesignStandIn, IService +{ + public const string SerializedName = "//QuickSelection"; + public const string ResolvedName = "Quick Design Bar Selection"; + + public bool Equals(IDesignStandIn? other) + => other is QuickSelectedDesign; + + public string ResolveName(bool incognito) + => ResolvedName; + + public Design? CurrentDesign + => combo.Design as Design; + + public ref readonly DesignData GetDesignData(in DesignData baseRef) + { + if (combo.Design != null) + return ref combo.Design.GetDesignData(baseRef); + + return ref baseRef; + } + + public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData() + => combo.Design?.GetMaterialData() ?? []; + + public string SerializeName() + => SerializedName; + + public StateSource AssociatedSource() + => StateSource.Manual; + + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks + => combo.Design?.AllLinks ?? []; + + public void AddData(JObject jObj) + { } + + public void ParseData(JObject jObj) + { } + + public bool ChangeData(object data) + => false; +} diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/EphemeralConfig.cs index 8497538..027685f 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/EphemeralConfig.cs @@ -9,15 +9,16 @@ namespace Glamourer; public class EphemeralConfig : ISavable { - public int Version { get; set; } = Configuration.Constants.CurrentVersion; - public bool IncognitoMode { get; set; } = false; - public bool UnlockDetailMode { get; set; } = true; - public bool ShowDesignQuickBar { get; set; } = false; - public bool LockDesignQuickBar { get; set; } = false; - public bool LockMainWindow { get; set; } = false; - public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; - public Guid SelectedDesign { get; set; } = Guid.Empty; - public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; + public int Version { get; set; } = Configuration.Constants.CurrentVersion; + public bool IncognitoMode { get; set; } = false; + public bool UnlockDetailMode { get; set; } = true; + public bool ShowDesignQuickBar { get; set; } = false; + public bool LockDesignQuickBar { get; set; } = false; + public bool LockMainWindow { get; set; } = false; + public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; + public Guid SelectedDesign { get; set; } = Guid.Empty; + public Guid SelectedQuickDesign { get; set; } = Guid.Empty; + public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; [JsonIgnore] diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index a4bfadb..1875b81 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -14,9 +14,9 @@ namespace Glamourer.Gui; public abstract class DesignComboBase : FilterComboCache>, IDisposable { - private readonly EphemeralConfig _config; - private readonly DesignChanged _designChanged; - private readonly DesignColors _designColors; + protected readonly EphemeralConfig Config; + protected readonly DesignChanged DesignChanged; + protected readonly DesignColors DesignColors; protected readonly TabSelected TabSelected; protected float InnerWidth; private IDesignStandIn? _currentDesign; @@ -25,19 +25,19 @@ public abstract class DesignComboBase : FilterComboCache _config.IncognitoMode; + => Config.IncognitoMode; void IDisposable.Dispose() { - _designChanged.Unsubscribe(OnDesignChange); + DesignChanged.Unsubscribe(OnDesignChange); GC.SuppressFinalize(this); } @@ -45,42 +45,47 @@ public abstract class DesignComboBase : FilterComboCache 0 && realDesign.Name != path) { - var start = ImGui.GetItemRectMin(); - var pos = start.X + ImGui.CalcTextSize(realDesign.Name).X; - var maxSize = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X; - var remainingSpace = maxSize - pos; - var requiredSize = ImGui.CalcTextSize(path).X + ImGui.GetStyle().ItemInnerSpacing.X; - var offset = remainingSpace - requiredSize; - if (ImGui.GetScrollMaxY() == 0) - offset -= ImGui.GetStyle().ItemInnerSpacing.X; - - if (offset < ImGui.GetStyle().ItemSpacing.X) - ImGuiUtil.HoverTooltip(path); - else - ImGui.GetWindowDrawList().AddText(start with { X = pos + offset }, - ImGui.GetColorU32(ImGuiCol.TextDisabled), path); + using var color = ImRaii.PushColor(ImGuiCol.Text, DesignColors.GetColor(realDesign)); + ret = base.DrawSelectable(globalIdx, selected); + DrawPath(path, realDesign); + return ret; } + case QuickSelectedDesign quickDesign: + { + ret = base.DrawSelectable(globalIdx, selected); + DrawResolvedDesign(quickDesign); + return ret; + } + default: return base.DrawSelectable(globalIdx, selected); } + } + + private static void DrawPath(string path, Design realDesign) + { + if (path.Length <= 0 || realDesign.Name == path) + return; + + DrawRightAligned(realDesign.Name, path, ImGui.GetColorU32(ImGuiCol.TextDisabled)); + } + + private void DrawResolvedDesign(QuickSelectedDesign quickDesign) + { + var linkedDesign = quickDesign.CurrentDesign; + if (linkedDesign != null) + DrawRightAligned(quickDesign.ResolveName(false), linkedDesign.Name.Text, DesignColors.GetColor(linkedDesign)); else - { - ret = base.DrawSelectable(globalIdx, selected); - } - - - return ret; + DrawRightAligned(quickDesign.ResolveName(false), "[Nothing]", DesignColors.MissingColor); } protected override int UpdateCurrentSelected(int currentSelected) { CurrentSelectionIdx = Items.IndexOf(p => _currentDesign == p.Item1); - CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : null; + UpdateSelection(CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : null); return CurrentSelectionIdx; } @@ -90,7 +95,7 @@ public abstract class DesignComboBase : FilterComboCache ReferenceEquals(s.Item1, CurrentSelection?.Item1)); if (CurrentSelectionIdx >= 0) { - CurrentSelection = Items[CurrentSelectionIdx]; + UpdateSelection(Items[CurrentSelectionIdx]); } else if (Items.Count > 0) { CurrentSelectionIdx = 0; - CurrentSelection = Items[0]; + UpdateSelection(Items[0]); } else { - CurrentSelection = null; + UpdateSelection(null); } if (!priorState) @@ -151,6 +158,47 @@ public abstract class DesignComboBase : FilterComboCache new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) .OrderBy(d => d.Item2), ]) - => AllowMouseWheel = MouseWheelType.Unmodified; + { + if (config.SelectedQuickDesign != Guid.Empty) + { + CurrentSelectionIdx = Items.IndexOf(t => t.Item1 is Design d && d.Identifier == config.SelectedQuickDesign); + if (CurrentSelectionIdx >= 0) + CurrentSelection = Items[CurrentSelectionIdx]; + else if (Items.Count > 0) + CurrentSelectionIdx = 0; + } + + AllowMouseWheel = MouseWheelType.Unmodified; + SelectionChanged += OnSelectionChange; + } + + private void OnSelectionChange(Tuple? old, Tuple? @new) + { + if (old == null) + { + if (@new?.Item1 is not Design d) + return; + + Config.SelectedQuickDesign = d.Identifier; + Config.Save(); + } + else if (@new?.Item1 is not Design d) + { + Config.SelectedQuickDesign = Guid.Empty; + Config.Save(); + } + else if (!old.Item1.Equals(@new.Item1)) + { + Config.SelectedQuickDesign = d.Identifier; + Config.Save(); + } + } } public sealed class LinkDesignCombo( @@ -253,11 +335,13 @@ public sealed class SpecialDesignCombo( DesignChanged designChanged, AutoDesignManager autoDesignManager, EphemeralConfig config, - RandomDesignGenerator rng) + RandomDesignGenerator rng, + QuickSelectedDesign quickSelectedDesign) : DesignComboBase(() => designs.Designs .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) .OrderBy(d => d.Item2) .Prepend(new Tuple(new RandomDesign(rng), string.Empty)) + .Prepend(new Tuple(quickSelectedDesign, string.Empty)) .Prepend(new Tuple(new RevertDesign(), string.Empty)) .ToList(), log, designChanged, tabSelected, config, designColors) { diff --git a/OtterGui b/OtterGui index 9d68a48..d71f854 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 9d68a487610266058fbec853efed9a35c9df6fbe +Subproject commit d71f8540a2c2efb4f2cb96e4706bb056397daf0a From fdb9479f2d93bc23647e3940e566c11039a11681 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 4 Mar 2024 14:10:37 +0000 Subject: [PATCH 303/786] [CI] Updating repo.json for 1.2.0.4 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index a0f5206..2cc6442 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.0.3", - "TestingAssemblyVersion": "1.2.0.3", + "AssemblyVersion": "1.2.0.4", + "TestingAssemblyVersion": "1.2.0.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.4/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.4/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From b5bdf52d16efb4d38c7637b7ff6a0b45a144db23 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 7 Mar 2024 16:37:17 +0100 Subject: [PATCH 304/786] Maybe fix weapon tracking? --- Glamourer/State/StateListener.cs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index c16af33..0cc21b4 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -235,8 +235,10 @@ public class StateListener : IDisposable foreach (var (slot, item, stain) in items) { var currentItem = state.BaseData.Item(slot); - var model = slot is EquipSlot.MainHand or EquipSlot.OffHand ? state.ModelData.Weapon(slot) : state.ModelData.Armor(slot).ToWeapon(0); - var current = currentItem.Weapon(state.BaseData.Stain(slot)); + var model = slot is EquipSlot.MainHand or EquipSlot.OffHand + ? state.ModelData.Weapon(slot) + : state.ModelData.Armor(slot).ToWeapon(0); + var current = currentItem.Weapon(state.BaseData.Stain(slot)); if (model.Value == current.Value || !_items.ItemData.TryGetValue(item, EquipSlot.MainHand, out var changedItem)) continue; @@ -292,8 +294,7 @@ public class StateListener : IDisposable || !_manager.TryGetValue(identifier, out var state)) return; - var baseType = state.BaseData.Item(slot).Type; - var apply = false; + var apply = false; switch (UpdateBaseData(actor, state, slot, weapon)) { // Do nothing. But this usually can not happen because the hooked function also writes to game objects later. @@ -314,14 +315,24 @@ public class StateListener : IDisposable break; } + var baseType = slot is EquipSlot.OffHand ? state.BaseData.MainhandType.Offhand() : state.BaseData.MainhandType; + var modelType = state.ModelData.Item(slot).Type; if (apply) { // Only allow overwriting identical weapons + var canApply = baseType == modelType + || _gPose.InGPose && actor.IsGPoseOrCutscene; var newWeapon = state.ModelData.Weapon(slot); - if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene) + if (canApply) + { weapon = newWeapon; - else if (weapon.Skeleton.Id != 0) - weapon = weapon.With(newWeapon.Stain); + } + else + { + if (weapon.Skeleton.Id != 0) + weapon = weapon.With(newWeapon.Stain); + _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game); + } } // Fist Weapon Offhand hack. @@ -498,7 +509,7 @@ public class StateListener : IDisposable if (baseData.Skeleton.Id != weapon.Skeleton.Id || baseData.Weapon.Id != weapon.Weapon.Id || baseData.Variant != weapon.Variant) { var item = _items.Identify(slot, weapon.Skeleton, weapon.Weapon, weapon.Variant, - slot is EquipSlot.OffHand ? state.BaseData.Item(EquipSlot.MainHand).Type : FullEquipType.Unknown); + slot is EquipSlot.OffHand ? state.BaseData.MainhandType : FullEquipType.Unknown); state.BaseData.SetItem(slot, item); change = UpdateState.Change; } From ee426eb29fe07b4c20099baf87c1098b46d6f2c1 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 7 Mar 2024 15:40:06 +0000 Subject: [PATCH 305/786] [CI] Updating repo.json for 1.2.0.5 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 2cc6442..7f77f9a 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.0.4", - "TestingAssemblyVersion": "1.2.0.4", + "AssemblyVersion": "1.2.0.5", + "TestingAssemblyVersion": "1.2.0.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.4/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.4/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.5/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c99aa51f8a886a7ecaf0fd6ffa377c61727ef9bb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Mar 2024 00:49:15 +0100 Subject: [PATCH 306/786] Fix sources, let invalid model state weapons reset to base if necessary, check design application against base type. --- Glamourer/Designs/Links/MergedDesign.cs | 4 +--- Glamourer/State/StateEditor.cs | 6 +++--- Glamourer/State/StateListener.cs | 3 ++- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index bcfaa39..d36a284 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -1,6 +1,4 @@ -using Glamourer.Events; -using Glamourer.GameData; -using Glamourer.Interop.Penumbra; +using Glamourer.Interop.Penumbra; using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 92e6295..b11cae2 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -276,7 +276,6 @@ public class StateEditor( if (settings.RespectManual && state.Sources[weaponSlot, false].IsManual()) continue; - var currentType = state.ModelData.Item(weaponSlot).Type; if (!settings.FromJobChange) { if (gPose.InGPose) @@ -291,10 +290,11 @@ public class StateEditor( }); } + var currentType = state.BaseData.Item(weaponSlot).Type; if (mergedDesign.Weapons.TryGetValue(currentType, out var weapon)) { var source = settings.UseSingleSource ? settings.Source : - weapon.Item2 is StateSource.Game ? StateSource.Game : weapon.Item2; + weapon.Item2 is StateSource.Game ? StateSource.Game : settings.Source; Editor.ChangeItem(state, weaponSlot, weapon.Item1, source, out _, settings.Key); } @@ -304,7 +304,7 @@ public class StateEditor( if (settings.FromJobChange) jobChange.Set(state, mergedDesign.Weapons.Values.Select(m => (m.Item1, settings.UseSingleSource ? settings.Source : - m.Item2 is StateSource.Game ? StateSource.Game : m.Item2))); + m.Item2 is StateSource.Game ? StateSource.Game : settings.Source))); foreach (var meta in MetaExtensions.AllRelevant.Where(mergedDesign.Design.DoApplyMeta)) { diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 0cc21b4..bf7b869 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -331,7 +331,8 @@ public class StateListener : IDisposable { if (weapon.Skeleton.Id != 0) weapon = weapon.With(newWeapon.Stain); - _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game); + // Force unlock if necessary. + _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game with { Key = state.Combination }); } } From c9160b8167568792bd4b6dda2fdbef7a99df943d Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 7 Mar 2024 23:55:36 +0000 Subject: [PATCH 307/786] [CI] Updating repo.json for 1.2.0.6 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 7f77f9a..f4c10ad 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.0.5", - "TestingAssemblyVersion": "1.2.0.5", + "AssemblyVersion": "1.2.0.6", + "TestingAssemblyVersion": "1.2.0.6", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.5/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.5/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.5/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.6/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.6/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.6/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 85d9dea2ddd28080fb6575987b6bcdc419cac493 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Mar 2024 16:44:53 +0100 Subject: [PATCH 308/786] Fix some advanced state applications. --- .../Api/GlamourerIpc.GetCustomization.cs | 2 +- Glamourer/Api/GlamourerIpc.cs | 4 +++- Glamourer/Designs/ApplicationRules.cs | 19 +++++++++++++------ Glamourer/Designs/DesignConverter.cs | 3 ++- Glamourer/State/StateEditor.cs | 7 +++++++ 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.GetCustomization.cs b/Glamourer/Api/GlamourerIpc.GetCustomization.cs index 0bcfedd..0067db6 100644 --- a/Glamourer/Api/GlamourerIpc.GetCustomization.cs +++ b/Glamourer/Api/GlamourerIpc.GetCustomization.cs @@ -41,6 +41,6 @@ public partial class GlamourerIpc return null; } - return _designConverter.ShareBase64(state, ApplicationRules.All); + return _designConverter.ShareBase64(state, ApplicationRules.AllWithConfig(_config)); } } diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 5eaf6fe..895a14f 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -25,10 +25,11 @@ public sealed partial class GlamourerIpc : IDisposable 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) + DesignManager designManager, ItemManager items, Configuration config) { _stateManager = stateManager; _objects = objects; @@ -36,6 +37,7 @@ public sealed partial class GlamourerIpc : IDisposable _designConverter = designConverter; _autoDesignApplier = autoDesignApplier; _items = items; + _config = config; _gPose = gPose; _stateChangedEvent = stateChangedEvent; _designManager = designManager; diff --git a/Glamourer/Designs/ApplicationRules.cs b/Glamourer/Designs/ApplicationRules.cs index aaaf1a7..c15b26a 100644 --- a/Glamourer/Designs/ApplicationRules.cs +++ b/Glamourer/Designs/ApplicationRules.cs @@ -10,10 +10,11 @@ public readonly struct ApplicationRules( CustomizeFlag customize, CrestFlag crest, CustomizeParameterFlag parameters, - MetaFlag meta) + MetaFlag meta, + bool materials) { public static readonly ApplicationRules All = new(EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, - CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All); + CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All, true); public static ApplicationRules FromModifiers(ActorState state) => FromModifiers(state, ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift); @@ -22,14 +23,17 @@ public readonly struct ApplicationRules( => NpcFromModifiers(ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift); public static ApplicationRules AllButParameters(ActorState state) - => new(All.Equip, All.Customize, All.Crest, ComputeParameters(state.ModelData, state.BaseData, All.Parameters), All.Meta); + => new(All.Equip, All.Customize, All.Crest, ComputeParameters(state.ModelData, state.BaseData, All.Parameters), All.Meta, true); + + public static ApplicationRules AllWithConfig(Configuration config) + => new(All.Equip, All.Customize, All.Crest, config.UseAdvancedParameters ? All.Parameters : 0, All.Meta, config.UseAdvancedDyes); public static ApplicationRules NpcFromModifiers(bool ctrl, bool shift) => new(ctrl || !shift ? EquipFlagExtensions.All : 0, !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0, 0, 0, - ctrl || !shift ? MetaFlag.VisorState : 0); + ctrl || !shift ? MetaFlag.VisorState : 0, false); public static ApplicationRules FromModifiers(ActorState state, bool ctrl, bool shift) { @@ -41,7 +45,7 @@ public readonly struct ApplicationRules( if (equip != 0) meta |= MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState; - return new ApplicationRules(equip, customize, crest, ComputeParameters(state.ModelData, state.BaseData, parameters), meta); + return new ApplicationRules(equip, customize, crest, ComputeParameters(state.ModelData, state.BaseData, parameters), meta, equip != 0); } public void Apply(DesignBase design) @@ -68,7 +72,10 @@ public readonly struct ApplicationRules( public MetaFlag Meta => meta & MetaExtensions.All; - public static CustomizeParameterFlag ComputeParameters(in DesignData model, in DesignData game, + public bool Materials + => materials; + + private static CustomizeParameterFlag ComputeParameters(in DesignData model, in DesignData game, CustomizeParameterFlag baseFlags = CustomizeParameterExtensions.All) { foreach (var flag in baseFlags.Iterate()) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index b68afa2..3b67ac6 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -56,7 +56,8 @@ public class DesignConverter( var design = _designs.CreateTemporary(); rules.Apply(design); design.SetDesignData(_customize, data); - ComputeMaterials(design.GetMaterialDataRef(), materials, rules.Equip); + if (rules.Materials) + ComputeMaterials(design.GetMaterialDataRef(), materials, rules.Equip); return design; } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index b11cae2..60142ae 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -235,6 +235,13 @@ public class StateEditor( settings.Key)) requiresRedraw |= changed.RequiresRedraw(); + if (settings.ResetMaterials) + { + state.ModelData.Parameters = state.BaseData.Parameters; + foreach (var parameter in CustomizeParameterExtensions.AllFlags) + state.Sources[parameter] = StateSource.Game; + } + foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate()) { if (settings.RespectManual && state.Sources[parameter].IsManual()) From e8907871af0651d38b1119194ba58e29cc7d8d99 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Mar 2024 16:45:11 +0100 Subject: [PATCH 309/786] Fix random design ignoring last design. --- Glamourer/Designs/Special/RandomDesignGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index 8b2e050..7ed4452 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -11,8 +11,8 @@ public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileS if (localDesigns.Count == 0) return null; - var idx = _rng.Next(0, localDesigns.Count - 1); - Glamourer.Log.Verbose($"[Random Design] Chose design {idx} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); + var idx = _rng.Next(0, localDesigns.Count); + Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); return localDesigns[idx]; } From 2c0423d2b5678e10e702b574d36c6a1c79380ab1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Mar 2024 16:45:20 +0100 Subject: [PATCH 310/786] Fix color of special designs. --- Glamourer/Gui/DesignCombo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 1875b81..4bd18ab 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -48,7 +48,6 @@ public abstract class DesignComboBase : FilterComboCache Date: Fri, 8 Mar 2024 15:55:11 +0000 Subject: [PATCH 311/786] [CI] Updating repo.json for 1.2.0.7 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index f4c10ad..77d4d17 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.0.6", - "TestingAssemblyVersion": "1.2.0.6", + "AssemblyVersion": "1.2.0.7", + "TestingAssemblyVersion": "1.2.0.7", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.6/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.6/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.6/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.7/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.7/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.7/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From fdd74c0514eaecc211c0c5ca6dd1b9d92f5cc546 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Mar 2024 12:25:53 +0100 Subject: [PATCH 312/786] Add some IPC stuff. --- Glamourer/Api/GlamourerIpc.Apply.cs | 6 ++++ .../Api/GlamourerIpc.GetCustomization.cs | 32 +++++++++++++++---- Glamourer/Api/GlamourerIpc.Revert.cs | 15 +++++++++ Glamourer/Api/GlamourerIpc.cs | 11 +++++-- Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 20 +++++++++++- Penumbra.Api | 2 +- 6 files changed, 75 insertions(+), 11 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs index 94896f0..fecce92 100644 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ b/Glamourer/Api/GlamourerIpc.Apply.cs @@ -88,6 +88,12 @@ public partial class GlamourerIpc 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); diff --git a/Glamourer/Api/GlamourerIpc.GetCustomization.cs b/Glamourer/Api/GlamourerIpc.GetCustomization.cs index 0067db6..d6caf45 100644 --- a/Glamourer/Api/GlamourerIpc.GetCustomization.cs +++ b/Glamourer/Api/GlamourerIpc.GetCustomization.cs @@ -8,11 +8,15 @@ namespace Glamourer.Api; public partial class GlamourerIpc { - public const string LabelGetAllCustomization = "Glamourer.GetAllCustomization"; - public const string LabelGetAllCustomizationFromCharacter = "Glamourer.GetAllCustomizationFromCharacter"; + 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 _getAllCustomizationFromCharacterProvider; + 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); @@ -20,13 +24,25 @@ public partial class GlamourerIpc 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)); + => GetCustomization(FindActors(characterName), 0); + + public string? GetAllCustomization(string characterName, uint lockCode) + => GetCustomization(FindActors(characterName), lockCode); public string? GetAllCustomizationFromCharacter(Character? character) - => GetCustomization(FindActors(character)); + => GetCustomization(FindActors(character), 0); - private string? GetCustomization(IEnumerable actors) + 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) @@ -40,6 +56,8 @@ public partial class GlamourerIpc 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 index c5e1005..50cc3c7 100644 --- a/Glamourer/Api/GlamourerIpc.Revert.cs +++ b/Glamourer/Api/GlamourerIpc.Revert.cs @@ -16,6 +16,7 @@ public partial class GlamourerIpc 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; @@ -29,6 +30,8 @@ public partial class GlamourerIpc private readonly FuncProvider _unlockNameProvider; private readonly FuncProvider _unlockProvider; + private readonly FuncProvider _unlockAllProvider; + public static ActionSubscriber RevertSubscriber(DalamudPluginInterface pi) => new(pi, LabelRevert); @@ -47,6 +50,9 @@ public partial class GlamourerIpc 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); @@ -71,6 +77,15 @@ public partial class GlamourerIpc 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); diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 895a14f..24039b8 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -16,7 +16,7 @@ namespace Glamourer.Api; public sealed partial class GlamourerIpc : IDisposable { public const int CurrentApiVersionMajor = 0; - public const int CurrentApiVersionMinor = 4; + public const int CurrentApiVersionMinor = 5; private readonly StateManager _stateManager; private readonly ObjectManager _objects; @@ -47,6 +47,9 @@ public sealed partial class GlamourerIpc : IDisposable _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); @@ -82,6 +85,7 @@ public sealed partial class GlamourerIpc : IDisposable _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); @@ -111,7 +115,9 @@ public sealed partial class GlamourerIpc : IDisposable _apiVersionsProvider.Dispose(); _getAllCustomizationProvider.Dispose(); + _getAllCustomizationLockedProvider.Dispose(); _getAllCustomizationFromCharacterProvider.Dispose(); + _getAllCustomizationFromLockedCharacterProvider.Dispose(); _applyAllProvider.Dispose(); _applyAllOnceProvider.Dispose(); @@ -139,6 +145,7 @@ public sealed partial class GlamourerIpc : IDisposable _revertCharacterProviderLock.Dispose(); _unlockNameProvider.Dispose(); _unlockProvider.Dispose(); + _unlockAllProvider.Dispose(); _revertToAutomationProvider.Dispose(); _revertToAutomationCharacterProvider.Dispose(); @@ -158,7 +165,7 @@ public sealed partial class GlamourerIpc : IDisposable private IEnumerable FindActors(string actorName) { if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString)) - return Array.Empty(); + return []; _objects.Update(); return _objects.Where(i => i.Key is { IsValid: true, Type: IdentifierType.Player } && i.Key.PlayerName == byteString) diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index d447f9f..98fbcab 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -64,6 +64,14 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag else ImGui.TextUnformatted("Error"); + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromLockedCharacter); + ImGui.TableNextColumn(); + var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface).Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); + if (base64Locked != null) + ImGuiUtil.CopyOnClickSelectable(base64Locked); + else + ImGui.TextUnformatted("Error"); + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevert); ImGui.TableNextColumn(); if (ImGui.Button("Revert##Name")) @@ -118,7 +126,6 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface) .Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character); - ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid); ImGui.TableNextColumn(); if (ImGui.Button("Apply##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1)) @@ -141,6 +148,11 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag GlamourerIpc.ApplyByGuidOnceToCharacterSubscriber(_pluginInterface) .Invoke(guid2Once, _objectManager.Objects[_gameObjectIndex] as Character); + ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllLock); + ImGui.TableNextColumn(); + if (ImGui.Button("Apply With Lock##CustomizeCharacter")) + GlamourerIpc.ApplyAllToCharacterLockSubscriber(_pluginInterface) + .Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character, 1337); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); ImGui.TableNextColumn(); @@ -148,6 +160,12 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag GlamourerIpc.UnlockSubscriber(_pluginInterface) .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 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")) diff --git a/Penumbra.Api b/Penumbra.Api index 79ffdd6..34921fd 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 79ffdd69a28141a1ac93daa24d76573b2fa0d71e +Subproject commit 34921fd2c5a9aff5d34aef664bdb78331e8b9436 From 7af0a1d562debf606714c6457c95357f8c7d4fc0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 14 Mar 2024 16:22:14 +0100 Subject: [PATCH 313/786] Fix revert to automation. --- Glamourer/Automation/AutoDesignApplier.cs | 1 + .../Gui/Tabs/AutomationTab/AutomationTab.cs | 18 ++++++------------ Glamourer/State/InternalStateEditor.cs | 3 +-- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 4cd07e5..e21d522 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -223,6 +223,7 @@ public sealed class AutoDesignApplier : IDisposable if (!GetPlayerSet(identifier, out var set)) return; + _state.ResetState(state, StateSource.Game); Reduce(actor, state, set, false, false); } diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs index 145531f..831ee7c 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs @@ -4,25 +4,19 @@ using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.AutomationTab; -public class AutomationTab : ITab +public class AutomationTab(SetSelector selector, SetPanel panel, Configuration config) : ITab { - private readonly SetSelector _selector; - private readonly SetPanel _panel; - - public AutomationTab(SetSelector selector, SetPanel panel) - { - _selector = selector; - _panel = panel; - } - public ReadOnlySpan Label => "Automation"u8; + public bool IsVisible + => config.EnableAutoDesigns; + public void DrawContent() { - _selector.Draw(GetSetSelectorSize()); + selector.Draw(GetSetSelectorSize()); ImGui.SameLine(); - _panel.Draw(); + panel.Draw(); } public float GetSetSelectorSize() diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs index 045c0df..eaf7c21 100644 --- a/Glamourer/State/InternalStateEditor.cs +++ b/Glamourer/State/InternalStateEditor.cs @@ -223,8 +223,7 @@ public class InternalStateEditor( /// Change the value of a single material color table entry. public bool ChangeMaterialValue(ActorState state, MaterialValueIndex index, in MaterialValueState newValue, StateSource source, - out ColorRow? oldValue, - uint key = 0) + out ColorRow? oldValue, uint key = 0) { // We already have an existing value. if (state.Materials.TryGetValue(index, out var old)) From 5ddf88207727f8967f2586d457f0730748286d46 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 14 Mar 2024 16:22:27 +0100 Subject: [PATCH 314/786] Fix issue with materials. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 2 ++ Glamourer/State/StateEditor.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 36e4320..df00e1c 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -162,6 +162,7 @@ public sealed unsafe class AdvancedDyePopup( ImGui.SetNextWindowPos(position); flags |= ImGuiWindowFlags.NoMove; } + var size = new Vector2(7 * ImGui.GetFrameHeight() + 3 * ImGui.GetStyle().ItemInnerSpacing.X + 300 * ImGuiHelpers.GlobalScale, 18 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + 2 * ImGui.GetStyle().ItemSpacing.Y); ImGui.SetNextWindowSize(size); @@ -261,6 +262,7 @@ public sealed unsafe class AdvancedDyePopup( else { _anyChanged = true; + value = new MaterialValueState(value.Game, value.Model, value.DrawData, StateSource.Manual); } var buttonSize = new Vector2(ImGui.GetFrameHeight()); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 60142ae..600ab17 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -331,7 +331,7 @@ public class StateEditor( var source = settings.Source.SetPending(); if (state.Materials.TryGetValue(idx, out var materialState)) { - if (settings.RespectManual && !materialState.Source.IsManual()) + if (settings.RespectManual && materialState.Source.IsManual()) continue; if (value.Revert) From 6362c79aa21d80cbbe1b53c27e0bd511a68f3a4b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 14 Mar 2024 16:22:41 +0100 Subject: [PATCH 315/786] Make penumbra preview work in gpose for all weapons. --- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index b4db251..6e4774b 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -1,5 +1,6 @@ using Dalamud; using Glamourer.Designs; +using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; @@ -19,6 +20,7 @@ public sealed class PenumbraChangedItemTooltip : IDisposable private readonly ItemManager _items; private readonly ObjectManager _objects; private readonly CustomizeService _customize; + private readonly GPoseService _gpose; private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2]; @@ -30,13 +32,14 @@ public sealed class PenumbraChangedItemTooltip : IDisposable public DateTime LastClick { get; private set; } = DateTime.MinValue; public PenumbraChangedItemTooltip(PenumbraService penumbra, StateManager stateManager, ItemManager items, ObjectManager objects, - CustomizeService customize) + CustomizeService customize, GPoseService gpose) { _penumbra = penumbra; _stateManager = stateManager; _items = items; _objects = objects; _customize = customize; + _gpose = gpose; _penumbra.Tooltip += OnPenumbraTooltip; _penumbra.Click += OnPenumbraClick; } @@ -185,6 +188,9 @@ public sealed class PenumbraChangedItemTooltip : IDisposable private bool CanApplyWeapon(EquipSlot slot, EquipItem item) { + if (_gpose.InGPose && slot is EquipSlot.MainHand) + return true; + var main = _objects.Player.GetMainhand(); var mainItem = _items.Identify(slot, main.Skeleton, main.Weapon, main.Variant); if (slot == EquipSlot.MainHand) From 8dde1689f7f8b149037784fda4819fa5071fc965 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 14 Mar 2024 15:24:31 +0000 Subject: [PATCH 316/786] [CI] Updating repo.json for 1.2.0.8 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 77d4d17..cf3e709 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.0.7", - "TestingAssemblyVersion": "1.2.0.7", + "AssemblyVersion": "1.2.0.8", + "TestingAssemblyVersion": "1.2.0.8", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.7/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.7/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.0.7/Glamourer.zip", + "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/1.2.0.8/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 05d261d4a3e52c32f6411bfcdad4858da7cfe6ba Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 15 Mar 2024 15:36:20 +0100 Subject: [PATCH 317/786] Add Reapply Automation with prior functionality and rework header buttons for performance. --- Glamourer/Api/GlamourerIpc.Revert.cs | 2 +- Glamourer/Automation/AutoDesignApplier.cs | 5 +- Glamourer/Gui/DesignQuickBar.cs | 66 +++- Glamourer/Gui/MainWindow.cs | 17 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 283 +++++++++++------- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 11 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 280 ++++++++++------- Glamourer/Gui/Tabs/HeaderDrawer.cs | 95 +++--- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 177 +++++++---- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 5 +- Glamourer/Services/CommandService.cs | 35 ++- 11 files changed, 612 insertions(+), 364 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.Revert.cs b/Glamourer/Api/GlamourerIpc.Revert.cs index 50cc3c7..8c289e7 100644 --- a/Glamourer/Api/GlamourerIpc.Revert.cs +++ b/Glamourer/Api/GlamourerIpc.Revert.cs @@ -124,7 +124,7 @@ public partial class GlamourerIpc if (_objects.TryGetValue(id, out var data)) foreach (var obj in data.Objects) { - _autoDesignApplier.ReapplyAutomation(obj, state.Identifier, state); + _autoDesignApplier.ReapplyAutomation(obj, state.Identifier, state, true); _stateManager.ReapplyState(obj, StateSource.IpcManual); } } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index e21d522..03fdda6 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -215,7 +215,7 @@ public sealed class AutoDesignApplier : IDisposable _state.ReapplyState(actor, StateSource.Fixed); } - public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state) + public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset) { if (!_config.EnableAutoDesigns) return; @@ -223,7 +223,8 @@ public sealed class AutoDesignApplier : IDisposable if (!GetPlayerSet(identifier, out var set)) return; - _state.ResetState(state, StateSource.Game); + if (reset) + _state.ResetState(state, StateSource.Game); Reduce(actor, state, set, false, false); } diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 35f23bb..50e976f 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -19,12 +19,13 @@ namespace Glamourer.Gui; [Flags] public enum QdbButtons { - ApplyDesign = 0x01, - RevertAll = 0x02, - RevertAutomation = 0x04, - RevertAdvanced = 0x08, - RevertEquip = 0x10, - RevertCustomize = 0x20, + ApplyDesign = 0x01, + RevertAll = 0x02, + RevertAutomation = 0x04, + RevertAdvanced = 0x08, + RevertEquip = 0x10, + RevertCustomize = 0x20, + ReapplyAutomation = 0x40, } public sealed class DesignQuickBar : Window, IDisposable @@ -118,6 +119,7 @@ public sealed class DesignQuickBar : Window, IDisposable DrawRevertCustomizeButton(buttonSize); DrawRevertAdvancedCustomization(buttonSize); DrawRevertAutomationButton(buttonSize); + DrawReapplyAutomationButton(buttonSize); } private ActorIdentifier _playerIdentifier; @@ -249,7 +251,47 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { - _autoDesignApplier.ReapplyAutomation(actor, id, state!); + _autoDesignApplier.ReapplyAutomation(actor, id, state!, true); + _stateManager.ReapplyState(actor, StateSource.Manual); + } + } + + private void DrawReapplyAutomationButton(Vector2 buttonSize) + { + if (!_config.EnableAutoDesigns) + return; + + if (!_config.QdbButtons.HasFlag(QdbButtons.ReapplyAutomation)) + return; + + var available = 0; + var tooltip = string.Empty; + + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) + { + available |= 1; + tooltip = "Left-Click: Reapply the player character's current automation on top of their current state."; + } + + if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Reapply {_targetIdentifier}'s current automation on top of their current state."; + } + + if (available == 0) + tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.Repeat, buttonSize, tooltip, available); + ImGui.SameLine(); + if (!clicked) + return; + + foreach (var actor in data.Objects) + { + _autoDesignApplier.ReapplyAutomation(actor, id, state!, false); _stateManager.ReapplyState(actor, StateSource.Manual); } } @@ -385,8 +427,14 @@ public sealed class DesignQuickBar : Window, IDisposable _numButtons = 0; if (_config.QdbButtons.HasFlag(QdbButtons.RevertAll)) ++_numButtons; - if (_config.EnableAutoDesigns && _config.QdbButtons.HasFlag(QdbButtons.RevertAutomation)) - ++_numButtons; + if (_config.EnableAutoDesigns) + { + if (_config.QdbButtons.HasFlag(QdbButtons.RevertAutomation)) + ++_numButtons; + if (_config.QdbButtons.HasFlag(QdbButtons.ReapplyAutomation)) + ++_numButtons; + } + if ((_config.UseAdvancedParameters || _config.UseAdvancedDyes) && _config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize)) diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index e3186af..0cc8b9b 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -55,12 +55,12 @@ public class MainWindow : Window, IDisposable public readonly NpcTab Npcs; public readonly MessagesTab Messages; - public TabType SelectTab = TabType.None; + public TabType SelectTab; 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) - : base(GetLabel()) + : base("GlamourerMainWindow") { pi.UiBuilder.DisableGposeUiHide = true; SizeConstraints = new WindowSizeConstraints() @@ -102,6 +102,7 @@ public class MainWindow : Window, IDisposable ? Flags | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize : Flags & ~(ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); _position.IsOpen = IsOpen; + WindowName = GetLabel(); } public void Dispose() @@ -177,8 +178,12 @@ public class MainWindow : Window, IDisposable IsOpen = true; } - private static string GetLabel() - => Glamourer.Version.Length == 0 - ? "Glamourer###GlamourerMainWindow" - : $"Glamourer v{Glamourer.Version}###GlamourerMainWindow"; + private string GetLabel() + => (Glamourer.Version.Length == 0, _config.Ephemeral.IncognitoMode) switch + { + (true, true) => "Glamourer (Incognito Mode)###GlamourerMainWindow", + (true, false) => "Glamourer###GlamourerMainWindow", + (false, false) => $"Glamourer v{Glamourer.Version}###GlamourerMainWindow", + (false, true) => $"Glamourer v{Glamourer.Version} (Incognito Mode)###GlamourerMainWindow", + }; } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index b205127..3cf50f5 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -21,22 +21,68 @@ using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorPanel( - ActorSelector _selector, - StateManager _stateManager, - CustomizationDrawer _customizationDrawer, - EquipmentDrawer _equipmentDrawer, - AutoDesignApplier _autoDesignApplier, - Configuration _config, - DesignConverter _converter, - ObjectManager _objects, - DesignManager _designManager, - ImportService _importService, - ICondition _conditions, - DictModelChara _modelChara, - CustomizeParameterDrawer _parameterDrawer, - AdvancedDyePopup _advancedDyes) +public class ActorPanel { + private readonly ActorSelector _selector; + private readonly StateManager _stateManager; + private readonly CustomizationDrawer _customizationDrawer; + private readonly EquipmentDrawer _equipmentDrawer; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly Configuration _config; + private readonly DesignConverter _converter; + private readonly ObjectManager _objects; + private readonly DesignManager _designManager; + private readonly ImportService _importService; + private readonly ICondition _conditions; + private readonly DictModelChara _modelChara; + private readonly CustomizeParameterDrawer _parameterDrawer; + private readonly AdvancedDyePopup _advancedDyes; + private readonly HeaderDrawer.Button[] _leftButtons; + private readonly HeaderDrawer.Button[] _rightButtons; + + public ActorPanel(ActorSelector selector, + StateManager stateManager, + CustomizationDrawer customizationDrawer, + EquipmentDrawer equipmentDrawer, + AutoDesignApplier autoDesignApplier, + Configuration config, + DesignConverter converter, + ObjectManager objects, + DesignManager designManager, + ImportService importService, + ICondition conditions, + DictModelChara modelChara, + CustomizeParameterDrawer parameterDrawer, + AdvancedDyePopup advancedDyes) + { + _selector = selector; + _stateManager = stateManager; + _customizationDrawer = customizationDrawer; + _equipmentDrawer = equipmentDrawer; + _autoDesignApplier = autoDesignApplier; + _config = config; + _converter = converter; + _objects = objects; + _designManager = designManager; + _importService = importService; + _conditions = conditions; + _modelChara = modelChara; + _parameterDrawer = parameterDrawer; + _advancedDyes = advancedDyes; + _leftButtons = + [ + new SetFromClipboardButton(this), + new ExportToClipboardButton(this), + new SaveAsDesignButton(this), + ]; + _rightButtons = + [ + new LockedButton(this), + new HeaderDrawer.IncognitoButton(_config.Ephemeral), + ]; + } + + private ActorIdentifier _identifier; private string _actorName = string.Empty; private Actor _actor = Actor.Null; @@ -81,9 +127,7 @@ public class ActorPanel( { var textColor = !_identifier.IsValid ? ImGui.GetColorU32(ImGuiCol.Text) : _data.Valid ? ColorId.ActorAvailable.Value() : ColorId.ActorUnavailable.Value(); - HeaderDrawer.Draw(_actorName, textColor, ImGui.GetColorU32(ImGuiCol.FrameBg), - 3, SetFromClipboardButton(), ExportToClipboardButton(), SaveAsDesignButton(), LockedButton(), - HeaderDrawer.Button.IncognitoButton(_selector.IncognitoMode, v => _selector.IncognitoMode = v)); + HeaderDrawer.Draw(_actorName, textColor, ImGui.GetColorU32(ImGuiCol.FrameBg), _leftButtons, _rightButtons); SaveDesignDrawPopup(); } @@ -269,59 +313,9 @@ public class ActorPanel( _stateManager.TurnHuman(_state, StateSource.Manual); } - private HeaderDrawer.Button SetFromClipboardButton() - => new() - { - Description = - "Try to apply a design from your clipboard.\nHold Control to only apply gear.\nHold Shift to only apply customizations.", - Icon = FontAwesomeIcon.Clipboard, - OnClick = SetFromClipboard, - Visible = _state != null, - Disabled = _state?.IsLocked ?? true, - }; - - private HeaderDrawer.Button ExportToClipboardButton() - => new() - { - Description = - "Copy the current design to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design.", - Icon = FontAwesomeIcon.Copy, - OnClick = ExportToClipboard, - Visible = _state?.ModelData.ModelId == 0, - }; - - private HeaderDrawer.Button SaveAsDesignButton() - => new() - { - Description = - "Save the current state as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design.", - Icon = FontAwesomeIcon.Save, - OnClick = SaveDesignOpen, - Visible = _state?.ModelData.ModelId == 0, - }; - - private HeaderDrawer.Button LockedButton() - => new() - { - Description = "The current state of this actor is locked by external tools.", - Icon = FontAwesomeIcon.Lock, - OnClick = () => { }, - Disabled = true, - Visible = _state?.IsLocked ?? false, - TextColor = ColorId.ActorUnavailable.Value(), - BorderColor = ColorId.ActorUnavailable.Value(), - }; - private string _newName = string.Empty; private DesignBase? _newDesign; - private void SaveDesignOpen() - { - ImGui.OpenPopup("Save as Design"); - _newName = _state!.Identifier.ToName(); - _newDesign = _converter.Convert(_state, ApplicationRules.FromModifiers(_state)); - } - private void SaveDesignDrawPopup() { if (!ImGuiUtil.OpenNameField("Save as Design", ref _newName)) @@ -333,37 +327,6 @@ public class ActorPanel( _newName = string.Empty; } - private void SetFromClipboard() - { - try - { - var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToBool(); - var text = ImGui.GetClipboardText(); - var design = _converter.FromBase64(text, applyCustomize, applyGear, out _) - ?? throw new Exception("The clipboard did not contain valid data."); - _stateManager.ApplyDesign(_state!, design, ApplySettings.ManualWithLinks); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {_identifier}.", - $"Could not apply clipboard to design {_identifier.Incognito(null)}", NotificationType.Error, false); - } - } - - private void ExportToClipboard() - { - try - { - var text = _converter.ShareBase64(_state!, ApplicationRules.FromModifiers(_state!)); - ImGui.SetClipboardText(text); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not copy {_identifier} data to clipboard.", - $"Could not copy data from design {_identifier.Incognito(null)} to clipboard", NotificationType.Error); - } - } - private void RevertButtons() { if (ImGuiUtil.DrawDisabledButton("Revert to Game", Vector2.Zero, "Revert the character to its actual state in the game.", @@ -371,18 +334,29 @@ public class ActorPanel( _stateManager.ResetState(_state!, StateSource.Manual); ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton("Reapply State", Vector2.Zero, "Try to reapply the configured state if something went wrong.", - _state!.IsLocked)) + + if (ImGuiUtil.DrawDisabledButton("Reapply Automation", Vector2.Zero, + "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); + } ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton("Reapply Automation", Vector2.Zero, + if (ImGuiUtil.DrawDisabledButton("Revert to Automation", Vector2.Zero, "Try to revert the character to the state it would have using automated designs.", !_config.EnableAutoDesigns || _state!.IsLocked)) { - _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!); + _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true); _stateManager.ReapplyState(_actor, 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); } private void DrawApplyToSelf() @@ -414,4 +388,107 @@ public class ActorPanel( _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), ApplySettings.Manual); } + + + private sealed class SetFromClipboardButton(ActorPanel panel) + : HeaderDrawer.Button + { + protected override string Description + => "Try to apply a design from your clipboard.\nHold Control to only apply gear.\nHold Shift to only apply customizations."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Clipboard; + + public override bool Visible + => panel._state != null; + + protected override bool Disabled + => panel._state?.IsLocked ?? true; + + protected override void OnClick() + { + try + { + var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToBool(); + var text = ImGui.GetClipboardText(); + var design = panel._converter.FromBase64(text, applyCustomize, applyGear, out _) + ?? throw new Exception("The clipboard did not contain valid data."); + panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {panel._identifier}.", + $"Could not apply clipboard to design {panel._identifier.Incognito(null)}", NotificationType.Error, false); + } + } + } + + private sealed class ExportToClipboardButton(ActorPanel panel) : HeaderDrawer.Button + { + protected override string Description + => "Copy the current design to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Copy; + + public override bool Visible + => panel._state?.ModelData.ModelId == 0; + + protected override void OnClick() + { + try + { + var text = panel._converter.ShareBase64(panel._state!, ApplicationRules.FromModifiers(panel._state!)); + ImGui.SetClipboardText(text); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._identifier} data to clipboard.", + $"Could not copy data from design {panel._identifier.Incognito(null)} to clipboard", NotificationType.Error); + } + } + } + + private sealed class SaveAsDesignButton(ActorPanel panel) : HeaderDrawer.Button + { + protected override string Description + => "Save the current state as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Save; + + public override bool Visible + => panel._state?.ModelData.ModelId == 0; + + protected override void OnClick() + { + ImGui.OpenPopup("Save as Design"); + panel._newName = panel._state!.Identifier.ToName(); + panel._newDesign = panel._converter.Convert(panel._state, ApplicationRules.FromModifiers(panel._state)); + } + } + + private sealed class LockedButton(ActorPanel panel) : HeaderDrawer.Button + { + protected override string Description + => "The current state of this actor is locked by external tools."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Lock; + + public override bool Visible + => panel._state?.IsLocked ?? false; + + protected override bool Disabled + => true; + + protected override uint BorderColor + => ColorId.ActorUnavailable.Value(); + + protected override uint TextColor + => ColorId.ActorUnavailable.Value(); + + protected override void OnClick() + { } + } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index bdf7861..7da95a3 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -29,10 +29,10 @@ public class SetPanel( Configuration _config, RandomRestrictionDrawer _randomDrawer) { - private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log); - - private string? _tempName; - private int _dragIndex = -1; + private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log); + private readonly HeaderDrawer.Button[] _rightButtons = [new HeaderDrawer.IncognitoButton(_config.Ephemeral)]; + private string? _tempName; + private int _dragIndex = -1; private Action? _endAction; @@ -47,8 +47,7 @@ public class SetPanel( } private void DrawHeader() - => HeaderDrawer.Draw(_selector.SelectionName, 0, ImGui.GetColorU32(ImGuiCol.FrameBg), 0, - HeaderDrawer.Button.IncognitoButton(_selector.IncognitoMode, v => _selector.IncognitoMode = v)); + => HeaderDrawer.Draw(_selector.SelectionName, 0, ImGui.GetColorU32(ImGuiCol.FrameBg), [], _rightButtons); private void DrawPanel() { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 1e3e468..f00a74c 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -15,79 +15,79 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using Penumbra.GameData.Enums; +using System; +using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignPanel( - DesignFileSystemSelector _selector, - CustomizationDrawer _customizationDrawer, - DesignManager _manager, - ObjectManager _objects, - StateManager _state, - EquipmentDrawer _equipmentDrawer, - ModAssociationsTab _modAssociations, - Configuration _config, - DesignDetailTab _designDetails, - DesignConverter _converter, - ImportService _importService, - MultiDesignPanel _multiDesignPanel, - CustomizeParameterDrawer _parameterDrawer, - DesignLinkDrawer _designLinkDrawer, - MaterialDrawer _materials) +public class DesignPanel { - private readonly FileDialogManager _fileDialog = new(); + private readonly FileDialogManager _fileDialog = new(); + private readonly DesignFileSystemSelector _selector; + private readonly CustomizationDrawer _customizationDrawer; + private readonly DesignManager _manager; + private readonly ObjectManager _objects; + private readonly StateManager _state; + private readonly EquipmentDrawer _equipmentDrawer; + private readonly ModAssociationsTab _modAssociations; + private readonly Configuration _config; + private readonly DesignDetailTab _designDetails; + private readonly ImportService _importService; + private readonly DesignConverter _converter; + private readonly MultiDesignPanel _multiDesignPanel; + private readonly CustomizeParameterDrawer _parameterDrawer; + private readonly DesignLinkDrawer _designLinkDrawer; + private readonly MaterialDrawer _materials; + private readonly Button[] _leftButtons; + private readonly Button[] _rightButtons; - private HeaderDrawer.Button LockButton() - => _selector.Selected == null - ? HeaderDrawer.Button.Invisible - : _selector.Selected.WriteProtected() - ? new HeaderDrawer.Button - { - Description = "Make this design editable.", - Icon = FontAwesomeIcon.Lock, - OnClick = () => _manager.SetWriteProtection(_selector.Selected!, false), - } - : new HeaderDrawer.Button - { - Description = "Write-protect this design.", - Icon = FontAwesomeIcon.LockOpen, - OnClick = () => _manager.SetWriteProtection(_selector.Selected!, true), - }; - private HeaderDrawer.Button SetFromClipboardButton() - => new() - { - Description = - "Try to apply a design from your clipboard over this design.\nHold Control to only apply gear.\nHold Shift to only apply customizations.", - Icon = FontAwesomeIcon.Clipboard, - OnClick = SetFromClipboard, - Visible = _selector.Selected != null, - Disabled = _selector.Selected?.WriteProtected() ?? true, - }; - - private HeaderDrawer.Button UndoButton() - => new() - { - Description = "Undo the last change if you accidentally overwrote your design with a different one.", - Icon = FontAwesomeIcon.Undo, - OnClick = UndoOverwrite, - Visible = _selector.Selected != null, - Disabled = !_manager.CanUndo(_selector.Selected), - }; - - private HeaderDrawer.Button ExportToClipboardButton() - => new() - { - Description = "Copy the current design to your clipboard.", - Icon = FontAwesomeIcon.Copy, - OnClick = ExportToClipboard, - Visible = _selector.Selected != null, - }; + public DesignPanel(DesignFileSystemSelector selector, + CustomizationDrawer customizationDrawer, + DesignManager manager, + ObjectManager objects, + StateManager state, + EquipmentDrawer equipmentDrawer, + ModAssociationsTab modAssociations, + Configuration config, + DesignDetailTab designDetails, + DesignConverter converter, + ImportService importService, + MultiDesignPanel multiDesignPanel, + CustomizeParameterDrawer parameterDrawer, + DesignLinkDrawer designLinkDrawer, + MaterialDrawer materials) + { + _selector = selector; + _customizationDrawer = customizationDrawer; + _manager = manager; + _objects = objects; + _state = state; + _equipmentDrawer = equipmentDrawer; + _modAssociations = modAssociations; + _config = config; + _designDetails = designDetails; + _importService = importService; + _converter = converter; + _multiDesignPanel = multiDesignPanel; + _parameterDrawer = parameterDrawer; + _designLinkDrawer = designLinkDrawer; + _materials = materials; + _leftButtons = + [ + new SetFromClipboardButton(this), + new UndoButton(this), + new ExportToClipboardButton(this), + ]; + _rightButtons = + [ + new LockButton(this), + new IncognitoButton(_config.Ephemeral), + ]; + } private void DrawHeader() - => HeaderDrawer.Draw(SelectionName, 0, ImGui.GetColorU32(ImGuiCol.FrameBg), - 3, SetFromClipboardButton(), UndoButton(), ExportToClipboardButton(), LockButton(), - HeaderDrawer.Button.IncognitoButton(_selector.IncognitoMode, v => _selector.IncognitoMode = v)); + => HeaderDrawer.Draw(SelectionName, 0, ImGui.GetColorU32(ImGuiCol.FrameBg), _leftButtons, _rightButtons); private string SelectionName => _selector.Selected == null ? "No Selection" : _selector.IncognitoMode ? _selector.Selected.Incognito : _selector.Selected.Name.Text; @@ -397,49 +397,6 @@ public class DesignPanel( DrawSaveToDat(); } - private void SetFromClipboard() - { - try - { - var text = ImGui.GetClipboardText(); - var (applyEquip, applyCustomize) = UiHelpers.ConvertKeysToBool(); - var design = _converter.FromBase64(text, applyCustomize, applyEquip, out _) - ?? throw new Exception("The clipboard did not contain valid data."); - _manager.ApplyDesign(_selector.Selected!, design); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not apply clipboard to {_selector.Selected!.Name}.", - $"Could not apply clipboard to design {_selector.Selected!.Identifier}", NotificationType.Error, false); - } - } - - private void UndoOverwrite() - { - try - { - _manager.UndoDesignChange(_selector.Selected!); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {_selector.Selected!.Name}.", NotificationType.Error, - false); - } - } - - private void ExportToClipboard() - { - try - { - var text = _converter.ShareBase64(_selector.Selected!); - ImGui.SetClipboardText(text); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not copy {_selector.Selected!.Name} data to clipboard.", - $"Could not copy data from design {_selector.Selected!.Identifier} to clipboard", NotificationType.Error, false); - } - } private void DrawApplyToSelf() { @@ -497,4 +454,111 @@ public class DesignPanel( private static unsafe string GetUserPath() => Framework.Instance()->UserPath; + + + private sealed class LockButton(DesignPanel panel) : Button + { + public override bool Visible + => panel._selector.Selected != null; + + protected override string Description + => panel._selector.Selected!.WriteProtected() + ? "Make this design editable." + : "Write-protect this design."; + + protected override FontAwesomeIcon Icon + => panel._selector.Selected!.WriteProtected() + ? FontAwesomeIcon.Lock + : FontAwesomeIcon.LockOpen; + + protected override void OnClick() + => panel._manager.SetWriteProtection(panel._selector.Selected!, !panel._selector.Selected!.WriteProtected()); + } + + private sealed class SetFromClipboardButton(DesignPanel panel) : Button + { + public override bool Visible + => panel._selector.Selected != null; + + protected override bool Disabled + => panel._selector.Selected?.WriteProtected() ?? true; + + protected override string Description + => "Try to apply a design from your clipboard over this design.\nHold Control to only apply gear.\nHold Shift to only apply customizations."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Clipboard; + + protected override void OnClick() + { + try + { + var text = ImGui.GetClipboardText(); + var (applyEquip, applyCustomize) = UiHelpers.ConvertKeysToBool(); + var design = panel._converter.FromBase64(text, applyCustomize, applyEquip, out _) + ?? 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 clipboard to {panel._selector.Selected!.Name}.", + $"Could not apply clipboard to design {panel._selector.Selected!.Identifier}", NotificationType.Error, false); + } + } + } + + private sealed class UndoButton(DesignPanel panel) : Button + { + public override bool Visible + => panel._selector.Selected != null; + + protected override bool Disabled + => !panel._manager.CanUndo(panel._selector.Selected); + + protected override string Description + => "Undo the last change if you accidentally overwrote your design with a different one."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Undo; + + protected override void OnClick() + { + try + { + panel._manager.UndoDesignChange(panel._selector.Selected!); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not undo last changes to {panel._selector.Selected!.Name}.", + NotificationType.Error, + false); + } + } + } + + private sealed class ExportToClipboardButton(DesignPanel panel) : Button + { + public override bool Visible + => panel._selector.Selected != null; + + protected override string Description + => "Copy the current design to your clipboard."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Copy; + + protected override void OnClick() + { + try + { + var text = panel._converter.ShareBase64(panel._selector.Selected!); + ImGui.SetClipboardText(text); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._selector.Selected!.Name} data to clipboard.", + $"Could not copy data from design {panel._selector.Selected!.Identifier} to clipboard", NotificationType.Error, false); + } + } + } } diff --git a/Glamourer/Gui/Tabs/HeaderDrawer.cs b/Glamourer/Gui/Tabs/HeaderDrawer.cs index 0690f97..0e9237d 100644 --- a/Glamourer/Gui/Tabs/HeaderDrawer.cs +++ b/Glamourer/Gui/Tabs/HeaderDrawer.cs @@ -8,76 +8,77 @@ namespace Glamourer.Gui.Tabs; public static class HeaderDrawer { - public struct Button + public abstract class Button { - public static readonly Button Invisible = new() - { - Visible = false, - Width = 0, - }; + protected abstract void OnClick(); - public Action? OnClick; - public string Description = string.Empty; - public float Width; - public uint BorderColor; - public uint TextColor; - public FontAwesomeIcon Icon; - public bool Disabled; - public bool Visible; + protected virtual string Description + => string.Empty; - public Button() - { - Visible = true; - Width = ImGui.GetFrameHeightWithSpacing(); - BorderColor = ColorId.HeaderButtons.Value(); - TextColor = ColorId.HeaderButtons.Value(); - Disabled = false; - } + protected virtual uint BorderColor + => ColorId.HeaderButtons.Value(); - public readonly void Draw() + protected virtual uint TextColor + => ColorId.HeaderButtons.Value(); + + protected virtual FontAwesomeIcon Icon + => FontAwesomeIcon.None; + + protected virtual bool Disabled + => false; + + public virtual bool Visible + => true; + + public void Draw(float width) { if (!Visible) return; using var color = ImRaii.PushColor(ImGuiCol.Border, BorderColor) .Push(ImGuiCol.Text, TextColor, TextColor != 0); - if (ImGuiUtil.DrawDisabledButton(Icon.ToIconString(), new Vector2(Width, ImGui.GetFrameHeight()), string.Empty, Disabled, true)) - OnClick?.Invoke(); + if (ImGuiUtil.DrawDisabledButton(Icon.ToIconString(), new Vector2(width, ImGui.GetFrameHeight()), string.Empty, Disabled, true)) + OnClick(); color.Pop(); ImGuiUtil.HoverTooltip(Description); } - - public static Button IncognitoButton(bool current, Action setter) - => current - ? new Button - { - Description = "Toggle incognito mode off.", - Icon = FontAwesomeIcon.EyeSlash, - OnClick = () => setter(false), - } - : new Button - { - Description = "Toggle incognito mode on.", - Icon = FontAwesomeIcon.Eye, - OnClick = () => setter(true), - }; } - public static void Draw(string text, uint textColor, uint frameColor, int leftButtons, params Button[] buttons) + public sealed class IncognitoButton(EphemeralConfig config) : Button { + protected override string Description + => config.IncognitoMode + ? "Toggle incognito mode off." + : "Toggle incognito mode on."; + + protected override FontAwesomeIcon Icon + => config.IncognitoMode + ? FontAwesomeIcon.EyeSlash + : FontAwesomeIcon.Eye; + + protected override void OnClick() + { + config.IncognitoMode = !config.IncognitoMode; + config.Save(); + } + } + + public static void Draw(string text, uint textColor, uint frameColor, Button[] leftButtons, Button[] rightButtons) + { + var width = ImGui.GetFrameHeightWithSpacing(); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) .Push(ImGuiStyleVar.FrameRounding, 0) .Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); var leftButtonSize = 0f; - foreach (var button in buttons.Take(leftButtons).Where(b => b.Visible)) + foreach (var button in leftButtons.Where(b => b.Visible)) { - button.Draw(); + button.Draw(width); ImGui.SameLine(); - leftButtonSize += button.Width; + leftButtonSize += width; } - var rightButtonSize = buttons.Length > leftButtons ? buttons.Skip(leftButtons).Where(b => b.Visible).Select(b => b.Width).Sum() : 0f; + var rightButtonSize = rightButtons.Count(b => b.Visible) * width; var midSize = ImGui.GetContentRegionAvail().X - rightButtonSize - ImGuiHelpers.GlobalScale; style.Pop(); @@ -89,10 +90,10 @@ public static class HeaderDrawer style.Pop(); style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); - foreach (var button in buttons.Skip(leftButtons).Where(b => b.Visible)) + foreach (var button in rightButtons.Where(b => b.Visible)) { ImGui.SameLine(); - button.Draw(); + button.Draw(width); } } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index eb61580..c08d5c9 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -12,23 +12,57 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using Penumbra.GameData.Enums; +using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.NpcTab; -public class NpcPanel( - NpcSelector _selector, - LocalNpcAppearanceData _favorites, - CustomizationDrawer _customizeDrawer, - EquipmentDrawer _equipDrawer, - DesignConverter _converter, - DesignManager _designManager, - StateManager _state, - ObjectManager _objects, - DesignColors _colors) +public class NpcPanel { - private readonly DesignColorCombo _colorCombo = new(_colors, true); - private string _newName = string.Empty; - private DesignBase? _newDesign; + private readonly DesignColorCombo _colorCombo; + private string _newName = string.Empty; + private DesignBase? _newDesign; + private readonly NpcSelector _selector; + private readonly LocalNpcAppearanceData _favorites; + private readonly CustomizationDrawer _customizeDrawer; + private readonly EquipmentDrawer _equipDrawer; + private readonly DesignConverter _converter; + private readonly DesignManager _designManager; + private readonly StateManager _state; + private readonly ObjectManager _objects; + private readonly DesignColors _colors; + private readonly Button[] _leftButtons; + private readonly Button[] _rightButtons; + + public NpcPanel(NpcSelector selector, + LocalNpcAppearanceData favorites, + CustomizationDrawer customizeDrawer, + EquipmentDrawer equipDrawer, + DesignConverter converter, + DesignManager designManager, + StateManager state, + ObjectManager objects, + DesignColors colors) + { + _selector = selector; + _favorites = favorites; + _customizeDrawer = customizeDrawer; + _equipDrawer = equipDrawer; + _converter = converter; + _designManager = designManager; + _state = state; + _objects = objects; + _colors = colors; + _colorCombo = new DesignColorCombo(colors, true); + _leftButtons = + [ + new ExportToClipboardButton(this), + new SaveAsDesignButton(this), + ]; + _rightButtons = + [ + new FavoriteButton(this), + ]; + } public void Draw() { @@ -41,67 +75,30 @@ public class NpcPanel( private void DrawHeader() { HeaderDrawer.Draw(_selector.HasSelection ? _selector.Selection.Name : "No Selection", ColorId.NormalDesign.Value(), - ImGui.GetColorU32(ImGuiCol.FrameBg), 2, ExportToClipboardButton(), SaveAsDesignButton(), FavoriteButton()); + ImGui.GetColorU32(ImGuiCol.FrameBg), _leftButtons, _rightButtons); SaveDesignDrawPopup(); } - private HeaderDrawer.Button FavoriteButton() + private sealed class FavoriteButton(NpcPanel panel) : Button { - var (desc, color) = _favorites.IsFavorite(_selector.Selection) - ? ("Remove this NPC appearance from your favorites.", ColorId.FavoriteStarOn.Value()) - : ("Add this NPC Appearance to your favorites.", 0x80000000); - return new HeaderDrawer.Button - { - Icon = FontAwesomeIcon.Star, - OnClick = () => _favorites.ToggleFavorite(_selector.Selection), - Visible = _selector.HasSelection, - Description = desc, - TextColor = color, - }; - } + protected override string Description + => panel._favorites.IsFavorite(panel._selector.Selection) + ? "Remove this NPC appearance from your favorites." + : "Add this NPC Appearance to your favorites."; - private HeaderDrawer.Button ExportToClipboardButton() - => new() - { - Description = - "Copy the current NPCs appearance to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design.", - Icon = FontAwesomeIcon.Copy, - OnClick = ExportToClipboard, - Visible = _selector.HasSelection, - }; + protected override uint TextColor + => panel._favorites.IsFavorite(panel._selector.Selection) + ? ColorId.FavoriteStarOn.Value() + : 0x80000000; - private HeaderDrawer.Button SaveAsDesignButton() - => new() - { - Description = - "Save this NPCs appearance as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design.", - Icon = FontAwesomeIcon.Save, - OnClick = SaveDesignOpen, - Visible = _selector.HasSelection, - }; + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Star; - private void ExportToClipboard() - { - try - { - var data = ToDesignData(); - var text = _converter.ShareBase64(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); - ImGui.SetClipboardText(text); - } - catch (Exception ex) - { - Glamourer.Messager.NotificationMessage(ex, $"Could not copy {_selector.Selection.Name}'s data to clipboard.", - $"Could not copy data from NPC appearance {_selector.Selection.Kind} {_selector.Selection.Id.Id} to clipboard", - NotificationType.Error); - } - } + public override bool Visible + => panel._selector.HasSelection; - private void SaveDesignOpen() - { - ImGui.OpenPopup("Save as Design"); - _newName = _selector.Selection.Name; - var data = ToDesignData(); - _newDesign = _converter.Convert(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); + protected override void OnClick() + => panel._favorites.ToggleFavorite(panel._selector.Selection); } private void SaveDesignDrawPopup() @@ -289,4 +286,52 @@ public class NpcPanel( ImGuiUtil.HoverTooltip("Click to copy to clipboard."); } } + + private sealed class ExportToClipboardButton(NpcPanel panel) : Button + { + protected override string Description + => "Copy the current NPCs appearance to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Copy; + + public override bool Visible + => panel._selector.HasSelection; + + protected override void OnClick() + { + try + { + var data = panel.ToDesignData(); + var text = panel._converter.ShareBase64(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); + ImGui.SetClipboardText(text); + } + catch (Exception ex) + { + Glamourer.Messager.NotificationMessage(ex, $"Could not copy {panel._selector.Selection.Name}'s data to clipboard.", + $"Could not copy data from NPC appearance {panel._selector.Selection.Kind} {panel._selector.Selection.Id.Id} to clipboard", + NotificationType.Error); + } + } + } + + private sealed class SaveAsDesignButton(NpcPanel panel) : Button + { + protected override string Description + => "Save this NPCs appearance as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Save; + + public override bool Visible + => panel._selector.HasSelection; + + protected override void OnClick() + { + ImGui.OpenPopup("Save as Design"); + panel._newName = panel._selector.Selection.Name; + var data = panel.ToDesignData(); + panel._newDesign = panel._converter.Convert(data, new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); + } + } } diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index e988d13..223a0a8 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -211,7 +211,7 @@ public class SettingsTab( { var showAuto = config.EnableAutoDesigns; var showAdvanced = config.UseAdvancedParameters || config.UseAdvancedDyes; - var numColumns = 6 - (showAuto ? 0 : 1) - (showAdvanced ? 0 : 1); + var numColumns = 7 - (showAuto ? 0 : 2) - (showAdvanced ? 0 : 1); ImGui.NewLine(); ImGui.TextUnformatted("Show the Following Buttons in the Quick Design Bar:"); ImGui.Dummy(Vector2.Zero); @@ -225,8 +225,9 @@ public class SettingsTab( (" Apply Design ", true, QdbButtons.ApplyDesign), (" Revert All ", true, QdbButtons.RevertAll), (" Revert to Auto ", showAuto, QdbButtons.RevertAutomation), + (" Reapply Auto ", showAuto, QdbButtons.ReapplyAutomation), (" Revert Equip ", true, QdbButtons.RevertEquip), - (" Revert Customization ", true, QdbButtons.RevertCustomize), + (" Revert Customize ", true, QdbButtons.RevertCustomize), (" Revert Advanced ", showAdvanced, QdbButtons.RevertAdvanced), }; diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index f9f5636..0b32cfc 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -115,16 +115,17 @@ public class CommandService : IDisposable var argument = argumentList.Length == 2 ? argumentList[1] : string.Empty; var _ = argumentList[0].ToLowerInvariant() switch { - "apply" => Apply(argument), - "reapply" => ReapplyState(argument), - "revert" => Revert(argument), - "reapplyautomation" => ReapplyAutomation(argument), - "automation" => SetAutomation(argument), - "copy" => CopyState(argument), - "save" => SaveState(argument), - "delete" => Delete(argument), - "applyitem" => ApplyItem(argument), - _ => PrintHelp(argumentList[0]), + "apply" => Apply(argument), + "reapply" => ReapplyState(argument), + "revert" => Revert(argument), + "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false), + "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true), + "automation" => SetAutomation(argument), + "copy" => CopyState(argument), + "save" => SaveState(argument), + "delete" => Delete(argument), + "applyitem" => ApplyItem(argument), + _ => PrintHelp(argumentList[0]), }; } @@ -143,6 +144,8 @@ public class CommandService : IDisposable _chat.Print(new SeStringBuilder().AddCommand("revert", "Reverts a given character to its game state. Use without arguments for help.") .BuiltString); _chat.Print(new SeStringBuilder().AddCommand("reapplyautomation", + "Reapplies the current automation state on top of the characters current state.. Use without arguments for help.").BuiltString); + _chat.Print(new SeStringBuilder().AddCommand("reverttoautomation", "Reverts a given character to its supposed state using automated designs. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder() .AddCommand("copy", "Copy the current state of a character to clipboard. Use without arguments for help.").BuiltString); @@ -296,11 +299,11 @@ public class CommandService : IDisposable return true; } - private bool ReapplyAutomation(string argument) + private bool ReapplyAutomation(string argument, string command, bool revert) { if (argument.Length == 0) { - _chat.Print(new SeStringBuilder().AddText("Use with /glamour reapplyautomation ").AddGreen("[Character Identifier]").BuiltString); + _chat.Print(new SeStringBuilder().AddText($"Use with /glamour {command} ").AddGreen("[Character Identifier]").BuiltString); PlayerIdentifierHelp(false, true); return true; } @@ -318,7 +321,7 @@ public class CommandService : IDisposable { if (_stateManager.GetOrCreate(identifier, actor, out var state)) { - _autoDesignApplier.ReapplyAutomation(actor, identifier, state); + _autoDesignApplier.ReapplyAutomation(actor, identifier, state, revert); _stateManager.ReapplyState(actor, StateSource.Manual); } } @@ -474,7 +477,10 @@ public class CommandService : IDisposable _chat.Print(new SeStringBuilder() .AddText(" 》 Clipboard as a single word will try to apply a design string currently in your clipboard.").BuiltString); _chat.Print(new SeStringBuilder() - .AddText(" 》 ").AddYellow("Random").AddText(" supports many restrictions, see the Restriction Builder when adding a Random design to Automations for valid strings.").BuiltString); + .AddText(" 》 ").AddYellow("Random") + .AddText( + " supports many restrictions, see the Restriction Builder when adding a Random design to Automations for valid strings.") + .BuiltString); _chat.Print(new SeStringBuilder() .AddText(" 》 ").AddBlue("").AddText(" is optional and can be omitted (together with the ;), ").AddBlue("true") .AddText(" or ").AddBlue("false").AddText(".").BuiltString); @@ -677,6 +683,7 @@ public class CommandService : IDisposable _chat.Print(new SeStringBuilder().AddText("No design matched your restrictions.").BuiltString); return false; } + _chat.Print($"Chose random design {((Design)design).Name}."); } catch (Exception ex) From 9750736d57ef0333b7ff72dea19e753a44cd01fb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 19 Mar 2024 23:19:46 +0100 Subject: [PATCH 318/786] Use Penumbra.GameData Actor and Model and ObjectManager. --- Glamourer/Automation/AutoDesign.cs | 2 +- Glamourer/Automation/AutoDesignApplier.cs | 3 +- Glamourer/Events/HeadGearVisibilityChanged.cs | 2 +- Glamourer/Events/SlotUpdating.cs | 2 +- Glamourer/Events/VisorStateChanged.cs | 2 +- Glamourer/Events/WeaponLoading.cs | 2 +- Glamourer/Events/WeaponVisibilityChanged.cs | 2 +- Glamourer/Glamourer.csproj | 2 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 2 + Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 30 +- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 5 +- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 2 +- Glamourer/Interop/ChangeCustomizeService.cs | 2 +- Glamourer/Interop/CrestService.cs | 2 +- Glamourer/Interop/JobService.cs | 4 +- .../Material/LiveColorTablePreviewer.cs | 13 +- Glamourer/Interop/Material/MaterialManager.cs | 2 +- Glamourer/Interop/Material/MaterialService.cs | 2 +- .../Interop/Material/MaterialValueIndex.cs | 2 +- Glamourer/Interop/Material/PrepareColorSet.cs | 2 +- Glamourer/Interop/MetaService.cs | 2 +- Glamourer/Interop/ObjectManager.cs | 63 ++--- .../Interop/Penumbra/ModSettingApplier.cs | 2 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 2 +- Glamourer/Interop/ScalingService.cs | 2 +- Glamourer/Interop/Structs/Actor.cs | 142 ---------- Glamourer/Interop/Structs/ActorData.cs | 5 +- Glamourer/Interop/Structs/Model.cs | 259 ------------------ Glamourer/Interop/Structs/ModelExtensions.cs | 67 +++++ Glamourer/Interop/UpdateSlotService.cs | 2 +- Glamourer/Interop/VisorService.cs | 3 +- Glamourer/Interop/WeaponService.cs | 2 +- .../Services/CollectionOverrideService.cs | 2 +- Glamourer/Services/CommandService.cs | 4 +- Glamourer/Services/DalamudServices.cs | 1 + Glamourer/State/FunModule.cs | 4 +- Glamourer/State/StateListener.cs | 6 +- Glamourer/State/StateManager.cs | 4 +- Glamourer/State/WorldSets.cs | 16 +- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 44 files changed, 171 insertions(+), 512 deletions(-) delete mode 100644 Glamourer/Interop/Structs/Actor.cs delete mode 100644 Glamourer/Interop/Structs/Model.cs create mode 100644 Glamourer/Interop/Structs/ModelExtensions.cs diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 7ceea6a..9fc8ca7 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -1,9 +1,9 @@ using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.GameData; -using Glamourer.Interop.Structs; using Newtonsoft.Json.Linq; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Automation; diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 03fdda6..a3b912d 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -5,12 +5,13 @@ using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Material; -using Glamourer.Interop.Structs; using Glamourer.State; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; +using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Automation; diff --git a/Glamourer/Events/HeadGearVisibilityChanged.cs b/Glamourer/Events/HeadGearVisibilityChanged.cs index 91454b3..d8f722b 100644 --- a/Glamourer/Events/HeadGearVisibilityChanged.cs +++ b/Glamourer/Events/HeadGearVisibilityChanged.cs @@ -1,5 +1,5 @@ -using Glamourer.Interop.Structs; using OtterGui.Classes; +using Penumbra.GameData.Interop; namespace Glamourer.Events; diff --git a/Glamourer/Events/SlotUpdating.cs b/Glamourer/Events/SlotUpdating.cs index 97f9be9..8a766fb 100644 --- a/Glamourer/Events/SlotUpdating.cs +++ b/Glamourer/Events/SlotUpdating.cs @@ -1,6 +1,6 @@ -using Glamourer.Interop.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Events; diff --git a/Glamourer/Events/VisorStateChanged.cs b/Glamourer/Events/VisorStateChanged.cs index d71eccd..8e70002 100644 --- a/Glamourer/Events/VisorStateChanged.cs +++ b/Glamourer/Events/VisorStateChanged.cs @@ -1,5 +1,5 @@ -using Glamourer.Interop.Structs; using OtterGui.Classes; +using Penumbra.GameData.Interop; namespace Glamourer.Events; diff --git a/Glamourer/Events/WeaponLoading.cs b/Glamourer/Events/WeaponLoading.cs index 41da2aa..fda0b2f 100644 --- a/Glamourer/Events/WeaponLoading.cs +++ b/Glamourer/Events/WeaponLoading.cs @@ -1,6 +1,6 @@ -using Glamourer.Interop.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Events; diff --git a/Glamourer/Events/WeaponVisibilityChanged.cs b/Glamourer/Events/WeaponVisibilityChanged.cs index a94eabf..f75fa68 100644 --- a/Glamourer/Events/WeaponVisibilityChanged.cs +++ b/Glamourer/Events/WeaponVisibilityChanged.cs @@ -1,5 +1,5 @@ -using Glamourer.Interop.Structs; using OtterGui.Classes; +using Penumbra.GameData.Interop; namespace Glamourer.Events; diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 87954e9..a552f17 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,6 +1,6 @@  - net7.0-windows + net8.0-windows preview x64 Glamourer diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index df00e1c..c2dbdbe 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -5,7 +5,6 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using Glamourer.Designs; using Glamourer.Interop.Material; -using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; using OtterGui; @@ -13,6 +12,7 @@ using OtterGui.Raii; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; +using Penumbra.GameData.Interop; using Penumbra.String; namespace Glamourer.Gui.Materials; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 3cf50f5..b8ecd53 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -18,6 +18,8 @@ using OtterGui.Raii; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; +using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Gui.Tabs.ActorTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index 98fbcab..2130efe 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -58,7 +58,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromCharacter); ImGui.TableNextColumn(); base64 = GlamourerIpc.GetAllCustomizationFromCharacterSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects[_gameObjectIndex] as Character); + .Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); if (base64 != null) ImGuiUtil.CopyOnClickSelectable(base64); else @@ -66,7 +66,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromLockedCharacter); ImGui.TableNextColumn(); - var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface).Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); + var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface).Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), 1337); if (base64Locked != null) ImGuiUtil.CopyOnClickSelectable(base64Locked); else @@ -80,7 +80,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertCharacter); ImGui.TableNextColumn(); if (ImGui.Button("Revert##Character")) - GlamourerIpc.RevertCharacterSubscriber(_pluginInterface).Invoke(_objectManager.Objects[_gameObjectIndex] as Character); + GlamourerIpc.RevertCharacterSubscriber(_pluginInterface).Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAll); ImGui.TableNextColumn(); @@ -96,13 +96,13 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Apply##AllCharacter")) GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character); + .Invoke(_base64Apply, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnceToCharacter); ImGui.TableNextColumn(); if (ImGui.Button("Apply Once##AllCharacter")) GlamourerIpc.ApplyAllOnceToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character); + .Invoke(_base64Apply, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment); ImGui.TableNextColumn(); @@ -113,7 +113,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Apply##EquipCharacter")) GlamourerIpc.ApplyOnlyEquipmentToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character); + .Invoke(_base64Apply, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomization); ImGui.TableNextColumn(); @@ -124,7 +124,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Apply##CustomizeCharacter")) GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character); + .Invoke(_base64Apply, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid); ImGui.TableNextColumn(); @@ -140,25 +140,25 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2)) GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface) - .Invoke(guid2, _objectManager.Objects[_gameObjectIndex] as Character); + .Invoke(guid2, _objectManager.Objects.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.Objects[_gameObjectIndex] as Character); + .Invoke(guid2Once, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllLock); ImGui.TableNextColumn(); if (ImGui.Button("Apply With Lock##CustomizeCharacter")) GlamourerIpc.ApplyAllToCharacterLockSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character, 1337); + .Invoke(_base64Apply, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), 1337); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); ImGui.TableNextColumn(); if (ImGui.Button("Unlock##CustomizeCharacter")) GlamourerIpc.UnlockSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); + .Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), 1337); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlockAll); ImGui.TableNextColumn(); @@ -170,7 +170,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Revert##CustomizeCharacter")) GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); + .Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), 1337); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList); @@ -184,7 +184,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Set##SetItem")) _setItemEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + .Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); if (_setItemEc != GlamourerIpc.GlamourerErrorCode.Success) { ImGui.SameLine(); @@ -195,7 +195,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Set Once##SetItem")) _setItemOnceEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemOnceSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + .Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); if (_setItemOnceEc != GlamourerIpc.GlamourerErrorCode.Success) { ImGui.SameLine(); @@ -229,7 +229,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag { var tmp = _customItemId.Id; if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp)) - _customItemId = (CustomItemId)tmp; + _customItemId = tmp; var width = ImGui.GetContentRegionAvail().X; EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot); var value = (int)_stainId.Id; diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index e5f691d..2268ee5 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -1,5 +1,4 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using FFXIVClientStructs.FFXIV.Shader; using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Structs; @@ -8,7 +7,9 @@ using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; +using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Gui.Tabs.DebugTab; @@ -30,7 +31,7 @@ public unsafe class ModelEvaluationPanel( public void Draw() { ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); - var actor = (Actor)_objectManager.Objects.GetObjectAddress(_gameObjectIndex); + var actor = _objectManager.Objects[_gameObjectIndex]; var model = actor.Model; using var table = ImRaii.Table("##evaluationTable", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); ImGui.TableNextColumn(); diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index e53a1f7..6b9a7a3 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -1,12 +1,12 @@ using Dalamud.Interface.Utility; using Glamourer.Interop.Penumbra; -using Glamourer.Interop.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index d055adc..cfff90f 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -2,8 +2,8 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Events; -using Glamourer.Interop.Structs; using OtterGui.Classes; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop; diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 7764573..75e2a81 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -3,9 +3,9 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Glamourer.Interop.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; namespace Glamourer.Interop; diff --git a/Glamourer/Interop/JobService.cs b/Glamourer/Interop/JobService.cs index bff849e..1797809 100644 --- a/Glamourer/Interop/JobService.cs +++ b/Glamourer/Interop/JobService.cs @@ -2,9 +2,9 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using Glamourer.Interop.Structs; using Penumbra.GameData; using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop; @@ -21,7 +21,7 @@ public class JobService : IDisposable public event Action? JobChanged; - public JobService(DictJob jobs, DictJobGroup jobGroups, IDataManager gameData, IGameInteropProvider interop) + public JobService(DictJob jobs, DictJobGroup jobGroups, IGameInteropProvider interop) { interop.InitializeFromAttributes(this); _characterDataOffset = Marshal.OffsetOf(nameof(Character.CharacterData)); diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index 5e1fa06..e732472 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -1,5 +1,4 @@ using Dalamud.Plugin.Services; -using Glamourer.Interop.Structs; using ImGuiNET; using OtterGui.Services; using Penumbra.GameData.Files; @@ -9,9 +8,9 @@ namespace Glamourer.Interop.Material; public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable { - private readonly IObjectTable _objects; - private readonly IFramework _framework; - private readonly DirectXService _directXService; + private readonly global::Penumbra.GameData.Interop.ObjectManager _objects; + private readonly IFramework _framework; + private readonly DirectXService _directXService; public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid; public MtrlFile.ColorTable LastOriginalColorTable { get; private set; } @@ -20,7 +19,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable private ObjectIndex _objectIndex = ObjectIndex.AnyIndex; private MtrlFile.ColorTable _originalColorTable; - public LiveColorTablePreviewer(IObjectTable objects, IFramework framework, DirectXService directXService) + public LiveColorTablePreviewer(global::Penumbra.GameData.Interop.ObjectManager objects, IFramework framework, DirectXService directXService) { _objects = objects; _framework = framework; @@ -33,7 +32,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable if (LastValueIndex.DrawObject is MaterialValueIndex.DrawObjectType.Invalid || _lastObjectIndex == ObjectIndex.AnyIndex) return; - var actor = (Actor)_objects.GetObjectAddress(_lastObjectIndex.Index); + var actor = _objects[_lastObjectIndex]; if (actor.IsCharacter && LastValueIndex.TryGetTexture(actor, out var texture)) _directXService.ReplaceColorTable(texture, LastOriginalColorTable); @@ -51,7 +50,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable return; } - var actor = (Actor)_objects.GetObjectAddress(_objectIndex.Index); + var actor = _objects[_objectIndex]; if (!actor.IsCharacter) { _valueIndex = MaterialValueIndex.Invalid; diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 40af5a2..1fb758b 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -2,12 +2,12 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using Glamourer.Designs; using Glamourer.Interop.Penumbra; -using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop.Material; diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index 48b5fe7..03165a3 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -1,7 +1,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; -using Glamourer.Interop.Structs; using Lumina.Data.Files; +using Penumbra.GameData.Interop; using static Penumbra.GameData.Files.MtrlFile; using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture; diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index f461637..ec9996b 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -1,9 +1,9 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.Interop; -using Glamourer.Interop.Structs; using Newtonsoft.Json; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; +using Penumbra.GameData.Interop; namespace Glamourer.Interop.Material; diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 0f5be0d..5257c4e 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -2,11 +2,11 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; -using Glamourer.Interop.Structs; using OtterGui.Classes; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop.Material; diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs index b6bd511..a862e59 100644 --- a/Glamourer/Interop/MetaService.cs +++ b/Glamourer/Interop/MetaService.cs @@ -2,7 +2,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; -using Glamourer.Interop.Structs; +using Penumbra.GameData.Interop; namespace Glamourer.Interop; diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index c44fcd0..6a1d8a1 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -4,28 +4,15 @@ using FFXIVClientStructs.FFXIV.Client.Game.Control; using Glamourer.Interop.Structs; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; namespace Glamourer.Interop; -public class ObjectManager : IReadOnlyDictionary +public class ObjectManager(IFramework framework, IClientState clientState, global::Penumbra.GameData.Interop.ObjectManager objects, ActorManager actors, ITargetManager targets) + : IReadOnlyDictionary { - private readonly IFramework _framework; - private readonly IClientState _clientState; - private readonly IObjectTable _objects; - private readonly ActorManager _actors; - private readonly ITargetManager _targets; - - public IObjectTable Objects - => _objects; - - public ObjectManager(IFramework framework, IClientState clientState, IObjectTable objects, ActorManager actors, ITargetManager targets) - { - _framework = framework; - _clientState = clientState; - _objects = objects; - _actors = actors; - _targets = targets; - } + public global::Penumbra.GameData.Interop.ObjectManager Objects + => objects; public DateTime LastUpdate { get; private set; } @@ -41,39 +28,39 @@ public class ObjectManager : IReadOnlyDictionary public void Update() { - var lastUpdate = _framework.LastUpdate; + var lastUpdate = framework.LastUpdate; if (lastUpdate <= LastUpdate) return; LastUpdate = lastUpdate; - World = (ushort)(_clientState.LocalPlayer?.CurrentWorld.Id ?? 0u); + World = (ushort)(clientState.LocalPlayer?.CurrentWorld.Id ?? 0u); _identifiers.Clear(); _allWorldIdentifiers.Clear(); _nonOwnedIdentifiers.Clear(); for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i) { - Actor character = _objects.GetObjectAddress(i); - if (character.Identifier(_actors, out var identifier)) + var character = objects[i]; + if (character.Identifier(actors, out var identifier)) HandleIdentifier(identifier, character); } for (var i = (int)ScreenActor.CutsceneStart; i < (int)ScreenActor.CutsceneEnd; ++i) { - Actor character = _objects.GetObjectAddress(i); + var character = objects[i]; // Technically the game does not create holes in cutscenes or GPose. // But for Brio compatibility, we allow holes in GPose. // Since GPose always has the event actor in the first cutscene slot, we can still optimize in this case. if (!character.Valid && i == (int)ScreenActor.CutsceneStart) break; - HandleIdentifier(character.GetIdentifier(_actors), character); + HandleIdentifier(character.GetIdentifier(actors), character); } void AddSpecial(ScreenActor idx, string label) { - Actor actor = _objects.GetObjectAddress((int)idx); - if (actor.Identifier(_actors, out var ident)) + var actor = objects[(int)idx]; + if (actor.Identifier(actors, out var ident)) { var data = new ActorData(actor, label); _identifiers.Add(ident, data); @@ -89,10 +76,10 @@ public class ObjectManager : IReadOnlyDictionary AddSpecial(ScreenActor.Card7, "Card Actor 7"); AddSpecial(ScreenActor.Card8, "Card Actor 8"); - for (var i = (int)ScreenActor.ScreenEnd; i < _objects.Length; ++i) + for (var i = (int)ScreenActor.ScreenEnd; i < objects.Count; ++i) { - Actor character = _objects.GetObjectAddress(i); - if (character.Identifier(_actors, out var identifier)) + var character = objects[i]; + if (character.Identifier(actors, out var identifier)) HandleIdentifier(identifier, character); } @@ -117,7 +104,7 @@ public class ObjectManager : IReadOnlyDictionary if (identifier.Type is IdentifierType.Player or IdentifierType.Owned) { - var allWorld = _actors.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, + var allWorld = actors.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind, identifier.DataId); @@ -134,7 +121,7 @@ public class ObjectManager : IReadOnlyDictionary if (identifier.Type is IdentifierType.Owned) { - var nonOwned = _actors.CreateNpc(identifier.Kind, identifier.DataId); + var nonOwned = actors.CreateNpc(identifier.Kind, identifier.DataId); if (!_nonOwnedIdentifiers.TryGetValue(nonOwned, out var nonOwnedData)) { nonOwnedData = new ActorData(character, nonOwned.ToString()); @@ -148,26 +135,26 @@ public class ObjectManager : IReadOnlyDictionary } public Actor GPosePlayer - => _objects.GetObjectAddress((int)ScreenActor.GPosePlayer); + => objects[(int)ScreenActor.GPosePlayer]; public Actor Player - => _objects.GetObjectAddress(0); + => objects[0]; public unsafe Actor Target - => _clientState.IsGPosing ? TargetSystem.Instance()->GPoseTarget : TargetSystem.Instance()->Target; + => clientState.IsGPosing ? TargetSystem.Instance()->GPoseTarget : TargetSystem.Instance()->Target; public Actor Focus - => _targets.FocusTarget?.Address ?? nint.Zero; + => targets.FocusTarget?.Address ?? nint.Zero; public Actor MouseOver - => _targets.MouseOverTarget?.Address ?? nint.Zero; + => targets.MouseOverTarget?.Address ?? nint.Zero; public (ActorIdentifier Identifier, ActorData Data) PlayerData { get { Update(); - return Player.Identifier(_actors, out var ident) && _identifiers.TryGetValue(ident, out var data) + return Player.Identifier(actors, out var ident) && _identifiers.TryGetValue(ident, out var data) ? (ident, data) : (ident, ActorData.Invalid); } @@ -178,7 +165,7 @@ public class ObjectManager : IReadOnlyDictionary get { Update(); - return Target.Identifier(_actors, out var ident) && _identifiers.TryGetValue(ident, out var data) + return Target.Identifier(actors, out var ident) && _identifiers.TryGetValue(ident, out var data) ? (ident, data) : (ident, ActorData.Invalid); } diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 198c6ad..2b72fff 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -1,8 +1,8 @@ using Glamourer.Designs.Links; -using Glamourer.Interop.Structs; using Glamourer.Services; using Glamourer.State; using OtterGui.Services; +using Penumbra.GameData.Interop; namespace Glamourer.Interop.Penumbra; diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 7f26e4f..4e1d9c4 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -1,11 +1,11 @@ using Dalamud.Interface.Internal.Notifications; using Dalamud.Plugin; using Glamourer.Events; -using Glamourer.Interop.Structs; using OtterGui.Classes; using Penumbra.Api; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop.Penumbra; diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index c7b80cb..c148bbd 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -3,7 +3,7 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using Glamourer.Interop.Structs; +using Penumbra.GameData.Interop; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; namespace Glamourer.Interop; diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs deleted file mode 100644 index a5164d7..0000000 --- a/Glamourer/Interop/Structs/Actor.cs +++ /dev/null @@ -1,142 +0,0 @@ -using Penumbra.GameData.Actors; -using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Game.Object; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.String; - -namespace Glamourer.Interop.Structs; - -public readonly unsafe struct Actor : IEquatable -{ - private Actor(nint address) - => Address = address; - - public static readonly Actor Null = new(nint.Zero); - - public readonly nint Address; - - public GameObject* AsObject - => (GameObject*)Address; - - public Character* AsCharacter - => (Character*)Address; - - public bool Valid - => Address != nint.Zero; - - public bool IsCharacter - => Valid && AsObject->IsCharacter(); - - public static implicit operator Actor(nint? pointer) - => new(pointer ?? nint.Zero); - - public static implicit operator Actor(GameObject* pointer) - => new((nint)pointer); - - public static implicit operator Actor(Character* pointer) - => new((nint)pointer); - - public static implicit operator nint(Actor actor) - => actor.Address; - - public bool IsGPoseOrCutscene - => Index.Index is >= (int)ScreenActor.CutsceneStart and < (int)ScreenActor.CutsceneEnd; - - public bool IsTransformed - => AsCharacter->CharacterData.TransformationId != 0; - - public ActorIdentifier GetIdentifier(ActorManager actors) - => actors.FromObject(AsObject, out _, true, true, false); - - public ByteString Utf8Name - => Valid ? new ByteString(AsObject->Name) : ByteString.Empty; - - public bool Identifier(ActorManager actors, out ActorIdentifier ident) - { - if (Valid) - { - ident = GetIdentifier(actors); - return ident.IsValid; - } - - ident = ActorIdentifier.Invalid; - return false; - } - - public ObjectIndex Index - => Valid ? AsObject->ObjectIndex : ObjectIndex.AnyIndex; - - public Model Model - => Valid ? AsObject->DrawObject : null; - - public byte Job - => IsCharacter ? AsCharacter->CharacterData.ClassJob : (byte)0; - - public static implicit operator bool(Actor actor) - => actor.Address != nint.Zero; - - public static bool operator true(Actor actor) - => actor.Address != nint.Zero; - - public static bool operator false(Actor actor) - => actor.Address == nint.Zero; - - public static bool operator !(Actor actor) - => actor.Address == nint.Zero; - - public bool Equals(Actor other) - => Address == other.Address; - - public override bool Equals(object? obj) - => obj is Actor other && Equals(other); - - public override int GetHashCode() - => Address.GetHashCode(); - - public static bool operator ==(Actor lhs, Actor rhs) - => lhs.Address == rhs.Address; - - public static bool operator !=(Actor lhs, Actor rhs) - => lhs.Address != rhs.Address; - - /// Only valid for characters. - public CharacterArmor GetArmor(EquipSlot slot) - => ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()]; - - public bool GetCrest(CrestFlag slot) - => CrestBitfield.HasFlag(slot); - - public CharacterWeapon GetMainhand() - => new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).ModelId.Value); - - public CharacterWeapon GetOffhand() - => new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).ModelId.Value); - - public CustomizeArray GetCustomize() - => *(CustomizeArray*)&AsCharacter->DrawData.CustomizeData; - - // TODO remove this when available in ClientStructs - internal ref CrestFlag CrestBitfield - => ref *(CrestFlag*)((byte*)Address + 0x1BBB); - - public override string ToString() - => $"0x{Address:X}"; - - public OnlineStatus OnlineStatus - => (OnlineStatus)AsCharacter->CharacterData.OnlineStatus; -} - -public enum OnlineStatus : byte -{ - Normal = 0x00, - Mentor = 0x1B, - PvEMentor = 0x1C, - TradeMentor = 0x1D, - PvPMentor = 0x1E, - Busy = 0x0C, - Away = 0x11, - MeldMateria = 0x15, - RolePlaying = 0x16, - LookingForGroup = 0x17, -} diff --git a/Glamourer/Interop/Structs/ActorData.cs b/Glamourer/Interop/Structs/ActorData.cs index c1a0ea4..5cfbcbe 100644 --- a/Glamourer/Interop/Structs/ActorData.cs +++ b/Glamourer/Interop/Structs/ActorData.cs @@ -1,4 +1,5 @@ using OtterGui.Log; +using Penumbra.GameData.Interop; namespace Glamourer.Interop.Structs; @@ -15,7 +16,7 @@ public readonly struct ActorData public ActorData(Actor actor, string label) { - Objects = new List { actor }; + Objects = [actor]; Label = label; } @@ -23,7 +24,7 @@ public readonly struct ActorData private ActorData(bool _) { - Objects = new List(0); + Objects = []; Label = string.Empty; } diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs deleted file mode 100644 index fc7b880..0000000 --- a/Glamourer/Interop/Structs/Model.cs +++ /dev/null @@ -1,259 +0,0 @@ -using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using FFXIVClientStructs.FFXIV.Shader; -using Glamourer.GameData; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object; -using ObjectType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType; - -namespace Glamourer.Interop.Structs; - -public readonly unsafe struct Model : IEquatable -{ - private Model(nint address) - => Address = address; - - public readonly nint Address; - - public static readonly Model Null = new(0); - - public DrawObject* AsDrawObject - => (DrawObject*)Address; - - public CharacterBase* AsCharacterBase - => (CharacterBase*)Address; - - public Weapon* AsWeapon - => (Weapon*)Address; - - public Human* AsHuman - => (Human*)Address; - - public static implicit operator Model(nint? pointer) - => new(pointer ?? nint.Zero); - - public static implicit operator Model(Object* pointer) - => new((nint)pointer); - - public static implicit operator Model(DrawObject* pointer) - => new((nint)pointer); - - public static implicit operator Model(Human* pointer) - => new((nint)pointer); - - public static implicit operator Model(CharacterBase* pointer) - => new((nint)pointer); - - public static implicit operator nint(Model model) - => model.Address; - - public bool Valid - => Address != nint.Zero; - - public bool IsCharacterBase - => Valid && AsDrawObject->Object.GetObjectType() == ObjectType.CharacterBase; - - public bool IsHuman - => IsCharacterBase && AsCharacterBase->GetModelType() == CharacterBase.ModelType.Human; - - public bool IsWeapon - => IsCharacterBase && AsCharacterBase->GetModelType() == CharacterBase.ModelType.Weapon; - - public static implicit operator bool(Model actor) - => actor.Address != nint.Zero; - - public static bool operator true(Model actor) - => actor.Address != nint.Zero; - - public static bool operator false(Model actor) - => actor.Address == nint.Zero; - - public static bool operator !(Model actor) - => actor.Address == nint.Zero; - - public bool Equals(Model other) - => Address == other.Address; - - public override bool Equals(object? obj) - => obj is Model other && Equals(other); - - public override int GetHashCode() - => Address.GetHashCode(); - - public static bool operator ==(Model lhs, Model rhs) - => lhs.Address == rhs.Address; - - public static bool operator !=(Model lhs, Model rhs) - => lhs.Address != rhs.Address; - - /// Only valid for humans. - public CharacterArmor GetArmor(EquipSlot slot) - => ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()]; - - public CustomizeArray GetCustomize() - => *(CustomizeArray*)&AsHuman->Customize; - - public (Model Address, CharacterWeapon Data) GetMainhand() - { - Model weapon = AsDrawObject->Object.ChildObject; - return !weapon.IsWeapon - ? (Null, CharacterWeapon.Empty) - : (weapon, new CharacterWeapon(weapon.AsWeapon->ModelSetId, weapon.AsWeapon->SecondaryId, (Variant)weapon.AsWeapon->Variant, - (StainId)weapon.AsWeapon->ModelUnknown)); - } - - public (Model Address, CharacterWeapon Data) GetOffhand() - { - var mainhand = AsDrawObject->Object.ChildObject; - if (mainhand == null) - return (Null, CharacterWeapon.Empty); - - Model offhand = mainhand->NextSiblingObject; - if (offhand == mainhand || !offhand.IsWeapon) - return (Null, CharacterWeapon.Empty); - - return (offhand, new CharacterWeapon(offhand.AsWeapon->ModelSetId, offhand.AsWeapon->SecondaryId, (Variant)offhand.AsWeapon->Variant, - (StainId)offhand.AsWeapon->ModelUnknown)); - } - - /// Obtain the mainhand and offhand and their data by guesstimating which child object is which. - public (Model Mainhand, Model Offhand, CharacterWeapon MainData, CharacterWeapon OffData) GetWeapons() - { - var (first, second, count) = GetChildrenWeapons(); - switch (count) - { - case 0: return (Null, Null, CharacterWeapon.Empty, CharacterWeapon.Empty); - case 1: - return (first, Null, new CharacterWeapon(first.AsWeapon->ModelSetId, first.AsWeapon->SecondaryId, - (Variant)first.AsWeapon->Variant, - (StainId)first.AsWeapon->ModelUnknown), CharacterWeapon.Empty); - default: - var (main, off) = DetermineMainhand(first, second); - var mainData = new CharacterWeapon(main.AsWeapon->ModelSetId, main.AsWeapon->SecondaryId, (Variant)main.AsWeapon->Variant, - (StainId)main.AsWeapon->ModelUnknown); - var offData = new CharacterWeapon(off.AsWeapon->ModelSetId, off.AsWeapon->SecondaryId, (Variant)off.AsWeapon->Variant, - (StainId)off.AsWeapon->ModelUnknown); - return (main, off, mainData, offData); - } - } - - /// Obtain the mainhand and offhand and their data by using the drawdata container from the corresponding actor. - public (Model Mainhand, Model Offhand, CharacterWeapon MainData, CharacterWeapon OffData) GetWeapons(Actor actor) - { - if (!Valid || !actor.IsCharacter || actor.Model.Address != Address) - return (Null, Null, CharacterWeapon.Empty, CharacterWeapon.Empty); - - Model main = actor.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).DrawObject; - var mainData = CharacterWeapon.Empty; - if (main.IsWeapon) - mainData = new CharacterWeapon(main.AsWeapon->ModelSetId, main.AsWeapon->SecondaryId, (Variant)main.AsWeapon->Variant, - (StainId)main.AsWeapon->ModelUnknown); - else - main = Null; - Model off = actor.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject; - var offData = CharacterWeapon.Empty; - if (off.IsWeapon) - offData = new CharacterWeapon(off.AsWeapon->ModelSetId, off.AsWeapon->SecondaryId, (Variant)off.AsWeapon->Variant, - (StainId)off.AsWeapon->ModelUnknown); - else - off = Null; - return (main, off, mainData, offData); - } - - public CustomizeParameterData GetParameterData() - { - if (!IsHuman) - return default; - - var cBuffer1 = AsHuman->CustomizeParameterCBuffer; - var cBuffer2 = AsHuman->DecalColorCBuffer; - var ptr1 = (CustomizeParameter*)(cBuffer1 == null ? null : cBuffer1->UnsafeSourcePointer); - var ptr2 = (DecalParameters*)(cBuffer2 == null ? null : cBuffer2->UnsafeSourcePointer); - return CustomizeParameterData.FromParameters(ptr1 != null ? *ptr1 : default, ptr2 != null ? *ptr2 : default); - } - - public void ApplyParameterData(CustomizeParameterFlag flags, in CustomizeParameterData data) - { - if (!IsHuman) - return; - - if (flags.HasFlag(CustomizeParameterFlag.DecalColor)) - { - var cBufferDecal = AsHuman->DecalColorCBuffer; - var ptrDecal = (DecalParameters*)(cBufferDecal == null ? null : cBufferDecal->UnsafeSourcePointer); - if (ptrDecal != null) - data.Apply(ref *ptrDecal); - } - - flags &= ~CustomizeParameterFlag.DecalColor; - var cBuffer = AsHuman->CustomizeParameterCBuffer; - var ptr = (CustomizeParameter*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); - if (ptr != null) - data.Apply(ref *ptr, flags); - } - - public bool ApplySingleParameterData(CustomizeParameterFlag flag, in CustomizeParameterData data) - { - if (!IsHuman) - return false; - - if (flag is CustomizeParameterFlag.DecalColor) - { - var cBuffer = AsHuman->DecalColorCBuffer; - var ptr = (DecalParameters*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); - if (ptr == null) - return false; - - data.Apply(ref *ptr); - return true; - } - else - { - var cBuffer = AsHuman->CustomizeParameterCBuffer; - var ptr = (CustomizeParameter*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); - if (ptr == null) - return false; - - data.ApplySingle(ref *ptr, flag); - return true; - } - } - - private (Model, Model, int) GetChildrenWeapons() - { - Span weapons = stackalloc Model[2]; - weapons[0] = Null; - weapons[1] = Null; - var count = 0; - - if (!Valid || AsDrawObject->Object.ChildObject == null) - return (weapons[0], weapons[1], count); - - Model starter = AsDrawObject->Object.ChildObject; - var iterator = starter; - do - { - if (iterator.IsWeapon) - weapons[count++] = iterator; - if (count == 2) - return (weapons[0], weapons[1], count); - - iterator = iterator.AsDrawObject->Object.NextSiblingObject; - } while (iterator.Address != starter.Address); - - return (weapons[0], weapons[1], count); - } - - /// I don't know a safe way to do this but in experiments this worked. - /// The first uint at +0x8 was set to non-zero for the mainhand and zero for the offhand. - private static (Model Mainhand, Model Offhand) DetermineMainhand(Model first, Model second) - { - var discriminator1 = *(ulong*)(first.Address + 0x10); - var discriminator2 = *(ulong*)(second.Address + 0x10); - return discriminator1 == 0 && discriminator2 != 0 ? (second, first) : (first, second); - } - - public override string ToString() - => $"0x{Address:X}"; -} diff --git a/Glamourer/Interop/Structs/ModelExtensions.cs b/Glamourer/Interop/Structs/ModelExtensions.cs new file mode 100644 index 0000000..207c72c --- /dev/null +++ b/Glamourer/Interop/Structs/ModelExtensions.cs @@ -0,0 +1,67 @@ +using FFXIVClientStructs.FFXIV.Shader; +using Glamourer.GameData; +using Penumbra.GameData.Interop; + +namespace Glamourer.Interop.Structs; + +public static unsafe class ModelExtensions +{ + public static CustomizeParameterData GetParameterData(this Model model) + { + if (!model.IsHuman) + return default; + + var cBuffer1 = model.AsHuman->CustomizeParameterCBuffer; + var cBuffer2 = model.AsHuman->DecalColorCBuffer; + var ptr1 = (CustomizeParameter*)(cBuffer1 == null ? null : cBuffer1->UnsafeSourcePointer); + var ptr2 = (DecalParameters*)(cBuffer2 == null ? null : cBuffer2->UnsafeSourcePointer); + return CustomizeParameterData.FromParameters(ptr1 != null ? *ptr1 : default, ptr2 != null ? *ptr2 : default); + } + + public static void ApplyParameterData(this Model model, CustomizeParameterFlag flags, in CustomizeParameterData data) + { + if (!model.IsHuman) + return; + + if (flags.HasFlag(CustomizeParameterFlag.DecalColor)) + { + var cBufferDecal = model.AsHuman->DecalColorCBuffer; + var ptrDecal = (DecalParameters*)(cBufferDecal == null ? null : cBufferDecal->UnsafeSourcePointer); + if (ptrDecal != null) + data.Apply(ref *ptrDecal); + } + + flags &= ~CustomizeParameterFlag.DecalColor; + var cBuffer = model.AsHuman->CustomizeParameterCBuffer; + var ptr = (CustomizeParameter*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); + if (ptr != null) + data.Apply(ref *ptr, flags); + } + + public static bool ApplySingleParameterData(this Model model, CustomizeParameterFlag flag, in CustomizeParameterData data) + { + if (!model.IsHuman) + return false; + + if (flag is CustomizeParameterFlag.DecalColor) + { + var cBuffer = model.AsHuman->DecalColorCBuffer; + var ptr = (DecalParameters*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); + if (ptr == null) + return false; + + data.Apply(ref *ptr); + return true; + } + else + { + var cBuffer = model.AsHuman->CustomizeParameterCBuffer; + var ptr = (CustomizeParameter*)(cBuffer == null ? null : cBuffer->UnsafeSourcePointer); + if (ptr == null) + return false; + + data.ApplySingle(ref *ptr, flag); + return true; + } + } +} diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 0891945..f2f7423 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -2,9 +2,9 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using Glamourer.Events; -using Glamourer.Interop.Structs; using Penumbra.GameData; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop; diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 082f6b8..4232446 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -1,10 +1,9 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; -using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Glamourer.Events; -using Glamourer.Interop.Structs; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; namespace Glamourer.Interop; diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index da7d9dd..d2aac1a 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -2,8 +2,8 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; -using Glamourer.Interop.Structs; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop; diff --git a/Glamourer/Services/CollectionOverrideService.cs b/Glamourer/Services/CollectionOverrideService.cs index 7cdbc47..a7c4364 100644 --- a/Glamourer/Services/CollectionOverrideService.cs +++ b/Glamourer/Services/CollectionOverrideService.cs @@ -1,11 +1,11 @@ using Glamourer.Interop.Penumbra; -using Glamourer.Interop.Structs; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Filesystem; using OtterGui.Services; using Penumbra.GameData.Actors; +using Penumbra.GameData.Interop; namespace Glamourer.Services; diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 0b32cfc..e2edd2d 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -5,16 +5,16 @@ using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.Gui; -using Glamourer.Interop; using Glamourer.Interop.Penumbra; -using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Classes; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; +using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Services; diff --git a/Glamourer/Services/DalamudServices.cs b/Glamourer/Services/DalamudServices.cs index 14100b1..66cb97b 100644 --- a/Glamourer/Services/DalamudServices.cs +++ b/Glamourer/Services/DalamudServices.cs @@ -26,5 +26,6 @@ public class DalamudServices services.AddDalamudService(pi); services.AddDalamudService(pi); services.AddDalamudService(pi); + services.AddDalamudService(pi); } } diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 7ddc42f..25b8946 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -2,15 +2,15 @@ using Dalamud.Interface.Internal.Notifications; using Glamourer.Designs; using Glamourer.Gui; -using Glamourer.Interop; -using Glamourer.Interop.Structs; using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; using CustomizeIndex = Penumbra.GameData.Enums.CustomizeIndex; +using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.State; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index bf7b869..ff227a8 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -12,6 +12,8 @@ using Dalamud.Plugin.Services; using Glamourer.GameData; using Penumbra.GameData.DataContainers; using Glamourer.Designs; +using Penumbra.GameData.Interop; +using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.State; @@ -550,10 +552,10 @@ public class StateListener : IDisposable /// only if we kept track of state of someone who went to the aesthetician, /// or if they used other tools to change things. /// - private UpdateState UpdateBaseData(Actor actor, ActorState state, CustomizeArray customize, bool checkTransform) + private unsafe UpdateState UpdateBaseData(Actor actor, ActorState state, CustomizeArray customize, bool checkTransform) { // Customize array does not agree between game object and draw object => transformation. - if (checkTransform && !actor.GetCustomize().Equals(customize)) + if (checkTransform && !actor.Customize->Equals(customize)) return UpdateState.Transformed; // Customize array did not change to stored state. diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 5519f8d..7fe2264 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -12,7 +12,7 @@ using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using System; +using Penumbra.GameData.Interop; namespace Glamourer.State; @@ -163,7 +163,7 @@ public sealed class StateManager( else { // Obtain all data from the game object. - ret.Customize = actor.GetCustomize(); + ret.Customize = *actor.Customize; foreach (var slot in EquipSlotExtensions.EqdpSlots) { diff --git a/Glamourer/State/WorldSets.cs b/Glamourer/State/WorldSets.cs index 5a26b77..eca0988 100644 --- a/Glamourer/State/WorldSets.cs +++ b/Glamourer/State/WorldSets.cs @@ -1,5 +1,5 @@ -using Glamourer.Interop.Structs; -using Penumbra.GameData.Enums; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.State; @@ -344,10 +344,10 @@ public class WorldSets } - private unsafe (byte, byte, Race, Gender) GetData(Actor actor) + private static unsafe (byte, byte, Race, Gender) GetData(Actor actor) { - var customize = actor.GetCustomize(); - return (actor.AsCharacter->CharacterData.Level, actor.Job, customize.Race, customize.Gender); + var customize = actor.Customize; + return (actor.AsCharacter->CharacterData.Level, actor.Job, customize->Race, customize->Gender); } public void Apply(Actor actor, Random rng, Span armor) @@ -356,7 +356,7 @@ public class WorldSets Apply(level, job, race, gender, rng, armor); } - public void Apply(byte level, byte job, Race race, Gender gender, Random rng, Span armor) + private void Apply(byte level, byte job, Race race, Gender gender, Random rng, Span armor) { var opt = GetGroup(level, job, race, gender, rng); if (opt == null) @@ -375,7 +375,7 @@ public class WorldSets Apply(level, job, race, gender, rng, ref armor, slot); } - public void Apply(byte level, byte job, Race race, Gender gender, Random rng, ref CharacterArmor armor, EquipSlot slot) + private void Apply(byte level, byte job, Race race, Gender gender, Random rng, ref CharacterArmor armor, EquipSlot slot) { var opt = GetGroup(level, job, race, gender, rng); if (opt == null) @@ -398,7 +398,7 @@ public class WorldSets Apply(level, job, race, gender, rng, ref weapon, slot); } - public void Apply(byte level, byte job, Race race, Gender gender, Random rng, ref CharacterWeapon weapon, EquipSlot slot) + private void Apply(byte level, byte job, Race race, Gender gender, Random rng, ref CharacterWeapon weapon, EquipSlot slot) { var opt = GetGroup(level, job, race, gender, rng); if (opt == null) diff --git a/OtterGui b/OtterGui index d71f854..b4b1436 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit d71f8540a2c2efb4f2cb96e4706bb056397daf0a +Subproject commit b4b14367d8235eabedd561ad3626beb1d2a83889 diff --git a/Penumbra.Api b/Penumbra.Api index 34921fd..d2a1406 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 34921fd2c5a9aff5d34aef664bdb78331e8b9436 +Subproject commit d2a1406bc32f715c0687613f02e3f74caf7ceea9 diff --git a/Penumbra.GameData b/Penumbra.GameData index d0db2f1..74a3057 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d0db2f1fbc3ce26d0756da5118157e5fc723c62f +Subproject commit 74a305768880cd783b21e85ef97e9be77b119885 diff --git a/Penumbra.String b/Penumbra.String index 620a7ed..14e00f7 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 620a7edf009b92288257ce7d64fffb8fba44d8b5 +Subproject commit 14e00f77d42bc677e02325660db765ef11932560 From 2d75c24371ca6d80e06b34588730b0b6b7564a1c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 20 Mar 2024 17:28:46 +0100 Subject: [PATCH 319/786] Use new context menu service. --- Glamourer/Glamourer.csproj | 1 - Glamourer/Interop/ContextMenuService.cs | 199 +++++++++++------------- Glamourer/Services/DalamudServices.cs | 1 + 3 files changed, 88 insertions(+), 113 deletions(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index a552f17..673a70b 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -88,7 +88,6 @@ - diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 3cfca50..bd8c333 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -1,8 +1,6 @@ -using Dalamud.ContextMenu; -using Dalamud.Game.Text; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Plugin; +using Dalamud.Game.Gui.ContextMenu; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; using Glamourer.Designs; using Glamourer.Services; using Glamourer.State; @@ -16,142 +14,119 @@ public class ContextMenuService : IDisposable public const int ItemSearchContextItemId = 0x1738; public const int ChatLogContextItemId = 0x948; - private readonly ItemManager _items; - private readonly DalamudContextMenu _contextMenu; - private readonly StateManager _state; - private readonly ObjectManager _objects; - private readonly IGameGui _gameGui; + private readonly ItemManager _items; + private readonly IContextMenu _contextMenu; + private readonly StateManager _state; + private readonly ObjectManager _objects; + private readonly IGameGui _gameGui; + private EquipItem _lastItem; + private StainId _lastStain; + + private readonly MenuItem _inventoryItem; public ContextMenuService(ItemManager items, StateManager state, ObjectManager objects, IGameGui gameGui, Configuration config, - DalamudPluginInterface pi) + IContextMenu context) { - _contextMenu = new DalamudContextMenu(pi); + _contextMenu = context; _items = items; _state = state; _objects = objects; _gameGui = gameGui; if (config.EnableGameContextMenu) Enable(); + + _inventoryItem = new MenuItem + { + IsEnabled = true, + IsReturn = false, + PrefixChar = 'G', + Name = "Try On", + OnClicked = OnClick, + IsSubmenu = false, + PrefixColor = 541, + }; } public void Enable() { - _contextMenu.OnOpenGameObjectContextMenu += AddGameObjectItem; - _contextMenu.OnOpenInventoryContextMenu += AddInventoryItem; + _contextMenu.OnMenuOpened += OnMenuOpened; + } + + private unsafe void OnMenuOpened(MenuOpenedArgs args) + { + if (args.MenuType is ContextMenuType.Inventory) + { + var arg = (MenuTargetInventory)args.Target; + if (arg.TargetItem.HasValue && HandleItem(arg.TargetItem.Value.ItemId)) + { + _lastStain = arg.TargetItem.Value.Stain; + args.AddMenuItem(_inventoryItem); + } + } + else + { + switch (args.AddonName) + { + case "ItemSearch" when args.AgentPtr != nint.Zero: + { + if (HandleItem((ItemId)AgentContext.Instance()->UpdateCheckerParam)) + args.AddMenuItem(_inventoryItem); + + break; + } + case "ChatLog": + { + var agent = _gameGui.FindAgentInterface("ChatLog"); + if (agent == nint.Zero || !ValidateChatLogContext(agent)) + return; + + if (HandleItem(*(ItemId*)(agent + ChatLogContextItemId))) + { + _lastStain = 0; + args.AddMenuItem(_inventoryItem); + } + + break; + } + } + } + } + + private bool HandleItem(ItemId id) + { + var itemId = Math.Clamp(id.Id, 0, 500000u); + return _items.ItemData.TryGetValue(itemId, EquipSlot.MainHand, out _lastItem); } public void Disable() { - _contextMenu.OnOpenGameObjectContextMenu -= AddGameObjectItem; - _contextMenu.OnOpenInventoryContextMenu -= AddInventoryItem; + _contextMenu.OnMenuOpened -= OnMenuOpened; } public void Dispose() { Disable(); - _contextMenu.Dispose(); } - private static readonly SeString TryOnString = new SeStringBuilder().AddUiForeground(SeIconChar.BoxedLetterG.ToIconString(), 541) - .AddText(" Try On").AddUiForegroundOff().BuiltString; - - private void AddInventoryItem(InventoryContextMenuOpenArgs args) + private void OnClick(MenuItemClickedArgs _) { - var item = CheckInventoryItem(args.ItemId); - if (item != null) - args.AddCustomItem(item); - } + var (id, playerData) = _objects.PlayerData; + if (!playerData.Valid) + return; - private InventoryContextMenuItem? CheckInventoryItem(uint itemId) - { - if (itemId > 500000) - itemId -= 500000; + if (!_state.GetOrCreate(id, playerData.Objects[0], out var state)) + return; - if (!_items.ItemData.TryGetValue(itemId, EquipSlot.MainHand, out var item)) - return null; + var slot = _lastItem.Type.ToSlot(); + _state.ChangeEquip(state, slot, _lastItem, _lastStain, ApplySettings.Manual); + if (!_lastItem.Type.ValidOffhand().IsOffhandType()) + return; - return new InventoryContextMenuItem(TryOnString, GetInventoryAction(item)); - } - - - private GameObjectContextMenuItem? CheckGameObjectItem(uint itemId) - { - if (itemId > 500000) - itemId -= 500000; - - if (!_items.ItemData.TryGetValue(itemId, EquipSlot.MainHand, out var item)) - return null; - - return new GameObjectContextMenuItem(TryOnString, GetGameObjectAction(item)); - } - - private unsafe GameObjectContextMenuItem? CheckGameObjectItem(IntPtr agent, int offset, Func validate) - => agent != IntPtr.Zero && validate(agent) ? CheckGameObjectItem(*(uint*)(agent + offset)) : null; - - private unsafe GameObjectContextMenuItem? CheckGameObjectItem(IntPtr agent, int offset) - => agent != IntPtr.Zero ? CheckGameObjectItem(*(uint*)(agent + offset)) : null; - - private GameObjectContextMenuItem? CheckGameObjectItem(string name, int offset, Func validate) - => CheckGameObjectItem(_gameGui.FindAgentInterface(name), offset, validate); - - private void AddGameObjectItem(GameObjectContextMenuOpenArgs args) - { - var item = args.ParentAddonName switch - { - "ItemSearch" => CheckGameObjectItem(args.Agent, ItemSearchContextItemId), - "ChatLog" => CheckGameObjectItem("ChatLog", ChatLogContextItemId, ValidateChatLogContext), - _ => null, - }; - if (item != null) - args.AddCustomItem(item); - } - - private DalamudContextMenu.InventoryContextMenuItemSelectedDelegate GetInventoryAction(EquipItem item) - { - return _ => - { - var (id, playerData) = _objects.PlayerData; - if (!playerData.Valid) - return; - - if (!_state.GetOrCreate(id, playerData.Objects[0], out var state)) - return; - - var slot = item.Type.ToSlot(); - _state.ChangeEquip(state, slot, item, 0, ApplySettings.Manual); - if (item.Type.ValidOffhand().IsOffhandType()) - { - if (item.PrimaryId.Id is > 1600 and < 1651 - && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) - _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, ApplySettings.Manual); - if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) - _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, ApplySettings.Manual); - } - }; - } - - private DalamudContextMenu.GameObjectContextMenuItemSelectedDelegate GetGameObjectAction(EquipItem item) - { - return _ => - { - var (id, playerData) = _objects.PlayerData; - if (!playerData.Valid) - return; - - if (!_state.GetOrCreate(id, playerData.Objects[0], out var state)) - return; - - var slot = item.Type.ToSlot(); - _state.ChangeEquip(state, slot, item, 0, ApplySettings.Manual); - if (item.Type.ValidOffhand().IsOffhandType()) - { - if (item.PrimaryId.Id is > 1600 and < 1651 - && _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) - _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, ApplySettings.Manual); - if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) - _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, ApplySettings.Manual); - } - }; + if (_lastItem.PrimaryId.Id is > 1600 and < 1651 + && _items.ItemData.TryGetValue(_lastItem.ItemId, EquipSlot.Hands, out var gauntlets)) + _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, _lastStain, ApplySettings.Manual); + if (_items.ItemData.TryGetValue(_lastItem.ItemId, EquipSlot.OffHand, out var offhand)) + _state.ChangeEquip(state, EquipSlot.OffHand, offhand, _lastStain, ApplySettings.Manual); } private static unsafe bool ValidateChatLogContext(nint agent) diff --git a/Glamourer/Services/DalamudServices.cs b/Glamourer/Services/DalamudServices.cs index 66cb97b..fd001d7 100644 --- a/Glamourer/Services/DalamudServices.cs +++ b/Glamourer/Services/DalamudServices.cs @@ -27,5 +27,6 @@ public class DalamudServices services.AddDalamudService(pi); services.AddDalamudService(pi); services.AddDalamudService(pi); + services.AddDalamudService(pi); } } From 626977776048992f2f818b2ec6ece244b0fab54c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 20 Mar 2024 17:36:13 +0100 Subject: [PATCH 320/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 74a3057..529e181 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 74a305768880cd783b21e85ef97e9be77b119885 +Subproject commit 529e18115023732794994bfb8df4818b68951ea4 From 7c8bd514de15d101c4ef4391180aa3da5a7c58dc Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 20 Mar 2024 17:50:55 +0100 Subject: [PATCH 321/786] Add Changelog. --- Glamourer/Gui/GlamourerChangelog.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 473a64b..0e4b934 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -31,6 +31,7 @@ public class GlamourerChangelog AddDummy(Changelog); AddDummy(Changelog); Add1_2_0_0(Changelog); + Add1_2_1_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -51,6 +52,25 @@ public class GlamourerChangelog } } + 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.") + .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 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("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) + .RegisterEntry("Fixed random design never applying the last design in the set. (1.2.0.7)") + .RegisterEntry("Fixed colors of special designs. (1.2.0.7)") + .RegisterEntry("Fixed issues with weapon tracking. (1.2.0.5, 1.2.0.6)") + .RegisterEntry("Fixed issues with moved items and gearset changes not being listened to. (1.2.0.4)") + .RegisterEntry("Fixed issues with applying advanced dyes in fixed states. (1.2.0.2)") + .RegisterEntry("Fixed issues turning non-humans human. (1.2.0.1)") + .RegisterEntry("Fixed issues with body type application. (1.2.0.1, 1.2.0.2)") + .RegisterEntry("Fixed issue with design link application rule checkboxes. (1.2.0.1)"); + private static void Add1_2_0_0(Changelog log) => log.NextVersion("Version 1.2.0.0") .RegisterHighlight("Added the option to link to other designs in a design, causing all of them to be applied at once.") From 43d5700d727b857d0b28a5bd455bd0523103b1cb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 20 Mar 2024 18:23:22 +0100 Subject: [PATCH 322/786] Update actions. --- .github/workflows/release.yml | 2 +- .github/workflows/test_release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50ed81a..821f20b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '7.x.x' + dotnet-version: '8.x.x' - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/.github/workflows/test_release.yml b/.github/workflows/test_release.yml index ccdc6e3..4435e39 100644 --- a/.github/workflows/test_release.yml +++ b/.github/workflows/test_release.yml @@ -15,7 +15,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '7.x.x' + dotnet-version: '8.x.x' - name: Restore dependencies run: dotnet restore - name: Download Dalamud From 411ab84d8013a44a76e15c885afe7593a9129ba0 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 20 Mar 2024 17:25:13 +0000 Subject: [PATCH 323/786] [CI] Updating repo.json for testing_1.2.1.0 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index cf3e709..890503f 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.0.8", - "TestingAssemblyVersion": "1.2.0.8", + "TestingAssemblyVersion": "1.2.1.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/1.2.0.8/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.2.1.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 633a01f176a7a558394ea6aac06b521c0f63571a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 20 Mar 2024 22:25:44 +0100 Subject: [PATCH 324/786] Remove Debug Assert that triggers now apparently. --- Glamourer/Interop/Penumbra/PenumbraService.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 4e1d9c4..c7297aa 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -162,7 +162,7 @@ public unsafe class PenumbraService : IDisposable try { collection ??= _currentCollection.Invoke(ApiCollectionType.Current); - var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled); + var ec = _setMod.Invoke(collection, mod.DirectoryName, mod.Name, settings.Enabled); switch (ec) { case PenumbraApiEc.ModMissing: return $"The mod {mod.Name} [{mod.DirectoryName}] could not be found."; @@ -188,10 +188,13 @@ public unsafe class PenumbraService : IDisposable case PenumbraApiEc.OptionMissing: sb.AppendLine($"Could not find all desired options in the option group {setting} in mod {mod.Name}."); break; + case PenumbraApiEc.Success: + case PenumbraApiEc.NothingChanged: + break; + default: + sb.AppendLine($"Could not apply options in the option group {setting} in mod {mod.Name} for unknown reason {ec}."); + break; } - - Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged, - "Missing Mod or Collection should not be possible here."); } return sb.ToString(); From eab1352340635b33b1173d444037dd07e162967e Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 20 Mar 2024 21:27:47 +0000 Subject: [PATCH 325/786] [CI] Updating repo.json for testing_1.2.1.1 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 890503f..2816c08 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.0.8", - "TestingAssemblyVersion": "1.2.1.0", + "TestingAssemblyVersion": "1.2.1.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.1.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.2.1.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 02fc8452d2cd7b59be8d3da364bb2e75013ab1cb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 21 Mar 2024 23:53:57 +0100 Subject: [PATCH 326/786] Update ObjectManager --- Glamourer/Api/GlamourerIpc.cs | 7 +- Glamourer/Glamourer.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 2 +- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs | 33 ++++---- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 2 +- .../Gui/Tabs/DebugTab/ObjectManagerPanel.cs | 2 +- Glamourer/Interop/ObjectManager.cs | 77 ++++++++----------- Glamourer/Services/ServiceManager.cs | 3 +- Penumbra.GameData | 2 +- 10 files changed, 57 insertions(+), 75 deletions(-) diff --git a/Glamourer/Api/GlamourerIpc.cs b/Glamourer/Api/GlamourerIpc.cs index 24039b8..428a5c7 100644 --- a/Glamourer/Api/GlamourerIpc.cs +++ b/Glamourer/Api/GlamourerIpc.cs @@ -168,8 +168,7 @@ public sealed partial class GlamourerIpc : IDisposable return []; _objects.Update(); - return _objects.Where(i => i.Key is { IsValid: true, Type: IdentifierType.Player } && i.Key.PlayerName == byteString) - .Select(i => i.Key); + return _objects.Keys.Where(i => i is { IsValid: true, Type: IdentifierType.Player } && i.PlayerName == byteString); } private IEnumerable FindActorsRevert(string actorName) @@ -178,8 +177,8 @@ public sealed partial class GlamourerIpc : IDisposable yield break; _objects.Update(); - foreach (var id in _objects.Where(i => i.Key is { IsValid: true, Type: IdentifierType.Player } && i.Key.PlayerName == byteString) - .Select(i => i.Key)) + 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)) diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 8f88216..9ad0630 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -30,7 +30,7 @@ public class Glamourer : IDalamudPlugin { try { - _services = ServiceManagerA.CreateProvider(pluginInterface, Log); + _services = StaticServiceManager.CreateProvider(pluginInterface, Log); Messager = _services.GetService(); _services.EnsureRequiredServices(); diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 3c23fbc..e688bce 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -56,7 +56,7 @@ public class ActorSelector(ObjectManager objects, ActorManager actors, Ephemeral objects.Update(); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); - var remainder = ImGuiClip.FilteredClippedDraw(objects, skips, CheckFilter, DrawSelectable); + var remainder = ImGuiClip.FilteredClippedDraw(objects.Identifiers, skips, CheckFilter, DrawSelectable); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 00df06b..4aa0163 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -23,7 +23,7 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM public void Draw() { _objectManager.Update(); - foreach (var (identifier, actors) in _objectManager) + foreach (var (identifier, actors) in _objectManager.Identifiers) { if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Trash.ToIconString()}##{actors.Label}", new Vector2(ImGui.GetFrameHeight()), string.Empty, !_stateManager.ContainsKey(identifier), true)) diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs index 2130efe..d96aefa 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTesterPanel.cs @@ -1,5 +1,4 @@ -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin; +using Dalamud.Plugin; using Glamourer.Api; using Glamourer.Interop; using ImGuiNET; @@ -32,7 +31,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag private GlamourerIpc.GlamourerErrorCode _setItemByActorNameEc; private GlamourerIpc.GlamourerErrorCode _setItemOnceByActorNameEc; - public unsafe void Draw() + public void Draw() { ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64); @@ -58,7 +57,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromCharacter); ImGui.TableNextColumn(); base64 = GlamourerIpc.GetAllCustomizationFromCharacterSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); + .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); if (base64 != null) ImGuiUtil.CopyOnClickSelectable(base64); else @@ -66,7 +65,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetAllCustomizationFromLockedCharacter); ImGui.TableNextColumn(); - var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface).Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), 1337); + var base64Locked = GlamourerIpc.GetAllCustomizationFromLockedCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); if (base64Locked != null) ImGuiUtil.CopyOnClickSelectable(base64Locked); else @@ -80,7 +79,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevertCharacter); ImGui.TableNextColumn(); if (ImGui.Button("Revert##Character")) - GlamourerIpc.RevertCharacterSubscriber(_pluginInterface).Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); + GlamourerIpc.RevertCharacterSubscriber(_pluginInterface).Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAll); ImGui.TableNextColumn(); @@ -96,13 +95,13 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Apply##AllCharacter")) GlamourerIpc.ApplyAllToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); + .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllOnceToCharacter); ImGui.TableNextColumn(); if (ImGui.Button("Apply Once##AllCharacter")) GlamourerIpc.ApplyAllOnceToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); + .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyEquipment); ImGui.TableNextColumn(); @@ -113,7 +112,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Apply##EquipCharacter")) GlamourerIpc.ApplyOnlyEquipmentToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); + .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyOnlyCustomization); ImGui.TableNextColumn(); @@ -124,7 +123,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Apply##CustomizeCharacter")) GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); + .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid); ImGui.TableNextColumn(); @@ -140,25 +139,25 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2)) GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface) - .Invoke(guid2, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex)); + .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.Objects.GetDalamudCharacter(_gameObjectIndex)); + .Invoke(guid2Once, _objectManager.GetDalamudCharacter(_gameObjectIndex)); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyAllLock); ImGui.TableNextColumn(); if (ImGui.Button("Apply With Lock##CustomizeCharacter")) GlamourerIpc.ApplyAllToCharacterLockSubscriber(_pluginInterface) - .Invoke(_base64Apply, _objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), 1337); + .Invoke(_base64Apply, _objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); ImGui.TableNextColumn(); if (ImGui.Button("Unlock##CustomizeCharacter")) GlamourerIpc.UnlockSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), 1337); + .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlockAll); ImGui.TableNextColumn(); @@ -170,7 +169,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Revert##CustomizeCharacter")) GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), 1337); + .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), 1337); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList); @@ -184,7 +183,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Set##SetItem")) _setItemEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); if (_setItemEc != GlamourerIpc.GlamourerErrorCode.Success) { ImGui.SameLine(); @@ -195,7 +194,7 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag ImGui.TableNextColumn(); if (ImGui.Button("Set Once##SetItem")) _setItemOnceEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemOnceSubscriber(_pluginInterface) - .Invoke(_objectManager.Objects.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); + .Invoke(_objectManager.GetDalamudCharacter(_gameObjectIndex), (byte)_slot, _customItemId.Id, _stainId.Id, 1337); if (_setItemOnceEc != GlamourerIpc.GlamourerErrorCode.Success) { ImGui.SameLine(); diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index 2268ee5..566ad52 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -31,7 +31,7 @@ public unsafe class ModelEvaluationPanel( public void Draw() { ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); - var actor = _objectManager.Objects[_gameObjectIndex]; + var actor = _objectManager[_gameObjectIndex]; var model = actor.Model; using var table = ImRaii.Table("##evaluationTable", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); ImGui.TableNextColumn(); diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs index e4ea44b..e519ea5 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -69,7 +69,7 @@ public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _acto var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); ImGui.TableNextRow(); - var remainder = ImGuiClip.FilteredClippedDraw(_objectManager, skips, + var remainder = ImGuiClip.FilteredClippedDraw(_objectManager.Identifiers, skips, p => p.Value.Label.Contains(_objectFilter, StringComparison.OrdinalIgnoreCase), p => { diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index 6a1d8a1..f59e95c 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -1,23 +1,31 @@ using Dalamud.Game.ClientState.Objects; +using Dalamud.Plugin; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Control; using Glamourer.Interop.Structs; +using OtterGui.Log; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; namespace Glamourer.Interop; -public class ObjectManager(IFramework framework, IClientState clientState, global::Penumbra.GameData.Interop.ObjectManager objects, ActorManager actors, ITargetManager targets) - : IReadOnlyDictionary +public class ObjectManager( + IFramework framework, + IClientState clientState, + IObjectTable objects, + DalamudPluginInterface pi, + Logger log, + ActorManager actors, + ITargetManager targets) + : global::Penumbra.GameData.Interop.ObjectManager(pi, log, framework, objects) { - public global::Penumbra.GameData.Interop.ObjectManager Objects - => objects; + public DateTime LastUpdate + => LastFrame; - public DateTime LastUpdate { get; private set; } - - public bool IsInGPose { get; private set; } - public ushort World { get; private set; } + private DateTime _identifierUpdate; + public bool IsInGPose { get; private set; } + public ushort World { get; private set; } private readonly Dictionary _identifiers = new(200); private readonly Dictionary _allWorldIdentifiers = new(200); @@ -26,40 +34,26 @@ public class ObjectManager(IFramework framework, IClientState clientState, globa public IReadOnlyDictionary Identifiers => _identifiers; - public void Update() + public override bool Update() { - var lastUpdate = framework.LastUpdate; - if (lastUpdate <= LastUpdate) - return; + if (!base.Update() && _identifierUpdate >= LastUpdate) + return false; - LastUpdate = lastUpdate; - World = (ushort)(clientState.LocalPlayer?.CurrentWorld.Id ?? 0u); + _identifierUpdate = LastUpdate; + World = (ushort)(this[0].Valid ? this[0].HomeWorld : 0); _identifiers.Clear(); _allWorldIdentifiers.Clear(); _nonOwnedIdentifiers.Clear(); - for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i) + foreach (var actor in BattleNpcs.Concat(CutsceneCharacters)) { - var character = objects[i]; - if (character.Identifier(actors, out var identifier)) - HandleIdentifier(identifier, character); - } - - for (var i = (int)ScreenActor.CutsceneStart; i < (int)ScreenActor.CutsceneEnd; ++i) - { - var character = objects[i]; - // Technically the game does not create holes in cutscenes or GPose. - // But for Brio compatibility, we allow holes in GPose. - // Since GPose always has the event actor in the first cutscene slot, we can still optimize in this case. - if (!character.Valid && i == (int)ScreenActor.CutsceneStart) - break; - - HandleIdentifier(character.GetIdentifier(actors), character); + if (actor.Identifier(actors, out var identifier)) + HandleIdentifier(identifier, actor); } void AddSpecial(ScreenActor idx, string label) { - var actor = objects[(int)idx]; + var actor = this[(int)idx]; if (actor.Identifier(actors, out var ident)) { var data = new ActorData(actor, label); @@ -76,15 +70,15 @@ public class ObjectManager(IFramework framework, IClientState clientState, globa AddSpecial(ScreenActor.Card7, "Card Actor 7"); AddSpecial(ScreenActor.Card8, "Card Actor 8"); - for (var i = (int)ScreenActor.ScreenEnd; i < objects.Count; ++i) + foreach (var actor in EventNpcs) { - var character = objects[i]; - if (character.Identifier(actors, out var identifier)) - HandleIdentifier(identifier, character); + if (actor.Identifier(actors, out var identifier)) + HandleIdentifier(identifier, actor); } var gPose = GPosePlayer; IsInGPose = gPose.Utf8Name.Length > 0; + return true; } private void HandleIdentifier(ActorIdentifier identifier, Actor character) @@ -135,10 +129,10 @@ public class ObjectManager(IFramework framework, IClientState clientState, globa } public Actor GPosePlayer - => objects[(int)ScreenActor.GPosePlayer]; + => this[(int)ScreenActor.GPosePlayer]; public Actor Player - => objects[0]; + => this[0]; public unsafe Actor Target => clientState.IsGPosing ? TargetSystem.Instance()->GPoseTarget : TargetSystem.Instance()->Target; @@ -171,15 +165,6 @@ public class ObjectManager(IFramework framework, IClientState clientState, globa } } - public IEnumerator> GetEnumerator() - => Identifiers.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - - public int Count - => Identifiers.Count; - /// Also handles All Worlds players and non-owned NPCs. public bool ContainsKey(ActorIdentifier key) => Identifiers.ContainsKey(key) || _allWorldIdentifiers.ContainsKey(key) || _nonOwnedIdentifiers.ContainsKey(key); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 24a3902..d5f92a9 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -26,12 +26,11 @@ using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; -using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Services; -public static class ServiceManagerA +public static class StaticServiceManager { public static ServiceManager CreateProvider(DalamudPluginInterface pi, Logger log) { diff --git a/Penumbra.GameData b/Penumbra.GameData index 529e181..6668764 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 529e18115023732794994bfb8df4818b68951ea4 +Subproject commit 66687643da2163c938575ad6949c8d0fbd03afe7 From 43c26f8499b17ce9d723055534ffc02051725e96 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Mar 2024 13:32:55 +0100 Subject: [PATCH 327/786] Update OtterGui. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index b4b1436..4e06921 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit b4b14367d8235eabedd561ad3626beb1d2a83889 +Subproject commit 4e06921da239788331a4527aa6a2943cf0e809fe From 9f3f78cf4c7eed3d519bf18c057ea5bbeb84f1ad Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Mar 2024 13:36:14 +0100 Subject: [PATCH 328/786] Remove DI reference. --- Glamourer/Glamourer.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 673a70b..d50c24e 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -87,7 +87,6 @@ - From 73122ad9bfca8a35c34e7311ba2151d6b2418588 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 22 Mar 2024 12:38:05 +0000 Subject: [PATCH 329/786] [CI] Updating repo.json for testing_1.2.1.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 2816c08..58dc1ee 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.0.8", - "TestingAssemblyVersion": "1.2.1.1", + "TestingAssemblyVersion": "1.2.1.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.1.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.2.1.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 71d6a658d6519456d5a7196120ba52b10d08bf87 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Mar 2024 16:42:27 +0100 Subject: [PATCH 330/786] Fix context menu. --- Glamourer/Interop/ContextMenuService.cs | 26 ++++++++++--------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index bd8c333..b80a247 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -47,11 +47,6 @@ public class ContextMenuService : IDisposable }; } - public void Enable() - { - _contextMenu.OnMenuOpened += OnMenuOpened; - } - private unsafe void OnMenuOpened(MenuOpenedArgs args) { if (args.MenuType is ContextMenuType.Inventory) @@ -92,21 +87,14 @@ public class ContextMenuService : IDisposable } } - private bool HandleItem(ItemId id) - { - var itemId = Math.Clamp(id.Id, 0, 500000u); - return _items.ItemData.TryGetValue(itemId, EquipSlot.MainHand, out _lastItem); - } + public void Enable() + => _contextMenu.OnMenuOpened += OnMenuOpened; public void Disable() - { - _contextMenu.OnMenuOpened -= OnMenuOpened; - } + => _contextMenu.OnMenuOpened -= OnMenuOpened; public void Dispose() - { - Disable(); - } + => Disable(); private void OnClick(MenuItemClickedArgs _) { @@ -129,6 +117,12 @@ public class ContextMenuService : IDisposable _state.ChangeEquip(state, EquipSlot.OffHand, offhand, _lastStain, ApplySettings.Manual); } + private bool HandleItem(ItemId id) + { + var itemId = id.Id % 500000u; + return _items.ItemData.TryGetValue(itemId, EquipSlot.MainHand, out _lastItem); + } + private static unsafe bool ValidateChatLogContext(nint agent) => *(uint*)(agent + ChatLogContextItemId + 8) == 3; } From 3d421881f6682fa51c5eca18c07620e9b75f3b42 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 28 Mar 2024 15:23:19 +0100 Subject: [PATCH 331/786] Maybe fix weapon behavior with multiple restricted designs with identical weapon types. --- Glamourer/Automation/AutoDesignApplier.cs | 8 +-- Glamourer/Designs/Design.cs | 5 +- Glamourer/Designs/IDesignStandIn.cs | 3 +- Glamourer/Designs/Links/DesignMerger.cs | 18 ++--- Glamourer/Designs/Links/MergedDesign.cs | 67 +++++++++++++++++-- .../Designs/Special/QuickSelectedDesign.cs | 3 +- Glamourer/Designs/Special/RandomDesign.cs | 7 +- Glamourer/Designs/Special/RevertDesign.cs | 5 +- Glamourer/Interop/ContextMenuService.cs | 2 +- Glamourer/State/JobChangeState.cs | 18 +++-- Glamourer/State/StateEditor.cs | 4 +- Penumbra.GameData | 2 +- 12 files changed, 104 insertions(+), 38 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index a3b912d..c739a75 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -77,7 +77,7 @@ public sealed class AutoDesignApplier : IDisposable { case EquipSlot.MainHand: { - if (_jobChangeState.TryGetValue(current.Type, out var data)) + if (_jobChangeState.TryGetValue(current.Type, actor.Job, out var data)) { Glamourer.Log.Verbose( $"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); @@ -89,7 +89,7 @@ public sealed class AutoDesignApplier : IDisposable } case EquipSlot.OffHand when current.Type == state.BaseData.MainhandType.Offhand(): { - if (_jobChangeState.TryGetValue(current.Type, out var data)) + if (_jobChangeState.TryGetValue(current.Type, actor.Job, out var data)) { Glamourer.Log.Verbose( $"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); @@ -204,7 +204,7 @@ public sealed class AutoDesignApplier : IDisposable return; } - if (!_state.TryGetValue(id, out var state)) + if (!_state.GetOrCreate(actor, out var state)) return; if (oldJob.Id == newJob.Id && state.LastJob == newJob.Id) @@ -279,7 +279,7 @@ public sealed class AutoDesignApplier : IDisposable return; var mergedDesign = _designMerger.Merge( - set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design.AllLinks.Select(l => (l.Design, l.Flags & d.Type))), + 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)); } diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 123f15a..83b6cfd 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -8,6 +8,7 @@ using Glamourer.State; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui.Classes; +using Penumbra.GameData.Structs; namespace Glamourer.Designs; @@ -50,8 +51,8 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public string Incognito => Identifier.ToString()[..8]; - public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks - => LinkContainer.GetAllLinks(this).Select(t => ((IDesignStandIn)t.Link.Link, t.Link.Type)); + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks + => LinkContainer.GetAllLinks(this).Select(t => ((IDesignStandIn)t.Link.Link, t.Link.Type, JobFlag.All)); #endregion diff --git a/Glamourer/Designs/IDesignStandIn.cs b/Glamourer/Designs/IDesignStandIn.cs index a6ee702..4865068 100644 --- a/Glamourer/Designs/IDesignStandIn.cs +++ b/Glamourer/Designs/IDesignStandIn.cs @@ -2,6 +2,7 @@ using Glamourer.Interop.Material; using Glamourer.State; using Newtonsoft.Json.Linq; +using Penumbra.GameData.Structs; namespace Glamourer.Designs; @@ -15,7 +16,7 @@ public interface IDesignStandIn : IEquatable public string SerializeName(); public StateSource AssociatedSource(); - public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks { get; } + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks { get; } public void AddData(JObject jObj); diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 92bc568..44f4db9 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -19,9 +19,9 @@ 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)), 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)> 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); @@ -29,7 +29,7 @@ public class DesignMerger( var startBodyType = currentCustomize.BodyType; CustomizeFlag fixFlags = 0; respectOwnership &= _config.UnlockedItemMode; - foreach (var (design, type) in designs) + foreach (var (design, type, jobs) in designs) { if (type is 0) continue; @@ -44,8 +44,8 @@ public class DesignMerger( ReduceMeta(data, applyMeta, ret, source); ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source, respectOwnership, startBodyType); ReduceEquip(data, equipFlags, ret, source, respectOwnership); - ReduceMainhands(data, equipFlags, ret, source, respectOwnership); - ReduceOffhands(data, equipFlags, ret, source, respectOwnership); + ReduceMainhands(data, jobs, equipFlags, ret, source, respectOwnership); + ReduceOffhands(data, jobs, equipFlags, ret, source, respectOwnership); ReduceCrests(data, crestFlags, ret, source); ReduceParameters(data, parameterFlags, ret, source); ReduceMods(design as Design, ret, modAssociations); @@ -170,7 +170,7 @@ public class DesignMerger( } } - private void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source, + private void ReduceMainhands(in DesignData design, JobFlag allowedJobs, EquipFlag equipFlags, MergedDesign ret, StateSource source, bool respectOwnership) { if (!equipFlags.HasFlag(EquipFlag.Mainhand)) @@ -186,10 +186,10 @@ public class DesignMerger( ret.Design.GetDesignDataRef().SetItem(EquipSlot.MainHand, weapon); } - ret.Weapons.TryAdd(weapon.Type, (weapon, source)); + ret.Weapons.TryAdd(weapon.Type, weapon, source, allowedJobs); } - private void ReduceOffhands(in DesignData design, 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; @@ -205,7 +205,7 @@ public class DesignMerger( } if (weapon.Valid) - ret.Weapons.TryAdd(weapon.Type, (weapon, source)); + ret.Weapons.TryAdd(weapon.Type, weapon, source, allowedJobs); } private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret, diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index d36a284..131b074 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -5,6 +5,61 @@ using Penumbra.GameData.Structs; namespace Glamourer.Designs.Links; +public readonly struct WeaponList +{ + private readonly Dictionary> _list = new(4); + + public IEnumerable<(EquipItem, StateSource, JobFlag)> Values + => _list.Values.SelectMany(t => t); + + public void Clear() + => _list.Clear(); + + public bool TryAdd(FullEquipType type, EquipItem item, StateSource source, JobFlag flags) + { + if (!_list.TryGetValue(type, out var list)) + { + list = new List<(EquipItem, StateSource, JobFlag)>(2); + _list.Add(type, list); + } + + var remainingFlags = list.Select(t => t.Item3) + .Aggregate(flags, (current, existingFlags) => current & ~existingFlags); + + if (remainingFlags == 0) + return false; + + list.Add((item, source, remainingFlags)); + return true; + } + + public bool TryGet(FullEquipType type, JobId id, out (EquipItem, StateSource) ret) + { + if (!_list.TryGetValue(type, out var list)) + { + ret = default; + return false; + } + + var flag = (JobFlag)(1ul << id.Id); + + foreach (var (item, source, flags) in list) + { + if (flags.HasFlag(flag)) + { + ret = (item, source); + return true; + } + } + + ret = default; + return false; + } + + public WeaponList() + { } +} + public sealed class MergedDesign { public MergedDesign(DesignManager designManager) @@ -24,14 +79,14 @@ public sealed class MergedDesign { var weapon = design.DesignData.Item(EquipSlot.MainHand); if (weapon.Valid) - Weapons.TryAdd(weapon.Type, (weapon, StateSource.Manual)); + Weapons.TryAdd(weapon.Type, weapon, StateSource.Manual, JobFlag.All); } if (design.DoApplyEquip(EquipSlot.OffHand)) { var weapon = design.DesignData.Item(EquipSlot.OffHand); if (weapon.Valid) - Weapons.TryAdd(weapon.Type, (weapon, StateSource.Manual)); + Weapons.TryAdd(weapon.Type, weapon, StateSource.Manual, JobFlag.All); } } @@ -42,8 +97,8 @@ public sealed class MergedDesign AssociatedMods[mod] = settings; } - public readonly DesignBase Design; - public readonly Dictionary Weapons = new(4); - public readonly SortedList AssociatedMods = []; - public StateSources Sources = new(); + public readonly DesignBase Design; + public readonly WeaponList Weapons = new(); + public readonly SortedList AssociatedMods = []; + public StateSources Sources = new(); } diff --git a/Glamourer/Designs/Special/QuickSelectedDesign.cs b/Glamourer/Designs/Special/QuickSelectedDesign.cs index dd0f00f..f347085 100644 --- a/Glamourer/Designs/Special/QuickSelectedDesign.cs +++ b/Glamourer/Designs/Special/QuickSelectedDesign.cs @@ -4,6 +4,7 @@ using Glamourer.Interop.Material; using Glamourer.State; using Newtonsoft.Json.Linq; using OtterGui.Services; +using Penumbra.GameData.Structs; namespace Glamourer.Designs.Special; @@ -38,7 +39,7 @@ public class QuickSelectedDesign(QuickDesignCombo combo) : IDesignStandIn, IServ public StateSource AssociatedSource() => StateSource.Manual; - public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks => combo.Design?.AllLinks ?? []; public void AddData(JObject jObj) diff --git a/Glamourer/Designs/Special/RandomDesign.cs b/Glamourer/Designs/Special/RandomDesign.cs index b40ba92..c09fd2b 100644 --- a/Glamourer/Designs/Special/RandomDesign.cs +++ b/Glamourer/Designs/Special/RandomDesign.cs @@ -2,6 +2,7 @@ using Glamourer.Interop.Material; using Glamourer.State; using Newtonsoft.Json.Linq; +using Penumbra.GameData.Structs; namespace Glamourer.Designs.Special; @@ -45,7 +46,7 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn public StateSource AssociatedSource() => StateSource.Manual; - public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks { get { @@ -53,8 +54,8 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn if (_currentDesign == null) yield break; - foreach (var (link, type) in _currentDesign.AllLinks) - yield return (link, type); + foreach (var (link, type, jobs) in _currentDesign.AllLinks) + yield return (link, type, jobs); } } diff --git a/Glamourer/Designs/Special/RevertDesign.cs b/Glamourer/Designs/Special/RevertDesign.cs index 0f0207b..e450ff1 100644 --- a/Glamourer/Designs/Special/RevertDesign.cs +++ b/Glamourer/Designs/Special/RevertDesign.cs @@ -2,6 +2,7 @@ using Glamourer.Interop.Material; using Glamourer.State; using Newtonsoft.Json.Linq; +using Penumbra.GameData.Structs; namespace Glamourer.Designs.Special; @@ -28,9 +29,9 @@ public class RevertDesign : IDesignStandIn public StateSource AssociatedSource() => StateSource.Game; - public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks { - get { yield return (this, ApplicationType.All); } + get { yield return (this, ApplicationType.All, JobFlag.All); } } public void AddData(JObject jObj) diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index b80a247..8cd5391 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -119,7 +119,7 @@ public class ContextMenuService : IDisposable private bool HandleItem(ItemId id) { - var itemId = id.Id % 500000u; + var itemId = id.StripModifiers; return _items.ItemData.TryGetValue(itemId, EquipSlot.MainHand, out _lastItem); } diff --git a/Glamourer/State/JobChangeState.cs b/Glamourer/State/JobChangeState.cs index 84aa3cc..0fe1820 100644 --- a/Glamourer/State/JobChangeState.cs +++ b/Glamourer/State/JobChangeState.cs @@ -1,18 +1,21 @@ -using OtterGui.Services; +using Glamourer.Designs.Links; +using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.State; -public sealed class JobChangeState : Dictionary, IService +public sealed class JobChangeState : IService { + private readonly WeaponList _weaponList = new(); + public ActorState? State { get; private set; } public void Reset() { State = null; - Clear(); + _weaponList.Clear(); } public bool HasState @@ -21,10 +24,13 @@ public sealed class JobChangeState : Dictionary State?.Identifier ?? ActorIdentifier.Invalid; - public void Set(ActorState state, IEnumerable<(EquipItem, StateSource)> items) + public bool TryGetValue(FullEquipType slot, JobId jobId, out (EquipItem, StateSource) data) + => _weaponList.TryGet(slot, jobId, out data); + + public void Set(ActorState state, IEnumerable<(EquipItem, StateSource, JobFlag)> items) { - foreach (var (item, source) in items.Where(p => p.Item1.Valid)) - TryAdd(item.Type, (item, source)); + foreach (var (item, source, flags) in items.Where(p => p.Item1.Valid)) + _weaponList.TryAdd(item.Type, item, source, flags); State = state; } } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 600ab17..9bae385 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -298,7 +298,7 @@ public class StateEditor( } var currentType = state.BaseData.Item(weaponSlot).Type; - if (mergedDesign.Weapons.TryGetValue(currentType, out var weapon)) + if (mergedDesign.Weapons.TryGet(currentType, state.LastJob, out var weapon)) { var source = settings.UseSingleSource ? settings.Source : weapon.Item2 is StateSource.Game ? StateSource.Game : settings.Source; @@ -311,7 +311,7 @@ public class StateEditor( if (settings.FromJobChange) jobChange.Set(state, mergedDesign.Weapons.Values.Select(m => (m.Item1, settings.UseSingleSource ? settings.Source : - m.Item2 is StateSource.Game ? StateSource.Game : settings.Source))); + m.Item2 is StateSource.Game ? StateSource.Game : settings.Source, m.Item3))); foreach (var meta in MetaExtensions.AllRelevant.Where(mergedDesign.Design.DoApplyMeta)) { diff --git a/Penumbra.GameData b/Penumbra.GameData index 6668764..04237f8 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 66687643da2163c938575ad6949c8d0fbd03afe7 +Subproject commit 04237f8e80e2277ea99701bd240a09fcffe4db97 From 8375abd6cbfa52b223ed0936642f2afa28efb787 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 4 Apr 2024 14:01:58 +0200 Subject: [PATCH 332/786] Maybe fix visor state issue. --- Glamourer/Events/VisorStateChanged.cs | 2 +- Glamourer/Interop/MetaService.cs | 2 +- Glamourer/Interop/VisorService.cs | 2 +- Glamourer/State/StateListener.cs | 9 ++++++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Glamourer/Events/VisorStateChanged.cs b/Glamourer/Events/VisorStateChanged.cs index 8e70002..d2d3a6c 100644 --- a/Glamourer/Events/VisorStateChanged.cs +++ b/Glamourer/Events/VisorStateChanged.cs @@ -12,7 +12,7 @@ namespace Glamourer.Events; /// /// public sealed class VisorStateChanged() - : EventWrapperRef2(nameof(VisorStateChanged)) + : EventWrapperRef3(nameof(VisorStateChanged)) { public enum Priority { diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs index a862e59..1bc7a32 100644 --- a/Glamourer/Interop/MetaService.cs +++ b/Glamourer/Interop/MetaService.cs @@ -93,7 +93,7 @@ public unsafe class MetaService : IDisposable private void ToggleVisorDetour(DrawDataContainer* drawData, bool value) { Actor actor = drawData->Parent; - _visorEvent.Invoke(actor.Model, ref value); + _visorEvent.Invoke(actor.Model, true, ref value); Glamourer.Log.Verbose($"[MetaService] Toggle Visor triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}."); _toggleVisorHook.Original(drawData, value); } diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 4232446..4487ef8 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -52,7 +52,7 @@ public class VisorService : IDisposable var originalOn = on; // Invoke an event that can change the requested value // and also control whether the function should be called at all. - Event.Invoke(human, ref on); + Event.Invoke(human, false, ref on); Glamourer.Log.Excessive( $"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn})."); diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index ff227a8..7a30108 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -568,7 +568,7 @@ public class StateListener : IDisposable } /// Handle visor state changes made by the game. - private void OnVisorChange(Model model, ref bool value) + private unsafe void OnVisorChange(Model model, bool game, ref bool value) { // Skip updates when in customize update. if (ChangeCustomizeService.InUpdate.InMethod) @@ -578,6 +578,13 @@ public class StateListener : IDisposable // We do not need to handle fixed designs, // since a fixed design would already have established state-tracking. var actor = _penumbra.GameObjectFromDrawObject(model); + + // Only actually change anything if the actor state changed, + // when equipping headgear the method is called with the current draw object state, + // which corrupts Glamourer's assumed game state otherwise. + if (!game && actor.AsCharacter->DrawData.IsVisorToggled != value) + return; + if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; From 0d427dcabaf197298b50bd443fb12b2159b158fd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 4 Apr 2024 14:02:12 +0200 Subject: [PATCH 333/786] Fix some obsoletes. --- Glamourer/Interop/ScalingService.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index c148bbd..1b55db2 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -100,11 +100,11 @@ public unsafe class ScalingService : IDisposable /// We do not change the Customize gender because the functions use the GetGender() vfunc, which uses the game objects gender value. private static (byte Race, byte Clan, byte Gender) GetScaleRelevantCustomize(Character* character) - => (character->DrawData.CustomizeData.Race, character->DrawData.CustomizeData.Clan, character->GameObject.Gender); + => (character->DrawData.CustomizeData.Race, character->DrawData.CustomizeData.Tribe, character->GameObject.Sex); private static (byte Gender, byte BodyType, byte Clan, byte Height) GetHeightRelevantCustomize(Character* character) => (character->DrawData.CustomizeData.Sex, character->DrawData.CustomizeData.BodyType, - character->DrawData.CustomizeData.Clan, character->DrawData.CustomizeData[(int)CustomizeIndex.Height]); + character->DrawData.CustomizeData.Tribe, character->DrawData.CustomizeData[(int)CustomizeIndex.Height]); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private static void SetScaleCustomize(Character* character, Model model) @@ -112,15 +112,15 @@ public unsafe class ScalingService : IDisposable if (!model.IsHuman) return; - SetScaleCustomize(character, model.AsHuman->Customize.Race, model.AsHuman->Customize.Clan, model.AsHuman->Customize.Sex); + SetScaleCustomize(character, model.AsHuman->Customize.Race, model.AsHuman->Customize.Tribe, model.AsHuman->Customize.Sex); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private static void SetScaleCustomize(Character* character, byte race, byte clan, byte gender) { - character->DrawData.CustomizeData.Race = race; - character->DrawData.CustomizeData.Clan = clan; - character->GameObject.Gender = gender; + character->DrawData.CustomizeData.Race = race; + character->DrawData.CustomizeData.Tribe = clan; + character->GameObject.Sex = gender; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -129,7 +129,7 @@ public unsafe class ScalingService : IDisposable if (!model.IsHuman) return; - SetHeightCustomize(character, model.AsHuman->Customize.Sex, model.AsHuman->Customize.BodyType, model.AsHuman->Customize.Clan, + SetHeightCustomize(character, model.AsHuman->Customize.Sex, model.AsHuman->Customize.BodyType, model.AsHuman->Customize.Tribe, model.AsHuman->Customize[(int)CustomizeIndex.Height]); } @@ -138,7 +138,7 @@ public unsafe class ScalingService : IDisposable { character->DrawData.CustomizeData.Sex = gender; character->DrawData.CustomizeData.BodyType = bodyType; - character->DrawData.CustomizeData.Clan = clan; + character->DrawData.CustomizeData.Tribe = clan; character->DrawData.CustomizeData.Data[(int)CustomizeIndex.Height] = height; } } From c573feefecfebaa91c972266a9cea330779315d0 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 4 Apr 2024 12:04:19 +0000 Subject: [PATCH 334/786] [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 335/786] 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 336/786] 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 337/786] 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 338/786] 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 339/786] 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 340/786] 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 341/786] 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 342/786] 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 343/786] 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 344/786] 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 345/786] 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 346/786] 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 347/786] 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 348/786] 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 349/786] 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 350/786] 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 351/786] 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 352/786] 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 353/786] 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 354/786] 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 355/786] 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 356/786] 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 357/786] 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 358/786] 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 359/786] 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 360/786] 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 361/786] 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 362/786] 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 363/786] 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 364/786] 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 365/786] 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 366/786] 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 367/786] 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 368/786] 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 369/786] [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 370/786] 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 371/786] 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 372/786] [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 373/786] 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 374/786] [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 375/786] 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 376/786] 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 377/786] 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 378/786] 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."), From b7d482a24e3e3e73a4d8e55eedaa58c0655b5f59 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 1 Jun 2024 09:55:40 +0000 Subject: [PATCH 379/786] [CI] Updating repo.json for 1.2.3.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 20c490f..7b730f3 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.0.8", - "TestingAssemblyVersion": "1.2.2.2", + "AssemblyVersion": "1.2.3.0", + "TestingAssemblyVersion": "1.2.3.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "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.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 67acdd2d133b108fba6c0b71a17db5715aabb82c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 1 Jun 2024 19:01:34 +0200 Subject: [PATCH 380/786] Ignore unused values in gmp entries for serialization --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 29b71cf..fed687b 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 29b71cf7b3cc68995d38f0954fa38c4b9500a81d +Subproject commit fed687b536b7c709484db251b690b8821c5ef403 From 4c32ca6e635fe5a9ea7fd1fbd8aeff2ef4a10505 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 1 Jun 2024 19:02:01 +0200 Subject: [PATCH 381/786] use the last matching game object instead of the first for advanced dyes, specifically highlighting. --- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 5a05058..8febcd9 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -167,7 +167,7 @@ public class ActorPanel DrawHumanPanel(); else DrawMonsterPanel(); - _advancedDyes.Draw(_actor, _state); + _advancedDyes.Draw(_data.Objects.Last(), _state); } private void DrawHumanPanel() From 86fc472144a3279aadeda9bd209ad8cf83ce88b6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 1 Jun 2024 19:39:11 +0200 Subject: [PATCH 382/786] Change Glamourer.Api to not use ssh. --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 856d5b3..137c8ba 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,5 +16,5 @@ branch = main [submodule "Glamourer.Api"] path = Glamourer.Api - url = git@github.com:Ottermandias/Glamourer.Api.git + url = https://github.com/Ottermandias/Glamourer.Api.git branch = main From 0450c4e3f7b70db54d2a3a77fb4fae75525410ce Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 1 Jun 2024 20:26:18 +0200 Subject: [PATCH 383/786] Add StateChangedWithType. --- Glamourer.Api | 2 +- Glamourer/Api/GlamourerApi.cs | 2 +- Glamourer/Api/StateApi.cs | 17 ++-- Glamourer/Events/StateChanged.cs | 78 +++++-------------- .../Tabs/DebugTab/IpcTester/StateIpcTester.cs | 6 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 5 +- Glamourer/State/StateEditor.cs | 31 ++++---- Glamourer/State/StateManager.cs | 7 +- 8 files changed, 56 insertions(+), 92 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 30cdd0a..a20d4ab 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 30cdd0a2386045b84f3cfdde483a1ffe60441f05 +Subproject commit a20d4ab1811a7f66918afab9b41ec723f75054f5 diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 2c555af..b9e21fd 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -6,7 +6,7 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; - public const int CurrentApiVersionMinor = 1; + public const int CurrentApiVersionMinor = 2; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 344fc5c..203f1b0 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -247,8 +247,9 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public event Action? StateChanged; - public event Action? GPoseChanged; + public event Action? StateChanged; + public event Action? StateChangedWithType; + public event Action? GPoseChanged; private void ApplyDesign(ActorState state, DesignBase design, uint key, ApplyFlag flags) { @@ -329,12 +330,14 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnGPoseChange(bool gPose) => GPoseChanged?.Invoke(gPose); - private void OnStateChange(StateChanged.Type _1, StateSource _2, ActorState _3, ActorData actors, object? _5) + private void OnStateChange(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, object? _5) { - if (StateChanged == null) - return; + if (StateChanged != null) + foreach (var actor in actors.Objects) + StateChanged.Invoke(actor.Address); - foreach (var actor in actors.Objects) - StateChanged.Invoke(actor.Address); + if (StateChangedWithType != null) + foreach (var actor in actors.Objects) + StateChangedWithType.Invoke(actor.Address, type); } } diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 4d878e8..641665c 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -1,67 +1,25 @@ +using Glamourer.Api.Enums; using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Classes; -namespace Glamourer.Events +namespace Glamourer.Events; + +/// +/// Triggered when a Design is edited in any way. +/// +/// Parameter is the type of the change +/// Parameter is the changed saved state. +/// Parameter is the existing actors using this saved state. +/// Parameter is any additional data depending on the type of change. +/// +/// +public sealed class StateChanged() + : EventWrapper(nameof(StateChanged)) { - /// - /// Triggered when a Design is edited in any way. - /// - /// Parameter is the type of the change - /// Parameter is the changed saved state. - /// Parameter is the existing actors using this saved state. - /// Parameter is any additional data depending on the type of change. - /// - /// - public sealed class StateChanged() - : EventWrapper(nameof(StateChanged)) + public enum Priority { - public enum Type - { - /// A characters saved state had the model id changed. This means everything may have changed. Data is the old model id and the new model id. [(uint, uint)] - Model, - - /// A characters saved state had multiple customization values changed. TData is the old customize array and the applied changes. [(Customize, CustomizeFlag)] - EntireCustomize, - - /// A characters saved state had a customization value changed. Data is the old value, the new value and the type. [(CustomizeValue, CustomizeValue, CustomizeIndex)]. - Customize, - - /// A characters saved state had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. - Equip, - - /// A characters saved state had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. - Weapon, - - /// A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. - Stain, - - /// A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. - Crest, - - /// A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. - Parameter, - - /// A characters saved state had a material color table value changed. Data is the old value, the new value and the index [(Vector3, Vector3, MaterialValueIndex)] or just the index for resets. - MaterialValue, - - /// A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] - Design, - - /// A characters saved state had its state reset to its game values. This means everything may have changed. Data is null. - Reset, - - /// A characters saved state had a meta toggle changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. - Other, - - /// A characters state was reapplied. Data is null. - Reapply, - } - - public enum Priority - { - GlamourerIpc = int.MinValue, - PenumbraAutoRedraw = 0, - } + GlamourerIpc = int.MinValue, + PenumbraAutoRedraw = 0, } -} +} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index f210b0f..81c8cab 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -30,7 +30,7 @@ public class StateIpcTester : IUiService, IDisposable private string _base64State = string.Empty; private string? _getStateString; - public readonly EventSubscriber StateChanged; + public readonly EventSubscriber StateChanged; private nint _lastStateChangeActor; private ByteString _lastStateChangeName = ByteString.Empty; private DateTime _lastStateChangeTime; @@ -44,7 +44,7 @@ public class StateIpcTester : IUiService, IDisposable public StateIpcTester(DalamudPluginInterface pluginInterface) { _pluginInterface = pluginInterface; - StateChanged = Api.IpcSubscribers.StateChanged.Subscriber(_pluginInterface, OnStateChanged); + StateChanged = Api.IpcSubscribers.StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange); StateChanged.Disable(); GPoseChanged.Disable(); @@ -195,7 +195,7 @@ public class StateIpcTester : IUiService, IDisposable ImGui.TextUnformatted($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}"); } - private void OnStateChanged(nint actor) + private void OnStateChanged(nint actor, StateChangeType _) { _lastStateChangeActor = actor; _lastStateChangeTime = DateTime.UtcNow; diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index 9359bea..72fb554 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -1,4 +1,5 @@ using Dalamud.Plugin.Services; +using Glamourer.Api.Enums; using Glamourer.Events; using Glamourer.Interop.Structs; using Glamourer.State; @@ -43,9 +44,9 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService private readonly ConcurrentSet _skips = []; private DateTime _frame; - private void OnStateChange(StateChanged.Type type, StateSource source, ActorState state, ActorData _1, object? _2) + private void OnStateChange(StateChangeType type, StateSource source, ActorState state, ActorData _1, object? _2) { - if (type is StateChanged.Type.Design && source.IsIpc()) + if (type is StateChangeType.Design && source.IsIpc()) _skips.TryAdd(state); } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index bd5d1e0..c20a69d 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,3 +1,4 @@ +using Glamourer.Api.Enums; using Glamourer.Designs; using Glamourer.Designs.Links; using Glamourer.Events; @@ -38,7 +39,7 @@ public class StateEditor( var actors = Applier.ForceRedraw(state, source.RequiresChange()); Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId)); + StateChanged.Invoke(StateChangeType.Model, source, state, actors, (old, modelId)); } /// @@ -51,7 +52,7 @@ public class StateEditor( var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.Customize, settings.Source, state, actors, (old, value, idx)); + StateChanged.Invoke(StateChangeType.Customize, settings.Source, state, actors, (old, value, idx)); } /// @@ -64,7 +65,7 @@ public class StateEditor( var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.EntireCustomize, settings.Source, state, actors, (old, applied)); + StateChanged.Invoke(StateChangeType.EntireCustomize, settings.Source, state, actors, (old, applied)); } /// @@ -74,8 +75,8 @@ public class StateEditor( if (!Editor.ChangeItem(state, slot, item, settings.Source, out var old, settings.Key)) return; - var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon; - var actors = type is StateChanged.Type.Equip + var type = slot.ToIndex() < 10 ? StateChangeType.Equip : StateChangeType.Weapon; + var actors = type is StateChangeType.Equip ? Applier.ChangeArmor(state, slot, settings.Source.RequiresChange()) : Applier.ChangeWeapon(state, slot, settings.Source.RequiresChange(), item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); @@ -107,8 +108,8 @@ public class StateEditor( out var old, out var oldStain, settings.Key)) return; - var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon; - var actors = type is StateChanged.Type.Equip + var type = slot.ToIndex() < 10 ? StateChangeType.Equip : StateChangeType.Weapon; + var actors = type is StateChangeType.Equip ? Applier.ChangeArmor(state, slot, settings.Source.RequiresChange()) : Applier.ChangeWeapon(state, slot, settings.Source.RequiresChange(), item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); @@ -119,7 +120,7 @@ public class StateEditor( 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")}.]"); StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot)); - StateChanged.Invoke(StateChanged.Type.Stain, settings.Source, state, actors, (oldStain, stain!.Value, slot)); + StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (oldStain, stain!.Value, slot)); } /// @@ -132,7 +133,7 @@ public class StateEditor( var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.Stain, settings.Source, state, actors, (old, stain, slot)); + StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (old, stain, slot)); } /// @@ -145,7 +146,7 @@ public class StateEditor( var actors = Applier.ChangeCrests(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.Crest, settings.Source, state, actors, (old, crest, slot)); + StateChanged.Invoke(StateChangeType.Crest, settings.Source, state, actors, (old, crest, slot)); } /// @@ -163,7 +164,7 @@ public class StateEditor( var actors = Applier.ChangeParameters(state, flag, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag)); + StateChanged.Invoke(StateChangeType.Parameter, settings.Source, state, actors, (old, @new, flag)); } public void ChangeMaterialValue(object data, MaterialValueIndex index, in MaterialValueState newValue, ApplySettings settings) @@ -175,7 +176,7 @@ public class StateEditor( var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index)); + StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index)); } public void ResetMaterialValue(object data, MaterialValueIndex index, ApplySettings settings) @@ -187,7 +188,7 @@ public class StateEditor( var actors = Applier.ChangeMaterialValue(state, index, true); Glamourer.Log.Verbose( $"Reset material value in state {state.Identifier.Incognito(null)} to game value. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, index); + StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, index); } /// @@ -200,7 +201,7 @@ public class StateEditor( var actors = Applier.ChangeMetaState(state, index, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.Other, settings.Source, state, actors, (old, value, MetaIndex.HatState)); + StateChanged.Invoke(StateChangeType.Other, settings.Source, state, actors, (old, value, MetaIndex.HatState)); } /// @@ -357,7 +358,7 @@ public class StateEditor( Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.Design, state.Sources[MetaIndex.Wetness], state, actors, mergedDesign.Design); + StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, mergedDesign.Design); return; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 77e8797..f057580 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -1,4 +1,5 @@ using Dalamud.Plugin.Services; +using Glamourer.Api.Enums; using Glamourer.Designs; using Glamourer.Designs.Links; using Glamourer.Events; @@ -257,7 +258,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null); + StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -282,7 +283,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null); + StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); } public void ResetCustomize(ActorState state, StateSource source, uint key = 0) @@ -414,7 +415,7 @@ public sealed class StateManager( { var data = Applier.ApplyAll(state, forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); - StateChanged.Invoke(StateChanged.Type.Reapply, source, state, data, null); + StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); } public void DeleteState(ActorIdentifier identifier) From 9a14f725b8e3c6659f99c0f5a58b6782c0d79054 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 1 Jun 2024 20:39:58 +0200 Subject: [PATCH 384/786] Add provider. --- Glamourer/Api/IpcProviders.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 3852fd6..638d228 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -43,6 +43,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.RevertToAutomation.Provider(pi, api.State), IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), IpcSubscribers.StateChanged.Provider(pi, api.State), + IpcSubscribers.StateChangedWithType.Provider(pi, api.State), IpcSubscribers.GPoseChanged.Provider(pi, api.State), ]; _initializedProvider.Invoke(); From 0f409064513ab8dca063b9e125f78f7f006b43aa Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 2 Jun 2024 00:01:56 +0200 Subject: [PATCH 385/786] Update Penumbra.Api --- Penumbra.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.Api b/Penumbra.Api index 69d106b..f1e4e52 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 69d106b457eb0f73d4b4caf1234da5631fd6fbf0 +Subproject commit f1e4e520daaa8f23e5c8b71d55e5992b8f6768e2 From 960548f7b27d24afd124cadb0234bf7e48fc74f2 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 1 Jun 2024 22:12:12 +0000 Subject: [PATCH 386/786] [CI] Updating repo.json for 1.2.3.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 7b730f3..af81105 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.3.0", - "TestingAssemblyVersion": "1.2.3.0", + "AssemblyVersion": "1.2.3.1", + "TestingAssemblyVersion": "1.2.3.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From eb7cc6ffa02f82ab33136d75e8062851e2354c13 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 5 Jun 2024 11:28:58 +0200 Subject: [PATCH 387/786] Add IpcPending. --- Glamourer/Interop/Material/MaterialManager.cs | 5 +++++ Glamourer/State/StateListener.cs | 6 ++++++ Glamourer/State/StateSource.cs | 21 +++++++++++++------ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 8e3936e..b8941e0 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -96,6 +96,11 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.Manual), out _); break; + case StateSource.IpcPending: + materialValue.Model.Apply(ref row); + state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.IpcManual), + out _); + break; case StateSource.IpcManual: case StateSource.Manual: deleteList.Add(idx); diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 73c8f0d..bd75faf 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -800,6 +800,12 @@ public class StateListener : IDisposable if (_config.UseAdvancedParameters) model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; + case StateSource.IpcPending: + state.BaseData.Parameters.Set(flag, newValue); + state.Sources[flag] = StateSource.IpcManual; + if (_config.UseAdvancedParameters) + model.ApplySingleParameterData(flag, state.ModelData.Parameters); + break; } } } diff --git a/Glamourer/State/StateSource.cs b/Glamourer/State/StateSource.cs index d489814..9a12214 100644 --- a/Glamourer/State/StateSource.cs +++ b/Glamourer/State/StateSource.cs @@ -10,8 +10,9 @@ public enum StateSource : byte IpcFixed, IpcManual, - // Only used for CustomizeParameters. + // Only used for CustomizeParameters and advanced dyes. Pending, + IpcPending, } public static class StateSourceExtensions @@ -19,9 +20,10 @@ public static class StateSourceExtensions public static StateSource Base(this StateSource source) => source switch { - StateSource.Manual or StateSource.IpcManual or StateSource.Pending => StateSource.Manual, - StateSource.Fixed or StateSource.IpcFixed => StateSource.Fixed, - _ => StateSource.Game, + StateSource.Manual or StateSource.Pending => StateSource.Manual, + StateSource.IpcManual or StateSource.IpcPending => StateSource.Manual, + StateSource.Fixed or StateSource.IpcFixed => StateSource.Fixed, + _ => StateSource.Game, }; public static bool IsGame(this StateSource source) @@ -34,7 +36,12 @@ public static class StateSourceExtensions => source.Base() is StateSource.Fixed; public static StateSource SetPending(this StateSource source) - => source is StateSource.Manual ? StateSource.Pending : source; + => source switch + { + StateSource.Manual => StateSource.Pending, + StateSource.IpcManual => StateSource.IpcPending, + _ => source, + }; public static bool RequiresChange(this StateSource source) => source switch @@ -46,7 +53,7 @@ public static class StateSourceExtensions }; public static bool IsIpc(this StateSource source) - => source is StateSource.IpcManual or StateSource.IpcFixed; + => source is StateSource.IpcManual or StateSource.IpcFixed or StateSource.IpcPending; } public unsafe struct StateSources @@ -97,6 +104,7 @@ public unsafe struct StateSources case (byte)StateSource.Manual | ((byte)StateSource.Fixed << 4): case (byte)StateSource.IpcFixed | ((byte)StateSource.Fixed << 4): case (byte)StateSource.Pending | ((byte)StateSource.Fixed << 4): + case (byte)StateSource.IpcPending | ((byte)StateSource.Fixed << 4): case (byte)StateSource.IpcManual | ((byte)StateSource.Fixed << 4): _data[i] = (byte)((value & 0x0F) | ((byte)StateSource.Manual << 4)); break; @@ -104,6 +112,7 @@ public unsafe struct StateSources case ((byte)StateSource.Manual << 4) | (byte)StateSource.Fixed: case ((byte)StateSource.IpcFixed << 4) | (byte)StateSource.Fixed: case ((byte)StateSource.Pending << 4) | (byte)StateSource.Fixed: + case ((byte)StateSource.IpcPending << 4) | (byte)StateSource.Fixed: case ((byte)StateSource.IpcManual << 4) | (byte)StateSource.Fixed: _data[i] = (byte)((value & 0xF0) | (byte)StateSource.Manual); break; From dec3598eff4f93ed54d0d608fc70f8c826366ca5 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 7 Jun 2024 14:41:01 +0000 Subject: [PATCH 388/786] [CI] Updating repo.json for 1.2.3.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index af81105..451bd6c 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.3.1", - "TestingAssemblyVersion": "1.2.3.1", + "AssemblyVersion": "1.2.3.2", + "TestingAssemblyVersion": "1.2.3.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 6207f3a67fefd88e19f4945546ac1cf73eaa63d8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 11 Jun 2024 12:30:24 +0200 Subject: [PATCH 389/786] Fix an exception. --- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 8febcd9..8371232 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -39,8 +39,8 @@ public class ActorPanel private readonly DictModelChara _modelChara; private readonly CustomizeParameterDrawer _parameterDrawer; private readonly AdvancedDyePopup _advancedDyes; - private readonly HeaderDrawer.Button[] _leftButtons; - private readonly HeaderDrawer.Button[] _rightButtons; + private readonly HeaderDrawer.Button[] _leftButtons; + private readonly HeaderDrawer.Button[] _rightButtons; public ActorPanel(ActorSelector selector, StateManager stateManager, @@ -167,7 +167,8 @@ public class ActorPanel DrawHumanPanel(); else DrawMonsterPanel(); - _advancedDyes.Draw(_data.Objects.Last(), _state); + if (_data.Objects.Count > 0) + _advancedDyes.Draw(_data.Objects.Last(), _state); } private void DrawHumanPanel() From 089c7d392d6f115a07bb406a6e8e890c566701b1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 11 Jun 2024 12:30:52 +0200 Subject: [PATCH 390/786] Update submodules. --- OtterGui | 2 +- Penumbra.GameData | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OtterGui b/OtterGui index 0b5afff..e95c0f0 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 0b5afffda19d3e16aec9e8682d18c8f11f67f1c6 +Subproject commit e95c0f04edc7e85aea67498fd8bf495a7fe6d3c8 diff --git a/Penumbra.GameData b/Penumbra.GameData index fed687b..6aeae34 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit fed687b536b7c709484db251b690b8821c5ef403 +Subproject commit 6aeae346332a255b7575ccfca554afb0f3cf1494 From cd498b539b0e30a9665fbfb5619c24ae041824e2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 11 Jun 2024 12:48:57 +0200 Subject: [PATCH 391/786] Ugh. --- Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs index e49b8d1..b4d3740 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs @@ -86,7 +86,7 @@ public class CodeDrawer(Configuration config, CodeService codeService, FunModule if (ImUtf8.IconButton(FontAwesomeIcon.Trash, $"Delete this cheat code.{(canDelete ? string.Empty : $"\nHold {config.DeleteDesignModifier} while clicking to delete.")}", - !canDelete)) + disabled: !canDelete)) { action!(false); config.Codes.RemoveAt(i--); From 205a95b254c0b5e56ab824407c1fb8cb36af71e8 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 11 Jun 2024 10:50:56 +0000 Subject: [PATCH 392/786] [CI] Updating repo.json for 1.2.3.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 451bd6c..3798270 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.3.2", - "TestingAssemblyVersion": "1.2.3.2", + "AssemblyVersion": "1.2.3.3", + "TestingAssemblyVersion": "1.2.3.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -26,9 +26,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 5cf6b0eb7558de2cbbca7b1c184ff6768707291d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 15 Jun 2024 11:52:38 +0200 Subject: [PATCH 393/786] Fix issue with Penumbra load order and visor state. --- Glamourer/Events/PenumbraReloaded.cs | 3 +++ Glamourer/Interop/VisorService.cs | 32 ++++++++++++++++++++++------ Glamourer/State/StateListener.cs | 2 ++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Glamourer/Events/PenumbraReloaded.cs b/Glamourer/Events/PenumbraReloaded.cs index 7df8b3f..20b58f9 100644 --- a/Glamourer/Events/PenumbraReloaded.cs +++ b/Glamourer/Events/PenumbraReloaded.cs @@ -12,5 +12,8 @@ public sealed class PenumbraReloaded() { /// ChangeCustomizeService = 0, + + /// + VisorService = 0, } } diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index af66dd6..9763682 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -9,17 +9,24 @@ namespace Glamourer.Interop; public class VisorService : IDisposable { - public readonly VisorStateChanged Event; + private readonly PenumbraReloaded _penumbra; + private readonly IGameInteropProvider _interop; + public readonly VisorStateChanged Event; - public unsafe VisorService(VisorStateChanged visorStateChanged, IGameInteropProvider interop) + public VisorService(VisorStateChanged visorStateChanged, IGameInteropProvider interop, PenumbraReloaded penumbra) { + _interop = interop; + _penumbra = penumbra; Event = visorStateChanged; - _setupVisorHook = interop.HookFromAddress((nint)Human.MemberFunctionPointers.SetupVisor, SetupVisorDetour); - _setupVisorHook.Enable(); + _setupVisorHook = Create(); + _penumbra.Subscribe(Restore, PenumbraReloaded.Priority.VisorService); } public void Dispose() - => _setupVisorHook.Dispose(); + { + _setupVisorHook.Dispose(); + _penumbra.Unsubscribe(Restore); + } /// Obtain the current state of the Visor for the given draw object (true: toggled). public static unsafe bool GetVisorState(Model characterBase) @@ -45,7 +52,7 @@ public class VisorService : IDisposable private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, byte on); - private readonly Hook _setupVisorHook; + private Hook _setupVisorHook; private void SetupVisorDetour(nint human, ushort modelId, byte value) { @@ -72,4 +79,17 @@ public class VisorService : IDisposable human.AsCharacterBase->VisorToggled = on; _setupVisorHook.Original(human.Address, modelId, on ? (byte)1 : (byte)0); } + + private unsafe Hook Create() + { + var hook = _interop.HookFromAddress((nint)Human.MemberFunctionPointers.SetupVisor, SetupVisorDetour); + hook.Enable(); + return hook; + } + + private void Restore() + { + _setupVisorHook.Dispose(); + _setupVisorHook = Create(); + } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index bd75faf..f3657a7 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -578,6 +578,8 @@ public class StateListener : IDisposable // We do not need to handle fixed designs, // since a fixed design would already have established state-tracking. var actor = _penumbra.GameObjectFromDrawObject(model); + if (!actor.IsCharacter) + return; // Only actually change anything if the actor state changed, // when equipping headgear the method is called with the current draw object state, From 52b89a4177b0375cda0dd27e70707aef5005acac Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 15 Jun 2024 11:52:58 +0200 Subject: [PATCH 394/786] Do not apply auto designs to transformed actors maybe? --- Glamourer/Automation/AutoDesignApplier.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index f2ac1b3..f90cb9b 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -280,6 +280,9 @@ public sealed class AutoDesignApplier : IDisposable if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) return; + if (actor.IsTransformed) + return; + var mergedDesign = _designMerger.Merge( 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); From 3f99d111794da2fd00c1a1781c2fc4271953aae0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 23 Jun 2024 22:55:31 +0200 Subject: [PATCH 395/786] Add support info to Glamourer. --- Glamourer/Glamourer.cs | 93 ++++++++++++++++++- Glamourer/Gui/MainWindow.cs | 37 +++++++- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 9 +- Glamourer/Services/CodeService.cs | 3 + Glamourer/Services/ServiceManager.cs | 5 +- Glamourer/State/StateIndex.cs | 2 +- OtterGui | 2 +- 7 files changed, 138 insertions(+), 13 deletions(-) diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index b368195..5dd6040 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -1,5 +1,7 @@ using Dalamud.Plugin; using Glamourer.Api; +using Glamourer.Automation; +using Glamourer.Designs; using Glamourer.Gui; using Glamourer.Interop; using Glamourer.Services; @@ -30,7 +32,7 @@ public class Glamourer : IDalamudPlugin { try { - _services = StaticServiceManager.CreateProvider(pluginInterface, Log); + _services = StaticServiceManager.CreateProvider(pluginInterface, Log, this); Messager = _services.GetService(); _services.EnsureRequiredServices(); @@ -50,6 +52,95 @@ public class Glamourer : IDalamudPlugin } } + public string GatherSupportInformation() + { + var sb = new StringBuilder(10240); + var config = _services.GetService(); + sb.AppendLine("**Settings**"); + sb.Append($"> **`Plugin Version: `** {Version}\n"); + sb.Append($"> **`Commit Hash: `** {CommitHash}\n"); + sb.Append($"> **`Enable Auto Designs: `** {config.EnableAutoDesigns}\n"); + sb.Append($"> **`Gear Protection: `** {config.UseRestrictedGearProtection}\n"); + sb.Append($"> **`Item Restriction: `** {config.UnlockedItemMode}\n"); + sb.Append($"> **`Keep Manual Changes: `** {config.RespectManualOnAutomationUpdate}\n"); + sb.Append($"> **`Auto-Reload Gear: `** {config.AutoRedrawEquipOnChanges}\n"); + sb.Append($"> **`Revert on Zone Change:`** {config.RevertManualChangesOnZoneChange}\n"); + sb.Append($"> **`Festival Easter-Eggs: `** {config.DisableFestivals}\n"); + sb.Append($"> **`Advanced Customize: `** {config.UseAdvancedParameters}\n"); + sb.Append($"> **`Advanced Dye: `** {config.UseAdvancedDyes}\n"); + sb.Append($"> **`Apply Entire Weapon: `** {config.ChangeEntireItem}\n"); + sb.Append($"> **`Apply Associated Mods:`** {config.AlwaysApplyAssociatedMods}\n"); + sb.Append($"> **`Show QDB: `** {config.Ephemeral.ShowDesignQuickBar}\n"); + sb.Append($"> **`QDB Hotkey: `** {config.ToggleQuickDesignBar}\n"); + sb.Append($"> **`Smaller Equip Display:`** {config.SmallEquip}\n"); + sb.Append($"> **`Debug Mode: `** {config.DebugMode}\n"); + sb.Append($"> **`Cheat Codes: `** {(ulong)_services.GetService().AllEnabled:X8}\n"); + sb.AppendLine("**Plugins**"); + GatherRelevantPlugins(sb); + var designManager = _services.GetService(); + var autoManager = _services.GetService(); + var stateManager = _services.GetService(); + var objectManager = _services.GetService(); + var currentPlayer = objectManager.PlayerData.Identifier; + var states = stateManager.Where(kvp => objectManager.ContainsKey(kvp.Key)).ToList(); + + sb.AppendLine("**Statistics**"); + sb.Append($"> **`Current Player: `** {(currentPlayer.IsValid ? currentPlayer.Incognito(null) : "None")}\n"); + sb.Append($"> **`Saved Designs: `** {designManager.Designs.Count}\n"); + sb.Append($"> **`Automation Sets: `** {autoManager.Count} ({autoManager.Count(set => set.Enabled)} Enabled)\n"); + sb.Append( + $"> **`Actor States: `** {stateManager.Count} ({states.Count} Visible, {stateManager.Values.Count(s => s.IsLocked)} Locked)\n"); + + var enabledAutomation = autoManager.Where(s => s.Enabled).ToList(); + if (enabledAutomation.Count > 0) + { + sb.AppendLine("**Enabled Automation**"); + foreach (var set in enabledAutomation) + { + sb.Append( + $"> **`{set.Identifiers.First().Incognito(null) + ':',-24}`** {(set.Name.Length >= 2 ? $"{set.Name.AsSpan(0, 2)}..." : set.Name)} ({set.Designs.Count} {(set.Designs.Count == 1 ? "Design" : "Designs")})\n"); + } + } + + if (states.Count > 0) + { + sb.AppendLine("**State**"); + foreach (var (ident, state) in states) + { + var sources = Enum.GetValues().Select(s => (0, s)).ToArray(); + foreach (var source in StateIndex.All.Select(s => state.Sources[s])) + ++sources[(int)source].Item1; + foreach (var material in state.Materials.Values) + ++sources[(int)material.Value.Source].Item1; + var sourcesString = string.Join(", ", sources.Where(s => s.Item1 > 0).Select(s => $"{s.s} {s.Item1}")); + sb.Append( + $"> **`{ident.Incognito(null) + ':',-24}`** {(state.IsLocked ? "Locked, " : string.Empty)}Job {state.LastJob.Id}, Zone {state.LastTerritory}, Materials {state.Materials.Values.Count}, {sourcesString}\n"); + } + } + + return sb.ToString(); + } + + + private void GatherRelevantPlugins(StringBuilder sb) + { + ReadOnlySpan relevantPlugins = + [ + "Penumbra", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge", + ]; + var plugins = _services.GetService().InstalledPlugins + .GroupBy(p => p.InternalName) + .ToDictionary(g => g.Key, g => + { + var item = g.OrderByDescending(p => p.IsLoaded).ThenByDescending(p => p.Version).First(); + return (item.IsLoaded, item.Version, item.Name); + }); + foreach (var plugin in relevantPlugins) + { + if (plugins.TryGetValue(plugin, out var data)) + sb.Append($"> **`{data.Name + ':',-22}`** {data.Version}{(data.IsLoaded ? string.Empty : " (Disabled)")}\n"); + } + } public void Dispose() => _services?.Dispose(); diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index f7cd589..007d936 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -1,4 +1,5 @@ -using Dalamud.Interface.Windowing; +using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Glamourer.Designs; using Glamourer.Events; @@ -13,6 +14,7 @@ using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Interop.Penumbra; using ImGuiNET; using OtterGui; +using OtterGui.Classes; using OtterGui.Custom; using OtterGui.Raii; using OtterGui.Services; @@ -131,7 +133,12 @@ public class MainWindow : Window, IDisposable 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 }) + 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 @@ -184,22 +191,42 @@ public class MainWindow : Window, IDisposable return TabType.None; } + /// The longest support button text. + public static ReadOnlySpan SupportInfoButtonText + => "Copy Support Info to Clipboard"u8; + /// Draw the support button group on the right-hand side of the window. - public static void DrawSupportButtons(Changelog changelog) + public static void DrawSupportButtons(Glamourer glamourer, Changelog changelog) { - var width = ImGui.CalcTextSize("Join Discord for Support").X + ImGui.GetStyle().FramePadding.X * 2; + var width = ImUtf8.CalcTextSize(SupportInfoButtonText).X + ImGui.GetStyle().FramePadding.X * 2; var xPos = ImGui.GetWindowWidth() - width; ImGui.SetCursorPos(new Vector2(xPos, 0)); CustomGui.DrawDiscordButton(Glamourer.Messager, width); ImGui.SetCursorPos(new Vector2(xPos, ImGui.GetFrameHeightWithSpacing())); - CustomGui.DrawGuideButton(Glamourer.Messager, width); + DrawSupportButton(glamourer); ImGui.SetCursorPos(new Vector2(xPos, 2 * ImGui.GetFrameHeightWithSpacing())); + CustomGui.DrawGuideButton(Glamourer.Messager, width); + + ImGui.SetCursorPos(new Vector2(xPos, 3 * ImGui.GetFrameHeightWithSpacing())); if (ImGui.Button("Show Changelogs", new Vector2(width, 0))) changelog.ForceOpen = true; } + /// + /// Draw a button that copies the support info to clipboards. + /// + private static void DrawSupportButton(Glamourer glamourer) + { + if (!ImUtf8.Button(SupportInfoButtonText)) + return; + + var text = glamourer.GatherSupportInformation(); + ImGui.SetClipboardText(text); + Glamourer.Messager.NotificationMessage("Copied Support Info to Clipboard.", NotificationType.Success, false); + } + private void OnTabSelected(TabType type, Design? _) { SelectTab = type; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 6259f06..834b9fc 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -10,6 +10,7 @@ using Glamourer.Interop.PalettePlus; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Text; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.SettingsTab; @@ -25,7 +26,8 @@ public class SettingsTab( PaletteImport paletteImport, PalettePlusChecker paletteChecker, CollectionOverrideDrawer overrides, - CodeDrawer codeDrawer) + CodeDrawer codeDrawer, + Glamourer glamourer) : ITab { private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); @@ -45,8 +47,9 @@ public class SettingsTab( ImGui.NewLine(); ImGui.NewLine(); ImGui.NewLine(); + ImGui.NewLine(); - using (var child2 = ImRaii.Child("SettingsChild")) + using (ImRaii.Child("SettingsChild")) { DrawBehaviorSettings(); DrawInterfaceSettings(); @@ -55,7 +58,7 @@ public class SettingsTab( codeDrawer.Draw(); } - MainWindow.DrawSupportButtons(changelog.Changelog); + MainWindow.DrawSupportButtons(glamourer, changelog.Changelog); } private void DrawBehaviorSettings() diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 7d513dd..61339e1 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -50,6 +50,9 @@ public class CodeService private CodeFlag _enabled; + public CodeFlag AllEnabled + => _enabled; + public bool Enabled(CodeFlag flag) => _enabled.HasFlag(flag); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index f06e014..005944e 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -33,7 +33,7 @@ namespace Glamourer.Services; public static class StaticServiceManager { - public static ServiceManager CreateProvider(DalamudPluginInterface pi, Logger log) + public static ServiceManager CreateProvider(DalamudPluginInterface pi, Logger log, Glamourer glamourer) { EventWrapperBase.ChangeLogger(log); var services = new ServiceManager(log) @@ -44,7 +44,8 @@ public static class StaticServiceManager .AddData() .AddDesigns() .AddState() - .AddUi(); + .AddUi() + .AddExistingService(glamourer); DalamudServices.AddServices(services, pi); services.AddIServices(typeof(EquipItem).Assembly); services.AddIServices(typeof(Glamourer).Assembly); diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs index a55d6b1..28cc722 100644 --- a/Glamourer/State/StateIndex.cs +++ b/Glamourer/State/StateIndex.cs @@ -200,7 +200,7 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators All + public static IEnumerable All => Enumerable.Range(0, Size - 1).Select(i => new StateIndex(i)); public bool GetApply(DesignBase data) diff --git a/OtterGui b/OtterGui index e95c0f0..fd79128 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit e95c0f04edc7e85aea67498fd8bf495a7fe6d3c8 +Subproject commit fd791285606d49a7644762ea0b4dc2bbb1368eac From c1d9af2dd0c0b0e44874befd2b6947bfab773f82 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 11 Jul 2024 14:20:19 +0200 Subject: [PATCH 396/786] Update Item API. --- Glamourer.Api | 2 +- Glamourer/Api/IpcProviders.cs | 9 ++++++++- Glamourer/Api/ItemsApi.cs | 12 ++++++------ Penumbra.Api | 2 +- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index a20d4ab..4aaece3 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit a20d4ab1811a7f66918afab9b41ec723f75054f5 +Subproject commit 4aaece34289d64363bc32aaa8fe52c8e7d3dce32 diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 638d228..efbc368 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -2,6 +2,8 @@ using Dalamud.Plugin; using Glamourer.Api.Api; using Glamourer.Api.Helpers; using OtterGui.Services; +using System.Reflection.Emit; +using Glamourer.Api.Enums; namespace Glamourer.Api; @@ -12,7 +14,7 @@ public sealed class IpcProviders : IDisposable, IApiService private readonly EventProvider _disposedProvider; private readonly EventProvider _initializedProvider; - public IpcProviders(DalamudPluginInterface pi, IGlamourerApi api) + public IpcProviders(IDalamudPluginInterface pi, IGlamourerApi api) { _disposedProvider = IpcSubscribers.Disposed.Provider(pi); _initializedProvider = IpcSubscribers.Initialized.Provider(pi); @@ -28,6 +30,11 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.SetItem.Provider(pi, api.Items), IpcSubscribers.SetItemName.Provider(pi, api.Items), + // backward compatibility + new FuncProvider(pi, IpcSubscribers.Legacy.SetItemV2.Label, + (a, b, c, d, e, f) => (int)api.Items.SetItem(a, (ApiEquipSlot)b, c, [d], e, (ApplyFlag)f)), + new FuncProvider(pi, IpcSubscribers.Legacy.SetItemName.Label, + (a, b, c, d, e, f) => (int)api.Items.SetItemName(a, (ApiEquipSlot)b, c, [d], e, (ApplyFlag)f)), IpcSubscribers.GetState.Provider(pi, api.State), IpcSubscribers.GetStateName.Provider(pi, api.State), diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index cda1980..a2e3533 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -11,9 +11,9 @@ 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) + public GlamourerApiEc SetItem(int objectIndex, ApiEquipSlot slot, ulong itemId, IReadOnlyList stains, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Index", objectIndex, "Slot", slot, "ID", itemId, "Stain", stain, "Key", key, "Flags", flags); + var args = ApiHelpers.Args("Index", objectIndex, "Slot", slot, "ID", itemId, "Stains", stains, "Key", key, "Flags", flags); if (!ResolveItem(slot, itemId, out var item)) return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); @@ -27,14 +27,14 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager 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); + stateManager.ChangeEquip(state, (EquipSlot)slot, item, new StainIds(stains), settings); ApiHelpers.Lock(state, key, flags); return GlamourerApiEc.Success; } - public GlamourerApiEc SetItemName(string playerName, ApiEquipSlot slot, ulong itemId, byte stain, uint key, ApplyFlag flags) + public GlamourerApiEc SetItemName(string playerName, ApiEquipSlot slot, ulong itemId, IReadOnlyList stains, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Name", playerName, "Slot", slot, "ID", itemId, "Stain", stain, "Key", key, "Flags", flags); + var args = ApiHelpers.Args("Name", playerName, "Slot", slot, "ID", itemId, "Stains", stains, "Key", key, "Flags", flags); if (!ResolveItem(slot, itemId, out var item)) return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); @@ -53,7 +53,7 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager continue; anyUnlocked = true; - stateManager.ChangeEquip(state, (EquipSlot)slot, item, stain, settings); + stateManager.ChangeEquip(state, (EquipSlot)slot, item, new StainIds(stains), settings); ApiHelpers.Lock(state, key, flags); } diff --git a/Penumbra.Api b/Penumbra.Api index f1e4e52..f4c6144 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit f1e4e520daaa8f23e5c8b71d55e5992b8f6768e2 +Subproject commit f4c6144ca2012b279e6d8aa52b2bef6cc2ba32d9 From 7caf6cc08a69e865a8dd8e02d661a872626e400f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 11 Jul 2024 14:21:25 +0200 Subject: [PATCH 397/786] Initial Update for multiple stains, some facewear support, and API X --- Glamourer/Automation/AutoDesignManager.cs | 2 +- Glamourer/Automation/FixedDesignMigrator.cs | 2 +- Glamourer/Configuration.cs | 2 +- Glamourer/Designs/Design.cs | 2 +- Glamourer/Designs/DesignBase.cs | 15 +- Glamourer/Designs/DesignBase64Migration.cs | 9 +- Glamourer/Designs/DesignColors.cs | 2 +- Glamourer/Designs/DesignConverter.cs | 8 +- Glamourer/Designs/DesignData.cs | 150 ++++++++++-------- Glamourer/Designs/DesignEditor.cs | 17 +- Glamourer/Designs/DesignFileSystem.cs | 2 +- Glamourer/Designs/IDesignEditor.cs | 6 +- Glamourer/Designs/Links/DesignLinkLoader.cs | 3 +- Glamourer/EphemeralConfig.cs | 2 +- Glamourer/Events/MovedEquipment.cs | 2 +- Glamourer/GameData/CustomizeManager.cs | 13 +- Glamourer/GameData/CustomizeSetFactory.cs | 12 +- Glamourer/GameData/NpcCustomizeSet.cs | 60 +++---- Glamourer/GameData/NpcData.cs | 27 ++-- Glamourer/Glamourer.cs | 4 +- Glamourer/Glamourer.csproj | 1 + Glamourer/Glamourer.json | 2 +- .../CustomizationDrawer.GenderRace.cs | 11 +- .../Customization/CustomizationDrawer.Icon.cs | 23 ++- .../Gui/Customization/CustomizationDrawer.cs | 27 +--- Glamourer/Gui/Equipment/EquipDrawData.cs | 14 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 49 +++--- Glamourer/Gui/GlamourerWindowSystem.cs | 4 +- Glamourer/Gui/MainWindow.cs | 4 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 4 +- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 4 +- .../Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 14 +- Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs | 4 +- .../DebugTab/IpcTester/DesignIpcTester.cs | 2 +- .../Tabs/DebugTab/IpcTester/IpcTesterPanel.cs | 2 +- .../Tabs/DebugTab/IpcTester/ItemsIpcTester.cs | 6 +- .../Tabs/DebugTab/IpcTester/StateIpcTester.cs | 4 +- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 42 ++++- .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 2 +- .../DesignTab/DesignFileSystemSelector.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 6 +- Glamourer/Gui/Tabs/DesignTab/DesignTab.cs | 2 +- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 3 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 17 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 70 +++++++- Glamourer/Interop/ChangeCustomizeService.cs | 2 +- Glamourer/Interop/CharaFile/CharaFile.cs | 4 +- Glamourer/Interop/CharaFile/CmaFile.cs | 10 +- Glamourer/Interop/ContextMenuService.cs | 18 ++- Glamourer/Interop/CrestService.cs | 17 +- Glamourer/Interop/ImportService.cs | 2 +- Glamourer/Interop/InventoryService.cs | 82 +++++----- Glamourer/Interop/Material/MaterialManager.cs | 9 +- .../Interop/Material/MaterialValueIndex.cs | 4 +- .../Interop/Material/MaterialValueManager.cs | 2 +- Glamourer/Interop/Material/PrepareColorSet.cs | 26 +-- Glamourer/Interop/MetaService.cs | 12 +- Glamourer/Interop/ObjectManager.cs | 2 +- .../Interop/PalettePlus/PaletteImport.cs | 2 +- .../Interop/PalettePlus/PalettePlusChecker.cs | 8 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 6 +- Glamourer/Interop/ScalingService.cs | 15 +- Glamourer/Interop/UpdateSlotService.cs | 34 +++- Glamourer/Interop/WeaponService.cs | 6 +- .../Services/CollectionOverrideService.cs | 4 +- Glamourer/Services/CustomizeService.cs | 18 --- Glamourer/Services/DalamudServices.cs | 2 +- Glamourer/Services/FilenameService.cs | 2 +- Glamourer/Services/HeightService.cs | 3 +- Glamourer/Services/ItemManager.cs | 10 +- Glamourer/Services/ServiceManager.cs | 5 +- Glamourer/Services/TextureService.cs | 6 +- Glamourer/State/FunEquipSet.cs | 7 +- Glamourer/State/FunModule.cs | 43 +++-- Glamourer/State/InternalStateEditor.cs | 16 +- Glamourer/State/StateApplier.cs | 26 +-- Glamourer/State/StateEditor.cs | 38 ++--- Glamourer/State/StateListener.cs | 34 ++-- Glamourer/State/StateManager.cs | 20 +-- Glamourer/State/WorldSets.cs | 2 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 7 +- Glamourer/Unlocks/FavoriteManager.cs | 2 +- Glamourer/Unlocks/ItemUnlockManager.cs | 12 +- Glamourer/Unlocks/UnlockDictionaryHelpers.cs | 2 +- OtterGui | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- repo.json | 2 +- 90 files changed, 654 insertions(+), 537 deletions(-) diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 1b95f73..da9b014 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -1,5 +1,5 @@ using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.Events; diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index 856561a..fb9bca4 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Interop; using Newtonsoft.Json.Linq; diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index dbd245f..d0b3f98 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -1,6 +1,6 @@ using Dalamud.Configuration; using Dalamud.Game.ClientState.Keys; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Gui; using Glamourer.Gui.Tabs.DesignTab; diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index f09e7bc..b8dc0a2 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Automation; using Glamourer.Designs.Links; using Glamourer.Interop.Material; diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 4910793..3791442 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Services; @@ -257,10 +257,10 @@ public class DesignBase foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) { var item = _designData.Item(slot); - var stain = _designData.Stain(slot); + var stains = _designData.Stain(slot); var crestSlot = slot.ToCrestFlag(); var crest = _designData.Crest(crestSlot); - ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot)); + ret[slot.ToString()] = Serialize(item.Id, stains, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot)); } ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply"); @@ -274,16 +274,15 @@ public class DesignBase return ret; - static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest) - => new() + static JObject Serialize(CustomItemId id, StainIds stains, bool crest, bool apply, bool applyStain, bool applyCrest) + => stains.AddToObject(new JObject { ["ItemId"] = id.Id, - ["Stain"] = stain.Id, ["Crest"] = crest, ["Apply"] = apply, ["ApplyStain"] = applyStain, ["ApplyCrest"] = applyCrest, - }; + }); } protected JObject SerializeCustomize() @@ -522,7 +521,7 @@ public class DesignBase return; } - static (CustomItemId, StainId, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item) + static (CustomItemId, StainIds, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item) { var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot).Id; var stain = (StainId)(item?["Stain"]?.ToObject() ?? 0); diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index a8b2f7b..d6d0886 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,5 +1,4 @@ using Glamourer.Services; -using Glamourer.State; using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -117,7 +116,7 @@ public class DesignBase64Migration } data.SetItem(slot, item); - data.SetStain(slot, mdl.Stain); + data.SetStain(slot, mdl.Stains); } var main = cur[0].Skeleton.Id == 0 @@ -130,7 +129,7 @@ public class DesignBase64Migration } data.SetItem(EquipSlot.MainHand, main); - data.SetStain(EquipSlot.MainHand, cur[0].Stain); + data.SetStain(EquipSlot.MainHand, cur[0].Stains); EquipItem off; // Fist weapon hack @@ -141,7 +140,7 @@ public class DesignBase64Migration if (gauntlet.Valid) { data.SetItem(EquipSlot.Hands, gauntlet); - data.SetStain(EquipSlot.Hands, cur[0].Stain); + data.SetStain(EquipSlot.Hands, cur[0].Stains); } } else @@ -158,7 +157,7 @@ public class DesignBase64Migration } data.SetItem(EquipSlot.OffHand, off); - data.SetStain(EquipSlot.OffHand, cur[1].Stain); + data.SetStain(EquipSlot.OffHand, cur[1].Stains); return data; } } diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index 8bc5539..5577c2c 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility.Raii; using Glamourer.Gui; using Glamourer.Services; diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index f3955c5..b1b5c61 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -176,7 +176,7 @@ public class DesignConverter( return System.Convert.ToBase64String(compressed); } - public IEnumerable<(EquipSlot Slot, EquipItem Item, StainId Stain)> FromDrawData(IReadOnlyList armors, + public IEnumerable<(EquipSlot Slot, EquipItem Item, StainIds Stains)> FromDrawData(IReadOnlyList armors, CharacterWeapon mainhand, CharacterWeapon offhand, bool skipWarnings) { if (armors.Count != 10) @@ -194,7 +194,7 @@ public class DesignConverter( item = ItemManager.NothingItem(slot); } - yield return (slot, item, armor.Stain); + yield return (slot, item, armor.Stains); } var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant); @@ -204,7 +204,7 @@ public class DesignConverter( mh = _items.DefaultSword; } - yield return (EquipSlot.MainHand, mh, mainhand.Stain); + yield return (EquipSlot.MainHand, mh, mainhand.Stains); var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type); if (!skipWarnings && !oh.Valid) @@ -215,7 +215,7 @@ public class DesignConverter( oh = ItemManager.NothingItem(FullEquipType.Shield); } - yield return (EquipSlot.OffHand, oh, offhand.Stain); + yield return (EquipSlot.OffHand, oh, offhand.Stains); } private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials, diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 6b84768..a762a84 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -9,6 +9,8 @@ namespace Glamourer.Designs; public unsafe struct DesignData { + public const int EquipmentByteSize = 10 * CharacterArmor.Size; + private string _nameHead = string.Empty; private string _nameBody = string.Empty; private string _nameHands = string.Empty; @@ -21,15 +23,14 @@ public unsafe struct DesignData private string _nameLFinger = string.Empty; private string _nameMainhand = string.Empty; private string _nameOffhand = string.Empty; + private string _nameFaceWear = string.Empty; private fixed uint _itemIds[12]; - private fixed ushort _iconIds[12]; - private fixed byte _equipmentBytes[48]; + private fixed uint _iconIds[12]; + private fixed byte _equipmentBytes[EquipmentByteSize + 16]; public CustomizeParameterData Parameters; public CustomizeArray Customize = CustomizeArray.Default; public uint ModelId; public CrestFlag CrestVisibility; - private SecondaryId _secondaryMainhand; - private SecondaryId _secondaryOffhand; private FullEquipType _typeMainhand; private FullEquipType _typeOffhand; private byte _states; @@ -50,12 +51,19 @@ public unsafe struct DesignData || name.IsContained(_nameRFinger) || name.IsContained(_nameLFinger) || name.IsContained(_nameMainhand) - || name.IsContained(_nameOffhand); + || name.IsContained(_nameOffhand) + || name.IsContained(_nameFaceWear); - public readonly StainId Stain(EquipSlot slot) + public readonly StainIds Stain(EquipSlot slot) { var index = slot.ToIndex(); - return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3]; + return index switch + { + < 10 => new StainIds(_equipmentBytes[CharacterArmor.Size * index + 3], _equipmentBytes[CharacterArmor.Size * index + 4]), + 10 => new StainIds(_equipmentBytes[EquipmentByteSize + 6], _equipmentBytes[EquipmentByteSize + 7]), + 11 => new StainIds(_equipmentBytes[EquipmentByteSize + 14], _equipmentBytes[EquipmentByteSize + 15]), + _ => StainIds.None, + }; } public readonly bool Crest(CrestFlag slot) @@ -69,24 +77,29 @@ public unsafe struct DesignData => _typeOffhand; public readonly EquipItem Item(EquipSlot slot) - => slot.ToIndex() switch + { + fixed (byte* ptr = _equipmentBytes) { + return slot.ToIndex() switch + { // @formatter:off - 0 => EquipItem.FromIds((ItemId)_itemIds[ 0], (IconId)_iconIds[ 0], (PrimaryId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 2], FullEquipType.Head, name: _nameHead ), - 1 => EquipItem.FromIds((ItemId)_itemIds[ 1], (IconId)_iconIds[ 1], (PrimaryId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 6], FullEquipType.Body, name: _nameBody ), - 2 => EquipItem.FromIds((ItemId)_itemIds[ 2], (IconId)_iconIds[ 2], (PrimaryId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[10], FullEquipType.Hands, name: _nameHands ), - 3 => EquipItem.FromIds((ItemId)_itemIds[ 3], (IconId)_iconIds[ 3], (PrimaryId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[14], FullEquipType.Legs, name: _nameLegs ), - 4 => EquipItem.FromIds((ItemId)_itemIds[ 4], (IconId)_iconIds[ 4], (PrimaryId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[18], FullEquipType.Feet, name: _nameFeet ), - 5 => EquipItem.FromIds((ItemId)_itemIds[ 5], (IconId)_iconIds[ 5], (PrimaryId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[22], FullEquipType.Ears, name: _nameEars ), - 6 => EquipItem.FromIds((ItemId)_itemIds[ 6], (IconId)_iconIds[ 6], (PrimaryId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[26], FullEquipType.Neck, name: _nameNeck ), - 7 => EquipItem.FromIds((ItemId)_itemIds[ 7], (IconId)_iconIds[ 7], (PrimaryId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[30], FullEquipType.Wrists, name: _nameWrists ), - 8 => EquipItem.FromIds((ItemId)_itemIds[ 8], (IconId)_iconIds[ 8], (PrimaryId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[34], FullEquipType.Finger, name: _nameRFinger ), - 9 => EquipItem.FromIds((ItemId)_itemIds[ 9], (IconId)_iconIds[ 9], (PrimaryId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[38], FullEquipType.Finger, name: _nameLFinger ), - 10 => EquipItem.FromIds((ItemId)_itemIds[10], (IconId)_iconIds[10], (PrimaryId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, (Variant)_equipmentBytes[42], _typeMainhand, name: _nameMainhand), - 11 => EquipItem.FromIds((ItemId)_itemIds[11], (IconId)_iconIds[11], (PrimaryId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, (Variant)_equipmentBytes[46], _typeOffhand, name: _nameOffhand ), + 0 => EquipItem.FromIds(_itemIds[ 0], _iconIds[ 0], ((CharacterArmor*)ptr)[0].Set, 0, ((CharacterArmor*)ptr)[0].Variant, FullEquipType.Head, name: _nameHead ), + 1 => EquipItem.FromIds(_itemIds[ 1], _iconIds[ 1], ((CharacterArmor*)ptr)[1].Set, 0, ((CharacterArmor*)ptr)[1].Variant, FullEquipType.Body, name: _nameBody ), + 2 => EquipItem.FromIds(_itemIds[ 2], _iconIds[ 2], ((CharacterArmor*)ptr)[2].Set, 0, ((CharacterArmor*)ptr)[2].Variant, FullEquipType.Hands, name: _nameHands ), + 3 => EquipItem.FromIds(_itemIds[ 3], _iconIds[ 3], ((CharacterArmor*)ptr)[3].Set, 0, ((CharacterArmor*)ptr)[3].Variant, FullEquipType.Legs, name: _nameLegs ), + 4 => EquipItem.FromIds(_itemIds[ 4], _iconIds[ 4], ((CharacterArmor*)ptr)[4].Set, 0, ((CharacterArmor*)ptr)[4].Variant, FullEquipType.Feet, name: _nameFeet ), + 5 => EquipItem.FromIds(_itemIds[ 5], _iconIds[ 5], ((CharacterArmor*)ptr)[5].Set, 0, ((CharacterArmor*)ptr)[5].Variant, FullEquipType.Ears, name: _nameEars ), + 6 => EquipItem.FromIds(_itemIds[ 6], _iconIds[ 6], ((CharacterArmor*)ptr)[6].Set, 0, ((CharacterArmor*)ptr)[6].Variant, FullEquipType.Neck, name: _nameNeck ), + 7 => EquipItem.FromIds(_itemIds[ 7], _iconIds[ 7], ((CharacterArmor*)ptr)[7].Set, 0, ((CharacterArmor*)ptr)[7].Variant, FullEquipType.Wrists, name: _nameWrists ), + 8 => EquipItem.FromIds(_itemIds[ 8], _iconIds[ 8], ((CharacterArmor*)ptr)[8].Set, 0, ((CharacterArmor*)ptr)[8].Variant, FullEquipType.Finger, name: _nameRFinger ), + 9 => EquipItem.FromIds(_itemIds[ 9], _iconIds[ 9], ((CharacterArmor*)ptr)[9].Set, 0, ((CharacterArmor*)ptr)[9].Variant, FullEquipType.Finger, name: _nameLFinger ), + 10 => EquipItem.FromIds(_itemIds[10], _iconIds[10], *(PrimaryId*)(ptr + EquipmentByteSize + 0), *(SecondaryId*)(ptr + EquipmentByteSize + 2), *(Variant*)(ptr + EquipmentByteSize + 4), _typeMainhand, name: _nameMainhand), + 11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], *(PrimaryId*)(ptr + EquipmentByteSize + 4), *(SecondaryId*)(ptr + EquipmentByteSize + 2), *(Variant*)(ptr + EquipmentByteSize + 4), _typeOffhand, name: _nameOffhand ), _ => new EquipItem(), - // @formatter:on - }; + // @formatter:on + }; + } + } public readonly CharacterArmor Armor(EquipSlot slot) { @@ -113,8 +126,8 @@ public unsafe struct DesignData { fixed (byte* ptr = _equipmentBytes) { - var armorPtr = (CharacterArmor*)ptr; - return slot is EquipSlot.MainHand ? armorPtr[10].ToWeapon(_secondaryMainhand) : armorPtr[11].ToWeapon(_secondaryOffhand); + var weaponPtr = (CharacterWeapon*)(ptr + EquipmentByteSize); + return weaponPtr[slot is EquipSlot.MainHand ? 0 : 1]; } } @@ -124,11 +137,11 @@ public unsafe struct DesignData if (index > 11) return false; - _itemIds[index] = item.ItemId.Id; - _iconIds[index] = item.IconId.Id; - _equipmentBytes[4 * index + 0] = (byte)item.PrimaryId.Id; - _equipmentBytes[4 * index + 1] = (byte)(item.PrimaryId.Id >> 8); - _equipmentBytes[4 * index + 2] = item.Variant.Id; + _itemIds[index] = item.ItemId.Id; + _iconIds[index] = item.IconId.Id; + _equipmentBytes[CharacterArmor.Size * index + 0] = (byte)item.PrimaryId.Id; + _equipmentBytes[CharacterArmor.Size * index + 1] = (byte)(item.PrimaryId.Id >> 8); + _equipmentBytes[CharacterArmor.Size * index + 2] = item.Variant.Id; switch (index) { // @formatter:off @@ -144,36 +157,40 @@ public unsafe struct DesignData case 9: _nameLFinger = item.Name; return true; // @formatter:on case 10: - _nameMainhand = item.Name; - _secondaryMainhand = item.SecondaryId; - _typeMainhand = item.Type; + _nameMainhand = item.Name; + _equipmentBytes[EquipmentByteSize + 2] = (byte)item.SecondaryId.Id; + _equipmentBytes[EquipmentByteSize + 3] = (byte)(item.SecondaryId.Id >> 8); + _typeMainhand = item.Type; return true; case 11: - _nameOffhand = item.Name; - _secondaryOffhand = item.SecondaryId; - _typeOffhand = item.Type; + _nameOffhand = item.Name; + _equipmentBytes[EquipmentByteSize + 2] = (byte)item.SecondaryId.Id; + _equipmentBytes[EquipmentByteSize + 3] = (byte)(item.SecondaryId.Id >> 8); + _typeOffhand = item.Type; return true; } return true; } - public bool SetStain(EquipSlot slot, StainId stain) + public bool SetStain(EquipSlot slot, StainIds stains) => slot.ToIndex() switch { - 0 => SetIfDifferent(ref _equipmentBytes[3], stain.Id), - 1 => SetIfDifferent(ref _equipmentBytes[7], stain.Id), - 2 => SetIfDifferent(ref _equipmentBytes[11], stain.Id), - 3 => SetIfDifferent(ref _equipmentBytes[15], stain.Id), - 4 => SetIfDifferent(ref _equipmentBytes[19], stain.Id), - 5 => SetIfDifferent(ref _equipmentBytes[23], stain.Id), - 6 => SetIfDifferent(ref _equipmentBytes[27], stain.Id), - 7 => SetIfDifferent(ref _equipmentBytes[31], stain.Id), - 8 => SetIfDifferent(ref _equipmentBytes[35], stain.Id), - 9 => SetIfDifferent(ref _equipmentBytes[39], stain.Id), - 10 => SetIfDifferent(ref _equipmentBytes[43], stain.Id), - 11 => SetIfDifferent(ref _equipmentBytes[47], stain.Id), - _ => false, + // @formatter:off + 0 => SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 4], stains.Stain2.Id), + 1 => SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 4], stains.Stain2.Id), + 2 => SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 4], stains.Stain2.Id), + 3 => SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 4], stains.Stain2.Id), + 4 => SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 4], stains.Stain2.Id), + 5 => SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 4], stains.Stain2.Id), + 6 => SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 4], stains.Stain2.Id), + 7 => SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 4], stains.Stain2.Id), + 8 => SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 4], stains.Stain2.Id), + 9 => SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 4], stains.Stain2.Id), + 10 => SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 6], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 7], stains.Stain2.Id), + 11 => SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 14], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 15], stains.Stain2.Id), + _ => false, + // @formatter:on }; public bool SetCrest(CrestFlag slot, bool visible) @@ -260,15 +277,15 @@ public unsafe struct DesignData foreach (var slot in EquipSlotExtensions.EqdpSlots) { SetItem(slot, ItemManager.NothingItem(slot)); - SetStain(slot, 0); + SetStain(slot, StainIds.None); SetCrest(slot.ToCrestFlag(), false); } SetItem(EquipSlot.MainHand, items.DefaultSword); - SetStain(EquipSlot.MainHand, 0); + SetStain(EquipSlot.MainHand, StainIds.None); SetCrest(CrestFlag.MainHand, false); SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); - SetStain(EquipSlot.OffHand, 0); + SetStain(EquipSlot.OffHand, StainIds.None); SetCrest(CrestFlag.OffHand, false); } @@ -291,21 +308,22 @@ public unsafe struct DesignData MemoryUtility.MemSet(ptr, 0, 10 * 4); } - fixed (ushort* ptr = _iconIds) + fixed (uint* ptr = _iconIds) { MemoryUtility.MemSet(ptr, 0, 10 * 2); } - _nameHead = string.Empty; - _nameBody = string.Empty; - _nameHands = string.Empty; - _nameLegs = string.Empty; - _nameFeet = string.Empty; - _nameEars = string.Empty; - _nameNeck = string.Empty; - _nameWrists = string.Empty; - _nameRFinger = string.Empty; - _nameLFinger = string.Empty; + _nameHead = string.Empty; + _nameBody = string.Empty; + _nameHands = string.Empty; + _nameLegs = string.Empty; + _nameFeet = string.Empty; + _nameEars = string.Empty; + _nameNeck = string.Empty; + _nameWrists = string.Empty; + _nameRFinger = string.Empty; + _nameLFinger = string.Empty; + _nameFaceWear = string.Empty; return true; } @@ -322,7 +340,7 @@ public unsafe struct DesignData public readonly byte[] GetEquipmentBytes() { - var ret = new byte[40]; + var ret = new byte[80]; fixed (byte* retPtr = ret, inPtr = _equipmentBytes) { MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length); @@ -343,8 +361,8 @@ public unsafe struct DesignData { fixed (byte* dataPtr = _equipmentBytes) { - var data = new Span(dataPtr, 40); - return Convert.TryFromBase64String(base64, data, out var written) && written == 40; + var data = new Span(dataPtr, 80); + return Convert.TryFromBase64String(base64, data, out var written) && written == 80; } } diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index 91050ec..32e38ac 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -3,7 +3,6 @@ using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Services; -using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -167,29 +166,29 @@ public class DesignEditor( } /// - public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings _ = default) + public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings _ = default) { var design = (Design)data; - if (Items.ValidateStain(stain, out var _, false).Length > 0) + if (Items.ValidateStain(stains, out var _, false).Length > 0) return; var oldStain = design.DesignData.Stain(slot); - if (!design.GetDesignDataRef().SetStain(slot, stain)) + if (!design.GetDesignDataRef().SetStain(slot, stains)) return; design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); - Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}."); - DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot)); + Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stains}."); + DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stains, slot)); } /// - public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings _ = default) + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings _ = default) { if (item.HasValue) ChangeItem(data, slot, item.Value, _); - if (stain.HasValue) - ChangeStain(data, slot, stain.Value, _); + if (stains.HasValue) + ChangeStains(data, slot, stains.Value, _); } /// diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index 00277c2..f154684 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Events; using Glamourer.Services; using Newtonsoft.Json; diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index a0aab84..cd51cf2 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -65,11 +65,11 @@ public interface IDesignEditor => ChangeEquip(data, slot, item, null, settings); /// Change the stain for any equipment piece. - public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings = default) - => ChangeEquip(data, slot, null, stain, settings); + public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings = default) + => ChangeEquip(data, slot, null, stains, settings); /// Change an equipment piece and its stain at the same time. - public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings = default); + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings settings = default); /// Change the crest visibility for any equipment piece. public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings = default); diff --git a/Glamourer/Designs/Links/DesignLinkLoader.cs b/Glamourer/Designs/Links/DesignLinkLoader.cs index 4d438bd..fc7a26d 100644 --- a/Glamourer/Designs/Links/DesignLinkLoader.cs +++ b/Glamourer/Designs/Links/DesignLinkLoader.cs @@ -1,7 +1,8 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using OtterGui; using OtterGui.Classes; using OtterGui.Services; +using Notification = OtterGui.Classes.Notification; namespace Glamourer.Designs.Links; diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/EphemeralConfig.cs index 027685f..3e13dc4 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/EphemeralConfig.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Gui; using Glamourer.Services; using Newtonsoft.Json; diff --git a/Glamourer/Events/MovedEquipment.cs b/Glamourer/Events/MovedEquipment.cs index 53491f1..9d24a03 100644 --- a/Glamourer/Events/MovedEquipment.cs +++ b/Glamourer/Events/MovedEquipment.cs @@ -11,7 +11,7 @@ namespace Glamourer.Events; /// /// public sealed class MovedEquipment() - : EventWrapper<(EquipSlot, uint, StainId)[], MovedEquipment.Priority>(nameof(MovedEquipment)) + : EventWrapper<(EquipSlot, uint, StainIds)[], MovedEquipment.Priority>(nameof(MovedEquipment)) { public enum Priority { diff --git a/Glamourer/GameData/CustomizeManager.cs b/Glamourer/GameData/CustomizeManager.cs index 57df104..5a06cf4 100644 --- a/Glamourer/GameData/CustomizeManager.cs +++ b/Glamourer/GameData/CustomizeManager.cs @@ -1,4 +1,5 @@ -using Dalamud.Interface.Internal; +using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; using OtterGui.Classes; using OtterGui.Services; @@ -32,8 +33,8 @@ public class CustomizeManager : IAsyncDataContainer } /// Get specific icons. - public IDalamudTextureWrap GetIcon(uint id) - => _icons.LoadIcon(id)!; + public ISharedImmediateTexture GetIcon(uint id) + => _icons.TextureProvider.GetFromGameIcon(id); /// Iterate over all supported genders and clans. public static IEnumerable<(SubRace Clan, Gender Gender)> AllSets() @@ -47,8 +48,8 @@ public class CustomizeManager : IAsyncDataContainer public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet) { - _icons = new IconStorage(textures, gameData); - var stopwatch = new Stopwatch(); + _icons = new TextureCache(gameData, textures); + var stopwatch = new Stopwatch(); var tmpTask = Task.Run(() => { stopwatch.Start(); @@ -72,7 +73,7 @@ public class CustomizeManager : IAsyncDataContainer public bool Finished => Awaiter.IsCompletedSuccessfully; - private readonly IconStorage _icons; + private readonly TextureCache _icons; private static readonly int ListSize = Clans.Count * Genders.Count; private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize]; diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index ba892ec..850c7c9 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -1,4 +1,5 @@ using Dalamud; +using Dalamud.Game; using Dalamud.Plugin.Services; using Dalamud.Utility; using Lumina.Excel; @@ -13,11 +14,11 @@ namespace Glamourer.GameData; internal class CustomizeSetFactory( IDataManager _gameData, IPluginLog _log, - IconStorage _icons, + TextureCache _icons, NpcCustomizeSet _npcCustomizeSet, ColorParameters _colors) { - public CustomizeSetFactory(IDataManager gameData, IPluginLog log, IconStorage icons, NpcCustomizeSet npcCustomizeSet) + public CustomizeSetFactory(IDataManager gameData, IPluginLog log, TextureCache icons, NpcCustomizeSet npcCustomizeSet) : this(gameData, log, icons, npcCustomizeSet, new ColorParameters(gameData, log)) { } @@ -87,7 +88,8 @@ internal class CustomizeSetFactory( var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>(); _npcCustomizeSet.Awaiter.Wait(); - foreach (var customize in _npcCustomizeSet.Select(s => s.Customize).Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1)) + foreach (var customize in _npcCustomizeSet.Select(s => s.Customize) + .Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1)) { foreach (var customizeIndex in customizeIndices) { @@ -346,10 +348,6 @@ internal class CustomizeSetFactory( /// Set the availability of options according to actual availability. private static void SetAvailability(CustomizeSet set, CharaMakeParams row) { - // TODO: Hrothgar female - if (set is { Race: Race.Hrothgar, Gender: Gender.Female }) - return; - Set(true, CustomizeIndex.Height); Set(set.Faces.Count > 0, CustomizeIndex.Face); Set(true, CustomizeIndex.Hairstyle); diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index d9d8f27..dbf74cc 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -80,25 +80,9 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList // Event NPCs have a reference to NpcEquip but also contain the appearance in their own row. // Prefer the NpcEquip reference if it is set, otherwise use the own. if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) - { ApplyNpcEquip(ref ret, equip); - } else - { - ret.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); - ret.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); - ret.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); - ret.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); - ret.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); - ret.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); - ret.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); - ret.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); - ret.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); - ret.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); - ret.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); - ret.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); - ret.VisorToggled = row.Visor; - } + ApplyNpcEquip(ref ret, row); list.Add(ret); } @@ -202,18 +186,36 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList /// Apply equipment from a NpcEquip row. private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) { - data.Set(0, row.ModelHead | (row.DyeHead.Row << 24)); - data.Set(1, row.ModelBody | (row.DyeBody.Row << 24)); - data.Set(2, row.ModelHands | (row.DyeHands.Row << 24)); - data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24)); - data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24)); - data.Set(5, row.ModelEars | (row.DyeEars.Row << 24)); - data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24)); - data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24)); - data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24)); - data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24)); - data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48)); - data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48)); + data.Set(0, row.ModelHead | (row.DyeHead.Row << 24) | ((ulong)row.Dye2Head.Row << 32)); + data.Set(1, row.ModelBody | (row.DyeBody.Row << 24) | ((ulong)row.Dye2Body.Row << 32)); + data.Set(2, row.ModelHands | (row.DyeHands.Row << 24) | ((ulong)row.Dye2Hands.Row << 32)); + data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24) | ((ulong)row.Dye2Legs.Row << 32)); + data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24) | ((ulong)row.Dye2Feet.Row << 32)); + data.Set(5, row.ModelEars | (row.DyeEars.Row << 24) | ((ulong)row.Dye2Ears.Row << 32)); + data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24) | ((ulong)row.Dye2Neck.Row << 32)); + data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24) | ((ulong)row.Dye2Wrists.Row << 32)); + data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24) | ((ulong)row.Dye2RightRing.Row << 32)); + data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24) | ((ulong)row.Dye2LeftRing.Row << 32)); + data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48) | ((ulong)row.Dye2MainHand.Row << 56)); + data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48) | ((ulong)row.Dye2OffHand.Row << 56)); + data.VisorToggled = row.Visor; + } + + /// Apply equipment from a ENpcBase Row row. + private static void ApplyNpcEquip(ref NpcData data, ENpcBase row) + { + data.Set(0, row.ModelHead | (row.DyeHead.Row << 24) | ((ulong)row.Dye2Head.Row << 32)); + data.Set(1, row.ModelBody | (row.DyeBody.Row << 24) | ((ulong)row.Dye2Body.Row << 32)); + data.Set(2, row.ModelHands | (row.DyeHands.Row << 24) | ((ulong)row.Dye2Hands.Row << 32)); + data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24) | ((ulong)row.Dye2Legs.Row << 32)); + data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24) | ((ulong)row.Dye2Feet.Row << 32)); + data.Set(5, row.ModelEars | (row.DyeEars.Row << 24) | ((ulong)row.Dye2Ears.Row << 32)); + data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24) | ((ulong)row.Dye2Neck.Row << 32)); + data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24) | ((ulong)row.Dye2Wrists.Row << 32)); + data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24) | ((ulong)row.Dye2RightRing.Row << 32)); + data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24) | ((ulong)row.Dye2LeftRing.Row << 32)); + data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48) | ((ulong)row.Dye2MainHand.Row << 56)); + data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48) | ((ulong)row.Dye2OffHand.Row << 56)); data.VisorToggled = row.Visor; } diff --git a/Glamourer/GameData/NpcData.cs b/Glamourer/GameData/NpcData.cs index 6b5f2bd..0076bb6 100644 --- a/Glamourer/GameData/NpcData.cs +++ b/Glamourer/GameData/NpcData.cs @@ -13,7 +13,7 @@ public unsafe struct NpcData public CustomizeArray Customize; /// The equipment appearance of the NPC, 10 * CharacterArmor. - private fixed byte _equip[40]; + private fixed byte _equip[CharacterArmor.Size * 10]; /// The mainhand weapon appearance of the NPC. public CharacterWeapon Mainhand; @@ -54,36 +54,35 @@ public unsafe struct NpcData { sb.Append(span[i].Set.Id.ToString("D4")) .Append('-') - .Append(span[i].Variant.Id.ToString("D3")) - .Append('-') - .Append(span[i].Stain.Id.ToString("D3")) - .Append(", "); + .Append(span[i].Variant.Id.ToString("D3")); + foreach (var stain in span[i].Stains) + sb.Append('-').Append(stain.Id.ToString("D3")); } sb.Append(Mainhand.Skeleton.Id.ToString("D4")) .Append('-') .Append(Mainhand.Weapon.Id.ToString("D4")) .Append('-') - .Append(Mainhand.Variant.Id.ToString("D3")) - .Append('-') - .Append(Mainhand.Stain.Id.ToString("D4")) - .Append(", ") + .Append(Mainhand.Variant.Id.ToString("D3")); + foreach (var stain in Mainhand.Stains) + sb.Append('-').Append(stain.Id.ToString("D3")); + sb.Append(", ") .Append(Offhand.Skeleton.Id.ToString("D4")) .Append('-') .Append(Offhand.Weapon.Id.ToString("D4")) .Append('-') - .Append(Offhand.Variant.Id.ToString("D3")) - .Append('-') - .Append(Offhand.Stain.Id.ToString("D3")); + .Append(Offhand.Variant.Id.ToString("D3")); + foreach (var stain in Mainhand.Stains) + sb.Append('-').Append(stain.Id.ToString("D3")); return sb.ToString(); } /// Set an equipment piece to a given value. - internal void Set(int idx, uint value) + internal void Set(int idx, ulong value) { fixed (byte* ptr = _equip) { - ((uint*)ptr)[idx] = value; + ((ulong*)ptr)[idx] = value; } } diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 5dd6040..84312d9 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -28,7 +28,7 @@ public class Glamourer : IDalamudPlugin private readonly ServiceManager _services; - public Glamourer(DalamudPluginInterface pluginInterface) + public Glamourer(IDalamudPluginInterface pluginInterface) { try { @@ -128,7 +128,7 @@ public class Glamourer : IDalamudPlugin [ "Penumbra", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge", ]; - var plugins = _services.GetService().InstalledPlugins + var plugins = _services.GetService().InstalledPlugins .GroupBy(p => p.InternalName) .ToDictionary(g => g.Key, g => { diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 9a1b95b..3251543 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -49,6 +49,7 @@ $(AppData)\XIVLauncher\addon\Hooks\dev\ + H:\Projects\FFPlugins\Dalamud\bin\Release\ diff --git a/Glamourer/Glamourer.json b/Glamourer/Glamourer.json index cdf2cba..08c18f5 100644 --- a/Glamourer/Glamourer.json +++ b/Glamourer/Glamourer.json @@ -8,7 +8,7 @@ "AssemblyVersion": "9.0.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 9, + "DalamudApiLevel": 10, "ImageUrls": null, "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png" } \ No newline at end of file diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index ae64075..2f67012 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -34,14 +34,13 @@ public partial class CustomizationDrawer private void DrawGenderSelector() { - using (var disabled = ImRaii.Disabled(_locked || _lockedRedraw)) + using (ImRaii.Disabled(_locked || _lockedRedraw)) { var icon = _customize.Gender switch { - Gender.Male when _customize.Race is Race.Hrothgar => FontAwesomeIcon.MarsDouble, - Gender.Male => FontAwesomeIcon.Mars, - Gender.Female => FontAwesomeIcon.Venus, - _ => FontAwesomeIcon.Question, + Gender.Male => FontAwesomeIcon.Mars, + Gender.Female => FontAwesomeIcon.Venus, + _ => FontAwesomeIcon.Question, }; if (ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, @@ -56,7 +55,7 @@ public partial class CustomizationDrawer private void DrawRaceCombo() { - using (var disabled = ImRaii.Disabled(_locked || _lockedRedraw)) + using (ImRaii.Disabled(_locked || _lockedRedraw)) { ImGui.SetNextItemWidth(_raceSelectorWidth); using (var combo = ImRaii.Combo("##subRaceCombo", _service.ClanName(_customize.Clan, _customize.Gender))) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index c0c45d2..8631481 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -29,10 +29,11 @@ public partial class CustomizationDrawer npc = true; } - var icon = _service.Manager.GetIcon(custom!.Value.IconId); + var icon = _service.Manager.GetIcon(custom!.Value.IconId); + var hasIcon = icon.TryGetWrap(out var wrap, out _); using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) { - if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) + if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize)) { ImGui.OpenPopup(IconSelectorPopup); } @@ -43,7 +44,8 @@ public partial class CustomizationDrawer } } - ImGuiUtil.HoverIconTooltip(icon, _iconSize); + if (hasIcon) + ImGuiUtil.HoverIconTooltip(wrap!, _iconSize); ImGui.SameLine(); using (_ = ImRaii.Group()) @@ -83,8 +85,9 @@ public partial class CustomizationDrawer using var frameColor = current == i ? ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed) : ImRaii.PushColor(ImGuiCol.Button, ColorId.FavoriteStarOn.Value(), isFavorite); + var hasIcon = icon.TryGetWrap(out var wrap, out var _); - if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize)) + if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize)) { UpdateValue(custom.Value); ImGui.CloseCurrentPopup(); @@ -96,8 +99,9 @@ public partial class CustomizationDrawer else _favorites.TryAdd(_set.Gender, _set.Clan, _currentIndex, custom.Value); - ImGuiUtil.HoverIconTooltip(icon, _iconSize, - FavoriteManager.TypeAllowed(_currentIndex) ? "Right-Click to toggle favorite." : string.Empty); + if (hasIcon) + ImGuiUtil.HoverIconTooltip(wrap!, _iconSize, + FavoriteManager.TypeAllowed(_currentIndex) ? "Right-Click to toggle favorite." : string.Empty); var text = custom.Value.ToString(); var textWidth = ImGui.CalcTextSize(text).X; @@ -199,14 +203,17 @@ public partial class CustomizationDrawer var icon = featureIdx == CustomizeIndex.LegacyTattoo ? _legacyTattoo ?? _service.Manager.GetIcon(feature.IconId) : _service.Manager.GetIcon(feature.IconId); - if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, + var hasIcon = icon.TryGetWrap(out var wrap, out _); + if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, + (int)ImGui.GetStyle().FramePadding.X, Vector4.Zero, enabled ? Vector4.One : _redTint)) { _customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max); Changed |= _currentFlag; } - ImGuiUtil.HoverIconTooltip(icon, _iconSize); + if (hasIcon) + ImGuiUtil.HoverIconTooltip(wrap!, _iconSize); if (idx % 4 != 3) ImGui.SameLine(); } diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 60251df..384307c 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -1,4 +1,6 @@ using Dalamud.Interface.Internal; +using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.GameData; @@ -6,6 +8,7 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; +using OtterGui.Classes; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -13,16 +16,15 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; public partial class CustomizationDrawer( - DalamudPluginInterface pi, + TextureCache textureCache, CustomizeService _service, CodeService _codes, Configuration _config, FavoriteManager _favorites, HeightService _heightService) - : IDisposable { - private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); - private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(pi); + private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); + private readonly ISharedImmediateTexture? _legacyTattoo = GetLegacyTattooIcon(textureCache); private Exception? _terminate; @@ -47,9 +49,6 @@ public partial class CustomizationDrawer( private float _raceSelectorWidth; private bool _withApply; - public void Dispose() - => _legacyTattoo?.Dispose(); - public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw) { _withApply = false; @@ -190,16 +189,6 @@ public partial class CustomizationDrawer( _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; } - private static IDalamudTextureWrap? GetLegacyTattooIcon(DalamudPluginInterface pi) - { - using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw"); - if (resource == null) - return null; - - var rawImage = new byte[resource.Length]; - var length = resource.Read(rawImage, 0, (int)resource.Length); - return length == resource.Length - ? pi.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4) - : null; - } + private static ISharedImmediateTexture? GetLegacyTattooIcon(TextureCache icons) + => icons.TextureProvider.GetFromManifestResource(Assembly.GetExecutingAssembly(), "Glamourer.LegacyTattoo.raw"); } diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index e6b5d0d..58f7efc 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -23,8 +23,8 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) public readonly void SetItem(EquipItem item) => _editor.ChangeItem(_object, Slot, item, ApplySettings.Manual); - public readonly void SetStain(StainId stain) - => _editor.ChangeStain(_object, Slot, stain, ApplySettings.Manual); + public readonly void SetStains(StainIds stains) + => _editor.ChangeStains(_object, Slot, stains, ApplySettings.Manual); public readonly void SetApplyItem(bool value) { @@ -40,10 +40,10 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) manager.ChangeApplyStain(design, Slot, value); } - public EquipItem CurrentItem = designData.Item(slot); - public StainId CurrentStain = designData.Stain(slot); - public EquipItem GameItem = default; - public StainId GameStain = default; + public EquipItem CurrentItem = designData.Item(slot); + public StainIds CurrentStains = designData.Stain(slot); + public EquipItem GameItem = default; + public StainIds GameStains = default; public bool CurrentApply; public bool CurrentApplyStain; @@ -69,7 +69,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) Locked = state.IsLocked, DisplayApplication = false, GameItem = state.BaseData.Item(slot), - GameStain = state.BaseData.Stain(slot), + GameStains = state.BaseData.Stain(slot), AllowRevert = true, }; } diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index c5e5f7e..53e8ed8 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -8,6 +8,7 @@ using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Text; using OtterGui.Widgets; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -236,14 +237,18 @@ public class EquipmentDrawer /// Draw an input for stain that can set arbitrary values instead of choosing valid stains. private static void DrawStainArtisan(EquipDrawData data) { - int stainId = data.CurrentStain.Id; - ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); - if (!ImGui.InputInt("##stain", ref stainId, 0, 0)) - return; + foreach (var (stain, index) in data.CurrentStains.WithIndex()) + { + using var id = ImUtf8.PushId(index); + int stainId = stain.Id; + ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); + if (!ImGui.InputInt("##stain", ref stainId, 0, 0)) + return; - var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue); - if (newStainId != data.CurrentStain.Id) - data.SetStain(newStainId); + var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue); + if (newStainId != stain.Id) + data.SetStains(data.CurrentStains.With(index, newStainId)); + } } /// Draw an input for armor that can set arbitrary values instead of choosing items. @@ -441,19 +446,27 @@ public class EquipmentDrawer private void DrawStain(in EquipDrawData data, bool small) { - var found = _stainData.TryGetValue(data.CurrentStain, out var stain); using var disabled = ImRaii.Disabled(data.Locked); - var change = small - ? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) - : _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength); - if (change) - if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain)) - data.SetStain(stain.RowIndex); - else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) - data.SetStain(Stain.None.RowIndex); + var width = (_comboLength - ImUtf8.ItemInnerSpacing.X * (data.CurrentStains.Count - 1)) / data.CurrentStains.Count; + foreach (var (stainId, index) in data.CurrentStains.WithIndex()) + { + using var id = ImUtf8.PushId(index); + var found = _stainData.TryGetValue(stainId, out var stain); + var change = small + ? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) + : _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, width); + if (index < data.CurrentStains.Count - 1) + ImUtf8.SameLineInner(); - if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out var newStain)) - data.SetStain(newStain); + if (change) + if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain)) + data.SetStains(data.CurrentStains.With(index, stain.RowIndex)); + else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) + data.SetStains(data.CurrentStains.With(index, Stain.None.RowIndex)); + if (ResetOrClear(data.Locked, false, data.AllowRevert, true, stainId, data.GameStains[index], Stain.None.RowIndex, + out var newStain)) + data.SetStains(data.CurrentStains.With(index, newStain)); + } } private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open) diff --git a/Glamourer/Gui/GlamourerWindowSystem.cs b/Glamourer/Gui/GlamourerWindowSystem.cs index 6b34c78..f86f42b 100644 --- a/Glamourer/Gui/GlamourerWindowSystem.cs +++ b/Glamourer/Gui/GlamourerWindowSystem.cs @@ -7,11 +7,11 @@ namespace Glamourer.Gui; public class GlamourerWindowSystem : IDisposable { private readonly WindowSystem _windowSystem = new("Glamourer"); - private readonly UiBuilder _uiBuilder; + private readonly IUiBuilder _uiBuilder; private readonly MainWindow _ui; private readonly PenumbraChangedItemTooltip _penumbraTooltip; - public GlamourerWindowSystem(UiBuilder uiBuilder, MainWindow ui, GenericPopupWindow popups, PenumbraChangedItemTooltip penumbraTooltip, + public GlamourerWindowSystem(IUiBuilder uiBuilder, MainWindow ui, GenericPopupWindow popups, PenumbraChangedItemTooltip penumbraTooltip, Configuration config, UnlocksTab unlocksTab, GlamourerChangelog changelog, DesignQuickBar quick) { _uiBuilder = uiBuilder; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 007d936..f21a2b7 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Glamourer.Designs; @@ -64,7 +64,7 @@ public class MainWindow : Window, IDisposable public TabType SelectTab; - public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs, + public MainWindow(IDalamudPluginInterface 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, PenumbraService penumbra) : base("GlamourerMainWindow") diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 8371232..5218581 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -1,6 +1,6 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Automation; @@ -208,7 +208,7 @@ public class ActorPanel var data = EquipDrawData.FromState(_stateManager, _state!, slot); _equipmentDrawer.DrawEquip(data); if (usedAllStain) - _stateManager.ChangeStain(_state, slot, newAllStain, ApplySettings.Manual); + _stateManager.ChangeStains(_state, slot, newAllStain, ApplySettings.Manual); } var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 4aa0163..0a29314 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -87,8 +87,8 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) { PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state.Sources[slot, false]); - ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString()); - ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString()); + ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).ToString()); + ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).ToString()); ImGuiUtil.DrawTableColumn(state.Sources[slot, true].ToString()); } diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index 2129c1f..ff9c2b8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -8,8 +8,10 @@ using Glamourer.Services; using Glamourer.State; using ImGuiNET; using OtterGui; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; @@ -51,7 +53,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer using (ImRaii.Group()) { ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)manager:X}"); - ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesSpan.Length.ToString()); + ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlates.Length.ToString()); ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesRequested.ToString()); ImGui.SameLine(); if (ImGui.SmallButton("Request Update")) @@ -67,13 +69,13 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer var (identifier, data) = _objects.PlayerData; var enabled = data.Valid && _state.GetOrCreate(identifier, data.Objects[0], out state); - for (var i = 0; i < manager->GlamourPlatesSpan.Length; ++i) + for (var i = 0; i < manager->GlamourPlates.Length; ++i) { using var tree = ImRaii.TreeNode($"Plate #{i + 1:D2}"); if (!tree) continue; - ref var plate = ref manager->GlamourPlatesSpan[i]; + ref var plate = ref manager->GlamourPlates[i]; if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled)) { var design = CreateDesign(plate); @@ -90,12 +92,12 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer using (ImRaii.Group()) { foreach (var (_, index) in EquipSlotExtensions.FullSlots.WithIndex()) - ImGui.TextUnformatted($"{plate.ItemIds[index]:D6}, {plate.StainIds[index]:D3}"); + ImGui.TextUnformatted($"{plate.ItemIds[index]:D6}, {StainIds.FromGlamourPlate(plate, index)}"); } } } - [Signature("E8 ?? ?? ?? ?? 32 C0 48 8B 5C 24 ?? 48 8B 6C 24 ?? 48 83 C4 ?? 5F")] + [Signature(Sigs.RequestGlamourPlates)] private readonly delegate* unmanaged _requestUpdate = null!; public void RequestGlamour() @@ -126,7 +128,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer continue; design.GetDesignDataRef().SetItem(slot, item); - design.GetDesignDataRef().SetStain(slot, plate.StainIds[index]); + design.GetDesignDataRef().SetStain(slot, StainIds.FromGlamourPlate(plate, index)); design.ApplyEquip |= slot.ToBothFlags(); } diff --git a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs index bd447e7..b021656 100644 --- a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs @@ -43,8 +43,8 @@ public unsafe class InventoryPanel : IGameDataDrawer } else { - ImGuiUtil.DrawTableColumn(item->ItemID.ToString()); - ImGuiUtil.DrawTableColumn(item->GlamourID.ToString()); + ImGuiUtil.DrawTableColumn(item->ItemId.ToString()); + ImGuiUtil.DrawTableColumn(item->GlamourId.ToString()); ImGui.TableNextColumn(); ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)item:X}"); } diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs index 1a74778..918c7ad 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs @@ -10,7 +10,7 @@ using OtterGui.Services; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; -public class DesignIpcTester(DalamudPluginInterface pluginInterface) : IUiService +public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiService { private Dictionary _designs = []; private int _gameObjectIndex; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs index 5e6f4a2..8f561af 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -7,7 +7,7 @@ using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; public class IpcTesterPanel( - DalamudPluginInterface pluginInterface, + IDalamudPluginInterface pluginInterface, DesignIpcTester designs, ItemsIpcTester items, StateIpcTester state, diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs index 3d61df7..5f9e748 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs @@ -11,7 +11,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; -public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService +public class ItemsIpcTester(IDalamudPluginInterface pluginInterface) : IUiService { private int _gameObjectIndex; private string _gameObjectName = string.Empty; @@ -40,12 +40,12 @@ public class ItemsIpcTester(DalamudPluginInterface pluginInterface) : IUiService IpcTesterHelpers.DrawIntro(SetItem.Label); if (ImGui.Button("Set##Idx")) - _lastError = new SetItem(pluginInterface).Invoke(_gameObjectIndex, (ApiEquipSlot)_slot, _customItemId.Id, _stainId.Id, _key, + _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, (ApiEquipSlot)_slot, _customItemId.Id, _stainId.Id, _key, + _lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, (ApiEquipSlot)_slot, _customItemId.Id, [_stainId.Id], _key, _flags); } diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index 81c8cab..f378625 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -18,7 +18,7 @@ namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; public class StateIpcTester : IUiService, IDisposable { - private readonly DalamudPluginInterface _pluginInterface; + private readonly IDalamudPluginInterface _pluginInterface; private int _gameObjectIndex; private string _gameObjectName = string.Empty; @@ -41,7 +41,7 @@ public class StateIpcTester : IUiService, IDisposable private int _numUnlocked; - public StateIpcTester(DalamudPluginInterface pluginInterface) + public StateIpcTester(IDalamudPluginInterface pluginInterface) { _pluginInterface = pluginInterface; StateChanged = Api.IpcSubscribers.StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index c557064..ddf42f1 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -5,6 +5,8 @@ using Glamourer.Interop.Structs; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Text; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Interop; @@ -18,7 +20,8 @@ public unsafe class ModelEvaluationPanel( VisorService _visorService, UpdateSlotService _updateSlotService, ChangeCustomizeService _changeCustomizeService, - CrestService _crestService) : IGameDataDrawer + CrestService _crestService, + DictGlasses _glasses) : IGameDataDrawer { public string Label => "Model Evaluation"; @@ -177,7 +180,7 @@ public unsafe class ModelEvaluationPanel( { using var id = ImRaii.PushId("Wetness"); ImGuiUtil.DrawTableColumn("Wetness"); - ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.AsCharacter->IsGPoseWet ? "GPose" : "None" : "No Character"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.IsGPoseWet ? "GPose" : "None" : "No Character"); var modelString = model.IsCharacterBase ? $"{model.AsCharacterBase->SwimmingWetness:F4} Swimming\n" + $"{model.AsCharacterBase->WeatherWetness:F4} Weather\n" @@ -190,13 +193,13 @@ public unsafe class ModelEvaluationPanel( return; if (ImGui.SmallButton("GPose On")) - actor.AsCharacter->IsGPoseWet = true; + actor.IsGPoseWet = true; ImGui.SameLine(); if (ImGui.SmallButton("GPose Off")) - actor.AsCharacter->IsGPoseWet = false; + actor.IsGPoseWet = false; ImGui.SameLine(); if (ImGui.SmallButton("GPose Toggle")) - actor.AsCharacter->IsGPoseWet = !actor.AsCharacter->IsGPoseWet; + actor.IsGPoseWet = !actor.IsGPoseWet; } private void DrawEquip(Actor actor, Model model) @@ -214,14 +217,39 @@ public unsafe class ModelEvaluationPanel( if (ImGui.SmallButton("Change Piece")) _updateSlotService.UpdateArmor(model, slot, - new CharacterArmor((PrimaryId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0)); + new CharacterArmor((PrimaryId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, StainIds.None)); ImGui.SameLine(); if (ImGui.SmallButton("Change Stain")) - _updateSlotService.UpdateStain(model, slot, 5); + _updateSlotService.UpdateStain(model, slot, new StainIds(5, 7)); ImGui.SameLine(); if (ImGui.SmallButton("Reset")) _updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot)); } + + using (ImRaii.PushId((int)EquipSlot.FaceWear)) + { + ImGuiUtil.DrawTableColumn(EquipSlot.FaceWear.ToName()); + if (!actor.IsCharacter) + { + ImGuiUtil.DrawTableColumn("No Character"); + } + else + { + var glassesId = actor.AsCharacter->DrawData.GlassesIds[(int)EquipSlot.FaceWear.ToBonusIndex()]; + if (_glasses.TryGetValue(glassesId, out var glasses)) + ImGuiUtil.DrawTableColumn($"{glasses.Id.Id},{glasses.Variant.Id} ({glassesId})"); + else + ImGuiUtil.DrawTableColumn($"{glassesId}"); + } + + ImGuiUtil.DrawTableColumn(model.IsHuman ? model.GetArmor(EquipSlot.FaceWear).ToString() : "No Human"); + ImGui.TableNextColumn(); + if (ImUtf8.SmallButton("Change Piece"u8)) + { + var data = model.GetArmor(EquipSlot.FaceWear); + _updateSlotService.UpdateSlot(model, EquipSlot.FaceWear, data with { Variant = (Variant)((data.Variant.Id + 1) % 12) }); + } + } } private void DrawCustomize(Actor actor, Model model) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index ecac046..e9fbcf4 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Services; using ImGuiNET; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 2608dd3..11147c8 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin.Services; using Glamourer.Designs; using Glamourer.Events; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index bf9ba69..50fd936 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.ImGuiFileDialog; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.System.Framework; using Glamourer.Automation; using Glamourer.Designs; @@ -106,7 +106,7 @@ public class DesignPanel var data = EquipDrawData.FromDesign(_manager, _selector.Selected!, slot); _equipmentDrawer.DrawEquip(data); if (usedAllStain) - _manager.ChangeStain(_selector.Selected, slot, newAllStain); + _manager.ChangeStains(_selector.Selected, slot, newAllStain); } var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand); @@ -453,7 +453,7 @@ public class DesignPanel } private static unsafe string GetUserPath() - => Framework.Instance()->UserPath; + => Framework.Instance()->UserPathString; private sealed class LockButton(DesignPanel panel) : Button diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index 7fca8c2..9832451 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 1915241..9db8c19 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility; using Dalamud.Utility; using Glamourer.Designs; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index c08d5c9..974912e 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; using Glamourer.Gui.Customization; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 834b9fc..f38d81d 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -10,7 +10,6 @@ using Glamourer.Interop.PalettePlus; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using OtterGui.Text; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.SettingsTab; @@ -19,7 +18,7 @@ public class SettingsTab( Configuration config, DesignFileSystemSelector selector, ContextMenuService contextMenuService, - UiBuilder uiBuilder, + IUiBuilder uiBuilder, GlamourerChangelog changelog, IKeyState keys, DesignColorUi designColorUi, diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index aa67fb4..9749ce6 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -116,20 +116,20 @@ public class UnlockOverview var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time); var icon = _customizations.Manager.GetIcon(customize.IconId); - - ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, + var hasIcon = icon.TryGetWrap(out var wrap, out _); + ImGui.Image(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (_favorites.Contains(_selected3, _selected2, customize.Index, customize.Value)) ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), 12 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 6 * ImGuiHelpers.GlobalScale); - if (ImGui.IsItemHovered()) + if (hasIcon && ImGui.IsItemHovered()) { using var tt = ImRaii.Tooltip(); - var size = new Vector2(icon.Width, icon.Height); + var size = new Vector2(wrap!.Width, wrap.Height); if (size.X >= iconSize.X && size.Y >= iconSize.Y) - ImGui.Image(icon.ImGuiHandle, size); + ImGui.Image(wrap.ImGuiHandle, size); ImGui.TextUnformatted(unlockData.Name); ImGui.TextUnformatted($"{customize.Index.ToDefaultName()} {customize.Value.Value}"); ImGui.TextUnformatted(unlocked ? $"Unlocked on {time:g}" : "Not unlocked."); @@ -191,7 +191,8 @@ public class UnlockOverview var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); - ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); + ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, + unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (_favorites.Contains(item)) ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); @@ -233,8 +234,8 @@ public class UnlockOverview ImGui.TextUnformatted($"For all {_jobs.AllJobGroups[item.JobRestrictions.Id].Name} of at least Level {item.Level}"); } - if (item.Flags.HasFlag(ItemFlags.IsDyable)) - ImGui.TextUnformatted("Dyable"); + if (item.Flags.HasFlag(ItemFlags.IsDyable1)) + ImGui.TextUnformatted(item.Flags.HasFlag(ItemFlags.IsDyable2) ? "Dyable (2 Slots)" : "Dyable"); if (item.Flags.HasFlag(ItemFlags.IsTradable)) ImGui.TextUnformatted("Tradable"); if (item.Flags.HasFlag(ItemFlags.IsCrestWorthy)) diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index d4fd4b0..600546f 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -1,4 +1,5 @@ using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Events; using Glamourer.Interop; @@ -249,7 +250,7 @@ public class UnlockTable : Table, IDisposable => 70 * ImGuiHelpers.GlobalScale; public override int ToValue(EquipItem item) - => (int) item.Id.Id; + => (int)item.Id.Id; public ItemIdColumn() : base(ComparisonMethod.Equal) @@ -378,13 +379,68 @@ public class UnlockTable : Table, IDisposable } } - private sealed class DyableColumn : YesNoColumn - { - public DyableColumn() - => Tooltip = "Whether the item is dyable."; - protected override bool GetValue(EquipItem item) - => item.Flags.HasFlag(ItemFlags.IsDyable); + private sealed class DyableColumn : ColumnFlags + { + [Flags] + public enum Dyable : byte + { + No = 1, + Yes = 2, + Two = 4, + } + + private Dyable _filterValue; + + public DyableColumn() + { + AllFlags = Dyable.No | Dyable.Yes | Dyable.Two; + Flags &= ~ImGuiTableColumnFlags.NoResize; + _filterValue = AllFlags; + } + + public override Dyable FilterValue + => _filterValue; + + protected override void SetValue(Dyable value, bool enable) + => _filterValue = enable ? _filterValue | value : _filterValue & ~value; + + public override float Width + => ImGui.GetFrameHeight() * 2; + + public override bool FilterFunc(EquipItem item) + => GetValue(item) switch + { + 0 => _filterValue.HasFlag(Dyable.No), + ItemFlags.IsDyable2 => _filterValue.HasFlag(Dyable.Yes), + ItemFlags.IsDyable1 => _filterValue.HasFlag(Dyable.Yes), + _ => _filterValue.HasFlag(Dyable.Two), + }; + + public override int Compare(EquipItem lhs, EquipItem rhs) + => GetValue(lhs).CompareTo(GetValue(rhs)); + + public override void DrawColumn(EquipItem item, int idx) + { + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGuiUtil.Center(Icon(item)); + } + + ImGuiUtil.HoverTooltip("Whether the item is dyable, and how many slots it has."); + } + + private static string Icon(EquipItem item) + => GetValue(item) switch + { + 0 => FontAwesomeIcon.Times.ToIconString(), + ItemFlags.IsDyable2 => FontAwesomeIcon.Check.ToIconString(), + ItemFlags.IsDyable1 => FontAwesomeIcon.Check.ToIconString(), + _ => FontAwesomeIcon.DiceTwo.ToIconString(), + }; + + private static ItemFlags GetValue(EquipItem item) + => item.Flags & (ItemFlags.IsDyable1 | ItemFlags.IsDyable2); } private sealed class TradableColumn : YesNoColumn diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index cfff90f..495d69c 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -17,7 +17,7 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 _original; + private readonly delegate* unmanaged _original; private readonly Post _postEvent = new(); diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs index 0613fb3..4bf08cd 100644 --- a/Glamourer/Interop/CharaFile/CharaFile.cs +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -60,7 +60,7 @@ public sealed class CharaFile return; data.SetItem(slot, item); - data.SetStain(slot, dye); + data.SetStain(slot, new StainIds(dye)); flags |= slot.ToFlag(); flags |= slot.ToStainFlag(); } @@ -79,7 +79,7 @@ public sealed class CharaFile return; data.SetItem(slot, item); - data.SetStain(slot, dye); + data.SetStain(slot, new StainIds(dye)); flags |= slot.ToFlag(); flags |= slot.ToStainFlag(); } diff --git a/Glamourer/Interop/CharaFile/CmaFile.cs b/Glamourer/Interop/CharaFile/CmaFile.cs index dab91ac..da3fb43 100644 --- a/Glamourer/Interop/CharaFile/CmaFile.cs +++ b/Glamourer/Interop/CharaFile/CmaFile.cs @@ -61,7 +61,7 @@ public sealed class CmaFile var armor = ((CharacterArmor*)ptr)[idx]; var item = items.Identify(slot, armor.Set, armor.Variant); data.SetItem(slot, item); - data.SetStain(slot, armor.Stain); + data.SetStain(slot, armor.Stains); } data.Customize.Read(ptr); @@ -74,7 +74,7 @@ public sealed class CmaFile if (mainhand == null) { data.SetItem(EquipSlot.MainHand, items.DefaultSword); - data.SetStain(EquipSlot.MainHand, 0); + data.SetStain(EquipSlot.MainHand, StainIds.None); return; } @@ -85,7 +85,7 @@ public sealed class CmaFile var item = items.Identify(EquipSlot.MainHand, set, type, variant); data.SetItem(EquipSlot.MainHand, item.Valid ? item : items.DefaultSword); - data.SetStain(EquipSlot.MainHand, stain); + data.SetStain(EquipSlot.MainHand, new StainIds(stain)); } private static void ParseOffHand(ItemManager items, JObject jObj, ref DesignData data) @@ -95,7 +95,7 @@ public sealed class CmaFile if (offhand == null) { data.SetItem(EquipSlot.MainHand, defaultOffhand); - data.SetStain(EquipSlot.MainHand, defaultOffhand.PrimaryId.Id == 0 ? 0 : data.Stain(EquipSlot.MainHand)); + data.SetStain(EquipSlot.MainHand, defaultOffhand.PrimaryId.Id == 0 ? StainIds.None : data.Stain(EquipSlot.MainHand)); return; } @@ -106,6 +106,6 @@ public sealed class CmaFile var item = items.Identify(EquipSlot.OffHand, set, type, variant, data.MainhandType); data.SetItem(EquipSlot.OffHand, item.Valid ? item : defaultOffhand); - data.SetStain(EquipSlot.OffHand, defaultOffhand.PrimaryId.Id == 0 ? 0 : (StainId)stain); + data.SetStain(EquipSlot.OffHand, defaultOffhand.PrimaryId.Id == 0 ? StainIds.None : new StainIds(stain)); } } diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 8cd5391..19a805f 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -20,7 +20,7 @@ public class ContextMenuService : IDisposable private readonly ObjectManager _objects; private readonly IGameGui _gameGui; private EquipItem _lastItem; - private StainId _lastStain; + private readonly StainId[] _lastStains = new StainId[StainId.NumStains]; private readonly MenuItem _inventoryItem; @@ -47,14 +47,15 @@ public class ContextMenuService : IDisposable }; } - private unsafe void OnMenuOpened(MenuOpenedArgs args) + private unsafe void OnMenuOpened(IMenuOpenedArgs args) { if (args.MenuType is ContextMenuType.Inventory) { var arg = (MenuTargetInventory)args.Target; if (arg.TargetItem.HasValue && HandleItem(arg.TargetItem.Value.ItemId)) { - _lastStain = arg.TargetItem.Value.Stain; + for (var i = 0; i < arg.TargetItem.Value.Stains.Length; ++i) + _lastStains[i] = (StainId)arg.TargetItem.Value.Stains[i]; args.AddMenuItem(_inventoryItem); } } @@ -77,7 +78,8 @@ public class ContextMenuService : IDisposable if (HandleItem(*(ItemId*)(agent + ChatLogContextItemId))) { - _lastStain = 0; + for (var i = 0; i < _lastStains.Length; ++i) + _lastStains[i] = 0; args.AddMenuItem(_inventoryItem); } @@ -96,7 +98,7 @@ public class ContextMenuService : IDisposable public void Dispose() => Disable(); - private void OnClick(MenuItemClickedArgs _) + private void OnClick(IMenuItemClickedArgs _) { var (id, playerData) = _objects.PlayerData; if (!playerData.Valid) @@ -106,15 +108,15 @@ public class ContextMenuService : IDisposable return; var slot = _lastItem.Type.ToSlot(); - _state.ChangeEquip(state, slot, _lastItem, _lastStain, ApplySettings.Manual); + _state.ChangeEquip(state, slot, _lastItem, _lastStains[0], ApplySettings.Manual); if (!_lastItem.Type.ValidOffhand().IsOffhandType()) return; if (_lastItem.PrimaryId.Id is > 1600 and < 1651 && _items.ItemData.TryGetValue(_lastItem.ItemId, EquipSlot.Hands, out var gauntlets)) - _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, _lastStain, ApplySettings.Manual); + _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, _lastStains[0], ApplySettings.Manual); if (_items.ItemData.TryGetValue(_lastItem.ItemId, EquipSlot.OffHand, out var offhand)) - _state.ChangeEquip(state, EquipSlot.OffHand, offhand, _lastStain, ApplySettings.Manual); + _state.ChangeEquip(state, EquipSlot.OffHand, offhand, _lastStains[0], ApplySettings.Manual); } private bool HandleItem(ItemId id) diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 75e2a81..2b6f1ac 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -4,6 +4,7 @@ using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; @@ -30,9 +31,9 @@ public sealed unsafe class CrestService : EventWrapperRef3(_humanVTable[96], HumanSetFreeCompanyCrestVisibleOnSlotDetour); + interop.HookFromAddress(_humanVTable[109], HumanSetFreeCompanyCrestVisibleOnSlotDetour); _weaponSetFreeCompanyCrestVisibleOnSlot = - interop.HookFromAddress(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour); + interop.HookFromAddress(_weaponVTable[109], WeaponSetFreeCompanyCrestVisibleOnSlotDetour); _humanSetFreeCompanyCrestVisibleOnSlot.Enable(); _weaponSetFreeCompanyCrestVisibleOnSlot.Enable(); _crestChangeHook.Enable(); @@ -63,7 +64,7 @@ public sealed unsafe class CrestService : EventWrapperRef3 _crestChangeHook = null!; private void CrestChangeDetour(Character* character, byte crestFlags) @@ -96,8 +97,7 @@ public sealed unsafe class CrestService : EventWrapperRef3)((nint*)model.AsCharacterBase->VTable)[95]; - return getter(model.AsHuman, index) != 0; + return model.AsHuman->IsFreeCompanyCrestVisibleOnSlot(index) != 0; } case CrestType.Offhand: { @@ -105,8 +105,7 @@ public sealed unsafe class CrestService : EventWrapperRef3)((nint*)model.AsCharacterBase->VTable)[95]; - return getter(model.AsWeapon, index) != 0; + return model.AsWeapon->IsFreeCompanyCrestVisibleOnSlot(index) != 0; } } @@ -117,10 +116,10 @@ public sealed unsafe class CrestService : EventWrapperRef3 _humanSetFreeCompanyCrestVisibleOnSlot; diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 9587feb..7dc3313 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.DragDrop; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Interop.CharaFile; using Glamourer.Services; diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 9ad8737..6d8e58b 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -12,9 +12,9 @@ namespace Glamourer.Interop; public sealed unsafe class InventoryService : IDisposable, IRequiredService { - private readonly MovedEquipment _movedItemsEvent; - private readonly EquippedGearset _gearsetEvent; - private readonly List<(EquipSlot, uint, StainId)> _itemList = new(12); + private readonly MovedEquipment _movedItemsEvent; + private readonly EquippedGearset _gearsetEvent; + private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { @@ -60,56 +60,56 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService if (glamourPlateId != 0) { - void Add(EquipSlot slot, uint glamourId, StainId glamourStain, ref RaptureGearsetModule.GearsetItem item) + void Add(EquipSlot slot, uint glamourId, StainIds glamourStain, ref RaptureGearsetModule.GearsetItem item) { - if (item.ItemID == 0) - _itemList.Add((slot, 0, 0)); + if (item.ItemId == 0) + _itemList.Add((slot, 0, StainIds.None)); else if (glamourId != 0) _itemList.Add((slot, glamourId, glamourStain)); else if (item.GlamourId != 0) - _itemList.Add((slot, item.GlamourId, item.Stain)); + _itemList.Add((slot, item.GlamourId, StainIds.FromGearsetItem(item))); else - _itemList.Add((slot, FixId(item.ItemID), item.Stain)); + _itemList.Add((slot, FixId(item.ItemId), StainIds.FromGearsetItem(item))); } - var plate = MirageManager.Instance()->GlamourPlatesSpan[glamourPlateId - 1]; - 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]); + var plate = MirageManager.Instance()->GlamourPlates[glamourPlateId - 1]; + Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]); + Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]); + Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]); + Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]); + Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]); + Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]); + Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]); + Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]); + Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]); + Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]); + Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]); + Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]); } else { void Add(EquipSlot slot, ref RaptureGearsetModule.GearsetItem item) { - if (item.ItemID == 0) - _itemList.Add((slot, 0, 0)); + if (item.ItemId == 0) + _itemList.Add((slot, 0, StainIds.None)); else if (item.GlamourId != 0) - _itemList.Add((slot, item.GlamourId, item.Stain)); + _itemList.Add((slot, item.GlamourId, StainIds.FromGearsetItem(item))); else - _itemList.Add((slot, FixId(item.ItemID), item.Stain)); + _itemList.Add((slot, FixId(item.ItemId), StainIds.FromGearsetItem(item))); } - 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]); + Add(EquipSlot.MainHand, ref entry->Items[0]); + Add(EquipSlot.OffHand, ref entry->Items[1]); + Add(EquipSlot.Head, ref entry->Items[2]); + Add(EquipSlot.Body, ref entry->Items[3]); + Add(EquipSlot.Hands, ref entry->Items[5]); + Add(EquipSlot.Legs, ref entry->Items[6]); + Add(EquipSlot.Feet, ref entry->Items[7]); + Add(EquipSlot.Ears, ref entry->Items[8]); + Add(EquipSlot.Neck, ref entry->Items[9]); + Add(EquipSlot.Wrists, ref entry->Items[10]); + Add(EquipSlot.RFinger, ref entry->Items[11]); + Add(EquipSlot.LFinger, ref entry->Items[12]); } _movedItemsEvent.Invoke(_itemList.ToArray()); @@ -155,7 +155,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService return ret; } - private static bool InvokeSource(InventoryType sourceContainer, uint sourceSlot, out (EquipSlot, uint, StainId) tuple) + private static bool InvokeSource(InventoryType sourceContainer, uint sourceSlot, out (EquipSlot, uint, StainIds) tuple) { tuple = default; if (sourceContainer is not InventoryType.EquippedItems) @@ -165,12 +165,12 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService if (slot is EquipSlot.Unknown) return false; - tuple = (slot, 0u, 0); + tuple = (slot, 0u, StainIds.None); return true; } private static bool InvokeTarget(InventoryManager* manager, InventoryType targetContainer, uint targetSlot, - out (EquipSlot, uint, StainId) tuple) + out (EquipSlot, uint, StainIds) tuple) { tuple = default; if (targetContainer is not InventoryType.EquippedItems) @@ -189,7 +189,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService if (item == null) return false; - tuple = (slot, item->GlamourID != 0 ? item->GlamourID : item->ItemID, item->Stain); + tuple = (slot, item->GlamourId != 0 ? item->GlamourId : item->ItemId, new StainIds(item->Stains)); return true; } diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index b8941e0..3e984c8 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -169,13 +169,13 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable if (!actor.AsObject->IsCharacter()) return false; - if (actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject == characterBase) + if (actor.AsCharacter->DrawData.WeaponData[0].DrawObject == characterBase) { type = MaterialValueIndex.DrawObjectType.Mainhand; return true; } - if (actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject == characterBase) + if (actor.AsCharacter->DrawData.WeaponData[1].DrawObject == characterBase) { type = MaterialValueIndex.DrawObjectType.Offhand; return true; @@ -199,10 +199,11 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// private static CharacterWeapon GetTempSlot(Weapon* weapon) { + // TODO: Fix offset var changedData = *(void**)((byte*)weapon + 0x918); if (changedData == null) - return new CharacterWeapon(weapon->ModelSetId, weapon->SecondaryId, (Variant)weapon->Variant, (StainId)weapon->ModelUnknown); + return new CharacterWeapon(weapon->ModelSetId, weapon->SecondaryId, (Variant)weapon->Variant, StainIds.FromWeapon(*weapon)); - return new CharacterWeapon(weapon->ModelSetId, *(SecondaryId*)changedData, ((Variant*)changedData)[2], ((StainId*)changedData)[3]); + return new CharacterWeapon(weapon->ModelSetId, *(SecondaryId*)changedData, ((Variant*)changedData)[2], new StainIds(((StainId*)changedData)[3], ((StainId*)changedData)[4])); } } diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 9bfcc4c..2096bc7 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -62,8 +62,8 @@ public readonly record struct MaterialValueIndex( model = DrawObject switch { DrawObjectType.Human => actor.Model, - DrawObjectType.Mainhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponDataSpan[0].DrawObject : Model.Null, - DrawObjectType.Offhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject : Model.Null, + DrawObjectType.Mainhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponData[0].DrawObject : Model.Null, + DrawObjectType.Offhand => actor.IsCharacter ? actor.AsCharacter->DrawData.WeaponData[1].DrawObject : Model.Null, _ => Model.Null, }; return model.IsCharacterBase; diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 483e6af..ae08c71 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -203,7 +203,7 @@ public struct MaterialValueState( => DrawData.Skeleton == rhsData.Skeleton && DrawData.Weapon == rhsData.Weapon && DrawData.Variant == rhsData.Variant - && DrawData.Stain == rhsData.Stain + && DrawData.Stains == rhsData.Stains && Game.NearEqual(rhsRow); public readonly MaterialValueDesign Convert() diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 1661037..3866d74 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -4,6 +4,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using OtterGui.Classes; using OtterGui.Services; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; @@ -22,7 +23,7 @@ public sealed unsafe class PrepareColorSet public PrepareColorSet(HookManager hooks) : base("Prepare Color Set ") - => _task = hooks.CreateHook(Name, "40 55 56 41 56 48 83 EC ?? 80 BA", Detour, true); + => _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); private readonly Task> _task; @@ -54,7 +55,7 @@ public sealed unsafe class PrepareColorSet return _task.Result.Original(characterBase, material, stainId); } - public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId, + public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainIds stainIds, out LegacyColorTable table) { if (material->ColorTable == null) @@ -64,8 +65,9 @@ public sealed unsafe class PrepareColorSet } var newTable = *(LegacyColorTable*)material->ColorTable; - if (stainId.Id != 0) - characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); + // TODO + //if (stainIds.Stain1.Id != 0 || stainIds.Stain2.Id != 0) + // characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); table = newTable; return true; } @@ -84,21 +86,21 @@ public sealed unsafe class PrepareColorSet return false; } - return TryGetColorTable(model.AsCharacterBase, handle, GetStain(), out table); + return TryGetColorTable(model.AsCharacterBase, handle, GetStains(), out table); - StainId GetStain() + StainIds GetStains() { switch (index.DrawObject) { case MaterialValueIndex.DrawObjectType.Human: - return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stain : 0; + return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stains : StainIds.None; case MaterialValueIndex.DrawObjectType.Mainhand: - var mainhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject; - return mainhand.IsWeapon ? (StainId)mainhand.AsWeapon->ModelUnknown : 0; + var mainhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject; + return mainhand.IsWeapon ? StainIds.FromWeapon(*mainhand.AsWeapon) : StainIds.None; case MaterialValueIndex.DrawObjectType.Offhand: - var offhand = (Model)actor.AsCharacter->DrawData.WeaponDataSpan[1].DrawObject; - return offhand.IsWeapon ? (StainId)offhand.AsWeapon->ModelUnknown : 0; - default: return 0; + var offhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject; + return offhand.IsWeapon ? StainIds.FromWeapon(*offhand.AsWeapon) : StainIds.None; + default: return StainIds.None; } } } diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs index 1bc7a32..062986b 100644 --- a/Glamourer/Interop/MetaService.cs +++ b/Glamourer/Interop/MetaService.cs @@ -49,11 +49,11 @@ public unsafe class MetaService : IDisposable return; // The function seems to not do anything if the head is 0, but also breaks for carbuncles turned human, sometimes? - var old = actor.AsCharacter->DrawData.Head.Id; + var old = actor.AsCharacter->DrawData.Equipment(DrawDataContainer.EquipmentSlot.Head).Id; if (old == 0 && actor.AsCharacter->CharacterData.ModelCharaId == 0) - actor.AsCharacter->DrawData.Head.Id = 1; + actor.AsCharacter->DrawData.Equipment(DrawDataContainer.EquipmentSlot.Head).Id = 1; _hideHatGearHook.Original(&actor.AsCharacter->DrawData, 0, (byte)(value ? 0 : 1)); - actor.AsCharacter->DrawData.Head.Id = old; + actor.AsCharacter->DrawData.Equipment(DrawDataContainer.EquipmentSlot.Head).Id = old; } public void SetWeaponState(Actor actor, bool value) @@ -72,7 +72,7 @@ public unsafe class MetaService : IDisposable return; } - Actor actor = drawData->Parent; + Actor actor = drawData->OwnerObject; var v = value == 0; _headGearEvent.Invoke(actor, ref v); value = (byte)(v ? 0 : 1); @@ -82,7 +82,7 @@ public unsafe class MetaService : IDisposable private void HideWeaponsDetour(DrawDataContainer* drawData, bool value) { - Actor actor = drawData->Parent; + Actor actor = drawData->OwnerObject; value = !value; _weaponEvent.Invoke(actor, ref value); value = !value; @@ -92,7 +92,7 @@ public unsafe class MetaService : IDisposable private void ToggleVisorDetour(DrawDataContainer* drawData, bool value) { - Actor actor = drawData->Parent; + Actor actor = drawData->OwnerObject; _visorEvent.Invoke(actor.Model, true, ref value); Glamourer.Log.Verbose($"[MetaService] Toggle Visor triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}."); _toggleVisorHook.Original(drawData, value); diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index f59e95c..f8f5813 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -14,7 +14,7 @@ public class ObjectManager( IFramework framework, IClientState clientState, IObjectTable objects, - DalamudPluginInterface pi, + IDalamudPluginInterface pi, Logger log, ActorManager actors, ITargetManager targets) diff --git a/Glamourer/Interop/PalettePlus/PaletteImport.cs b/Glamourer/Interop/PalettePlus/PaletteImport.cs index 8513036..93c3fa2 100644 --- a/Glamourer/Interop/PalettePlus/PaletteImport.cs +++ b/Glamourer/Interop/PalettePlus/PaletteImport.cs @@ -6,7 +6,7 @@ using OtterGui.Services; namespace Glamourer.Interop.PalettePlus; -public class PaletteImport(DalamudPluginInterface pluginInterface, DesignManager designManager, DesignFileSystem designFileSystem) : IService +public class PaletteImport(IDalamudPluginInterface pluginInterface, DesignManager designManager, DesignFileSystem designFileSystem) : IService { private string ConfigFile => Path.Combine(Path.GetDirectoryName(pluginInterface.GetPluginConfigDirectory())!, "PalettePlus.json"); diff --git a/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs b/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs index 6a23e90..a5a5ed9 100644 --- a/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs +++ b/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs @@ -1,7 +1,7 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin; -using OtterGui.Classes; using OtterGui.Services; +using Notification = OtterGui.Classes.Notification; namespace Glamourer.Interop.PalettePlus; @@ -9,9 +9,9 @@ public sealed class PalettePlusChecker : IRequiredService, IDisposable { private readonly Timer _paletteTimer; private readonly Configuration _config; - private readonly DalamudPluginInterface _pluginInterface; + private readonly IDalamudPluginInterface _pluginInterface; - public PalettePlusChecker(Configuration config, DalamudPluginInterface pluginInterface) + public PalettePlusChecker(Configuration config, IDalamudPluginInterface pluginInterface) { _config = config; _pluginInterface = pluginInterface; diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 58422d7..85235e7 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin; using Glamourer.Events; using OtterGui.Classes; @@ -36,7 +36,7 @@ public unsafe class PenumbraService : IDisposable public const int RequiredPenumbraBreakingVersion = 5; public const int RequiredPenumbraFeatureVersion = 0; - private readonly DalamudPluginInterface _pluginInterface; + private readonly IDalamudPluginInterface _pluginInterface; private readonly EventSubscriber _tooltipSubscriber; private readonly EventSubscriber _clickSubscriber; private readonly EventSubscriber _creatingCharacterBase; @@ -68,7 +68,7 @@ public unsafe class PenumbraService : IDisposable public int CurrentMinor { get; private set; } public DateTime AttachTime { get; private set; } - public PenumbraService(DalamudPluginInterface pi, PenumbraReloaded penumbraReloaded) + public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded) { _pluginInterface = pi; _penumbraReloaded = penumbraReloaded; diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 66036d9..f7b8f08 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -3,6 +3,7 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; +using Penumbra.GameData; using Penumbra.GameData.Interop; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; @@ -14,7 +15,7 @@ public unsafe class ScalingService : IDisposable { interop.InitializeFromAttributes(this); _setupMountHook = - interop.HookFromAddress((nint)Character.MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); + interop.HookFromAddress((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); _setupOrnamentHook = interop.HookFromAddress((nint)Ornament.MemberFunctionPointers.SetupOrnament, SetupOrnamentDetour); _calculateHeightHook = interop.HookFromAddress((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); @@ -33,7 +34,7 @@ public unsafe class ScalingService : IDisposable _calculateHeightHook.Dispose(); } - private delegate void SetupMount(Character.MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4); + private delegate void SetupMount(MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4); private delegate void SetupOrnament(Ornament* ornament, uint* unk1, float* unk2); private delegate void PlaceMinion(Companion* character); private delegate float CalculateHeight(Character* character); @@ -45,15 +46,15 @@ public unsafe class ScalingService : IDisposable private readonly Hook _calculateHeightHook; // TODO: Use client structs sig. - [Signature("48 89 5C 24 ?? 55 57 41 57 48 8D 6C 24", DetourName = nameof(PlaceMinionDetour))] + [Signature(Sigs.PlaceMinion, DetourName = nameof(PlaceMinionDetour))] private readonly Hook _placeMinionHook = null!; - private void SetupMountDetour(Character.MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4) + private void SetupMountDetour(MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4) { - var (race, clan, gender) = GetScaleRelevantCustomize(&container->OwnerObject->Character); - SetScaleCustomize(&container->OwnerObject->Character, container->OwnerObject->Character.GameObject.DrawObject); + var (race, clan, gender) = GetScaleRelevantCustomize(container->OwnerObject); + SetScaleCustomize(container->OwnerObject, container->OwnerObject->DrawObject); _setupMountHook.Original(container, mountId, unk1, unk2, unk3, unk4); - SetScaleCustomize(&container->OwnerObject->Character, race, clan, gender); + SetScaleCustomize(container->OwnerObject, race, clan, gender); } private void SetupOrnamentDetour(Ornament* ornament, uint* unk1, float* unk2) diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index f2f7423..65a168e 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -18,33 +18,44 @@ public unsafe class UpdateSlotService : IDisposable SlotUpdatingEvent = slotUpdating; interop.InitializeFromAttributes(this); _flagSlotForUpdateHook.Enable(); + _flagBonusSlotForUpdateHook.Enable(); } public void Dispose() - => _flagSlotForUpdateHook.Dispose(); + { + _flagSlotForUpdateHook.Dispose(); + _flagBonusSlotForUpdateHook.Dispose(); + } public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data) { if (!drawObject.IsCharacterBase) return; - FlagSlotForUpdateInterop(drawObject, slot, data); + var bonusSlot = slot.ToBonusIndex(); + if (bonusSlot == uint.MaxValue) + FlagSlotForUpdateInterop(drawObject, slot, data); + else + _flagBonusSlotForUpdateHook.Original(drawObject.Address, bonusSlot, &data); } - public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainId stain) - => UpdateSlot(drawObject, slot, armor.With(stain)); + public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains) + => UpdateSlot(drawObject, slot, armor.With(stains)); public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor) - => UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stain); + => UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stains); - public void UpdateStain(Model drawObject, EquipSlot slot, StainId stain) - => UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stain); + public void UpdateStain(Model drawObject, EquipSlot slot, StainIds stains) + => UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stains); private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data); [Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))] private readonly Hook _flagSlotForUpdateHook = null!; + [Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))] + private readonly Hook _flagBonusSlotForUpdateHook = null!; + private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { var slot = slotIdx.ToEquipSlot(); @@ -54,6 +65,15 @@ public unsafe class UpdateSlotService : IDisposable return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } + private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) + { + var slot = slotIdx.ToBonusSlot(); + var returnValue = ulong.MaxValue; + SlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); + Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); + return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + } + private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) => _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index d2aac1a..5ab2a40 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -70,7 +70,7 @@ public unsafe class WeaponService : IDisposable if (tmpWeapon.Value != weapon.Value) { if (tmpWeapon.Skeleton.Id == 0) - tmpWeapon.Stain = 0; + tmpWeapon.Stains = StainIds.None; _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); } @@ -107,12 +107,12 @@ public unsafe class WeaponService : IDisposable } } - public void LoadStain(Actor character, EquipSlot slot, StainId stain) + public void LoadStain(Actor character, EquipSlot slot, StainIds stains) { var mdl = character.Model; var (_, _, mh, oh) = mdl.GetWeapons(character); var value = slot == EquipSlot.OffHand ? oh : mh; - var weapon = value.With(value.Skeleton.Id == 0 ? 0 : stain); + var weapon = value.With(value.Skeleton.Id == 0 ? StainIds.None : stains); LoadWeapon(character, slot, weapon); } } diff --git a/Glamourer/Services/CollectionOverrideService.cs b/Glamourer/Services/CollectionOverrideService.cs index 691118f..fcc9998 100644 --- a/Glamourer/Services/CollectionOverrideService.cs +++ b/Glamourer/Services/CollectionOverrideService.cs @@ -1,13 +1,13 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; 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; using Penumbra.GameData.Interop; +using Notification = OtterGui.Classes.Notification; namespace Glamourer.Services; diff --git a/Glamourer/Services/CustomizeService.cs b/Glamourer/Services/CustomizeService.cs index bb9737d..6505846 100644 --- a/Glamourer/Services/CustomizeService.cs +++ b/Glamourer/Services/CustomizeService.cs @@ -161,13 +161,6 @@ public sealed class CustomizeService( return $"The gender {gender.ToName()} is unknown, reset to {Gender.Male.ToName()}."; } - // TODO: Female Hrothgar - if (gender is Gender.Female && race is Race.Hrothgar) - { - actualGender = Gender.Male; - return $"{Race.Hrothgar.ToName()} do not currently support {Gender.Female.ToName()} characters, reset to {Gender.Male.ToName()}."; - } - actualGender = gender; return string.Empty; } @@ -225,13 +218,6 @@ public sealed class CustomizeService( customize.Race = newRace; customize.Clan = newClan; - // TODO Female Hrothgar - if (newRace == Race.Hrothgar) - { - customize.Gender = Gender.Male; - flags |= CustomizeFlag.Gender; - } - var set = Manager.GetSet(customize.Clan, customize.Gender); return FixValues(set, ref customize) | flags; } @@ -242,10 +228,6 @@ public sealed class CustomizeService( if (customize.Gender == newGender) return 0; - // TODO Female Hrothgar - if (customize.Race is Race.Hrothgar) - return 0; - if (ValidateGender(customize.Race, newGender, out newGender).Length > 0) return 0; diff --git a/Glamourer/Services/DalamudServices.cs b/Glamourer/Services/DalamudServices.cs index fd001d7..02df634 100644 --- a/Glamourer/Services/DalamudServices.cs +++ b/Glamourer/Services/DalamudServices.cs @@ -8,7 +8,7 @@ namespace Glamourer.Services; public class DalamudServices { - public static void AddServices(ServiceManager services, DalamudPluginInterface pi) + public static void AddServices(ServiceManager services, IDalamudPluginInterface pi) { services.AddExistingService(pi); services.AddExistingService(pi.UiBuilder); diff --git a/Glamourer/Services/FilenameService.cs b/Glamourer/Services/FilenameService.cs index e19e289..cd25c64 100644 --- a/Glamourer/Services/FilenameService.cs +++ b/Glamourer/Services/FilenameService.cs @@ -19,7 +19,7 @@ public class FilenameService public readonly string NpcAppearanceFile; public readonly string CollectionOverrideFile; - public FilenameService(DalamudPluginInterface pi) + public FilenameService(IDalamudPluginInterface pi) { ConfigDirectory = pi.ConfigDirectory.FullName; ConfigFile = pi.ConfigFile.FullName; diff --git a/Glamourer/Services/HeightService.cs b/Glamourer/Services/HeightService.cs index 48f0dd6..0a6c7bb 100644 --- a/Glamourer/Services/HeightService.cs +++ b/Glamourer/Services/HeightService.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Services; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -9,7 +10,7 @@ namespace Glamourer.Services; public unsafe class HeightService : IService { - [Signature("E8 ?? ?? ?? FF 48 8B 0D ?? ?? ?? ?? 0F 28 F0")] + [Signature(Sigs.CalculateHeight)] private readonly delegate* unmanaged[Stdcall] _calculateHeight = null!; public HeightService(IGameInteropProvider interop) diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 8f52815..c5f537f 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -195,16 +195,16 @@ public class ItemManager /// The returned stain id is either the input or 0. /// The return value is an empty string if there was no problem and a warning otherwise. /// - public string ValidateStain(StainId stain, out StainId ret, bool allowUnknown) + public string ValidateStain(StainIds stains, out StainIds ret, bool allowUnknown) { - if (allowUnknown || IsStainValid(stain)) + if (allowUnknown || stains.All(IsStainValid)) { - ret = stain; + ret = stains; return string.Empty; } - ret = 0; - return $"The Stain {stain} does not exist, reset to unstained."; + ret = StainIds.None; + return $"The Stain {stains} does not exist, reset to unstained."; } /// Returns whether an offhand is valid given the required offhand type. diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 005944e..48632ab 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -33,7 +33,7 @@ namespace Glamourer.Services; public static class StaticServiceManager { - public static ServiceManager CreateProvider(DalamudPluginInterface pi, Logger log, Glamourer glamourer) + public static ServiceManager CreateProvider(IDalamudPluginInterface pi, Logger log, Glamourer glamourer) { EventWrapperBase.ChangeLogger(log); var services = new ServiceManager(log) @@ -165,5 +165,6 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); } diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index 0619279..99436e4 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Interface.Internal; +using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; using OtterGui.Classes; using Penumbra.GameData.Enums; @@ -7,7 +7,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Services; -public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider) +public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManager, ITextureProvider textureProvider) : TextureCache(dataManager, textureProvider), IDisposable { private readonly IDalamudTextureWrap?[] _slotIcons = CreateSlotIcons(uiBuilder); @@ -32,7 +32,7 @@ public sealed class TextureService(UiBuilder uiBuilder, IDataManager dataManager } } - private static IDalamudTextureWrap?[] CreateSlotIcons(UiBuilder uiBuilder) + private static IDalamudTextureWrap?[] CreateSlotIcons(IUiBuilder uiBuilder) { var ret = new IDalamudTextureWrap?[12]; diff --git a/Glamourer/State/FunEquipSet.cs b/Glamourer/State/FunEquipSet.cs index 91e6419..c1ae02e 100644 --- a/Glamourer/State/FunEquipSet.cs +++ b/Glamourer/State/FunEquipSet.cs @@ -1,5 +1,4 @@ -using Glamourer.Interop.Structs; -using Penumbra.GameData.Structs; +using Penumbra.GameData.Structs; namespace Glamourer.State; @@ -21,8 +20,8 @@ internal class FunEquipSet { public Group(ushort headS, byte headV, ushort bodyS, byte bodyV, ushort handsS, byte handsV, ushort legsS, byte legsV, ushort feetS, byte feetV, StainId[]? stains = null) - : this(new CharacterArmor(headS, headV, 0), new CharacterArmor(bodyS, bodyV, 0), new CharacterArmor(handsS, handsV, 0), - new CharacterArmor(legsS, legsV, 0), new CharacterArmor(feetS, feetV, 0), stains) + : this(new CharacterArmor(headS, headV, StainIds.None), new CharacterArmor(bodyS, bodyV, StainIds.None), new CharacterArmor(handsS, handsV, StainIds.None), + new CharacterArmor(legsS, legsV, StainIds.None), new CharacterArmor(feetS, feetV, StainIds.None), stains) { } public static Group FullSetWithoutHat(ushort modelSet, byte variant, StainId[]? stains = null) diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 25b8946..a9ce8e2 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -1,5 +1,5 @@ -using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; +using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; using Glamourer.Gui; using Glamourer.Services; @@ -106,7 +106,7 @@ public unsafe class FunModule : IDisposable && actor.OnlineStatus is OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor && slot.IsEquipment()) { - armor = new CharacterArmor(6117, 1, 0); + armor = new CharacterArmor(6117, 1, StainIds.None); return; } @@ -171,7 +171,7 @@ public unsafe class FunModule : IDisposable break; case CodeService.CodeFlag.Dolphins: SetDolphin(EquipSlot.Body, ref armor[1]); - SetDolphin(EquipSlot.Head, ref armor[0]); + SetDolphin(EquipSlot.Head, ref armor[0]); break; case CodeService.CodeFlag.World when actor.Index != 0: _worldSets.Apply(actor, _rng, armor); @@ -198,7 +198,7 @@ public unsafe class FunModule : IDisposable private static bool ValidFunTarget(Actor actor) => actor.IsCharacter - && actor.AsObject->ObjectKind is (byte)ObjectKind.Player + && actor.AsObject->ObjectKind is ObjectKind.Pc && !actor.IsTransformed && actor.AsCharacter->CharacterData.ModelCharaId == 0; @@ -208,7 +208,7 @@ public unsafe class FunModule : IDisposable private void SetRandomDye(ref CharacterArmor armor) { var stainIdx = _rng.Next(0, _stains.Length - 1); - armor.Stain = _stains[stainIdx]; + armor.Stains = _stains[stainIdx]; } private void SetRandomItem(EquipSlot slot, ref CharacterArmor armor) @@ -235,17 +235,17 @@ public unsafe class FunModule : IDisposable private static IReadOnlyList DolphinBodies => [ - new CharacterArmor(6089, 1, 4), // Toad - new CharacterArmor(6089, 1, 4), // Toad - new CharacterArmor(6089, 1, 4), // Toad - new CharacterArmor(6023, 1, 4), // Swine - new CharacterArmor(6023, 1, 4), // Swine - new CharacterArmor(6023, 1, 4), // Swine - new CharacterArmor(6133, 1, 4), // Gaja - new CharacterArmor(6182, 1, 3), // Imp - new CharacterArmor(6182, 1, 3), // Imp - new CharacterArmor(6182, 1, 4), // Imp - new CharacterArmor(6182, 1, 4), // Imp + new CharacterArmor(6089, 1, new StainIds(4)), // Toad + new CharacterArmor(6089, 1, new StainIds(4)), // Toad + new CharacterArmor(6089, 1, new StainIds(4)), // Toad + new CharacterArmor(6023, 1, new StainIds(4)), // Swine + new CharacterArmor(6023, 1, new StainIds(4)), // Swine + new CharacterArmor(6023, 1, new StainIds(4)), // Swine + new CharacterArmor(6133, 1, new StainIds(4)), // Gaja + new CharacterArmor(6182, 1, new StainIds(3)), // Imp + new CharacterArmor(6182, 1, new StainIds(3)), // Imp + new CharacterArmor(6182, 1, new StainIds(4)), // Imp + new CharacterArmor(6182, 1, new StainIds(4)), // Imp ]; private void SetDolphin(EquipSlot slot, ref CharacterArmor armor) @@ -253,7 +253,7 @@ public unsafe class FunModule : IDisposable armor = slot switch { EquipSlot.Body => DolphinBodies[_rng.Next(0, DolphinBodies.Count - 1)], - EquipSlot.Head => new CharacterArmor(5040, 1, 0), + EquipSlot.Head => new CharacterArmor(5040, 1, StainIds.None), _ => armor, }; } @@ -270,7 +270,7 @@ public unsafe class FunModule : IDisposable private static void SetCrown(Span armor) { - var clown = new CharacterArmor(6117, 1, 0); + var clown = new CharacterArmor(6117, 1, StainIds.None); armor[0] = clown; armor[1] = clown; armor[2] = clown; @@ -285,15 +285,12 @@ public unsafe class FunModule : IDisposable return; var targetClan = (SubRace)((int)race * 2 - (int)customize.Clan % 2); - // TODO Female Hrothgar - if (race is Race.Hrothgar && customize.Gender is Gender.Female) - targetClan = targetClan is SubRace.Lost ? SubRace.Seawolf : SubRace.Hellsguard; _customizations.ChangeClan(ref customize, targetClan); } private void SetGender(ref CustomizeArray customize) { - if (!_codes.Enabled(CodeService.CodeFlag.SixtyThree) || customize.Race is Race.Hrothgar) // TODO Female Hrothgar + if (!_codes.Enabled(CodeService.CodeFlag.SixtyThree)) return; _customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male); diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs index eaf7c21..17072e7 100644 --- a/Glamourer/State/InternalStateEditor.cs +++ b/Glamourer/State/InternalStateEditor.cs @@ -152,11 +152,11 @@ public class InternalStateEditor( } /// Change a single piece of equipment including stain. - public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, out EquipItem oldItem, - out StainId oldStain, uint key = 0) + public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainIds stains, StateSource source, out EquipItem oldItem, + out StainIds oldStains, uint key = 0) { oldItem = state.ModelData.Item(slot); - oldStain = state.ModelData.Stain(slot); + oldStains = state.ModelData.Stain(slot); if (!state.CanUnlock(key)) return false; @@ -168,7 +168,7 @@ public class InternalStateEditor( return false; var old = oldItem; - var oldS = oldStain; + var oldS = oldStains; gPose.AddActionOnLeave(() => { if (old.Type == state.BaseData.Item(slot).Type) @@ -177,20 +177,20 @@ public class InternalStateEditor( } state.ModelData.SetItem(slot, item); - state.ModelData.SetStain(slot, stain); + state.ModelData.SetStain(slot, stains); state.Sources[slot, false] = source; state.Sources[slot, true] = source; return true; } /// Change only the stain of an equipment piece. - public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, out StainId oldStain, uint key = 0) + public bool ChangeStains(ActorState state, EquipSlot slot, StainIds stains, StateSource source, out StainIds oldStains, uint key = 0) { - oldStain = state.ModelData.Stain(slot); + oldStains = state.ModelData.Stain(slot); if (!state.CanUnlock(key)) return false; - state.ModelData.SetStain(slot, stain); + state.ModelData.SetStain(slot, stains); state.Sources[slot, true] = source; return true; } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 9c06ce5..bf0fc30 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -130,22 +130,22 @@ public class StateApplier( /// Change the stain of a single piece of armor or weapon. /// If the offhand is empty, the stain will be fixed to 0 to prevent crashes. /// - public void ChangeStain(ActorData data, EquipSlot slot, StainId stain) + public void ChangeStain(ActorData data, EquipSlot slot, StainIds stains) { var idx = slot.ToIndex(); switch (idx) { case < 10: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _updateSlot.UpdateStain(actor.Model, slot, stain); + _updateSlot.UpdateStain(actor.Model, slot, stains); break; case 10: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadStain(actor, EquipSlot.MainHand, stain); + _weapon.LoadStain(actor, EquipSlot.MainHand, stains); break; case 11: foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadStain(actor, EquipSlot.OffHand, stain); + _weapon.LoadStain(actor, EquipSlot.OffHand, stains); break; } } @@ -162,12 +162,12 @@ public class StateApplier( /// Apply a weapon to the appropriate slot. - public void ChangeWeapon(ActorData data, EquipSlot slot, EquipItem item, StainId stain) + public void ChangeWeapon(ActorData data, EquipSlot slot, EquipItem item, StainIds stains) { if (slot is EquipSlot.MainHand) - ChangeMainhand(data, item, stain); + ChangeMainhand(data, item, stains); else - ChangeOffhand(data, item, stain); + ChangeOffhand(data, item, stains); } /// @@ -186,19 +186,19 @@ public class StateApplier( /// /// Apply a weapon to the mainhand. If the weapon type has no associated offhand type, apply both. /// - public void ChangeMainhand(ActorData data, EquipItem weapon, StainId stain) + public void ChangeMainhand(ActorData data, EquipItem weapon, StainIds stains) { var slot = weapon.Type.ValidOffhand() == FullEquipType.Unknown ? EquipSlot.BothHand : EquipSlot.MainHand; foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stain)); + _weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stains)); } /// Apply a weapon to the offhand. - public void ChangeOffhand(ActorData data, EquipItem weapon, StainId stain) + public void ChangeOffhand(ActorData data, EquipItem weapon, StainIds stains) { - stain = weapon.PrimaryId.Id == 0 ? 0 : stain; + stains = weapon.PrimaryId.Id == 0 ? StainIds.None : stains; foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) - _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain)); + _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stains)); } /// Change a meta state. @@ -209,7 +209,7 @@ public class StateApplier( case MetaIndex.Wetness: { foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - actor.AsCharacter->IsGPoseWet = value; + actor.IsGPoseWet = value; return; } case MetaIndex.HatState: diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index c20a69d..dccb283 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -90,22 +90,22 @@ public class StateEditor( } /// - public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings) + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings settings) { - switch (item.HasValue, stain.HasValue) + switch (item.HasValue, stains.HasValue) { case (false, false): return; case (true, false): ChangeItem(data, slot, item!.Value, settings); return; case (false, true): - ChangeStain(data, slot, stain!.Value, settings); + ChangeStains(data, slot, stains!.Value, settings); return; } var state = (ActorState)data; - if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stain ?? state.ModelData.Stain(slot), settings.Source, - out var old, out var oldStain, settings.Key)) + if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stains ?? state.ModelData.Stain(slot), settings.Source, + out var old, out var oldStains, settings.Key)) return; var type = slot.ToIndex() < 10 ? StateChangeType.Equip : StateChangeType.Weapon; @@ -115,25 +115,25 @@ public class StateEditor( item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); if (slot is EquipSlot.MainHand) - ApplyMainhandPeriphery(state, item, stain, settings); + ApplyMainhandPeriphery(state, item, stains, settings); Glamourer.Log.Verbose( - $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStain.Id} to {stain!.Value.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot)); - StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (oldStain, stain!.Value, slot)); + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot)); + StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, (oldStains, stains!.Value, slot)); } /// - public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings) + public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings) { var state = (ActorState)data; - if (!Editor.ChangeStain(state, slot, stain, settings.Source, out var old, settings.Key)) + if (!Editor.ChangeStains(state, slot, stains, settings.Source, out var old, settings.Key)) return; var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); Glamourer.Log.Verbose( - $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Stain, settings.Source, state, actors, (old, stain, slot)); + $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, (old, stains, slot)); } /// @@ -269,7 +269,7 @@ public class StateEditor( if (mergedDesign.Design.DoApplyStain(slot)) if (!settings.RespectManual || !state.Sources[slot, true].IsManual()) - Editor.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot), + Editor.ChangeStains(state, slot, mergedDesign.Design.DesignData.Stain(slot), Source(slot.ToState(true)), out _, settings.Key); } @@ -277,7 +277,7 @@ public class StateEditor( { if (mergedDesign.Design.DoApplyStain(weaponSlot)) if (!settings.RespectManual || !state.Sources[weaponSlot, true].IsManual()) - Editor.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot), + Editor.ChangeStains(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot), Source(weaponSlot.ToState(true)), out _, settings.Key); if (!mergedDesign.Design.DoApplyEquip(weaponSlot)) @@ -392,19 +392,19 @@ public class StateEditor( /// Apply offhand item and potentially gauntlets if configured. - private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StainId? newStain, ApplySettings settings) + private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StainIds? newStains, 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); + var stains = newStains ?? state.ModelData.Stain(EquipSlot.MainHand); if (offhand.Valid) - ChangeEquip(state, EquipSlot.OffHand, offhand, stain, settings); + ChangeEquip(state, EquipSlot.OffHand, offhand, stains, 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), - stain, settings); + stains, settings); } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index f3657a7..8b3b5e7 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -227,7 +227,7 @@ public class StateListener : IDisposable (_, armor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender); } - private void OnMovedEquipment((EquipSlot, uint, StainId)[] items) + private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items) { _objects.Update(); var (identifier, objects) = _objects.PlayerData; @@ -250,14 +250,14 @@ public class StateListener : IDisposable && current.Weapon == changed.Weapon && !state.Sources[slot, false].IsFixed(); - var stainChanged = current.Stain == changed.Stain && !state.Sources[slot, true].IsFixed(); + var stainChanged = current.Stains == changed.Stains && !state.Sources[slot, true].IsFixed(); switch ((itemChanged, stainChanged)) { case (true, true): - _manager.ChangeEquip(state, slot, currentItem, current.Stain, ApplySettings.Game); + _manager.ChangeEquip(state, slot, currentItem, current.Stains, ApplySettings.Game); if (slot is EquipSlot.MainHand or EquipSlot.OffHand) - _applier.ChangeWeapon(objects, slot, currentItem, current.Stain); + _applier.ChangeWeapon(objects, slot, currentItem, current.Stains); else _applier.ChangeArmor(objects, slot, current.ToArmor(), !state.Sources[slot, false].IsFixed(), state.ModelData.IsHatVisible()); @@ -265,14 +265,14 @@ public class StateListener : IDisposable case (true, false): _manager.ChangeItem(state, slot, currentItem, ApplySettings.Game); if (slot is EquipSlot.MainHand or EquipSlot.OffHand) - _applier.ChangeWeapon(objects, slot, currentItem, model.Stain); + _applier.ChangeWeapon(objects, slot, currentItem, model.Stains); else - _applier.ChangeArmor(objects, slot, current.ToArmor(model.Stain), !state.Sources[slot, false].IsFixed(), + _applier.ChangeArmor(objects, slot, current.ToArmor(model.Stains), !state.Sources[slot, false].IsFixed(), state.ModelData.IsHatVisible()); break; case (false, true): - _manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game); - _applier.ChangeStain(objects, slot, current.Stain); + _manager.ChangeStains(state, slot, current.Stains, ApplySettings.Game); + _applier.ChangeStain(objects, slot, current.Stains); break; } } @@ -308,7 +308,7 @@ public class StateListener : IDisposable apply = true; if (!state.Sources[slot, true].IsFixed()) - _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); + _manager.ChangeStains(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); else apply = true; break; @@ -332,7 +332,7 @@ public class StateListener : IDisposable else { if (weapon.Skeleton.Id != 0) - weapon = weapon.With(newWeapon.Stain); + weapon = weapon.With(newWeapon.Stains); // Force unlock if necessary. _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game with { Key = state.Combination }); } @@ -341,7 +341,7 @@ public class StateListener : IDisposable // Fist Weapon Offhand hack. if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651) _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, - weapon.Stain); + weapon.Stains); _funModule.ApplyFunToWeapon(actor, ref weapon, slot); } @@ -365,7 +365,7 @@ public class StateListener : IDisposable { var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant); state.BaseData.SetItem(EquipSlot.Head, item); - state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stain); + state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stains); return UpdateState.Change; } @@ -378,9 +378,9 @@ public class StateListener : IDisposable var baseData = state.BaseData.Armor(slot); var change = UpdateState.NoChange; - if (baseData.Stain != armor.Stain) + if (baseData.Stains != armor.Stains) { - state.BaseData.SetStain(slot, armor.Stain); + state.BaseData.SetStain(slot, armor.Stains); change = UpdateState.Change; } @@ -418,7 +418,7 @@ public class StateListener : IDisposable apply = true; if (!state.Sources[slot, true].IsFixed()) - _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); + _manager.ChangeStains(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); else apply = true; @@ -503,9 +503,9 @@ public class StateListener : IDisposable if (slot is EquipSlot.OffHand && weapon.Value == 0 && actor.GetMainhand().Skeleton.Id is > 1600 and < 1651) return UpdateState.NoChange; - if (baseData.Stain != weapon.Stain) + if (baseData.Stains != weapon.Stains) { - state.BaseData.SetStain(slot, weapon.Stain); + state.BaseData.SetStain(slot, weapon.Stains); change = UpdateState.Change; } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index f057580..92cf6e5 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -117,7 +117,7 @@ public sealed class StateManager( if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) { ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData, - (nint)(&actor.AsCharacter->DrawData.Head)); + (nint)Unsafe.AsPointer(ref actor.AsCharacter->DrawData.EquipmentModelIds[0])); return ret; } @@ -141,7 +141,7 @@ public sealed class StateManager( var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head); var headItem = Items.Identify(EquipSlot.Head, head.Set, head.Variant); ret.SetItem(EquipSlot.Head, headItem); - ret.SetStain(EquipSlot.Head, head.Stain); + ret.SetStain(EquipSlot.Head, head.Stains); // The other slots can be used from the draw object. foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1)) @@ -149,7 +149,7 @@ public sealed class StateManager( var armor = model.GetArmor(slot); var item = Items.Identify(slot, armor.Set, armor.Variant); ret.SetItem(slot, item); - ret.SetStain(slot, armor.Stain); + ret.SetStain(slot, armor.Stains); } // Weapons use the draw objects of the weapons, but require the game object either way. @@ -171,7 +171,7 @@ public sealed class StateManager( var armor = actor.GetArmor(slot); var item = Items.Identify(slot, armor.Set, armor.Variant); ret.SetItem(slot, item); - ret.SetStain(slot, armor.Stain); + ret.SetStain(slot, armor.Stains); } main = actor.GetMainhand(); @@ -187,13 +187,13 @@ public sealed class StateManager( var mainItem = Items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant); var offItem = Items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type); ret.SetItem(EquipSlot.MainHand, mainItem); - ret.SetStain(EquipSlot.MainHand, main.Stain); + ret.SetStain(EquipSlot.MainHand, main.Stains); ret.SetItem(EquipSlot.OffHand, offItem); - ret.SetStain(EquipSlot.OffHand, off.Stain); + ret.SetStain(EquipSlot.OffHand, off.Stains); // Wetness can technically only be set in GPose or via external tools. // It is only available in the game object. - ret.SetIsWet(actor.AsCharacter->IsGPoseWet); + ret.SetIsWet(actor.IsGPoseWet); // Weapon visibility could technically be inferred from the weapon draw objects, // but since we use hat visibility from the game object we can also use weapon visibility from it. @@ -214,7 +214,7 @@ public sealed class StateManager( offhand.Variant = mainhand.Variant; offhand.Weapon = mainhand.Weapon; ret.SetItem(EquipSlot.Hands, gauntlets); - ret.SetStain(EquipSlot.Hands, mainhand.Stain); + ret.SetStain(EquipSlot.Hands, mainhand.Stains); } /// Turn an actor human. @@ -414,7 +414,9 @@ public sealed class StateManager( public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source) { var data = Applier.ApplyAll(state, - forceRedraw || !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(StateChangeType.Reapply, source, state, data, null); } diff --git a/Glamourer/State/WorldSets.cs b/Glamourer/State/WorldSets.cs index eca0988..6a497ea 100644 --- a/Glamourer/State/WorldSets.cs +++ b/Glamourer/State/WorldSets.cs @@ -22,7 +22,7 @@ public class WorldSets [(Gender.Male, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0257, 2), [(Gender.Female, Race.AuRa)] = FunEquipSet.Group.FullSetWithoutHat(0258, 2), [(Gender.Male, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0597, 1), - [(Gender.Female, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0000, 0), // TODO Hrothgar Female + [(Gender.Female, Race.Hrothgar)] = FunEquipSet.Group.FullSetWithoutHat(0829, 1), [(Gender.Male, Race.Viera)] = FunEquipSet.Group.FullSetWithoutHat(0744, 1), [(Gender.Female, Race.Viera)] = FunEquipSet.Group.FullSetWithoutHat(0581, 1), }; diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index b63a98e..a204a1c 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -1,4 +1,4 @@ -using Dalamud; +using Dalamud.Game; using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility; @@ -8,6 +8,7 @@ using Glamourer.GameData; using Glamourer.Events; using Glamourer.Services; using Lumina.Excel.GeneratedSheets; +using Penumbra.GameData; using Penumbra.GameData.Enums; namespace Glamourer.Unlocks; @@ -127,8 +128,8 @@ public class CustomizeUnlockManager : IDisposable, ISavable } private delegate void SetUnlockLinkValueDelegate(nint uiState, uint data, byte value); - - [Signature("48 83 EC ?? 8B C2 44 8B D2", DetourName = nameof(SetUnlockLinkValueDetour))] + + [Signature(Sigs.SetUnlockLinkValue, DetourName = nameof(SetUnlockLinkValueDetour))] private readonly Hook _setUnlockLinkValueHook = null!; private void SetUnlockLinkValueDetour(nint uiState, uint data, byte value) diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index 33242c9..de22ea8 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Glamourer.Services; using Newtonsoft.Json; using OtterGui.Classes; diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index de35335..0b95b94 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -144,8 +144,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary(mirageManager->PrismBoxItemIds, 800); + var span = mirageManager->PrismBoxItemIds; foreach (var item in span) changes |= AddItem(item, time); } @@ -154,10 +153,9 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionaryGlamourPlatesSpan) + foreach (var plate in mirageManager->GlamourPlates) { - // TODO: Make independent from hardcoded value - var span = new ReadOnlySpan(plate.ItemIds, 12); + var span = plate.ItemIds; foreach (var item in span) changes |= AddItem(item, time); } @@ -176,8 +174,8 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionaryGetInventorySlot(_currentInventoryIndex++); if (item != null) { - changes |= AddItem(item->ItemID, time); - changes |= AddItem(item->GlamourID, time); + changes |= AddItem(item->ItemId, time); + changes |= AddItem(item->GlamourId, time); } } else diff --git a/Glamourer/Unlocks/UnlockDictionaryHelpers.cs b/Glamourer/Unlocks/UnlockDictionaryHelpers.cs index 28f5793..edc9472 100644 --- a/Glamourer/Unlocks/UnlockDictionaryHelpers.cs +++ b/Glamourer/Unlocks/UnlockDictionaryHelpers.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using OtterGui.Classes; namespace Glamourer.Unlocks; diff --git a/OtterGui b/OtterGui index fd79128..c2738e1 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit fd791285606d49a7644762ea0b4dc2bbb1368eac +Subproject commit c2738e1d42974cddbe5a31238c6ed236a831d17d diff --git a/Penumbra.GameData b/Penumbra.GameData index 6aeae34..8ec296d 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 6aeae346332a255b7575ccfca554afb0f3cf1494 +Subproject commit 8ec296d1f8113ae2ba509527749cd3e8f54debbf diff --git a/Penumbra.String b/Penumbra.String index caa58c5..f04abba 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit caa58c5c92710e69ce07b9d736ebe2d228cb4488 +Subproject commit f04abbabedf5e757c5cbb970f3e513fef23e53cf diff --git a/repo.json b/repo.json index 3798270..c5ef399 100644 --- a/repo.json +++ b/repo.json @@ -21,7 +21,7 @@ "TestingAssemblyVersion": "1.2.3.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 9, + "DalamudApiLevel": 10, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1, From 7a602d6ec5cf9c4a985fe26dfbe70f24508b70bd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 11 Jul 2024 19:45:54 +0200 Subject: [PATCH 398/786] Extricate bonus slots somewhat. --- Glamourer/Events/BonusSlotUpdating.cs | 25 ++++++++++ .../{SlotUpdating.cs => EquipSlotUpdating.cs} | 8 ++-- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 23 ++++----- Glamourer/Interop/UpdateSlotService.cs | 47 ++++++++++++++----- Glamourer/Services/ServiceManager.cs | 2 +- Glamourer/State/StateApplier.cs | 4 +- Glamourer/State/StateListener.cs | 12 ++--- Penumbra.GameData | 2 +- 8 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 Glamourer/Events/BonusSlotUpdating.cs rename Glamourer/Events/{SlotUpdating.cs => EquipSlotUpdating.cs} (80%) diff --git a/Glamourer/Events/BonusSlotUpdating.cs b/Glamourer/Events/BonusSlotUpdating.cs new file mode 100644 index 0000000..3685f3c --- /dev/null +++ b/Glamourer/Events/BonusSlotUpdating.cs @@ -0,0 +1,25 @@ +using OtterGui.Classes; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; +using Penumbra.GameData.Structs; + +namespace Glamourer.Events; + +/// +/// Triggered when a model flags a bonus slot for an update. +/// +/// Parameter is the model with a flagged slot. +/// Parameter is the bonus slot changed. +/// Parameter is the model values to change the bonus piece to. +/// Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. +/// +/// +public sealed class BonusSlotUpdating() + : EventWrapperRef34(nameof(BonusSlotUpdating)) +{ + public enum Priority + { + /// + StateListener = 0, + } +} diff --git a/Glamourer/Events/SlotUpdating.cs b/Glamourer/Events/EquipSlotUpdating.cs similarity index 80% rename from Glamourer/Events/SlotUpdating.cs rename to Glamourer/Events/EquipSlotUpdating.cs index 8a766fb..a2daf85 100644 --- a/Glamourer/Events/SlotUpdating.cs +++ b/Glamourer/Events/EquipSlotUpdating.cs @@ -14,12 +14,12 @@ namespace Glamourer.Events; /// Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. /// /// -public sealed class SlotUpdating() - : EventWrapperRef34(nameof(SlotUpdating)) +public sealed class EquipSlotUpdating() + : EventWrapperRef34(nameof(EquipSlotUpdating)) { public enum Priority { - /// + /// StateListener = 0, } -} +} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index ddf42f1..dd1c125 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -90,7 +90,7 @@ public unsafe class ModelEvaluationPanel( : "No CharacterBase"); } - private void DrawParameters(Actor actor, Model model) + private static void DrawParameters(Actor actor, Model model) { if (!model.IsHuman) return; @@ -140,13 +140,13 @@ public unsafe class ModelEvaluationPanel( return; if (ImGui.SmallButton("Hide")) - _updateSlotService.UpdateSlot(model, EquipSlot.Head, CharacterArmor.Empty); + _updateSlotService.UpdateEquipSlot(model, EquipSlot.Head, CharacterArmor.Empty); ImGui.SameLine(); if (ImGui.SmallButton("Show")) - _updateSlotService.UpdateSlot(model, EquipSlot.Head, actor.GetArmor(EquipSlot.Head)); + _updateSlotService.UpdateEquipSlot(model, EquipSlot.Head, actor.GetArmor(EquipSlot.Head)); ImGui.SameLine(); if (ImGui.SmallButton("Toggle")) - _updateSlotService.UpdateSlot(model, EquipSlot.Head, + _updateSlotService.UpdateEquipSlot(model, EquipSlot.Head, model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty); } @@ -223,31 +223,32 @@ public unsafe class ModelEvaluationPanel( _updateSlotService.UpdateStain(model, slot, new StainIds(5, 7)); ImGui.SameLine(); if (ImGui.SmallButton("Reset")) - _updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot)); + _updateSlotService.UpdateEquipSlot(model, slot, actor.GetArmor(slot)); } - using (ImRaii.PushId((int)EquipSlot.FaceWear)) + foreach (var slot in BonusSlotExtensions.AllFlags) { - ImGuiUtil.DrawTableColumn(EquipSlot.FaceWear.ToName()); + using var id2 = ImRaii.PushId((int)slot.ToModelIndex()); + ImGuiUtil.DrawTableColumn(slot.ToName()); if (!actor.IsCharacter) { ImGuiUtil.DrawTableColumn("No Character"); } else { - var glassesId = actor.AsCharacter->DrawData.GlassesIds[(int)EquipSlot.FaceWear.ToBonusIndex()]; + var glassesId = actor.GetBonusSlot(slot); if (_glasses.TryGetValue(glassesId, out var glasses)) ImGuiUtil.DrawTableColumn($"{glasses.Id.Id},{glasses.Variant.Id} ({glassesId})"); else ImGuiUtil.DrawTableColumn($"{glassesId}"); } - ImGuiUtil.DrawTableColumn(model.IsHuman ? model.GetArmor(EquipSlot.FaceWear).ToString() : "No Human"); + ImGuiUtil.DrawTableColumn(model.IsHuman ? model.GetBonus(slot).ToString() : "No Human"); ImGui.TableNextColumn(); if (ImUtf8.SmallButton("Change Piece"u8)) { - var data = model.GetArmor(EquipSlot.FaceWear); - _updateSlotService.UpdateSlot(model, EquipSlot.FaceWear, data with { Variant = (Variant)((data.Variant.Id + 1) % 12) }); + var data = model.GetBonus(slot); + _updateSlotService.UpdateBonusSlot(model, slot, data with { Variant = (Variant)((data.Variant.Id + 1) % 12) }); } } } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 65a168e..2be31cf 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -3,6 +3,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using Glamourer.Events; using Penumbra.GameData; +using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; @@ -11,11 +12,16 @@ namespace Glamourer.Interop; public unsafe class UpdateSlotService : IDisposable { - public readonly SlotUpdating SlotUpdatingEvent; + public readonly EquipSlotUpdating EquipSlotUpdatingEvent; + public readonly BonusSlotUpdating BonusSlotUpdatingEvent; + private readonly DictGlasses _glasses; - public UpdateSlotService(SlotUpdating slotUpdating, IGameInteropProvider interop) + public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, IGameInteropProvider interop, + DictGlasses glasses) { - SlotUpdatingEvent = slotUpdating; + EquipSlotUpdatingEvent = equipSlotUpdating; + BonusSlotUpdatingEvent = bonusSlotUpdating; + _glasses = glasses; interop.InitializeFromAttributes(this); _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); @@ -27,20 +33,37 @@ public unsafe class UpdateSlotService : IDisposable _flagBonusSlotForUpdateHook.Dispose(); } - public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data) + public void UpdateEquipSlot(Model drawObject, EquipSlot slot, CharacterArmor data) { if (!drawObject.IsCharacterBase) return; - var bonusSlot = slot.ToBonusIndex(); - if (bonusSlot == uint.MaxValue) - FlagSlotForUpdateInterop(drawObject, slot, data); - else - _flagBonusSlotForUpdateHook.Original(drawObject.Address, bonusSlot, &data); + FlagSlotForUpdateInterop(drawObject, slot, data); + } + + public void UpdateBonusSlot(Model drawObject, BonusEquipFlag slot, CharacterArmor data) + { + if (!drawObject.IsCharacterBase) + return; + + var index = slot.ToIndex(); + if (index == uint.MaxValue) + return; + + _flagBonusSlotForUpdateHook.Original(drawObject.Address, index, &data); + } + + public void UpdateGlasses(Model drawObject, GlassesId id) + { + if (!_glasses.TryGetValue(id, out var glasses)) + return; + + var armor = new CharacterArmor(glasses.Id, glasses.Variant, StainIds.None); + _flagBonusSlotForUpdateHook.Original(drawObject.Address, BonusEquipFlag.Glasses.ToIndex(), &armor); } public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains) - => UpdateSlot(drawObject, slot, armor.With(stains)); + => UpdateEquipSlot(drawObject, slot, armor.With(stains)); public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor) => UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stains); @@ -60,7 +83,7 @@ public unsafe class UpdateSlotService : IDisposable { var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; - SlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); + EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } @@ -69,7 +92,7 @@ public unsafe class UpdateSlotService : IDisposable { var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; - SlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); + BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 48632ab..baae507 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -70,7 +70,7 @@ public static class StaticServiceManager private static ServiceManager AddEvents(this ServiceManager services) => services.AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index bf0fc30..5a6c21e 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -105,11 +105,11 @@ public class StateApplier( { var customize = mdl.GetCustomize(); var (_, resolvedItem) = _items.ResolveRestrictedGear(armor, slot, customize.Race, customize.Gender); - _updateSlot.UpdateSlot(actor.Model, slot, resolvedItem); + _updateSlot.UpdateEquipSlot(actor.Model, slot, resolvedItem); } else { - _updateSlot.UpdateSlot(actor.Model, slot, armor); + _updateSlot.UpdateEquipSlot(actor.Model, slot, armor); } } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 8b3b5e7..72b3122 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -32,7 +32,7 @@ public class StateListener : IDisposable private readonly ItemManager _items; private readonly CustomizeService _customizations; private readonly PenumbraService _penumbra; - private readonly SlotUpdating _slotUpdating; + private readonly EquipSlotUpdating _equipSlotUpdating; private readonly WeaponLoading _weaponLoading; private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly VisorStateChanged _visorState; @@ -52,7 +52,7 @@ public class StateListener : IDisposable private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty; public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, - SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, + EquipSlotUpdating equipSlotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, CrestService crestService) @@ -62,7 +62,7 @@ public class StateListener : IDisposable _penumbra = penumbra; _actors = actors; _config = config; - _slotUpdating = slotUpdating; + _equipSlotUpdating = equipSlotUpdating; _weaponLoading = weaponLoading; _visorState = visorState; _weaponVisibility = weaponVisibility; @@ -202,7 +202,7 @@ public class StateListener : IDisposable /// A draw model loads a new equipment piece. /// Update base data, apply or update model data, and protect against restricted gear. /// - private void OnSlotUpdating(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue) + private void OnEquipSlotUpdating(Model model, EquipSlot slot, ref CharacterArmor armor, ref ulong returnValue) { var actor = _penumbra.GameObjectFromDrawObject(model); if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) @@ -699,7 +699,7 @@ public class StateListener : IDisposable { _penumbra.CreatingCharacterBase += OnCreatingCharacterBase; _penumbra.CreatedCharacterBase += OnCreatedCharacterBase; - _slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener); + _equipSlotUpdating.Subscribe(OnEquipSlotUpdating, EquipSlotUpdating.Priority.StateListener); _movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener); _weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener); _visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener); @@ -715,7 +715,7 @@ public class StateListener : IDisposable { _penumbra.CreatingCharacterBase -= OnCreatingCharacterBase; _penumbra.CreatedCharacterBase -= OnCreatedCharacterBase; - _slotUpdating.Unsubscribe(OnSlotUpdating); + _equipSlotUpdating.Unsubscribe(OnEquipSlotUpdating); _movedEquipment.Unsubscribe(OnMovedEquipment); _weaponLoading.Unsubscribe(OnWeaponLoading); _visorState.Unsubscribe(OnVisorChange); diff --git a/Penumbra.GameData b/Penumbra.GameData index 8ec296d..d83303c 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 8ec296d1f8113ae2ba509527749cd3e8f54debbf +Subproject commit d83303ccc3ec5d7237f5da621e9c2433ad28f9e1 From 81059411e5e21df5ef17e21ed6a70ac591bde5bf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 15 Jul 2024 15:19:51 +0200 Subject: [PATCH 399/786] Current state. --- Glamourer.Api | 2 +- Glamourer/Api/ApiHelpers.cs | 13 +- Glamourer/Automation/ApplicationType.cs | 17 +- Glamourer/Automation/AutoDesign.cs | 2 +- Glamourer/Designs/ApplicationCollection.cs | 61 +++++++ Glamourer/Designs/ApplicationRules.cs | 52 ++---- Glamourer/Designs/DesignBase.cs | 171 +++++++++--------- Glamourer/Designs/DesignColors.cs | 2 +- Glamourer/Designs/DesignConverter.cs | 18 +- Glamourer/Designs/DesignData.cs | 95 +++++++--- Glamourer/Designs/DesignEditor.cs | 5 + Glamourer/Designs/DesignManager.cs | 12 ++ Glamourer/Designs/IDesignEditor.cs | 3 + Glamourer/Designs/Links/DesignMerger.cs | 41 +++-- Glamourer/Designs/Links/MergedDesign.cs | 8 +- Glamourer/Events/BonusSlotUpdating.cs | 2 +- Glamourer/Events/DesignChanged.cs | 3 + Glamourer/Gui/DesignQuickBar.cs | 3 +- Glamourer/Gui/Equipment/BonusDrawData.cs | 57 ++++++ Glamourer/Gui/Equipment/BonusItemCombo.cs | 123 +++++++++++++ Glamourer/Gui/Equipment/EquipDrawData.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 103 ++++++++++- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 3 + Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 6 + Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 6 +- .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 2 +- .../Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 8 +- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 20 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 23 ++- Glamourer/Gui/UiHelpers.cs | 67 ++++--- .../Interop/Material/MaterialValueIndex.cs | 6 + .../Interop/PalettePlus/PaletteImport.cs | 8 +- Glamourer/Interop/UpdateSlotService.cs | 16 +- Glamourer/Services/ItemManager.cs | 32 +++- Glamourer/State/InternalStateEditor.cs | 12 ++ Glamourer/State/StateApplier.cs | 27 +++ Glamourer/State/StateEditor.cs | 23 ++- Glamourer/State/StateIndex.cs | 28 +-- Glamourer/State/StateListener.cs | 68 ++++++- Glamourer/State/StateManager.cs | 32 ++++ Glamourer/Unlocks/FavoriteManager.cs | 49 +++-- Penumbra.GameData | 2 +- 42 files changed, 913 insertions(+), 320 deletions(-) create mode 100644 Glamourer/Designs/ApplicationCollection.cs create mode 100644 Glamourer/Gui/Equipment/BonusDrawData.cs create mode 100644 Glamourer/Gui/Equipment/BonusItemCombo.cs diff --git a/Glamourer.Api b/Glamourer.Api index 4aaece3..ca00339 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 4aaece34289d64363bc32aaa8fe52c8e7d3dce32 +Subproject commit ca003395306791b9e595683c47824b4718385311 diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index cf67912..a54a0ec 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -35,7 +35,8 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM state = null; return GlamourerApiEc.ActorNotFound; } - stateManager.TryGetValue(identifier, out state); + + stateManager.TryGetValue(identifier, out state); return GlamourerApiEc.Success; } @@ -54,12 +55,10 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM 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), + ApplyFlag.Equipment => design.TemporarilyRestrictApplication(ApplicationCollection.Equipment), + ApplyFlag.Customization => design.TemporarilyRestrictApplication(ApplicationCollection.Customizations), + ApplyFlag.Equipment | ApplyFlag.Customization => design.TemporarilyRestrictApplication(ApplicationCollection.All), + _ => design.TemporarilyRestrictApplication(ApplicationCollection.None), }; [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs index 12dac50..3d409cb 100644 --- a/Glamourer/Automation/ApplicationType.cs +++ b/Glamourer/Automation/ApplicationType.cs @@ -28,8 +28,7 @@ public static class ApplicationTypeExtensions (ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), ]; - public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat( - this ApplicationType type, IDesignStandIn designStandIn) + public static ApplicationCollection Collection(this ApplicationType type) { var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0) | (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0) @@ -37,18 +36,18 @@ public static class ApplicationTypeExtensions | (type.HasFlag(ApplicationType.GearCustomization) ? StainFlags : 0); var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0; var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0; - var crestFlag = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0; - var metaFlag = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0) + var crestFlags = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0; + var metaFlags = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0) | (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0) | (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0); + var bonusFlags = type.HasFlag(ApplicationType.Armor) ? BonusExtensions.All : 0; - if (designStandIn is not DesignBase design) - return (equipFlags, customizeFlags, crestFlag, parameterFlags, metaFlag); - - return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest, - parameterFlags & design.ApplyParameters, metaFlag & design.ApplyMeta); + return new ApplicationCollection(equipFlags, bonusFlags, customizeFlags, crestFlags, parameterFlags, metaFlags); } + public static ApplicationCollection ApplyWhat(this ApplicationType type, IDesignStandIn designStandIn) + => designStandIn is not DesignBase design ? type.Collection() : type.Collection().Restrict(design.Application); + public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand; public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet; public const EquipFlag AccessoryFlags = EquipFlag.Ears | EquipFlag.Neck | EquipFlag.Wrist | EquipFlag.RFinger | EquipFlag.LFinger; diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 9fc8ca7..e31fb16 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -61,6 +61,6 @@ public class AutoDesign return ret; } - public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat() + public ApplicationCollection ApplyWhat() => Type.ApplyWhat(Design); } diff --git a/Glamourer/Designs/ApplicationCollection.cs b/Glamourer/Designs/ApplicationCollection.cs new file mode 100644 index 0000000..b31ff2e --- /dev/null +++ b/Glamourer/Designs/ApplicationCollection.cs @@ -0,0 +1,61 @@ +using Glamourer.GameData; +using ImGuiNET; +using Penumbra.GameData.Enums; + +namespace Glamourer.Designs; + +public record struct ApplicationCollection( + EquipFlag Equip, + BonusItemFlag BonusItem, + CustomizeFlag Customize, + CrestFlag Crest, + CustomizeParameterFlag Parameters, + MetaFlag Meta) +{ + public static readonly ApplicationCollection All = new(EquipFlagExtensions.All, BonusExtensions.All, + CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All); + + public static readonly ApplicationCollection None = new(0, 0, 0, 0, 0, 0); + + public static readonly ApplicationCollection Equipment = new(EquipFlagExtensions.All, BonusExtensions.All, + 0, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState); + + public static readonly ApplicationCollection Customizations = new(0, 0, CustomizeFlagExtensions.AllRelevant, 0, + CustomizeParameterExtensions.All, MetaFlag.Wetness); + + public static readonly ApplicationCollection Default = new(EquipFlagExtensions.All, BonusExtensions.All, + CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState); + + public static ApplicationCollection FromKeys() + => (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch + { + (false, false) => All, + (true, true) => All, + (true, false) => Equipment, + (false, true) => Customizations, + }; + + public void RemoveEquip() + { + Equip = 0; + BonusItem = 0; + Crest = 0; + Meta &= ~(MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState); + } + + public void RemoveCustomize() + { + Customize = 0; + Parameters = 0; + Meta &= MetaFlag.Wetness; + } + + public ApplicationCollection Restrict(ApplicationCollection old) + => new(old.Equip & Equip, old.BonusItem & BonusItem, old.Customize & Customize, old.Crest & Crest, + old.Parameters & Parameters, old.Meta & Meta); + + public ApplicationCollection CloneSecure() + => new(Equip & EquipFlagExtensions.All, BonusItem & BonusExtensions.All, + (Customize & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType, Crest & CrestExtensions.AllRelevant, + Parameters & CustomizeParameterExtensions.All, Meta & MetaExtensions.All); +} diff --git a/Glamourer/Designs/ApplicationRules.cs b/Glamourer/Designs/ApplicationRules.cs index c15b26a..3c5fed2 100644 --- a/Glamourer/Designs/ApplicationRules.cs +++ b/Glamourer/Designs/ApplicationRules.cs @@ -5,16 +5,9 @@ using Penumbra.GameData.Enums; namespace Glamourer.Designs; -public readonly struct ApplicationRules( - EquipFlag equip, - CustomizeFlag customize, - CrestFlag crest, - CustomizeParameterFlag parameters, - MetaFlag meta, - bool materials) +public readonly struct ApplicationRules(ApplicationCollection application, bool materials) { - public static readonly ApplicationRules All = new(EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, - CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All, true); + public static readonly ApplicationRules All = new(ApplicationCollection.All, true); public static ApplicationRules FromModifiers(ActorState state) => FromModifiers(state, ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift); @@ -23,54 +16,43 @@ public readonly struct ApplicationRules( => NpcFromModifiers(ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift); public static ApplicationRules AllButParameters(ActorState state) - => new(All.Equip, All.Customize, All.Crest, ComputeParameters(state.ModelData, state.BaseData, All.Parameters), All.Meta, true); + => new(ApplicationCollection.All with { Parameters = ComputeParameters(state.ModelData, state.BaseData, All.Parameters) }, true); public static ApplicationRules AllWithConfig(Configuration config) - => new(All.Equip, All.Customize, All.Crest, config.UseAdvancedParameters ? All.Parameters : 0, All.Meta, config.UseAdvancedDyes); + => new(ApplicationCollection.All with { Parameters = config.UseAdvancedParameters ? All.Parameters : 0 }, config.UseAdvancedDyes); public static ApplicationRules NpcFromModifiers(bool ctrl, bool shift) - => new(ctrl || !shift ? EquipFlagExtensions.All : 0, - !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0, - 0, - 0, - ctrl || !shift ? MetaFlag.VisorState : 0, false); + { + var equip = ctrl || !shift ? EquipFlagExtensions.All : 0; + var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0; + var visor = equip != 0 ? MetaFlag.VisorState : 0; + return new ApplicationRules(new ApplicationCollection(equip, 0, customize, 0, 0, visor), false); + } public static ApplicationRules FromModifiers(ActorState state, bool ctrl, bool shift) { var equip = ctrl || !shift ? EquipFlagExtensions.All : 0; var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0; + var bonus = equip == 0 ? 0 : BonusExtensions.All; var crest = equip == 0 ? 0 : CrestExtensions.AllRelevant; var parameters = customize == 0 ? 0 : CustomizeParameterExtensions.All; var meta = state.ModelData.IsWet() ? MetaFlag.Wetness : 0; if (equip != 0) meta |= MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState; - return new ApplicationRules(equip, customize, crest, ComputeParameters(state.ModelData, state.BaseData, parameters), meta, equip != 0); + var collection = new ApplicationCollection(equip, bonus, customize, crest, + ComputeParameters(state.ModelData, state.BaseData, parameters), meta); + return new ApplicationRules(collection, equip != 0); } public void Apply(DesignBase design) - { - design.ApplyEquip = Equip; - design.ApplyCustomize = Customize; - design.ApplyCrest = Crest; - design.ApplyParameters = Parameters; - design.ApplyMeta = Meta; - } + => design.Application = application; public EquipFlag Equip - => equip & EquipFlagExtensions.All; - - public CustomizeFlag Customize - => customize & CustomizeFlagExtensions.AllRelevant; - - public CrestFlag Crest - => crest & CrestExtensions.AllRelevant; + => application.Equip & EquipFlagExtensions.All; public CustomizeParameterFlag Parameters - => parameters & CustomizeParameterExtensions.All; - - public MetaFlag Meta - => meta & MetaExtensions.All; + => application.Parameters & CustomizeParameterExtensions.All; public bool Materials => materials; diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 3791442..be12737 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -42,23 +42,19 @@ public class DesignBase /// Used when importing .cma or .chara files. internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) { - _designData = designData; - ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; - ApplyEquip = equipFlags & EquipFlagExtensions.All; - ApplyMeta = 0; - CustomizeSet = SetCustomizationSet(customize); + _designData = designData; + ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; + Application.Equip = equipFlags & EquipFlagExtensions.All; + Application.Meta = 0; + CustomizeSet = SetCustomizationSet(customize); } internal DesignBase(DesignBase clone) { - _designData = clone._designData; - _materials = clone._materials.Clone(); - CustomizeSet = clone.CustomizeSet; - ApplyCustomize = clone.ApplyCustomizeRaw; - ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; - ApplyParameters = clone.ApplyParameters & CustomizeParameterExtensions.All; - ApplyCrest = clone.ApplyCrest & CrestExtensions.All; - ApplyMeta = clone.ApplyMeta & MetaExtensions.All; + _designData = clone._designData; + _materials = clone._materials.Clone(); + CustomizeSet = clone.CustomizeSet; + Application = clone.Application.CloneSecure(); } /// Ensure that the customization set is updated when the design data changes. @@ -70,27 +66,20 @@ public class DesignBase #region Application Data - private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; - public CustomizeSet CustomizeSet { get; private set; } + public CustomizeSet CustomizeSet { get; private set; } - public CustomizeParameterFlag ApplyParameters { get; internal set; } + public ApplicationCollection Application = ApplicationCollection.Default; internal CustomizeFlag ApplyCustomize { - get => _applyCustomize.FixApplication(CustomizeSet); - set => _applyCustomize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType; + get => Application.Customize.FixApplication(CustomizeSet); + set => Application.Customize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType; } internal CustomizeFlag ApplyCustomizeExcludingBodyType - => _applyCustomize.FixApplication(CustomizeSet) & ~CustomizeFlag.BodyType; + => Application.Customize.FixApplication(CustomizeSet) & ~CustomizeFlag.BodyType; - internal CustomizeFlag ApplyCustomizeRaw - => _applyCustomize; - - internal EquipFlag ApplyEquip = EquipFlagExtensions.All; - internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; - internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState; - private bool _writeProtected; + private bool _writeProtected; public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize) { @@ -103,18 +92,18 @@ public class DesignBase } public bool DoApplyMeta(MetaIndex index) - => ApplyMeta.HasFlag(index.ToFlag()); + => Application.Meta.HasFlag(index.ToFlag()); public bool WriteProtected() => _writeProtected; public bool SetApplyMeta(MetaIndex index, bool value) { - var newFlag = value ? ApplyMeta | index.ToFlag() : ApplyMeta & ~index.ToFlag(); - if (newFlag == ApplyMeta) + var newFlag = value ? Application.Meta | index.ToFlag() : Application.Meta & ~index.ToFlag(); + if (newFlag == Application.Meta) return false; - ApplyMeta = newFlag; + Application.Meta = newFlag; return true; } @@ -128,103 +117,100 @@ public class DesignBase } public bool DoApplyEquip(EquipSlot slot) - => ApplyEquip.HasFlag(slot.ToFlag()); + => Application.Equip.HasFlag(slot.ToFlag()); public bool DoApplyStain(EquipSlot slot) - => ApplyEquip.HasFlag(slot.ToStainFlag()); + => Application.Equip.HasFlag(slot.ToStainFlag()); public bool DoApplyCustomize(CustomizeIndex idx) - => ApplyCustomize.HasFlag(idx.ToFlag()); + => Application.Customize.HasFlag(idx.ToFlag()); public bool DoApplyCrest(CrestFlag slot) - => ApplyCrest.HasFlag(slot); + => Application.Crest.HasFlag(slot); public bool DoApplyParameter(CustomizeParameterFlag flag) - => ApplyParameters.HasFlag(flag); + => Application.Parameters.HasFlag(flag); + + public bool DoApplyBonusItem(BonusItemFlag slot) + => Application.BonusItem.HasFlag(slot); internal bool SetApplyEquip(EquipSlot slot, bool value) { - var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag(); - if (newValue == ApplyEquip) + var newValue = value ? Application.Equip | slot.ToFlag() : Application.Equip & ~slot.ToFlag(); + if (newValue == Application.Equip) return false; - ApplyEquip = newValue; + Application.Equip = newValue; + return true; + } + + internal bool SetApplyBonusItem(BonusItemFlag slot, bool value) + { + var newValue = value ? Application.BonusItem | slot : Application.BonusItem & ~slot; + if (newValue == Application.BonusItem) + return false; + + Application.BonusItem = newValue; return true; } internal bool SetApplyStain(EquipSlot slot, bool value) { - var newValue = value ? ApplyEquip | slot.ToStainFlag() : ApplyEquip & ~slot.ToStainFlag(); - if (newValue == ApplyEquip) + var newValue = value ? Application.Equip | slot.ToStainFlag() : Application.Equip & ~slot.ToStainFlag(); + if (newValue == Application.Equip) return false; - ApplyEquip = newValue; + Application.Equip = newValue; return true; } internal bool SetApplyCustomize(CustomizeIndex idx, bool value) { - var newValue = value ? _applyCustomize | idx.ToFlag() : _applyCustomize & ~idx.ToFlag(); - if (newValue == _applyCustomize) + var newValue = value ? Application.Customize | idx.ToFlag() : Application.Customize & ~idx.ToFlag(); + if (newValue == Application.Customize) return false; - _applyCustomize = newValue; + Application.Customize = newValue; return true; } internal bool SetApplyCrest(CrestFlag slot, bool value) { - var newValue = value ? ApplyCrest | slot : ApplyCrest & ~slot; - if (newValue == ApplyCrest) + var newValue = value ? Application.Crest | slot : Application.Crest & ~slot; + if (newValue == Application.Crest) return false; - ApplyCrest = newValue; + Application.Crest = newValue; return true; } internal bool SetApplyParameter(CustomizeParameterFlag flag, bool value) { - var newValue = value ? ApplyParameters | flag : ApplyParameters & ~flag; - if (newValue == ApplyParameters) + var newValue = value ? Application.Parameters | flag : Application.Parameters & ~flag; + if (newValue == Application.Parameters) return false; - ApplyParameters = newValue; + Application.Parameters = newValue; return true; } - internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, - CustomizeParameterFlag parameterFlags) - => new(this, equipFlags, customizeFlags, crestFlags, parameterFlags); + internal FlagRestrictionResetter TemporarilyRestrictApplication(ApplicationCollection restrictions) + => new(this, restrictions); internal readonly struct FlagRestrictionResetter : IDisposable { - private readonly DesignBase _design; - private readonly EquipFlag _oldEquipFlags; - private readonly CustomizeFlag _oldCustomizeFlags; - private readonly CrestFlag _oldCrestFlags; - private readonly CustomizeParameterFlag _oldParameterFlags; + private readonly DesignBase _design; + private readonly ApplicationCollection _oldFlags; - public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, - CustomizeParameterFlag parameterFlags) + public FlagRestrictionResetter(DesignBase d, ApplicationCollection restrictions) { - _design = d; - _oldEquipFlags = d.ApplyEquip; - _oldCustomizeFlags = d.ApplyCustomizeRaw; - _oldCrestFlags = d.ApplyCrest; - _oldParameterFlags = d.ApplyParameters; - d.ApplyEquip &= equipFlags; - d.ApplyCustomize &= customizeFlags; - d.ApplyCrest &= crestFlags; - d.ApplyParameters &= parameterFlags; + _design = d; + _oldFlags = d.Application; + _design.Application = restrictions.Restrict(_oldFlags); } public void Dispose() - { - _design.ApplyEquip = _oldEquipFlags; - _design.ApplyCustomize = _oldCustomizeFlags; - _design.ApplyCrest = _oldCrestFlags; - _design.ApplyParameters = _oldParameterFlags; - } + => _design.Application = _oldFlags; } private CustomizeSet SetCustomizationSet(CustomizeService customize) @@ -285,6 +271,22 @@ public class DesignBase }); } + protected JObject SerializeBonusItems() + { + var ret = new JObject(); + foreach (var slot in BonusExtensions.AllFlags) + { + var item = _designData.BonusItem(slot); + ret[slot.ToString()] = new JObject() + { + ["BonusId"] = item.ModelId.Id, + ["Apply"] = DoApplyBonusItem(slot), + }; + } + + return ret; + } + protected JObject SerializeCustomize() { var ret = new JObject() @@ -299,7 +301,7 @@ public class DesignBase ret[idx.ToString()] = new JObject() { ["Value"] = customize[idx].Value, - ["Apply"] = ApplyCustomizeRaw.HasFlag(idx.ToFlag()), + ["Apply"] = Application.Customize.HasFlag(idx.ToFlag()), }; } else @@ -382,7 +384,7 @@ public class DesignBase { var k = uint.Parse(key.Name, NumberStyles.HexNumber); var v = value.ToObject(); - if (!MaterialValueIndex.FromKey(k, out var idx)) + if (!MaterialValueIndex.FromKey(k, out _)) { Glamourer.Messager.NotificationMessage($"Invalid material value key {k} for design {name}, skipped.", NotificationType.Warning); @@ -429,7 +431,7 @@ public class DesignBase { if (parameters == null) { - design.ApplyParameters = 0; + design.Application.Parameters = 0; design.GetDesignDataRef().Parameters = default; return; } @@ -490,7 +492,7 @@ public class DesignBase return true; } - design.ApplyParameters &= ~flag; + design.Application.Parameters &= ~flag; design.GetDesignDataRef().Parameters[flag] = CustomizeParameterValue.Zero; return false; } @@ -669,11 +671,12 @@ public class DesignBase { _designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags, out var writeProtected, out var applyMeta); - ApplyEquip = equipFlags; - ApplyCustomize = customizeFlags; - ApplyParameters = 0; - ApplyCrest = 0; - ApplyMeta = applyMeta; + Application.Equip = equipFlags; + ApplyCustomize = customizeFlags; + Application.Parameters = 0; + Application.Crest = 0; + Application.Meta = applyMeta; + Application.BonusItem = 0; SetWriteProtected(writeProtected); CustomizeSet = SetCustomizationSet(customize); } diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index 5577c2c..96592bf 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -270,7 +270,7 @@ public class DesignColors : ISavable, IReadOnlyDictionary public static uint AutoColor(DesignBase design) { var customize = design.ApplyCustomizeExcludingBodyType == 0; - var equip = design.ApplyEquip == 0; + var equip = design.Application.Equip == 0; return (customize, equip) switch { (true, true) => ColorId.StateDesign.Value(), diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index b1b5c61..21172fe 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -72,16 +72,11 @@ public class DesignConverter( ? Design.LoadDesign(_customize, _items, _linkLoader, jObject) : DesignBase.LoadDesignBase(_customize, _items, jObject); - ret.SetApplyMeta(MetaIndex.Wetness, customize); if (!customize) - ret.ApplyCustomize = 0; + ret.Application.RemoveCustomize(); if (!equip) - { - ret.ApplyEquip = 0; - ret.ApplyCrest = 0; - ret.ApplyMeta &= ~(MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState); - } + ret.Application.RemoveEquip(); return ret; } @@ -155,16 +150,11 @@ public class DesignConverter( return null; } - ret.SetApplyMeta(MetaIndex.Wetness, customize); if (!customize) - ret.ApplyCustomize = 0; + ret.Application.RemoveCustomize(); if (!equip) - { - ret.ApplyEquip = 0; - ret.ApplyCrest = 0; - ret.ApplyMeta &= ~(MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState); - } + ret.Application.RemoveEquip(); return ret; } diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index a762a84..7fb2f72 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -9,24 +9,31 @@ namespace Glamourer.Designs; public unsafe struct DesignData { - public const int EquipmentByteSize = 10 * CharacterArmor.Size; + public const int NumEquipment = 10; + public const int EquipmentByteSize = NumEquipment * CharacterArmor.Size; + public const int NumBonusItems = 1; + public const int NumWeapons = 2; - private string _nameHead = string.Empty; - private string _nameBody = string.Empty; - private string _nameHands = string.Empty; - private string _nameLegs = string.Empty; - private string _nameFeet = string.Empty; - private string _nameEars = string.Empty; - private string _nameNeck = string.Empty; - private string _nameWrists = string.Empty; - private string _nameRFinger = string.Empty; - private string _nameLFinger = string.Empty; - private string _nameMainhand = string.Empty; - private string _nameOffhand = string.Empty; - private string _nameFaceWear = string.Empty; - private fixed uint _itemIds[12]; - private fixed uint _iconIds[12]; - private fixed byte _equipmentBytes[EquipmentByteSize + 16]; + private string _nameHead = string.Empty; + private string _nameBody = string.Empty; + private string _nameHands = string.Empty; + private string _nameLegs = string.Empty; + private string _nameFeet = string.Empty; + private string _nameEars = string.Empty; + private string _nameNeck = string.Empty; + private string _nameWrists = string.Empty; + private string _nameRFinger = string.Empty; + private string _nameLFinger = string.Empty; + private string _nameMainhand = string.Empty; + private string _nameOffhand = string.Empty; + private string _nameGlasses = string.Empty; + + private fixed uint _itemIds[NumEquipment + NumWeapons]; + private fixed uint _iconIds[NumEquipment + NumWeapons + NumBonusItems]; + private fixed byte _equipmentBytes[EquipmentByteSize + NumWeapons * CharacterWeapon.Size]; + private fixed ushort _bonusIds[NumBonusItems]; + private fixed ushort _bonusModelIds[NumBonusItems]; + private fixed byte _bonusVariants[NumBonusItems]; public CustomizeParameterData Parameters; public CustomizeArray Customize = CustomizeArray.Default; public uint ModelId; @@ -52,7 +59,7 @@ public unsafe struct DesignData || name.IsContained(_nameLFinger) || name.IsContained(_nameMainhand) || name.IsContained(_nameOffhand) - || name.IsContained(_nameFaceWear); + || name.IsContained(_nameGlasses); public readonly StainIds Stain(EquipSlot slot) { @@ -101,6 +108,15 @@ public unsafe struct DesignData } } + public readonly BonusItem BonusItem(BonusItemFlag slot) + => slot switch + { + // @formatter:off + BonusItemFlag.Glasses => new BonusItem(_nameGlasses, _iconIds[12], _bonusIds[0], _bonusModelIds[0], _bonusVariants[0], BonusItemFlag.Glasses), + _ => Penumbra.GameData.Structs.BonusItem.Empty(slot), + // @formatter:on + }; + public readonly CharacterArmor Armor(EquipSlot slot) { fixed (byte* ptr = _equipmentBytes) @@ -134,7 +150,7 @@ public unsafe struct DesignData public bool SetItem(EquipSlot slot, EquipItem item) { var index = slot.ToIndex(); - if (index > 11) + if (index > NumEquipment + NumWeapons) return false; _itemIds[index] = item.ItemId.Id; @@ -173,6 +189,25 @@ public unsafe struct DesignData return true; } + public bool SetBonusItem(BonusItemFlag slot, BonusItem item) + { + var index = slot.ToIndex(); + if (index > NumBonusItems) + return false; + + _iconIds[NumEquipment + NumWeapons + index] = item.Icon.Id; + _bonusIds[index] = item.Id.Id; + _bonusModelIds[index] = item.ModelId.Id; + _bonusVariants[index] = item.Variant.Id; + switch (index) + { + case 0: + _nameGlasses = item.Name; + return true; + default: return false; + } + } + public bool SetStain(EquipSlot slot, StainIds stains) => slot.ToIndex() switch { @@ -313,17 +348,17 @@ public unsafe struct DesignData MemoryUtility.MemSet(ptr, 0, 10 * 2); } - _nameHead = string.Empty; - _nameBody = string.Empty; - _nameHands = string.Empty; - _nameLegs = string.Empty; - _nameFeet = string.Empty; - _nameEars = string.Empty; - _nameNeck = string.Empty; - _nameWrists = string.Empty; - _nameRFinger = string.Empty; - _nameLFinger = string.Empty; - _nameFaceWear = string.Empty; + _nameHead = string.Empty; + _nameBody = string.Empty; + _nameHands = string.Empty; + _nameLegs = string.Empty; + _nameFeet = string.Empty; + _nameEars = string.Empty; + _nameNeck = string.Empty; + _nameWrists = string.Empty; + _nameRFinger = string.Empty; + _nameLFinger = string.Empty; + _nameGlasses = string.Empty; return true; } diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index 32e38ac..08cb241 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -165,6 +165,11 @@ public class DesignEditor( } } + public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default) + { + + } + /// public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings _ = default) { diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 7bd949c..725d562 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -347,6 +347,18 @@ public sealed class DesignManager : DesignEditor DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, slot); } + /// Change whether to apply a specific equipment piece. + public void ChangeApplyBonusItem(Design design, BonusItemFlag slot, bool value) + { + if (!design.SetApplyBonusItem(slot, value)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set applying of {slot} bonus item to {value}."); + DesignChanged.Invoke(DesignChanged.Type.ApplyBonus, design, slot); + } + /// Change whether to apply a specific stain. public void ChangeApplyStain(Design design, EquipSlot slot, bool value) { diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index cd51cf2..9eefa63 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -64,6 +64,9 @@ public interface IDesignEditor public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default) => ChangeEquip(data, slot, item, null, settings); + /// Change a bonus item. + public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default); + /// Change the stain for any equipment piece. public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings = default) => ChangeEquip(data, slot, null, stains, settings); diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 558377a..9832ead 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -42,14 +42,15 @@ public class DesignMerger( if (!data.IsHuman) continue; - var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyMeta) = type.ApplyWhat(design); - ReduceMeta(data, applyMeta, ret, source); - ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source, respectOwnership, startBodyType); - ReduceEquip(data, equipFlags, ret, source, respectOwnership); - ReduceMainhands(data, jobs, equipFlags, ret, source, respectOwnership); - ReduceOffhands(data, jobs, equipFlags, ret, source, respectOwnership); - ReduceCrests(data, crestFlags, ret, source); - ReduceParameters(data, parameterFlags, ret, source); + var collection = type.ApplyWhat(design); + ReduceMeta(data, collection.Meta, ret, source); + ReduceCustomize(data, collection.Customize, ref fixFlags, ret, source, respectOwnership, startBodyType); + ReduceEquip(data, collection.Equip, ret, source, respectOwnership); + ReduceBonusItems(data, collection.BonusItem, ret, source, respectOwnership); + ReduceMainhands(data, jobs, collection.Equip, ret, source, respectOwnership); + ReduceOffhands(data, jobs, collection.Equip, ret, source, respectOwnership); + ReduceCrests(data, collection.Crest, ret, source); + ReduceParameters(data, collection.Parameters, ret, source); ReduceMods(design as Design, ret, modAssociations); if (type.HasFlag(ApplicationType.GearCustomization)) ReduceMaterials(design, ret); @@ -83,7 +84,7 @@ public class DesignMerger( private static void ReduceMeta(in DesignData design, MetaFlag applyMeta, MergedDesign ret, StateSource source) { - applyMeta &= ~ret.Design.ApplyMeta; + applyMeta &= ~ret.Design.Application.Meta; if (applyMeta == 0) return; @@ -100,7 +101,7 @@ public class DesignMerger( private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateSource source) { - crestFlags &= ~ret.Design.ApplyCrest; + crestFlags &= ~ret.Design.Application.Crest; if (crestFlags == 0) return; @@ -118,7 +119,7 @@ public class DesignMerger( private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret, StateSource source) { - parameterFlags &= ~ret.Design.ApplyParameters; + parameterFlags &= ~ret.Design.Application.Parameters; if (parameterFlags == 0) return; @@ -136,7 +137,7 @@ public class DesignMerger( private void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source, bool respectOwnership) { - equipFlags &= ~ret.Design.ApplyEquip; + equipFlags &= ~ret.Design.Application.Equip; if (equipFlags == 0) return; @@ -174,6 +175,22 @@ public class DesignMerger( } } + private void ReduceBonusItems(in DesignData design, BonusItemFlag bonusItems, MergedDesign ret, StateSource source, bool respectOwnership) + { + bonusItems &= ~ret.Design.Application.BonusItem; + if (bonusItems == 0) + return; + + foreach (var slot in BonusExtensions.AllFlags.Where(b => bonusItems.HasFlag(b))) + { + var item = design.BonusItem(slot); + if (!respectOwnership || true) // TODO: maybe check unlocks + ret.Design.GetDesignDataRef().SetBonusItem(slot, item); + ret.Design.SetApplyBonusItem(slot, true); + ret.Sources[slot] = source; + } + } + private void ReduceMainhands(in DesignData design, JobFlag allowedJobs, EquipFlag equipFlags, MergedDesign ret, StateSource source, bool respectOwnership) { diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index d3c7664..da6cb54 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -64,12 +64,8 @@ public sealed class MergedDesign { public MergedDesign(DesignManager designManager) { - Design = designManager.CreateTemporary(); - Design.ApplyEquip = 0; - Design.ApplyCustomize = 0; - Design.ApplyCrest = 0; - Design.ApplyParameters = 0; - Design.ApplyMeta = 0; + Design = designManager.CreateTemporary(); + Design.Application = ApplicationCollection.None; } public MergedDesign(DesignBase design) diff --git a/Glamourer/Events/BonusSlotUpdating.cs b/Glamourer/Events/BonusSlotUpdating.cs index 3685f3c..3f6e761 100644 --- a/Glamourer/Events/BonusSlotUpdating.cs +++ b/Glamourer/Events/BonusSlotUpdating.cs @@ -15,7 +15,7 @@ namespace Glamourer.Events; /// /// public sealed class BonusSlotUpdating() - : EventWrapperRef34(nameof(BonusSlotUpdating)) + : EventWrapperRef34(nameof(BonusSlotUpdating)) { public enum Priority { diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 1837aad..1588a96 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -89,6 +89,9 @@ public sealed class DesignChanged() /// An existing design changed whether a specific equipment piece is applied. Data is the slot of the equipment [EquipSlot]. ApplyEquip, + /// An existing design changed whether a specific bonus item is applied. Data is the slot of the item [BonusItemFlag]. + ApplyBonus, + /// An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. ApplyStain, diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index a0a341a..c3829fa 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -179,8 +179,7 @@ public sealed class DesignQuickBar : Window, IDisposable return; } - var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); - using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); + using var _ = design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); } diff --git a/Glamourer/Gui/Equipment/BonusDrawData.cs b/Glamourer/Gui/Equipment/BonusDrawData.cs new file mode 100644 index 0000000..d2a6b2e --- /dev/null +++ b/Glamourer/Gui/Equipment/BonusDrawData.cs @@ -0,0 +1,57 @@ +using Glamourer.Designs; +using Glamourer.State; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Equipment; + +public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) +{ + private IDesignEditor _editor; + private object _object; + public readonly BonusItemFlag Slot = slot; + public bool Locked; + public bool DisplayApplication; + public bool AllowRevert; + + public readonly bool IsDesign + => _object is Design; + + public readonly bool IsState + => _object is ActorState; + + public readonly void SetItem(BonusItem item) + => _editor.ChangeBonusItem(_object, Slot, item, ApplySettings.Manual); + + public readonly void SetApplyItem(bool value) + { + var manager = (DesignManager)_editor; + var design = (Design)_object; + manager.ChangeApplyBonusItem(design, Slot, value); + } + + public BonusItem CurrentItem = designData.BonusItem(slot); + public BonusItem GameItem = default; + public bool CurrentApply; + + public static BonusDrawData FromDesign(DesignManager manager, Design design, BonusItemFlag slot) + => new(slot, design.DesignData) + { + _editor = manager, + _object = design, + CurrentApply = design.DoApplyBonusItem(slot), + Locked = design.WriteProtected(), + DisplayApplication = true, + }; + + public static BonusDrawData FromState(StateManager manager, ActorState state, BonusItemFlag slot) + => new(slot, state.ModelData) + { + _editor = manager, + _object = state, + Locked = state.IsLocked, + DisplayApplication = false, + GameItem = state.BaseData.BonusItem(slot), + AllowRevert = true, + }; +} diff --git a/Glamourer/Gui/Equipment/BonusItemCombo.cs b/Glamourer/Gui/Equipment/BonusItemCombo.cs new file mode 100644 index 0000000..ae8bf19 --- /dev/null +++ b/Glamourer/Gui/Equipment/BonusItemCombo.cs @@ -0,0 +1,123 @@ +using Dalamud.Plugin.Services; +using Glamourer.Services; +using Glamourer.Unlocks; +using ImGuiNET; +using Lumina.Excel.GeneratedSheets; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Log; +using OtterGui.Raii; +using OtterGui.Widgets; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Equipment; + +public sealed class BonusItemCombo : FilterComboCache +{ + private readonly FavoriteManager _favorites; + public readonly string Label; + private BonusItemId _currentItem; + private float _innerWidth; + + public PrimaryId CustomSetId { get; private set; } + public Variant CustomVariant { get; private set; } + + public BonusItemCombo(IDataManager gameData, ItemManager items, BonusItemFlag slot, Logger log, FavoriteManager favorites) + : base(() => GetItems(favorites, items, slot), MouseWheelType.Control, log) + { + _favorites = favorites; + Label = GetLabel(gameData, slot); + _currentItem = 0; + SearchByParts = true; + } + + protected override void DrawList(float width, float itemHeight) + { + base.DrawList(width, itemHeight); + if (NewSelection != null && Items.Count > NewSelection.Value) + CurrentSelection = Items[NewSelection.Value]; + } + + protected override int UpdateCurrentSelected(int currentSelected) + { + if (CurrentSelection.Id == _currentItem) + return currentSelected; + + CurrentSelectionIdx = Items.IndexOf(i => i.Id == _currentItem); + CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default; + return base.UpdateCurrentSelected(CurrentSelectionIdx); + } + + public bool Draw(string previewName, BonusItemId previewIdx, float width, float innerWidth) + { + _innerWidth = innerWidth; + _currentItem = previewIdx; + CustomVariant = 0; + return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); + } + + protected override float GetFilterWidth() + => _innerWidth - 2 * ImGui.GetStyle().FramePadding.X; + + protected override bool DrawSelectable(int globalIdx, bool selected) + { + var obj = Items[globalIdx]; + var name = ToString(obj); + if (UiHelpers.DrawFavoriteStar(_favorites, obj) && CurrentSelectionIdx == globalIdx) + { + CurrentSelectionIdx = -1; + _currentItem = obj.Id; + CurrentSelection = default; + } + + ImGui.SameLine(); + var ret = ImGui.Selectable(name, selected); + ImGui.SameLine(); + using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080); + ImGuiUtil.RightAlign($"({obj.ModelId.Id}-{obj.Variant.Id})"); + return ret; + } + + protected override bool IsVisible(int globalIndex, LowerString filter) + => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Id.ToString()); + + protected override string ToString(BonusItem obj) + => obj.Name; + + private static string GetLabel(IDataManager gameData, BonusItemFlag slot) + { + var sheet = gameData.GetExcelSheet()!; + + return slot switch + { + BonusItemFlag.Glasses => sheet.GetRow(16050)?.Text.ToString() ?? "Facewear", + BonusItemFlag.UnkSlot => sheet.GetRow(16051)?.Text.ToString() ?? "Facewear", + + _ => string.Empty, + }; + } + + private static List GetItems(FavoriteManager favorites, ItemManager items, BonusItemFlag slot) + { + var nothing = BonusItem.Empty(slot); + if (slot is not BonusItemFlag.Glasses) + return [nothing]; + + return items.DictBonusItems.Values.OrderByDescending(favorites.Contains).ThenBy(i => i.Id.Id).Prepend(nothing).ToList(); + } + + protected override void OnClosePopup() + { + // If holding control while the popup closes, try to parse the input as a full pair of set id and variant, and set a custom item for that. + if (!ImGui.GetIO().KeyCtrl) + return; + + var split = Filter.Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (split.Length != 2 || !ushort.TryParse(split[0], out var setId) || !byte.TryParse(split[1], out var variant)) + return; + + CustomSetId = setId; + CustomVariant = variant; + } +} diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 58f7efc..8058169 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -72,4 +72,4 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) GameStains = state.BaseData.Stain(slot), AllowRevert = true, }; -} +} \ No newline at end of file diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 53e8ed8..afd3fe5 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -24,6 +24,7 @@ public class EquipmentDrawer private readonly GlamourerColorCombo _stainCombo; private readonly DictStain _stainData; private readonly ItemCombo[] _itemCombo; + private readonly BonusItemCombo[] _bonusItemCombo; private readonly Dictionary _weaponCombo; private readonly CodeService _codes; private readonly TextureService _textures; @@ -37,16 +38,17 @@ public class EquipmentDrawer public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures, Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes) { - _items = items; - _codes = codes; - _textures = textures; - _config = config; - _gPose = gPose; - _advancedDyes = advancedDyes; - _stainData = items.Stains; - _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); - _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); - _weaponCombo = new Dictionary(FullEquipTypeExtensions.WeaponTypes.Count * 2); + _items = items; + _codes = codes; + _textures = textures; + _config = config; + _gPose = gPose; + _advancedDyes = advancedDyes; + _stainData = items.Stains; + _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); + _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); + _bonusItemCombo = BonusExtensions.AllFlags.Select(f => new BonusItemCombo(gameData, items, f, Glamourer.Log, favorites)).ToArray(); + _weaponCombo = new Dictionary(FullEquipTypeExtensions.WeaponTypes.Count * 2); foreach (var type in Enum.GetValues()) { if (type.ToSlot() is EquipSlot.MainHand) @@ -100,6 +102,21 @@ public class EquipmentDrawer DrawEquipNormal(equipDrawData); } + public void DrawBonusItem(BonusDrawData bonusDrawData) + { + if (_config.HideApplyCheckmarks) + bonusDrawData.DisplayApplication = false; + + using var id = ImRaii.PushId(100 + (int)bonusDrawData.Slot); + var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); + + if (_config.SmallEquip) + DrawBonusItemSmall(bonusDrawData); + else + DrawBonusItemNormal(bonusDrawData); + } + public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) { if (mainhand.CurrentItem.PrimaryId.Id == 0) @@ -302,6 +319,25 @@ public class EquipmentDrawer ImGui.TextUnformatted(label); } + private void DrawBonusItemSmall(in BonusDrawData bonusDrawData) + { + ImGui.Dummy(new Vector2(StainId.NumStains * ImUtf8.FrameHeight + (StainId.NumStains - 1) * ImUtf8.ItemSpacing.X, ImUtf8.FrameHeight)); + ImGui.SameLine(); + DrawBonusItem(bonusDrawData, out var label, true, false, false); + if (bonusDrawData.DisplayApplication) + { + ImGui.SameLine(); + DrawApply(bonusDrawData); + } + else if (bonusDrawData.IsState) + { + _advancedDyes.DrawButton(bonusDrawData.Slot); + } + + ImGui.SameLine(); + ImGui.TextUnformatted(label); + } + private void DrawWeaponsSmall(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) { DrawStain(mainhand, true); @@ -382,6 +418,27 @@ public class EquipmentDrawer } } + private void DrawBonusItemNormal(in BonusDrawData bonusDrawData) + { + ImGui.Dummy(_iconSize with { Y = ImUtf8.FrameHeight }); + var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); + var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); + ImGui.SameLine(); + DrawBonusItem(bonusDrawData, out var label, false, right, left); + if (bonusDrawData.DisplayApplication) + { + ImGui.SameLine(); + DrawApply(bonusDrawData); + } + else if (bonusDrawData.IsState) + { + _advancedDyes.DrawButton(bonusDrawData.Slot); + } + + ImGui.SameLine(); + ImGui.TextUnformatted(label); + } + private void DrawWeaponsNormal(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) { using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, @@ -491,6 +548,25 @@ public class EquipmentDrawer data.SetItem(item); } + private void DrawBonusItem(in BonusDrawData data, out string label, bool small, bool clear, bool open) + { + var combo = _bonusItemCombo[data.Slot.ToIndex()]; + label = combo.Label; + if (!data.Locked && open) + UiHelpers.OpenCombo($"##{combo.Label}"); + + using var disabled = ImRaii.Disabled(data.Locked); + var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, + _requiredComboWidth); + if (change) + data.SetItem(combo.CurrentSelection); + else if (combo.CustomVariant.Id > 0) + data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); + + if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, BonusItem.Empty(data.Slot), out var item)) + data.SetItem(item); + } + private static bool ResetOrClear(bool locked, bool clicked, bool allowRevert, bool allowClear, in T currentItem, in T revertItem, in T clearItem, out T? item) where T : IEquatable { @@ -590,6 +666,13 @@ public class EquipmentDrawer data.SetApplyItem(enabled); } + private static void DrawApply(in BonusDrawData data) + { + if (UiHelpers.DrawCheckbox($"##apply{data.Slot}", "Apply this bonus item when applying the Design.", data.CurrentApply, out var enabled, + data.Locked)) + data.SetApplyItem(enabled); + } + private static void DrawApplyStain(in EquipDrawData data) { if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this dye to the item when applying the Design.", data.CurrentApplyStain, diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 232541e..3160bcb 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -46,6 +46,9 @@ public sealed unsafe class AdvancedDyePopup( public void DrawButton(EquipSlot slot) => DrawButton(MaterialValueIndex.FromSlot(slot)); + public void DrawButton(BonusItemFlag slot) + => DrawButton(MaterialValueIndex.FromSlot(slot)); + private void DrawButton(MaterialValueIndex index) { if (!config.UseAdvancedDyes) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 5218581..4074574 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -215,6 +215,12 @@ public class ActorPanel var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand); _equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose()); + foreach (var slot in BonusExtensions.AllFlags) + { + var data = BonusDrawData.FromState(_stateManager, _state!, slot); + _equipmentDrawer.DrawBonusItem(data); + } + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); DrawEquipmentMetaToggles(); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 7da95a3..8d52a76 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -277,13 +277,13 @@ public class SetPanel( var size = new Vector2(ImGui.GetFrameHeight()); size.X += ImGuiHelpers.GlobalScale; - var (equipFlags, customizeFlags, _, _, _) = design.ApplyWhat(); + var collection = design.ApplyWhat(); var sb = new StringBuilder(); var designData = design.Design.GetDesignData(default); foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)) { var flag = slot.ToFlag(); - if (!equipFlags.HasFlag(flag)) + if (!collection.Equip.HasFlag(flag)) continue; var item = designData.Item(slot); @@ -308,7 +308,7 @@ public class SetPanel( foreach (var type in CustomizationExtensions.All) { var flag = type.ToFlag(); - if (!customizeFlags.HasFlag(flag)) + if (!collection.Customize.HasFlag(flag)) continue; if (flag.RequiresRedraw()) diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index 85b4010..b562ecf 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -25,7 +25,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ continue; DrawDesign(design, _designFileSystem); - var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, design.ApplyMeta, + var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize, design.Application.Meta, design.WriteProtected()); using var font = ImRaii.PushFont(UiBuilder.MonoFont); ImGuiUtil.TextWrapped(base64); diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index ff9c2b8..394bd7f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -112,11 +112,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer public DesignBase CreateDesign(in MirageManager.GlamourPlate plate) { var design = _design.CreateTemporary(); - design.ApplyCustomize = 0; - design.ApplyCrest = 0; - design.ApplyMeta = 0; - design.ApplyParameters = 0; - design.ApplyEquip = 0; + design.Application = ApplicationCollection.None; foreach (var (slot, index) in EquipSlotExtensions.FullSlots.WithIndex()) { var itemId = plate.ItemIds[index]; @@ -129,7 +125,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer design.GetDesignDataRef().SetItem(slot, item); design.GetDesignDataRef().SetStain(slot, StainIds.FromGlamourPlate(plate, index)); - design.ApplyEquip |= slot.ToBothFlags(); + design.Application.Equip |= slot.ToBothFlags(); } return design; diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index dd1c125..7307f22 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -21,7 +21,7 @@ public unsafe class ModelEvaluationPanel( UpdateSlotService _updateSlotService, ChangeCustomizeService _changeCustomizeService, CrestService _crestService, - DictGlasses _glasses) : IGameDataDrawer + DictBonusItems bonusItems) : IGameDataDrawer { public string Label => "Model Evaluation"; @@ -57,6 +57,16 @@ public unsafe class ModelEvaluationPanel( ImGui.TextUnformatted($"Transformation Id: {actor.AsCharacter->CharacterData.TransformationId}"); if (actor.AsCharacter->CharacterData.ModelCharaId_2 != -1) ImGui.TextUnformatted($"ModelChara2 {actor.AsCharacter->CharacterData.ModelCharaId_2}"); + + ImGuiUtil.DrawTableColumn("Character Mode"); + ImGuiUtil.DrawTableColumn($"{actor.AsCharacter->Mode}"); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + + ImGuiUtil.DrawTableColumn("Animation"); + ImGuiUtil.DrawTableColumn($"{((ushort*)&actor.AsCharacter->Timeline)[0x78]}"); + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); } ImGuiUtil.DrawTableColumn("Mainhand"); @@ -226,7 +236,7 @@ public unsafe class ModelEvaluationPanel( _updateSlotService.UpdateEquipSlot(model, slot, actor.GetArmor(slot)); } - foreach (var slot in BonusSlotExtensions.AllFlags) + foreach (var slot in BonusExtensions.AllFlags) { using var id2 = ImRaii.PushId((int)slot.ToModelIndex()); ImGuiUtil.DrawTableColumn(slot.ToName()); @@ -236,9 +246,9 @@ public unsafe class ModelEvaluationPanel( } else { - var glassesId = actor.GetBonusSlot(slot); - if (_glasses.TryGetValue(glassesId, out var glasses)) - ImGuiUtil.DrawTableColumn($"{glasses.Id.Id},{glasses.Variant.Id} ({glassesId})"); + var glassesId = actor.GetBonusItem(slot); + if (bonusItems.TryGetValue(glassesId, out var glasses)) + ImGuiUtil.DrawTableColumn($"{glasses.ModelId.Id},{glasses.Variant.Id} ({glassesId})"); else ImGuiUtil.DrawTableColumn($"{glassesId}"); } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 50fd936..b20e00d 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -112,6 +112,13 @@ public class DesignPanel var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand); var offhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.OffHand); _equipmentDrawer.DrawWeapons(mainhand, offhand, true); + + foreach (var slot in BonusExtensions.AllFlags) + { + var data = BonusDrawData.FromDesign(_manager, _selector.Selected!, slot); + _equipmentDrawer.DrawBonusItem(data); + } + ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); DrawEquipmentMetaToggles(); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); @@ -149,7 +156,7 @@ public class DesignPanel if (!h) return; - if (_customizationDrawer.Draw(_selector.Selected!.DesignData.Customize, _selector.Selected.ApplyCustomizeRaw, + if (_customizationDrawer.Draw(_selector.Selected!.DesignData.Customize, _selector.Selected.Application.Customize, _selector.Selected!.WriteProtected(), false)) foreach (var idx in Enum.GetValues()) { @@ -224,7 +231,7 @@ public class DesignPanel private void DrawCrestApplication() { using var id = ImRaii.PushId("Crests"); - var flags = (uint)_selector.Selected!.ApplyCrest; + var flags = (uint)_selector.Selected!.Application.Crest; var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); foreach (var flag in CrestExtensions.AllRelevantSet) { @@ -255,7 +262,7 @@ public class DesignPanel { void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable slots) { - var flags = (uint)(allFlags & _selector.Selected!.ApplyEquip); + var flags = (uint)(allFlags & _selector.Selected!.Application.Equip); using var id = ImRaii.PushId(label); var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); if (stain) @@ -302,7 +309,7 @@ public class DesignPanel { using var id = ImRaii.PushId("Meta"); const uint all = (uint)MetaExtensions.All; - var flags = (uint)_selector.Selected!.ApplyMeta; + var flags = (uint)_selector.Selected!.Application.Meta; var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); var labels = new[] @@ -324,7 +331,7 @@ public class DesignPanel private void DrawParameterApplication() { using var id = ImRaii.PushId("Parameter"); - var flags = (uint)_selector.Selected!.ApplyParameters; + var flags = (uint)_selector.Selected!.Application.Parameters; var bigChange = ImGui.CheckboxFlags("Apply All Customize Parameters", ref flags, (uint)CustomizeParameterExtensions.All); foreach (var flag in CustomizeParameterExtensions.AllFlags) { @@ -408,8 +415,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); - using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); + using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks); } } @@ -427,8 +433,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); - using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters); + using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks); } } diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 7e22ff1..88f51f5 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -1,6 +1,5 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; -using Glamourer.GameData; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; @@ -98,15 +97,6 @@ public static class UiHelpers return (currentValue != newValue, currentApply != newApply); } - public static (EquipFlag, CustomizeFlag, CrestFlag, CustomizeParameterFlag) ConvertKeysToFlags() - => (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch - { - (false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All), - (true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All), - (true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0, CrestExtensions.All, 0), - (false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant, 0, CustomizeParameterExtensions.All), - }; - public static (bool, bool) ConvertKeysToBool() => (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch { @@ -126,16 +116,36 @@ public static class UiHelpers using var c = ImRaii.PushColor(ImGuiCol.Text, hovering ? ColorId.FavoriteStarHovered.Value() : favorite ? ColorId.FavoriteStarOn.Value() : ColorId.FavoriteStarOff.Value()); ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString()); - if (ImGui.IsItemClicked()) - { - if (favorite) - favorites.Remove(item); - else - favorites.TryAdd(item); - return true; - } + if (!ImGui.IsItemClicked()) + return false; + + if (favorite) + favorites.Remove(item); + else + favorites.TryAdd(item); + return true; + + } + + public static bool DrawFavoriteStar(FavoriteManager favorites, BonusItem item) + { + var favorite = favorites.Contains(item); + var hovering = ImGui.IsMouseHoveringRect(ImGui.GetCursorScreenPos(), + ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight())); + + using var font = ImRaii.PushFont(UiBuilder.IconFont); + using var c = ImRaii.PushColor(ImGuiCol.Text, + hovering ? ColorId.FavoriteStarHovered.Value() : favorite ? ColorId.FavoriteStarOn.Value() : ColorId.FavoriteStarOff.Value()); + ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString()); + if (!ImGui.IsItemClicked()) + return false; + + if (favorite) + favorites.Remove(item); + else + favorites.TryAdd(item); + return true; - return false; } public static bool DrawFavoriteStar(FavoriteManager favorites, StainId stain) @@ -149,15 +159,14 @@ public static class UiHelpers hovering ? ColorId.FavoriteStarHovered.Value() : favorite ? ColorId.FavoriteStarOn.Value() : ColorId.FavoriteStarOff.Value()); ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString()); - if (ImGui.IsItemClicked()) - { - if (favorite) - favorites.Remove(stain); - else - favorites.TryAdd(stain); - return true; - } + if (!ImGui.IsItemClicked()) + return false; + + if (favorite) + favorites.Remove(stain); + else + favorites.TryAdd(stain); + return true; - return false; } -} +} \ No newline at end of file diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 2096bc7..254675e 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -42,6 +42,12 @@ public readonly record struct MaterialValueIndex( return Invalid; } + public static MaterialValueIndex FromSlot(BonusItemFlag slot) + { + var idx = slot.ToIndex(); + return idx > 2 ? Invalid : new MaterialValueIndex(DrawObjectType.Human, (byte)(idx + 16), 0, 0); + } + public EquipSlot ToEquipSlot() => DrawObject switch { diff --git a/Glamourer/Interop/PalettePlus/PaletteImport.cs b/Glamourer/Interop/PalettePlus/PaletteImport.cs index 93c3fa2..4887255 100644 --- a/Glamourer/Interop/PalettePlus/PaletteImport.cs +++ b/Glamourer/Interop/PalettePlus/PaletteImport.cs @@ -37,17 +37,13 @@ public class PaletteImport(IDalamudPluginInterface pluginInterface, DesignManage } var design = designManager.CreateEmpty(fullPath, true); - design.ApplyCustomize = 0; - design.ApplyEquip = 0; - design.ApplyCrest = 0; - designManager.ChangeApplyMeta(design, MetaIndex.VisorState, false); - designManager.ChangeApplyMeta(design, MetaIndex.HatState, false); - designManager.ChangeApplyMeta(design, MetaIndex.WeaponState, false); + design.Application = ApplicationCollection.None; foreach (var flag in flags.Iterate()) { designManager.ChangeApplyParameter(design, flag, true); designManager.ChangeCustomizeParameter(design, flag, palette[flag]); } + Glamourer.Log.Information($"Added design for palette {name} at {fullPath}."); } } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 2be31cf..55db36d 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -14,14 +14,14 @@ public unsafe class UpdateSlotService : IDisposable { public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent; - private readonly DictGlasses _glasses; + private readonly DictBonusItems _bonusItems; public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, IGameInteropProvider interop, - DictGlasses glasses) + DictBonusItems bonusItems) { EquipSlotUpdatingEvent = equipSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating; - _glasses = glasses; + _bonusItems = bonusItems; interop.InitializeFromAttributes(this); _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); @@ -41,7 +41,7 @@ public unsafe class UpdateSlotService : IDisposable FlagSlotForUpdateInterop(drawObject, slot, data); } - public void UpdateBonusSlot(Model drawObject, BonusEquipFlag slot, CharacterArmor data) + public void UpdateBonusSlot(Model drawObject, BonusItemFlag slot, CharacterArmor data) { if (!drawObject.IsCharacterBase) return; @@ -53,13 +53,13 @@ public unsafe class UpdateSlotService : IDisposable _flagBonusSlotForUpdateHook.Original(drawObject.Address, index, &data); } - public void UpdateGlasses(Model drawObject, GlassesId id) + public void UpdateGlasses(Model drawObject, BonusItemId id) { - if (!_glasses.TryGetValue(id, out var glasses)) + if (!_bonusItems.TryGetValue(id, out var glasses)) return; - var armor = new CharacterArmor(glasses.Id, glasses.Variant, StainIds.None); - _flagBonusSlotForUpdateHook.Original(drawObject.Address, BonusEquipFlag.Glasses.ToIndex(), &armor); + var armor = new CharacterArmor(glasses.ModelId, glasses.Variant, StainIds.None); + _flagBonusSlotForUpdateHook.Original(drawObject.Address, BonusItemFlag.Glasses.ToIndex(), &armor); } public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains) diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index c5f537f..7b83199 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -20,12 +20,13 @@ public class ItemManager public readonly ExcelSheet ItemSheet; public readonly DictStain Stains; public readonly ItemData ItemData; + public readonly DictBonusItems DictBonusItems; public readonly RestrictedGear RestrictedGear; public readonly EquipItem DefaultSword; public ItemManager(Configuration config, IDataManager gameData, ObjectIdentification objectIdentification, - ItemData itemData, DictStain stains, RestrictedGear restrictedGear) + ItemData itemData, DictStain stains, RestrictedGear restrictedGear, DictBonusItems dictBonusItems) { _config = config; ItemSheet = gameData.GetExcelSheet()!; @@ -33,6 +34,7 @@ public class ItemManager ItemData = itemData; Stains = stains; RestrictedGear = restrictedGear; + DictBonusItems = dictBonusItems; DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)!); // Weathered Shortsword } @@ -124,6 +126,22 @@ public class ItemManager } } + public BonusItem Identify(BonusItemFlag slot, PrimaryId id, Variant variant) + { + var index = slot.ToIndex(); + if (index == uint.MaxValue) + return new BonusItem($"Invalid ({id.Id}-{variant})", 0, 0, id, variant, slot); + + if (id.Id == 0) + return BonusItem.Empty(slot); + + return ObjectIdentification.Identify(id, variant, slot) + .FirstOrDefault(new BonusItem($"Invalid ({id.Id}-{variant})", 0, 0, id, variant, slot)); + } + + public BonusItem Resolve(BonusItemFlag slot, BonusItemId id) + => IsBonusItemValid(slot, id, out var item) ? item : new BonusItem($"Invalid ({id.Id})", 0, id, 0, 0, slot); + /// Return the default offhand for a given mainhand, that is for both handed weapons, return the correct offhand part, and for everything else Nothing. public EquipItem GetDefaultOffhand(EquipItem mainhand) { @@ -161,6 +179,18 @@ public class ItemManager return item.Valid; } + /// Returns whether a bonus item id represents a valid item for a slot and gives the item. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool IsBonusItemValid(BonusItemFlag slot, BonusItemId itemId, out BonusItem item) + { + if (itemId.Id != 0) + return DictBonusItems.TryGetValue(itemId, out item) && slot == item.Slot; + + item = BonusItem.Empty(slot); + return true; + } + + /// /// Check whether an item id resolves to an existing item of the correct slot (which should not be weapons.) /// The returned item is either the resolved correct item, or the Nothing item for that slot. diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs index 17072e7..71af0aa 100644 --- a/Glamourer/State/InternalStateEditor.cs +++ b/Glamourer/State/InternalStateEditor.cs @@ -151,6 +151,18 @@ public class InternalStateEditor( return true; } + /// Change a single bonus item. + public bool ChangeBonusItem(ActorState state, BonusItemFlag slot, BonusItem item, StateSource source, out BonusItem oldItem, uint key = 0) + { + oldItem = state.ModelData.BonusItem(slot); + if (!state.CanUnlock(key)) + return false; + + state.ModelData.SetBonusItem(slot, item); + state.Sources[slot] = source; + return true; + } + /// Change a single piece of equipment including stain. public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainIds stains, StateSource source, out EquipItem oldItem, out StainIds oldStains, uint key = 0) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 5a6c21e..a4a1df4 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -125,6 +125,33 @@ public class StateApplier( return data; } + public void ChangeBonusItem(ActorData data, BonusItemFlag slot, PrimaryId id, Variant variant) + { + var item = new CharacterArmor(id, variant, StainIds.None); + foreach (var actor in data.Objects.Where(a => a.IsCharacter)) + { + var mdl = actor.Model; + if (!mdl.IsHuman) + continue; + + _updateSlot.UpdateBonusSlot(actor.Model, slot, item); + } + } + + /// + public ActorData ChangeBonusItem(ActorState state, BonusItemFlag slot, bool apply) + { + // If the source is not IPC we do not want to apply restrictions. + var data = GetData(state); + if (apply) + { + var item = state.ModelData.BonusItem(slot); + ChangeBonusItem(data, slot, item.ModelId, item.Variant); + } + + return data; + } + /// /// Change the stain of a single piece of armor or weapon. diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index dccb283..c39fd31 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -89,6 +89,18 @@ public class StateEditor( StateChanged.Invoke(type, settings.Source, state, actors, (old, item, slot)); } + public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default) + { + var state = (ActorState)data; + if (!Editor.ChangeBonusItem(state, slot, item, settings.Source, out var old, settings.Key)) + return; + + var actors = Applier.ChangeBonusItem(state, slot, settings.Source.RequiresChange()); + Glamourer.Log.Verbose( + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChangeType.BonusItem, settings.Source, state, actors, (old, item, slot)); + } + /// public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings settings) { @@ -226,7 +238,7 @@ public class StateEditor( out _, settings.Key); } - var customizeFlags = mergedDesign.Design.ApplyCustomizeRaw; + var customizeFlags = mergedDesign.Design.Application.Customize; if (mergedDesign.Design.DoApplyCustomize(CustomizeIndex.Clan)) customizeFlags |= CustomizeFlag.Race; @@ -245,7 +257,7 @@ public class StateEditor( state.Sources[parameter] = StateSource.Game; } - foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate()) + foreach (var parameter in mergedDesign.Design.Application.Parameters.Iterate()) { if (settings.RespectManual && state.Sources[parameter].IsManual()) continue; @@ -273,6 +285,13 @@ public class StateEditor( Source(slot.ToState(true)), out _, settings.Key); } + foreach (var slot in BonusExtensions.AllFlags) + { + if (mergedDesign.Design.DoApplyBonusItem(slot)) + if (!settings.RespectManual || !state.Sources[slot].IsManual()) + Editor.ChangeBonusItem(state, slot, mergedDesign.Design.DesignData.BonusItem(slot), Source(slot), out _, settings.Key); + } + foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots) { if (mergedDesign.Design.DoApplyStain(weaponSlot)) diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs index 28cc722..a569499 100644 --- a/Glamourer/State/StateIndex.cs +++ b/Glamourer/State/StateIndex.cs @@ -38,6 +38,13 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators Invalid, }; + public static implicit operator StateIndex(BonusItemFlag flag) + => flag switch + { + BonusItemFlag.Glasses => new StateIndex(BonusItemGlasses), + _ => Invalid, + }; + public static implicit operator StateIndex(CustomizeIndex index) => index switch { @@ -198,23 +205,13 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators All => Enumerable.Range(0, Size - 1).Select(i => new StateIndex(i)); - public bool GetApply(DesignBase data) - => GetFlag() switch - { - EquipFlag e => data.ApplyEquip.HasFlag(e), - CustomizeFlag c => data.ApplyCustomize.HasFlag(c), - MetaFlag m => data.ApplyMeta.HasFlag(m), - CrestFlag c => data.ApplyCrest.HasFlag(c), - CustomizeParameterFlag c => data.ApplyParameters.HasFlag(c), - bool v => v, - _ => false, - }; - public string ToName() => GetFlag() switch { @@ -223,6 +220,7 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators m.ToIndex().ToName(), CrestFlag c => c.ToLabel(), CustomizeParameterFlag c => c.ToName(), + BonusItemFlag b => b.ToName(), bool v => "Model ID", _ => "Unknown", }; @@ -317,6 +315,8 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators CustomizeParameterFlag.FacePaintUvOffset, ParamDecalColor => CustomizeParameterFlag.DecalColor, + BonusItemGlasses => BonusItemFlag.Glasses, + _ => -1, }; @@ -411,6 +411,8 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators data.Parameters[CustomizeParameterFlag.FacePaintUvOffset], ParamDecalColor => data.Parameters[CustomizeParameterFlag.DecalColor], + BonusItemGlasses => data.BonusItem(BonusItemFlag.Glasses), + _ => null, }; } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 72b3122..f82b2fc 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -32,7 +32,8 @@ public class StateListener : IDisposable private readonly ItemManager _items; private readonly CustomizeService _customizations; private readonly PenumbraService _penumbra; - private readonly EquipSlotUpdating _equipSlotUpdating; + private readonly EquipSlotUpdating _equipSlotUpdating; + private readonly BonusSlotUpdating _bonusSlotUpdating; private readonly WeaponLoading _weaponLoading; private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly VisorStateChanged _visorState; @@ -52,17 +53,18 @@ public class StateListener : IDisposable private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty; public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, - EquipSlotUpdating equipSlotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, - HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, - StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, - ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, CrestService crestService) + EquipSlotUpdating equipSlotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, + WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, + FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, + GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, + CrestService crestService, BonusSlotUpdating bonusSlotUpdating) { _manager = manager; _items = items; _penumbra = penumbra; _actors = actors; _config = config; - _equipSlotUpdating = equipSlotUpdating; + _equipSlotUpdating = equipSlotUpdating; _weaponLoading = weaponLoading; _visorState = visorState; _weaponVisibility = weaponVisibility; @@ -78,6 +80,7 @@ public class StateListener : IDisposable _customizations = customizations; _condition = condition; _crestService = crestService; + _bonusSlotUpdating = bonusSlotUpdating; Subscribe(); } @@ -227,6 +230,35 @@ public class StateListener : IDisposable (_, armor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender); } + private void OnBonusSlotUpdating(Model model, BonusItemFlag slot, ref CharacterArmor item, ref ulong returnValue) + { + var actor = _penumbra.GameObjectFromDrawObject(model); + if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + return; + + if (actor.Identifier(_actors, out var identifier) + && _manager.TryGetValue(identifier, out var state)) + switch (UpdateBaseData(actor, state, slot, item)) + { + // Base data changed equipment while actors were not there. + // Update model state if not on fixed design. + case UpdateState.Change: + var apply = false; + if (!state.Sources[slot].IsFixed()) + _manager.ChangeBonusItem(state, slot, state.BaseData.BonusItem(slot), ApplySettings.Game); + else + apply = true; + if (apply) + item = state.ModelData.BonusItem(slot).ToArmor(); + break; + // Use current model data. + case UpdateState.NoChange: + item = state.ModelData.BonusItem(slot).ToArmor(); + break; + case UpdateState.Transformed: break; + } + } + private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items) { _objects.Update(); @@ -403,6 +435,28 @@ public class StateListener : IDisposable } } + private UpdateState UpdateBaseData(Actor actor, ActorState state, BonusItemFlag slot, CharacterArmor item) + { + var actorItemId = actor.GetBonusItem(slot); + if (!_items.IsBonusItemValid(slot, actorItemId, out var actorItem)) + return UpdateState.NoChange; + + // The actor item does not correspond to the model item, thus the actor is transformed. + if (actorItem.ModelId != item.Set || actorItem.Variant != item.Variant) + return UpdateState.Transformed; + + var baseData = state.BaseData.BonusItem(slot); + var change = UpdateState.NoChange; + if (baseData.Id != actorItem.Id || baseData.ModelId != item.Set || baseData.Variant != item.Variant) + { + var identified = _items.Identify(slot, item.Set, item.Variant); + state.BaseData.SetBonusItem(slot, identified); + change = UpdateState.Change; + } + + return change; + } + /// Handle a full equip slot update for base data and model data. private void HandleEquipSlot(Actor actor, ActorState state, EquipSlot slot, ref CharacterArmor armor) { @@ -700,6 +754,7 @@ public class StateListener : IDisposable _penumbra.CreatingCharacterBase += OnCreatingCharacterBase; _penumbra.CreatedCharacterBase += OnCreatedCharacterBase; _equipSlotUpdating.Subscribe(OnEquipSlotUpdating, EquipSlotUpdating.Priority.StateListener); + _bonusSlotUpdating.Subscribe(OnBonusSlotUpdating, BonusSlotUpdating.Priority.StateListener); _movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener); _weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener); _visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener); @@ -716,6 +771,7 @@ public class StateListener : IDisposable _penumbra.CreatingCharacterBase -= OnCreatingCharacterBase; _penumbra.CreatedCharacterBase -= OnCreatedCharacterBase; _equipSlotUpdating.Unsubscribe(OnEquipSlotUpdating); + _bonusSlotUpdating.Unsubscribe(OnBonusSlotUpdating); _movedEquipment.Unsubscribe(OnMovedEquipment); _weaponLoading.Unsubscribe(OnWeaponLoading); _visorState.Unsubscribe(OnVisorChange); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 92cf6e5..5033577 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -160,6 +160,13 @@ public sealed class StateManager( foreach (var slot in CrestExtensions.AllRelevantSet) ret.SetCrest(slot, CrestService.GetModelCrest(actor, slot)); + + foreach (var slot in BonusExtensions.AllFlags) + { + var data = model.GetBonus(slot); + var item = Items.Identify(slot, data.Set, data.Variant); + ret.SetBonusItem(slot, item); + } } else { @@ -181,6 +188,13 @@ public sealed class StateManager( foreach (var slot in CrestExtensions.AllRelevantSet) ret.SetCrest(slot, actor.GetCrest(slot)); + + foreach (var slot in BonusExtensions.AllFlags) + { + var id = actor.GetBonusItem(slot); + var item = Items.Resolve(slot, id); + ret.SetBonusItem(slot, item); + } } // Set the weapons regardless of source. @@ -241,6 +255,9 @@ public sealed class StateManager( state.Sources[slot, false] = StateSource.Game; } + foreach (var slot in BonusExtensions.AllFlags) + state.Sources[slot] = StateSource.Game; + foreach (var type in Enum.GetValues()) state.Sources[type] = StateSource.Game; @@ -328,6 +345,12 @@ public sealed class StateManager( state.ModelData.IsHatVisible()); } + foreach (var slot in BonusExtensions.AllFlags) + { + var item = state.ModelData.BonusItem(slot); + Applier.ChangeBonusItem(actors, slot, item.ModelId, item.Variant); + } + var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; Applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors; @@ -364,6 +387,15 @@ public sealed class StateManager( } } + foreach (var slot in BonusExtensions.AllFlags) + { + if (state.Sources[slot] is StateSource.Fixed) + { + state.Sources[slot] = StateSource.Game; + state.ModelData.SetBonusItem(slot, state.BaseData.BonusItem(slot)); + } + } + foreach (var slot in CrestExtensions.AllRelevantSet) { if (state.Sources[slot] is StateSource.Fixed) diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index de22ea8..f4576c6 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -25,6 +25,7 @@ public class FavoriteManager : ISavable private readonly HashSet _favorites = []; private readonly HashSet _favoriteColors = []; private readonly HashSet _favoriteHairStyles = []; + private readonly HashSet _favoriteBonusItems = []; public FavoriteManager(SaveService saveService) { @@ -62,6 +63,7 @@ public class FavoriteManager : ISavable _favorites.UnionWith(load!.FavoriteItems.Select(i => (ItemId)i)); _favoriteColors.UnionWith(load!.FavoriteColors.Select(i => (StainId)i)); _favoriteHairStyles.UnionWith(load!.FavoriteHairStyles.Select(t => new FavoriteHairStyle(t))); + _favoriteBonusItems.UnionWith(load!.FavoriteBonusItems.Select(b => new BonusItemId(b))); break; default: throw new Exception($"Unknown Version {load?.Version ?? 0}"); @@ -109,6 +111,11 @@ public class FavoriteManager : ISavable foreach (var hairStyle in _favoriteHairStyles) j.WriteValue(hairStyle.ToValue()); j.WriteEndArray(); + j.WriteStartArray(); + j.WritePropertyName(nameof(LoadIntermediary.FavoriteBonusItems)); + foreach (var item in _favoriteBonusItems) + j.WriteValue(item.Id); + j.WriteEndArray(); j.WriteEndObject(); } @@ -124,9 +131,6 @@ public class FavoriteManager : ISavable return true; } - public bool TryAdd(Stain stain) - => TryAdd(stain.RowIndex); - public bool TryAdd(StainId stain) { if (stain.Id == 0 || !_favoriteColors.Add(stain)) @@ -136,6 +140,15 @@ public class FavoriteManager : ISavable return true; } + public bool TryAdd(BonusItem bonusItem) + { + if (bonusItem.Id == 0 || !_favoriteBonusItems.Add(bonusItem.Id)) + return false; + + Save(); + return true; + } + public bool TryAdd(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value) { if (!TypeAllowed(type) || !_favoriteHairStyles.Add(new FavoriteHairStyle(gender, race, type, value))) @@ -157,9 +170,6 @@ public class FavoriteManager : ISavable return true; } - public bool Remove(Stain stain) - => Remove(stain.RowIndex); - public bool Remove(StainId stain) { if (!_favoriteColors.Remove(stain)) @@ -169,6 +179,15 @@ public class FavoriteManager : ISavable return true; } + public bool Remove(BonusItem bonusItem) + { + if (!_favoriteBonusItems.Remove(bonusItem.Id)) + return false; + + Save(); + return true; + } + public bool Remove(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value) { if (!_favoriteHairStyles.Remove(new FavoriteHairStyle(gender, race, type, value))) @@ -181,23 +200,21 @@ public class FavoriteManager : ISavable public bool Contains(EquipItem item) => _favorites.Contains(item.ItemId); - public bool Contains(Stain stain) - => _favoriteColors.Contains(stain.RowIndex); - - public bool Contains(ItemId item) - => _favorites.Contains(item); - public bool Contains(StainId stain) => _favoriteColors.Contains(stain); + public bool Contains(BonusItem bonusItem) + => _favoriteBonusItems.Contains(bonusItem.Id); + public bool Contains(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value) => _favoriteHairStyles.Contains(new FavoriteHairStyle(gender, race, type, value)); private class LoadIntermediary { - public int Version = CurrentVersion; - public uint[] FavoriteItems = []; - public byte[] FavoriteColors = []; - public uint[] FavoriteHairStyles = []; + public int Version = CurrentVersion; + public uint[] FavoriteItems = []; + public byte[] FavoriteColors = []; + public uint[] FavoriteHairStyles = []; + public ushort[] FavoriteBonusItems = []; } } diff --git a/Penumbra.GameData b/Penumbra.GameData index d83303c..8928015 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d83303ccc3ec5d7237f5da621e9c2433ad28f9e1 +Subproject commit 8928015f38f951810a9a6fbb44fb4a0cb9a712dd From 9529963aa2a8db7b6b5ac4573c237c0b563f748c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jul 2024 18:19:34 +0200 Subject: [PATCH 400/786] Update other things. --- Glamourer/Designs/Design.cs | 2 + Glamourer/Designs/DesignBase.cs | 97 +++++++---- Glamourer/Designs/DesignConverter.cs | 2 +- Glamourer/Designs/DesignData.cs | 43 +++-- Glamourer/Designs/DesignEditor.cs | 14 +- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Events/DesignChanged.cs | 5 +- Glamourer/GameData/CustomizeParameterData.cs | 95 +++++++--- Glamourer/GameData/CustomizeParameterFlag.cs | 13 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 12 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 12 +- Glamourer/Gui/Materials/ColorRowClipboard.cs | 6 +- Glamourer/Gui/Materials/MaterialDrawer.cs | 4 +- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 4 +- .../DebugTab/AdvancedCustomizationDrawer.cs | 135 ++++++++++++++ Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 3 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 164 ++++++++++++------ Glamourer/Gui/UiHelpers.cs | 26 ++- Glamourer/Interop/Material/DirectXService.cs | 18 +- .../Material/LiveColorTablePreviewer.cs | 8 +- Glamourer/Interop/Material/MaterialManager.cs | 4 +- Glamourer/Interop/Material/MaterialService.cs | 14 +- .../Interop/Material/MaterialValueIndex.cs | 2 +- .../Interop/Material/MaterialValueManager.cs | 4 +- Glamourer/Interop/Material/PrepareColorSet.cs | 6 +- Glamourer/Interop/WeaponService.cs | 1 - Glamourer/Services/TextureService.cs | 53 ++++-- Glamourer/State/StateApplier.cs | 9 + Glamourer/State/StateEditor.cs | 2 +- Glamourer/State/StateIndex.cs | 107 +----------- Penumbra.GameData | 2 +- 31 files changed, 575 insertions(+), 294 deletions(-) create mode 100644 Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index b8dc0a2..05ee948 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -106,6 +106,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn ["Tags"] = JArray.FromObject(Tags), ["WriteProtected"] = WriteProtected(), ["Equipment"] = SerializeEquipment(), + ["Bonus"] = SerializeBonusItems(), ["Customize"] = SerializeCustomize(), ["Parameters"] = SerializeParameters(), ["Materials"] = SerializeMaterials(), @@ -171,6 +172,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn design.SetWriteProtected(json["WriteProtected"]?.ToObject() ?? false); LoadCustomize(customizations, json["Customize"], design, design.Name, true, false); LoadEquip(items, json["Equipment"], design, design.Name, true); + LoadBonus(items, design, json["Bonus"]); LoadMods(json["Mods"], design); LoadParameters(json["Parameters"], design, design.Name); LoadMaterials(json["Materials"], design, design.Name); diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index be12737..06e55d7 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -228,6 +228,7 @@ public class DesignBase { ["FileVersion"] = FileVersion, ["Equipment"] = SerializeEquipment(), + ["Bonus"] = SerializeBonusItems(), ["Customize"] = SerializeCustomize(), ["Parameters"] = SerializeParameters(), ["Materials"] = SerializeMaterials(), @@ -279,7 +280,7 @@ public class DesignBase var item = _designData.BonusItem(slot); ret[slot.ToString()] = new JObject() { - ["BonusId"] = item.ModelId.Id, + ["BonusId"] = item.Id.Id, ["Apply"] = DoApplyBonusItem(slot), }; } @@ -424,19 +425,44 @@ public class DesignBase LoadEquip(items, json["Equipment"], ret, "Temporary Design", true); LoadParameters(json["Parameters"], ret, "Temporary Design"); LoadMaterials(json["Materials"], ret, "Temporary Design"); + LoadBonus(items, ret, json["Bonus"]); return ret; } + protected static void LoadBonus(ItemManager items, DesignBase design, JToken? json) + { + if (json is not JObject obj) + { + design.Application.BonusItem = 0; + return; + } + + foreach (var slot in BonusExtensions.AllFlags) + { + var itemJson = json[slot.ToString()] as JObject; + if (itemJson == null) + { + design.Application.BonusItem &= ~slot; + design.GetDesignDataRef().SetBonusItem(slot, BonusItem.Empty(slot)); + continue; + } + + design.SetApplyBonusItem(slot, itemJson["Apply"]?.ToObject() ?? false); + var id = itemJson["BonusId"]?.ToObject() ?? 0; + var item = items.Resolve(slot, id); + design.GetDesignDataRef().SetBonusItem(slot, item); + } + } + protected static void LoadParameters(JToken? parameters, DesignBase design, string name) { if (parameters == null) { - design.Application.Parameters = 0; + design.Application.Parameters = 0; design.GetDesignDataRef().Parameters = default; return; } - foreach (var flag in CustomizeParameterExtensions.ValueFlags) { if (!TryGetToken(flag, out var token)) @@ -492,7 +518,7 @@ public class DesignBase return true; } - design.Application.Parameters &= ~flag; + design.Application.Parameters &= ~flag; design.GetDesignDataRef().Parameters[flag] = CustomizeParameterValue.Zero; return false; } @@ -523,32 +549,15 @@ public class DesignBase return; } - static (CustomItemId, StainIds, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item) - { - var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot).Id; - var stain = (StainId)(item?["Stain"]?.ToObject() ?? 0); - var crest = item?["Crest"]?.ToObject() ?? false; - var apply = item?["Apply"]?.ToObject() ?? false; - var applyStain = item?["ApplyStain"]?.ToObject() ?? false; - var applyCrest = item?["ApplyCrest"]?.ToObject() ?? false; - return (id, stain, crest, apply, applyStain, applyCrest); - } - - void PrintWarning(string msg) - { - if (msg.Length > 0 && name != "Temporary Design") - Glamourer.Messager.NotificationMessage($"{msg} ({name})", NotificationType.Warning); - } - foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(slot, equip[slot.ToString()]); + var (id, stains, crest, apply, applyStain, applyCrest) = ParseItem(slot, equip[slot.ToString()]); PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown)); - PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); + PrintWarning(items.ValidateStain(stains, out stains, allowUnknown)); var crestSlot = slot.ToCrestFlag(); design._designData.SetItem(slot, item); - design._designData.SetStain(slot, stain); + design._designData.SetStain(slot, stains); design._designData.SetCrest(crestSlot, crest); design.SetApplyEquip(slot, apply); design.SetApplyStain(slot, applyStain); @@ -556,21 +565,21 @@ public class DesignBase } { - var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); + var (id, stains, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.MainHand)) id = items.DefaultSword.ItemId; - var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) = + var (idOff, stainsOff, crestOff, applyOff, applyStainOff, applyCrestOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.OffHand)) id = ItemManager.NothingId(FullEquipType.Shield); PrintWarning(items.ValidateWeapons(id, idOff, out var main, out var off, allowUnknown)); - PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); - PrintWarning(items.ValidateStain(stainOff, out stainOff, allowUnknown)); + PrintWarning(items.ValidateStain(stains, out stains, allowUnknown)); + PrintWarning(items.ValidateStain(stainsOff, out stainsOff, allowUnknown)); design._designData.SetItem(EquipSlot.MainHand, main); design._designData.SetItem(EquipSlot.OffHand, off); - design._designData.SetStain(EquipSlot.MainHand, stain); - design._designData.SetStain(EquipSlot.OffHand, stainOff); + design._designData.SetStain(EquipSlot.MainHand, stains); + design._designData.SetStain(EquipSlot.OffHand, stainsOff); design._designData.SetCrest(CrestFlag.MainHand, crest); design._designData.SetCrest(CrestFlag.OffHand, crestOff); design.SetApplyEquip(EquipSlot.MainHand, apply); @@ -591,6 +600,24 @@ public class DesignBase metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse); design.SetApplyMeta(MetaIndex.VisorState, metaValue.Enabled); design._designData.SetVisor(metaValue.ForcedValue); + return; + + void PrintWarning(string msg) + { + if (msg.Length > 0 && name != "Temporary Design") + Glamourer.Messager.NotificationMessage($"{msg} ({name})", NotificationType.Warning); + } + + static (CustomItemId, StainIds, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item) + { + var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot).Id; + var stains = StainIds.ParseFromObject(item as JObject); + var crest = item?["Crest"]?.ToObject() ?? false; + var apply = item?["Apply"]?.ToObject() ?? false; + var applyStain = item?["ApplyStain"]?.ToObject() ?? false; + var applyCrest = item?["ApplyCrest"]?.ToObject() ?? false; + return (id, stains, crest, apply, applyStain, applyCrest); + } } protected static void LoadCustomize(CustomizeService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman, @@ -671,12 +698,12 @@ public class DesignBase { _designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags, out var writeProtected, out var applyMeta); - Application.Equip = equipFlags; - ApplyCustomize = customizeFlags; + Application.Equip = equipFlags; + ApplyCustomize = customizeFlags; Application.Parameters = 0; - Application.Crest = 0; - Application.Meta = applyMeta; - Application.BonusItem = 0; + Application.Crest = 0; + Application.Meta = applyMeta; + Application.BonusItem = 0; SetWriteProtected(writeProtected); CustomizeSet = SetCustomizationSet(customize); } diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 21172fe..c3235ab 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -214,7 +214,7 @@ public class DesignConverter( foreach (var (key, value) in materials.Values) { var idx = MaterialValueIndex.FromKey(key); - if (idx.RowIndex >= LegacyColorTable.NumUsedRows) + if (idx.RowIndex >= ColorTable.NumUsedRows) continue; if (idx.MaterialIndex >= MaterialService.MaterialsPerModel) continue; diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 7fb2f72..0a52861 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -101,7 +101,7 @@ public unsafe struct DesignData 8 => EquipItem.FromIds(_itemIds[ 8], _iconIds[ 8], ((CharacterArmor*)ptr)[8].Set, 0, ((CharacterArmor*)ptr)[8].Variant, FullEquipType.Finger, name: _nameRFinger ), 9 => EquipItem.FromIds(_itemIds[ 9], _iconIds[ 9], ((CharacterArmor*)ptr)[9].Set, 0, ((CharacterArmor*)ptr)[9].Variant, FullEquipType.Finger, name: _nameLFinger ), 10 => EquipItem.FromIds(_itemIds[10], _iconIds[10], *(PrimaryId*)(ptr + EquipmentByteSize + 0), *(SecondaryId*)(ptr + EquipmentByteSize + 2), *(Variant*)(ptr + EquipmentByteSize + 4), _typeMainhand, name: _nameMainhand), - 11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], *(PrimaryId*)(ptr + EquipmentByteSize + 4), *(SecondaryId*)(ptr + EquipmentByteSize + 2), *(Variant*)(ptr + EquipmentByteSize + 4), _typeOffhand, name: _nameOffhand ), + 11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], *(PrimaryId*)(ptr + EquipmentByteSize + 8), *(SecondaryId*)(ptr + EquipmentByteSize + 10), *(Variant*)(ptr + EquipmentByteSize + 12), _typeOffhand, name: _nameOffhand ), _ => new EquipItem(), // @formatter:on }; @@ -176,13 +176,15 @@ public unsafe struct DesignData _nameMainhand = item.Name; _equipmentBytes[EquipmentByteSize + 2] = (byte)item.SecondaryId.Id; _equipmentBytes[EquipmentByteSize + 3] = (byte)(item.SecondaryId.Id >> 8); + _equipmentBytes[EquipmentByteSize + 4] = item.Variant.Id; _typeMainhand = item.Type; return true; case 11: - _nameOffhand = item.Name; - _equipmentBytes[EquipmentByteSize + 2] = (byte)item.SecondaryId.Id; - _equipmentBytes[EquipmentByteSize + 3] = (byte)(item.SecondaryId.Id >> 8); - _typeOffhand = item.Type; + _nameOffhand = item.Name; + _equipmentBytes[EquipmentByteSize + 10] = (byte)item.SecondaryId.Id; + _equipmentBytes[EquipmentByteSize + 11] = (byte)(item.SecondaryId.Id >> 8); + _equipmentBytes[EquipmentByteSize + 12] = item.Variant.Id; + _typeOffhand = item.Type; return true; } @@ -212,18 +214,18 @@ public unsafe struct DesignData => slot.ToIndex() switch { // @formatter:off - 0 => SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 4], stains.Stain2.Id), - 1 => SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 4], stains.Stain2.Id), - 2 => SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 4], stains.Stain2.Id), - 3 => SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 4], stains.Stain2.Id), - 4 => SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 4], stains.Stain2.Id), - 5 => SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 4], stains.Stain2.Id), - 6 => SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 4], stains.Stain2.Id), - 7 => SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 4], stains.Stain2.Id), - 8 => SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 4], stains.Stain2.Id), - 9 => SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 3], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 4], stains.Stain2.Id), - 10 => SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 6], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 7], stains.Stain2.Id), - 11 => SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 14], stains.Stain1.Id) || SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 15], stains.Stain2.Id), + 0 => SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 4], stains.Stain2.Id), + 1 => SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 4], stains.Stain2.Id), + 2 => SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 4], stains.Stain2.Id), + 3 => SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 4], stains.Stain2.Id), + 4 => SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 4], stains.Stain2.Id), + 5 => SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 4], stains.Stain2.Id), + 6 => SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 4], stains.Stain2.Id), + 7 => SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 4], stains.Stain2.Id), + 8 => SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 4], stains.Stain2.Id), + 9 => SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 4], stains.Stain2.Id), + 10 => SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 6], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 7], stains.Stain2.Id), + 11 => SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 14], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 15], stains.Stain2.Id), _ => false, // @formatter:on }; @@ -322,6 +324,13 @@ public unsafe struct DesignData SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); SetStain(EquipSlot.OffHand, StainIds.None); SetCrest(CrestFlag.OffHand, false); + SetDefaultBonusItems(); + } + + public void SetDefaultBonusItems() + { + foreach (var slot in BonusExtensions.AllFlags) + SetBonusItem(slot, Penumbra.GameData.Structs.BonusItem.Empty(slot)); } diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index 08cb241..4a104ca 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -165,9 +165,21 @@ public class DesignEditor( } } + /// public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default) { - + var design = (Design)data; + if (!Items.IsBonusItemValid(slot, item.Id, out item)) + return; + + var oldItem = design.DesignData.BonusItem(slot); + if (!design.GetDesignDataRef().SetBonusItem(slot, item)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set {slot} bonus item to {item}."); + DesignChanged.Invoke(DesignChanged.Type.BonusItem, design, (oldItem, item, slot)); } /// diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 725d562..97058a9 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -356,7 +356,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {slot} bonus item to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyBonus, design, slot); + DesignChanged.Invoke(DesignChanged.Type.ApplyBonusItem, design, slot); } /// Change whether to apply a specific stain. diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 1588a96..199c7d4 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -62,6 +62,9 @@ public sealed class DesignChanged() /// An existing design had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. Equip, + /// An existing design had a bonus item changed. Data is the old value, the new value and the slot [(BonusItem, BonusItem, BonusItemFlag)]. + BonusItem, + /// An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand, the new offhand (if any) and the new gauntlets (if any). [(EquipItem, EquipItem, EquipItem, EquipItem?, EquipItem?)]. Weapon, @@ -90,7 +93,7 @@ public sealed class DesignChanged() ApplyEquip, /// An existing design changed whether a specific bonus item is applied. Data is the slot of the item [BonusItemFlag]. - ApplyBonus, + ApplyBonusItem, /// An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. ApplyStain, diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs index f10289f..09ce65a 100644 --- a/Glamourer/GameData/CustomizeParameterData.cs +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -12,7 +12,9 @@ public struct CustomizeParameterData public Vector3 HairSpecular; public Vector3 HairHighlight; public Vector3 LeftEye; + public float LeftScleraIntensity; public Vector3 RightEye; + public float RightScleraIntensity; public Vector3 FeatureColor; public float FacePaintUvMultiplier; public float FacePaintUvOffset; @@ -33,7 +35,9 @@ public struct CustomizeParameterData CustomizeParameterFlag.HairSpecular => new CustomizeParameterValue(HairSpecular), CustomizeParameterFlag.HairHighlight => new CustomizeParameterValue(HairHighlight), CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye), + CustomizeParameterFlag.LeftScleraIntensity => new CustomizeParameterValue(LeftScleraIntensity), CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye), + CustomizeParameterFlag.RightScleraIntensity => new CustomizeParameterValue(RightScleraIntensity), CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(FeatureColor), CustomizeParameterFlag.DecalColor => new CustomizeParameterValue(DecalColor), CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(FacePaintUvMultiplier), @@ -57,7 +61,9 @@ public struct CustomizeParameterData CustomizeParameterFlag.HairSpecular => SetIfDifferent(ref HairSpecular, value.InternalTriple), CustomizeParameterFlag.HairHighlight => SetIfDifferent(ref HairHighlight, value.InternalTriple), CustomizeParameterFlag.LeftEye => SetIfDifferent(ref LeftEye, value.InternalTriple), + CustomizeParameterFlag.LeftScleraIntensity => SetIfDifferent(ref LeftScleraIntensity, value.Single), CustomizeParameterFlag.RightEye => SetIfDifferent(ref RightEye, value.InternalTriple), + CustomizeParameterFlag.RightScleraIntensity => SetIfDifferent(ref RightScleraIntensity, value.Single), CustomizeParameterFlag.FeatureColor => SetIfDifferent(ref FeatureColor, value.InternalTriple), CustomizeParameterFlag.DecalColor => SetIfDifferent(ref DecalColor, value.InternalQuadruple), CustomizeParameterFlag.FacePaintUvMultiplier => SetIfDifferent(ref FacePaintUvMultiplier, value.Single), @@ -67,7 +73,7 @@ public struct CustomizeParameterData } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public readonly void Apply(ref CustomizeParameter parameters, CustomizeParameterFlag flags = CustomizeParameterExtensions.All) + public readonly unsafe void Apply(ref CustomizeParameter parameters, CustomizeParameterFlag flags = CustomizeParameterExtensions.All) { parameters.SkinColor = (flags & (CustomizeParameterFlag.SkinDiffuse | CustomizeParameterFlag.MuscleTone)) switch { @@ -77,20 +83,20 @@ public struct CustomizeParameterData _ => new CustomizeParameterValue(SkinDiffuse, MuscleTone).XivQuadruple, }; - parameters.LeftColor = (flags & (CustomizeParameterFlag.LeftEye | CustomizeParameterFlag.FacePaintUvMultiplier)) switch + parameters.LeftColor = (flags & (CustomizeParameterFlag.LeftEye | CustomizeParameterFlag.LeftScleraIntensity)) switch { - 0 => parameters.LeftColor, - CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple, - CustomizeParameterFlag.FacePaintUvMultiplier => parameters.LeftColor with { W = FacePaintUvMultiplier }, - _ => new CustomizeParameterValue(LeftEye, FacePaintUvMultiplier).XivQuadruple, + 0 => parameters.LeftColor, + CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple, + CustomizeParameterFlag.LeftScleraIntensity => parameters.LeftColor with { W = LeftScleraIntensity }, + _ => new CustomizeParameterValue(LeftEye, LeftScleraIntensity).XivQuadruple, }; - parameters.RightColor = (flags & (CustomizeParameterFlag.RightEye | CustomizeParameterFlag.FacePaintUvOffset)) switch + parameters.RightColor = (flags & (CustomizeParameterFlag.RightEye | CustomizeParameterFlag.RightScleraIntensity)) switch { - 0 => parameters.RightColor, - CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye, parameters.RightColor.W).XivQuadruple, - CustomizeParameterFlag.FacePaintUvOffset => parameters.RightColor with { W = FacePaintUvOffset }, - _ => new CustomizeParameterValue(RightEye, FacePaintUvOffset).XivQuadruple, + 0 => parameters.RightColor, + CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye, parameters.RightColor.W).XivQuadruple, + CustomizeParameterFlag.RightScleraIntensity => parameters.RightColor with { W = RightScleraIntensity }, + _ => new CustomizeParameterValue(RightEye, RightScleraIntensity).XivQuadruple, }; if (flags.HasFlag(CustomizeParameterFlag.SkinSpecular)) @@ -101,6 +107,10 @@ public struct CustomizeParameterData parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple; if (flags.HasFlag(CustomizeParameterFlag.HairHighlight)) parameters.MeshColor = new CustomizeParameterValue(HairHighlight).XivTriple; + if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvMultiplier)) + GetUvMultiplierWrite(ref parameters) = FacePaintUvMultiplier; + if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvOffset)) + GetUvOffsetWrite(ref parameters) = FacePaintUvOffset; if (flags.HasFlag(CustomizeParameterFlag.LipDiffuse)) parameters.LipColor = new CustomizeParameterValue(LipDiffuse).XivQuadruple; if (flags.HasFlag(CustomizeParameterFlag.FeatureColor)) @@ -115,7 +125,7 @@ public struct CustomizeParameterData } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public readonly void ApplySingle(ref CustomizeParameter parameters, CustomizeParameterFlag flag) + public readonly unsafe void ApplySingle(ref CustomizeParameter parameters, CustomizeParameterFlag flag) { switch (flag) { @@ -150,19 +160,25 @@ public struct CustomizeParameterData parameters.OptionColor = new CustomizeParameterValue(FeatureColor).XivTriple; break; case CustomizeParameterFlag.FacePaintUvMultiplier: - parameters.LeftColor.W = FacePaintUvMultiplier; + GetUvMultiplierWrite(ref parameters) = FacePaintUvMultiplier; break; case CustomizeParameterFlag.FacePaintUvOffset: - parameters.RightColor.W = FacePaintUvOffset; + GetUvOffsetWrite(ref parameters) = FacePaintUvOffset; + break; + case CustomizeParameterFlag.LeftScleraIntensity: + parameters.LeftColor.W = LeftScleraIntensity; + break; + case CustomizeParameterFlag.RightScleraIntensity: + parameters.RightColor.W = RightScleraIntensity; break; } } - public static CustomizeParameterData FromParameters(in CustomizeParameter parameter, in DecalParameters decal) + public static unsafe CustomizeParameterData FromParameters(in CustomizeParameter parameter, in DecalParameters decal) => new() { - FacePaintUvOffset = parameter.RightColor.W, - FacePaintUvMultiplier = parameter.LeftColor.W, + FacePaintUvOffset = GetUvOffset(parameter), + FacePaintUvMultiplier = GetUvMultiplier(parameter), MuscleTone = parameter.SkinColor.W, SkinDiffuse = new CustomizeParameterValue(parameter.SkinColor).InternalTriple, SkinSpecular = new CustomizeParameterValue(parameter.SkinFresnelValue0).InternalTriple, @@ -171,12 +187,14 @@ public struct CustomizeParameterData HairSpecular = new CustomizeParameterValue(parameter.HairFresnelValue0).InternalTriple, HairHighlight = new CustomizeParameterValue(parameter.MeshColor).InternalTriple, LeftEye = new CustomizeParameterValue(parameter.LeftColor).InternalTriple, + LeftScleraIntensity = new CustomizeParameterValue(parameter.LeftColor.W).Single, RightEye = new CustomizeParameterValue(parameter.RightColor).InternalTriple, + RightScleraIntensity = new CustomizeParameterValue(parameter.RightColor.W).Single, FeatureColor = new CustomizeParameterValue(parameter.OptionColor).InternalTriple, DecalColor = FromParameter(decal), }; - public static CustomizeParameterValue FromParameter(in CustomizeParameter parameter, CustomizeParameterFlag flag) + public static unsafe CustomizeParameterValue FromParameter(in CustomizeParameter parameter, CustomizeParameterFlag flag) => flag switch { CustomizeParameterFlag.SkinDiffuse => new CustomizeParameterValue(parameter.SkinColor), @@ -189,8 +207,8 @@ public struct CustomizeParameterData CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(parameter.LeftColor), CustomizeParameterFlag.RightEye => new CustomizeParameterValue(parameter.RightColor), CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(parameter.OptionColor), - CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(parameter.LeftColor.W), - CustomizeParameterFlag.FacePaintUvOffset => new CustomizeParameterValue(parameter.RightColor.W), + CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(GetUvMultiplier(parameter)), + CustomizeParameterFlag.FacePaintUvOffset => new CustomizeParameterValue(GetUvOffset(parameter)), _ => CustomizeParameterValue.Zero, }; @@ -223,4 +241,41 @@ public struct CustomizeParameterData val = @new; return true; } + + + private static unsafe float GetUvOffset(in CustomizeParameter parameter) + { + // TODO CS Update + fixed (CustomizeParameter* ptr = ¶meter) + { + return ((float*)ptr)[23]; + } + } + + private static unsafe ref float GetUvOffsetWrite(ref CustomizeParameter parameter) + { + // TODO CS Update + fixed (CustomizeParameter* ptr = ¶meter) + { + return ref ((float*)ptr)[23]; + } + } + + private static unsafe float GetUvMultiplier(in CustomizeParameter parameter) + { + // TODO CS Update + fixed (CustomizeParameter* ptr = ¶meter) + { + return ((float*)ptr)[15]; + } + } + + private static unsafe ref float GetUvMultiplierWrite(ref CustomizeParameter parameter) + { + // TODO CS Update + fixed (CustomizeParameter* ptr = ¶meter) + { + return ref ((float*)ptr)[15]; + } + } } diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index 59a3511..aabcdae 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -16,17 +16,24 @@ public enum CustomizeParameterFlag : ushort FacePaintUvMultiplier = 0x0400, FacePaintUvOffset = 0x0800, DecalColor = 0x1000, + LeftScleraIntensity = 0x2000, + RightScleraIntensity = 0x4000, } public static class CustomizeParameterExtensions { - public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x1FFF; + // Speculars are not available anymore. + public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x1FDB; public const CustomizeParameterFlag RgbTriples = All & ~(RgbaQuadruples | Percentages | Values); public const CustomizeParameterFlag RgbaQuadruples = CustomizeParameterFlag.DecalColor | CustomizeParameterFlag.LipDiffuse; - public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone; + + public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone + | CustomizeParameterFlag.LeftScleraIntensity + | CustomizeParameterFlag.RightScleraIntensity; + public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier; public static readonly IReadOnlyList AllFlags = [.. Enum.GetValues()]; @@ -60,6 +67,8 @@ public static class CustomizeParameterExtensions CustomizeParameterFlag.FacePaintUvMultiplier => "Multiplier for Face Paint", CustomizeParameterFlag.FacePaintUvOffset => "Offset of Face Paint", CustomizeParameterFlag.DecalColor => "Face Paint Color", + CustomizeParameterFlag.LeftScleraIntensity => "Left Sclera Intensity", + CustomizeParameterFlag.RightScleraIntensity => "Right Sclera Intensity", _ => string.Empty, }; } diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index afd3fe5..e65981e 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -158,22 +158,22 @@ public class EquipmentDrawer } } - public bool DrawAllStain(out StainId ret, bool locked) + public bool DrawAllStain(out StainIds ret, bool locked) { using var disabled = ImRaii.Disabled(locked); var change = _stainCombo.Draw("Dye All Slots", Stain.None.RgbaColor, string.Empty, false, false, MouseWheelType.None); - ret = Stain.None.RowIndex; + ret = StainIds.None; if (change) if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out var stain)) - ret = stain.RowIndex; + ret = StainIds.All(stain.RowIndex); else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex) - ret = Stain.None.RowIndex; + ret = StainIds.None; if (!locked) { if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _config.DeleteDesignModifier.IsActive()) { - ret = Stain.None.RowIndex; + ret = StainIds.None; change = true; } @@ -420,7 +420,7 @@ public class EquipmentDrawer private void DrawBonusItemNormal(in BonusDrawData bonusDrawData) { - ImGui.Dummy(_iconSize with { Y = ImUtf8.FrameHeight }); + bonusDrawData.CurrentItem.DrawIcon(_textures, _iconSize, bonusDrawData.Slot); var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); ImGui.SameLine(); diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 3160bcb..173109b 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -51,6 +51,8 @@ public sealed unsafe class AdvancedDyePopup( private void DrawButton(MaterialValueIndex index) { + // TODO fix when working + return; if (!config.UseAdvancedDyes) return; @@ -193,11 +195,11 @@ public sealed unsafe class AdvancedDyePopup( DrawWindow(textures); } - private void DrawTable(MaterialValueIndex materialIndex, in LegacyColorTable table) + private void DrawTable(MaterialValueIndex materialIndex, in ColorTable table) { using var disabled = ImRaii.Disabled(_state.IsLocked); _anyChanged = false; - for (byte i = 0; i < LegacyColorTable.NumUsedRows; ++i) + for (byte i = 0; i < ColorTable.NumUsedRows; ++i) { var index = materialIndex with { RowIndex = i }; ref var row = ref table[i]; @@ -208,7 +210,7 @@ public sealed unsafe class AdvancedDyePopup( DrawAllRow(materialIndex, table); } - private void DrawAllRow(MaterialValueIndex materialIndex, in LegacyColorTable table) + private void DrawAllRow(MaterialValueIndex materialIndex, in ColorTable table) { using var id = ImRaii.PushId(100); var buttonSize = new Vector2(ImGui.GetFrameHeight()); @@ -245,11 +247,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 < LegacyColorTable.NumUsedRows; ++i) + for (byte i = 0; i < ColorTable.NumUsedRows; ++i) stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = i }, ApplySettings.Game); } - private void DrawRow(ref LegacyColorTable.Row row, MaterialValueIndex index, in LegacyColorTable table) + private void DrawRow(ref ColorTable.Row row, MaterialValueIndex index, in ColorTable 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 4d99018..4213cc5 100644 --- a/Glamourer/Gui/Materials/ColorRowClipboard.cs +++ b/Glamourer/Gui/Materials/ColorRowClipboard.cs @@ -5,14 +5,14 @@ namespace Glamourer.Gui.Materials; public static class ColorRowClipboard { - private static ColorRow _row; - private static LegacyColorTable _table; + private static ColorRow _row; + private static ColorTable _table; public static bool IsSet { get; private set; } public static bool IsTableSet { get; private set; } - public static LegacyColorTable Table + public static ColorTable Table { get => _table; set diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 26432e9..e7a061f 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -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, LegacyColorTable.NumUsedRows, "Row #%i")) + if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, ColorTable.NumUsedRows, "Row #%i")) { - _newRowIdx = Math.Clamp(_newRowIdx, 1, LegacyColorTable.NumUsedRows); + _newRowIdx = Math.Clamp(_newRowIdx, 1, ColorTable.NumUsedRows); _newKey = _newKey with { RowIndex = (byte)(_newRowIdx - 1) }; } diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 0a29314..c875cf1 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -9,6 +9,7 @@ using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using FFXIVClientStructs.FFXIV.Shader; namespace Glamourer.Gui.Tabs.DebugTab; @@ -94,7 +95,8 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM foreach (var type in Enum.GetValues()) { - PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state.Sources[type]); + PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, + state.Sources[type]); ImGui.TableNextRow(); } diff --git a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs new file mode 100644 index 0000000..4d6a2cd --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs @@ -0,0 +1,135 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using Glamourer.Interop; +using ImGuiNET; +using OtterGui.Raii; +using OtterGui.Text; +using Penumbra.GameData.Gui.Debug; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public unsafe class AdvancedCustomizationDrawer(ObjectManager objects) : IGameDataDrawer +{ + public string Label + => "Advanced Customizations"; + + public bool Disabled + => false; + + public void Draw() + { + var (player, data) = objects.PlayerData; + if (!data.Valid) + { + ImUtf8.Text("Invalid player."u8); + return; + } + + var model = data.Objects[0].Model; + if (!model.IsHuman) + { + ImUtf8.Text("Invalid model."u8); + return; + } + + DrawCBuffer("Customize"u8, model.AsHuman->CustomizeParameterCBuffer, 0); + DrawCBuffer("Decal"u8, model.AsHuman->DecalColorCBuffer, 1); + DrawCBuffer("Unk1"u8, *(ConstantBuffer**)((byte*)model.AsHuman + 0xBA0), 2); + DrawCBuffer("Unk2"u8, *(ConstantBuffer**)((byte*)model.AsHuman + 0xBA8), 3); + } + + + private static void DrawCBuffer(ReadOnlySpan label, ConstantBuffer* cBuffer, int type) + { + using var tree = ImUtf8.TreeNode(label); + if (!tree) + return; + + if (cBuffer == null) + { + ImUtf8.Text("Invalid CBuffer."u8); + return; + } + + ImUtf8.Text($"{cBuffer->ByteSize / 4}"); + ImUtf8.Text($"{cBuffer->Flags}"); + ImUtf8.Text($"0x{(ulong)cBuffer:X}"); + var parameters = (float*)cBuffer->UnsafeSourcePointer; + if (parameters == null) + { + ImUtf8.Text("No Parameters."u8); + return; + } + + var start = parameters; + using (ImUtf8.Group()) + { + for (var end = start + cBuffer->ByteSize / 4; parameters < end; parameters += 2) + DrawParameters(parameters, type, (int)(parameters - start)); + } + + ImGui.SameLine(0, 50 * ImUtf8.GlobalScale); + parameters = start + 1; + using (ImUtf8.Group()) + { + for (var end = start + cBuffer->ByteSize / 4; parameters < end; parameters += 2) + DrawParameters(parameters, type, (int)(parameters - start)); + } + } + + private static void DrawParameters(float* param, int type, int idx) + { + using var id = ImUtf8.PushId((nint)param); + ImUtf8.TextFrameAligned($"{idx:D2}: "); + ImUtf8.SameLineInner(); + ImGui.SetNextItemWidth(200 * ImUtf8.GlobalScale); + if (TryGetKnown(type, idx, out var known)) + { + ImUtf8.DragScalar(known, ref *param, float.MinValue, float.MaxValue, 0.01f); + } + else + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled)); + ImUtf8.DragScalar($"+0x{idx * 4:X2}", ref *param, float.MinValue, float.MaxValue, 0.01f); + } + } + + private static bool TryGetKnown(int type, int idx, out ReadOnlySpan text) + { + if (type == 0) + text = idx switch + { + 0 => "Diffuse.R"u8, + 1 => "Diffuse.G"u8, + 2 => "Diffuse.B"u8, + 3 => "Muscle Tone"u8, + 8 => "Lipstick.R"u8, + 9 => "Lipstick.G"u8, + 10 => "Lipstick.B"u8, + 11 => "Lipstick.Opacity"u8, + 12 => "Hair.R"u8, + 13 => "Hair.G"u8, + 14 => "Hair.B"u8, + 15 => "Facepaint.Offset"u8, + 20 => "Highlight.R"u8, + 21 => "Highlight.G"u8, + 22 => "Highlight.B"u8, + 23 => "Facepaint.Multiplier"u8, + 24 => "LeftEye.R"u8, + 25 => "LeftEye.G"u8, + 26 => "LeftEye.B"u8, + 27 => "LeftSclera"u8, + 28 => "RightEye.R"u8, + 29 => "RightEye.G"u8, + 30 => "RightEye.B"u8, + 31 => "RightSclera"u8, + 32 => "Feature.R"u8, + 33 => "Feature.G"u8, + 34 => "Feature.B"u8, + _ => [], + }; + else + text = []; + + return text.Length > 0; + } +} diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index 3dce124..90282e8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -38,7 +38,8 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), - provider.GetRequiredService() + provider.GetRequiredService(), + provider.GetRequiredService() ); public static DebugTabHeader CreateGameData(IServiceProvider provider) diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 9749ce6..d1624f6 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -1,5 +1,6 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.Utility; +using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Services; @@ -12,23 +13,23 @@ using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.UnlocksTab; -public class UnlockOverview +public class UnlockOverview( + ItemManager items, + CustomizeService customizations, + ItemUnlockManager itemUnlocks, + CustomizeUnlockManager customizeUnlocks, + PenumbraChangedItemTooltip tooltip, + TextureService textures, + CodeService codes, + JobService jobs, + FavoriteManager favorites) { - private readonly ItemManager _items; - private readonly ItemUnlockManager _itemUnlocks; - private readonly CustomizeService _customizations; - private readonly CustomizeUnlockManager _customizeUnlocks; - private readonly PenumbraChangedItemTooltip _tooltip; - private readonly TextureService _textures; - private readonly CodeService _codes; - private readonly JobService _jobs; - private readonly FavoriteManager _favorites; - private static readonly Vector4 UnavailableTint = new(0.3f, 0.3f, 0.3f, 1.0f); private FullEquipType _selected1 = FullEquipType.Unknown; private SubRace _selected2 = SubRace.Unknown; private Gender _selected3 = Gender.Unknown; + private BonusItemFlag _selected4 = BonusItemFlag.Unknown; private void DrawSelector() { @@ -38,7 +39,7 @@ public class UnlockOverview foreach (var type in Enum.GetValues()) { - if (type.IsOffhandType() || !_items.ItemData.ByType.TryGetValue(type, out var items) || items.Count == 0) + if (type.IsOffhandType() || !items.ItemData.ByType.TryGetValue(type, out var value) || value.Count == 0) continue; if (ImGui.Selectable(type.ToName(), _selected1 == type)) @@ -46,12 +47,21 @@ public class UnlockOverview _selected1 = type; _selected2 = SubRace.Unknown; _selected3 = Gender.Unknown; + _selected4 = BonusItemFlag.Unknown; } } + if (ImGui.Selectable("Bonus Items", _selected4 == BonusItemFlag.Glasses)) + { + _selected1 = FullEquipType.Unknown; + _selected2 = SubRace.Unknown; + _selected3 = Gender.Unknown; + _selected4 = BonusItemFlag.Glasses; + } + foreach (var (clan, gender) in CustomizeManager.AllSets()) { - if (_customizations.Manager.GetSet(clan, gender).HairStyles.Count == 0) + if (customizations.Manager.GetSet(clan, gender).HairStyles.Count == 0) continue; if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint", @@ -60,25 +70,11 @@ public class UnlockOverview _selected1 = FullEquipType.Unknown; _selected2 = clan; _selected3 = gender; + _selected4 = BonusItemFlag.Unknown; } } } - public UnlockOverview(ItemManager items, CustomizeService customizations, ItemUnlockManager itemUnlocks, - CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureService textures, CodeService codes, - JobService jobs, FavoriteManager favorites) - { - _items = items; - _customizations = customizations; - _itemUnlocks = itemUnlocks; - _customizeUnlocks = customizeUnlocks; - _tooltip = tooltip; - _textures = textures; - _codes = codes; - _jobs = jobs; - _favorites = favorites; - } - public void Draw() { using var color = ImRaii.PushColor(ImGuiCol.Border, ImGui.GetColorU32(ImGuiCol.TableBorderStrong)); @@ -97,11 +93,13 @@ public class UnlockOverview DrawItems(); else if (_selected2 is not SubRace.Unknown && _selected3 is not Gender.Unknown) DrawCustomizations(); + else if (_selected4 is not BonusItemFlag.Unknown) + DrawBonusItems(); } private void DrawCustomizations() { - var set = _customizations.Manager.GetSet(_selected2, _selected3); + var set = customizations.Manager.GetSet(_selected2, _selected3); var spacing = IconSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -111,16 +109,16 @@ public class UnlockOverview var counter = 0; foreach (var customize in set.HairStyles.Concat(set.FacePaints)) { - if (!_customizeUnlocks.Unlockable.TryGetValue(customize, out var unlockData)) + if (!customizeUnlocks.Unlockable.TryGetValue(customize, out var unlockData)) continue; - var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time); - var icon = _customizations.Manager.GetIcon(customize.IconId); + var unlocked = customizeUnlocks.IsUnlocked(customize, out var time); + var icon = customizations.Manager.GetIcon(customize.IconId); var hasIcon = icon.TryGetWrap(out var wrap, out _); ImGui.Image(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, - unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); + unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); - if (_favorites.Contains(_selected3, _selected2, customize.Index, customize.Value)) + if (favorites.Contains(_selected3, _selected2, customize.Index, customize.Value)) ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), 12 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 6 * ImGuiHelpers.GlobalScale); @@ -147,24 +145,90 @@ public class UnlockOverview } } + private void DrawBonusItems() + { + var spacing = IconSpacing; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); + var iconSize = ImGuiHelpers.ScaledVector2(64); + var iconsPerRow = IconsPerRow(iconSize.X, spacing.X); + var numRows = (items.DictBonusItems.Count + iconsPerRow - 1) / iconsPerRow; + var numVisibleRows = (int)(Math.Ceiling(ImGui.GetContentRegionAvail().Y / (iconSize.Y + spacing.Y)) + 0.5f) + 1; + + var skips = ImGuiClip.GetNecessarySkips(iconSize.Y + spacing.Y); + var start = skips * iconsPerRow; + var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, items.DictBonusItems.Count); + var counter = 0; + + foreach (var item in items.DictBonusItems.Values.Skip(start).Take(end - start)) + { + DrawItem(item); + if (counter != iconsPerRow - 1) + { + ImGui.SameLine(); + ++counter; + } + else + { + counter = 0; + } + } + + if (ImGui.GetCursorPosX() != 0) + ImGui.NewLine(); + var remainder = numRows - numVisibleRows - skips; + if (remainder > 0) + ImGuiClip.DrawEndDummy(remainder, iconSize.Y + spacing.Y); + + void DrawItem(BonusItem item) + { + // TODO check unlocks + var unlocked = true; + if (!textures.TryLoadIcon(item.Icon.Id, out var iconHandle)) + return; + + var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); + + ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, + unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); + if (favorites.Contains(item)) + ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), + 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); + + // TODO handle clicking + if (ImGui.IsItemHovered()) + { + using var tt = ImRaii.Tooltip(); + if (size.X >= iconSize.X && size.Y >= iconSize.Y) + ImGui.Image(icon, size); + ImGui.TextUnformatted(item.Name); + ImGui.TextUnformatted($"{item.Slot.ToName()}"); + ImGui.TextUnformatted($"{item.ModelId.Id}-{item.Variant.Id}"); + // TODO + ImGui.TextUnformatted("Always Unlocked"); // : $"Unlocked on {time:g}" : "Not Unlocked."); + // TODO + //tooltip.CreateTooltip(item, string.Empty, false); + } + } + } + private void DrawItems() { - if (!_items.ItemData.ByType.TryGetValue(_selected1, out var items)) + if (!items.ItemData.ByType.TryGetValue(_selected1, out var value)) return; var spacing = IconSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); var iconSize = ImGuiHelpers.ScaledVector2(64); var iconsPerRow = IconsPerRow(iconSize.X, spacing.X); - var numRows = (items.Count + iconsPerRow - 1) / iconsPerRow; + var numRows = (value.Count + iconsPerRow - 1) / iconsPerRow; var numVisibleRows = (int)(Math.Ceiling(ImGui.GetContentRegionAvail().Y / (iconSize.Y + spacing.Y)) + 0.5f) + 1; var skips = ImGuiClip.GetNecessarySkips(iconSize.Y + spacing.Y); - var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, items.Count); + var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, value.Count); var counter = 0; for (var idx = skips * iconsPerRow; idx < end; ++idx) { - DrawItem(items[idx]); + DrawItem(value[idx]); if (counter != iconsPerRow - 1) { ImGui.SameLine(); @@ -185,23 +249,23 @@ public class UnlockOverview void DrawItem(EquipItem item) { - var unlocked = _itemUnlocks.IsUnlocked(item.Id, out var time); - if (!_textures.TryLoadIcon(item.IconId.Id, out var iconHandle)) + var unlocked = itemUnlocks.IsUnlocked(item.Id, out var time); + if (!textures.TryLoadIcon(item.IconId.Id, out var iconHandle)) return; var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, - unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); - if (_favorites.Contains(item)) + unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); + if (favorites.Contains(item)) ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); if (ImGui.IsItemClicked()) Glamourer.Messager.Chat.Print(new SeStringBuilder().AddItemLink(item.ItemId.Id, false).BuiltString); - if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _tooltip.Player(out var state)) - _tooltip.ApplyItem(state, item); + if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && tooltip.Player(out var state)) + tooltip.ApplyItem(state, item); if (ImGui.IsItemHovered()) { @@ -213,7 +277,7 @@ public class UnlockOverview ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})"); if (item.Type.ValidOffhand().IsOffhandType()) ImGui.TextUnformatted( - $"{item.Weapon()}{(_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}"); + $"{item.Weapon()}{(items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}"); else ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}"); ImGui.TextUnformatted( @@ -221,17 +285,17 @@ public class UnlockOverview if (item.Level.Value <= 1) { - if (item.JobRestrictions.Id <= 1 || item.JobRestrictions.Id >= _jobs.AllJobGroups.Count) + if (item.JobRestrictions.Id <= 1 || item.JobRestrictions.Id >= jobs.AllJobGroups.Count) ImGui.TextUnformatted("For Everyone"); else - ImGui.TextUnformatted($"For all {_jobs.AllJobGroups[item.JobRestrictions.Id].Name}"); + ImGui.TextUnformatted($"For all {jobs.AllJobGroups[item.JobRestrictions.Id].Name}"); } else { - if (item.JobRestrictions.Id <= 1 || item.JobRestrictions.Id >= _jobs.AllJobGroups.Count) + if (item.JobRestrictions.Id <= 1 || item.JobRestrictions.Id >= jobs.AllJobGroups.Count) ImGui.TextUnformatted($"For Everyone of at least Level {item.Level}"); else - ImGui.TextUnformatted($"For all {_jobs.AllJobGroups[item.JobRestrictions.Id].Name} of at least Level {item.Level}"); + ImGui.TextUnformatted($"For all {jobs.AllJobGroups[item.JobRestrictions.Id].Name} of at least Level {item.Level}"); } if (item.Flags.HasFlag(ItemFlags.IsDyable1)) @@ -240,7 +304,7 @@ public class UnlockOverview ImGui.TextUnformatted("Tradable"); if (item.Flags.HasFlag(ItemFlags.IsCrestWorthy)) ImGui.TextUnformatted("Can apply Crest"); - _tooltip.CreateTooltip(item, string.Empty, false); + tooltip.CreateTooltip(item, string.Empty, false); } } } diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 88f51f5..8499728 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -29,8 +29,30 @@ public static class UiHelpers if (empty) { var (bgColor, tint) = isEmpty - ? (ImGui.GetColorU32(ImGuiCol.FrameBg), new Vector4(0.1f, 0.1f, 0.1f, 0.5f)) - : (ImGui.GetColorU32(ImGuiCol.FrameBgActive), new Vector4(0.3f, 0.3f, 0.3f, 0.8f)); + ? (ImGui.GetColorU32(ImGuiCol.FrameBg), Vector4.One) + : (ImGui.GetColorU32(ImGuiCol.FrameBgActive), new Vector4(0.3f, 0.3f, 0.3f, 1f)); + var pos = ImGui.GetCursorScreenPos(); + ImGui.GetWindowDrawList().AddRectFilled(pos, pos + size, bgColor, 5 * ImGuiHelpers.GlobalScale); + if (ptr != nint.Zero) + ImGui.Image(ptr, size, Vector2.Zero, Vector2.One, tint); + else + ImGui.Dummy(size); + } + else + { + ImGuiUtil.HoverIcon(ptr, textureSize, size); + } + } + + public static void DrawIcon(this BonusItem item, TextureService textures, Vector2 size, BonusItemFlag slot) + { + var isEmpty = item.ModelId.Id == 0; + var (ptr, textureSize, empty) = textures.GetIcon(item, slot); + if (empty) + { + var (bgColor, tint) = isEmpty + ? (ImGui.GetColorU32(ImGuiCol.FrameBg), Vector4.One) + : (ImGui.GetColorU32(ImGuiCol.FrameBgActive), new Vector4(0.3f, 0.3f, 0.3f, 1f)); var pos = ImGui.GetCursorScreenPos(); ImGui.GetWindowDrawList().AddRectFilled(pos, pos + size, bgColor, 5 * ImGuiHelpers.GlobalScale); if (ptr != nint.Zero) diff --git a/Glamourer/Interop/Material/DirectXService.cs b/Glamourer/Interop/Material/DirectXService.cs index 6d9c71b..3316491 100644 --- a/Glamourer/Interop/Material/DirectXService.cs +++ b/Glamourer/Interop/Material/DirectXService.cs @@ -15,13 +15,13 @@ namespace Glamourer.Interop.Material; public unsafe class DirectXService(IFramework framework) : IService { private readonly object _lock = new(); - private readonly ConcurrentDictionary _textures = []; + 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 LegacyColorTable colorTable) + public bool ReplaceColorTable(Texture** original, in ColorTable colorTable) { if (original == null) return false; @@ -38,7 +38,7 @@ public unsafe class DirectXService(IFramework framework) : IService if (texture.IsInvalid) return false; - fixed (LegacyColorTable* ptr = &colorTable) + fixed (ColorTable* 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 LegacyColorTable table) + public bool TryGetColorTable(Texture* texture, out ColorTable 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 LegacyColorTable table) + private static bool TextureColorTable(Texture* texture, out ColorTable 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 LegacyColorTable GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map) + private static ColorTable 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 LegacyColorTable ReadTexture(nint data, int length, int height, int pitch) + private static ColorTable 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(LegacyColorTable) != expectedSize || height != MaterialService.TextureHeight) + if (length < expectedSize || sizeof(ColorTable) != expectedSize || height != MaterialService.TextureHeight) return default; - var ret = new LegacyColorTable(); + var ret = new ColorTable(); 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 aa4c358..a9f3a74 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -13,11 +13,11 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable private readonly DirectXService _directXService; public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid; - public LegacyColorTable LastOriginalColorTable { get; private set; } + public ColorTable LastOriginalColorTable { get; private set; } private MaterialValueIndex _valueIndex = MaterialValueIndex.Invalid; private ObjectIndex _lastObjectIndex = ObjectIndex.AnyIndex; private ObjectIndex _objectIndex = ObjectIndex.AnyIndex; - private LegacyColorTable _originalColorTable; + private ColorTable _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 < LegacyColorTable.NumUsedRows; ++i) + for (var i = 0; i < ColorTable.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, LegacyColorTable table) + public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, ColorTable 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 3e984c8..5f2b553 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -40,6 +40,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainId stain, ref nint ret) { + // TODO fix when working + return; if (!_config.UseAdvancedDyes) return; @@ -76,7 +78,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 LegacyColorTable colorTable) + ref ColorTable colorTable) { var deleteList = _deleteList.Value!; deleteList.Clear(); diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index 4c8706c..b2626be 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -9,11 +9,11 @@ namespace Glamourer.Interop.Material; public static unsafe class MaterialService { - public const int TextureWidth = 4; - public const int TextureHeight = LegacyColorTable.NumUsedRows; - public const int MaterialsPerModel = 4; + public const int TextureWidth = 8; + public const int TextureHeight = ColorTable.NumUsedRows; + public const int MaterialsPerModel = 10; - public static bool GenerateNewColorTable(in LegacyColorTable colorTable, out Texture* texture) + public static bool GenerateNewColorTable(in ColorTable 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 (LegacyColorTable* ptr = &colorTable) + fixed (ColorTable* 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 LegacyColorTable* GetMaterialColorTable(Model model, int modelSlot, byte materialSlot) + public static ColorTable* 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 (LegacyColorTable*)material->ColorTable; + return (ColorTable*)material->ColorTable; } } diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 254675e..8101c05 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -149,7 +149,7 @@ public readonly record struct MaterialValueIndex( => materialIndex < MaterialService.MaterialsPerModel; public static bool ValidateRow(byte rowIndex) - => rowIndex < LegacyColorTable.NumUsedRows; + => rowIndex < ColorTable.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 ae08c71..897f1bf 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -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 LegacyColorTable.Row row) + public ColorRow(in ColorTable.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 LegacyColorTable.Row row) + public readonly bool Apply(ref ColorTable.Row row) { var ret = false; var d = Square(Diffuse); diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 3866d74..cdbff11 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -56,7 +56,7 @@ public sealed unsafe class PrepareColorSet } public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainIds stainIds, - out LegacyColorTable table) + out ColorTable table) { if (material->ColorTable == null) { @@ -64,7 +64,7 @@ public sealed unsafe class PrepareColorSet return false; } - var newTable = *(LegacyColorTable*)material->ColorTable; + var newTable = *(ColorTable*)material->ColorTable; // TODO //if (stainIds.Stain1.Id != 0 || stainIds.Stain2.Id != 0) // characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); @@ -73,7 +73,7 @@ public sealed unsafe class PrepareColorSet } /// Assumes the actor is valid. - public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out LegacyColorTable table) + public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out ColorTable table) { var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex; if (!index.TryGetModel(actor, out var model)) diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index 5ab2a40..b0bdd19 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -13,7 +13,6 @@ public unsafe class WeaponService : IDisposable private readonly WeaponLoading _event; private readonly ThreadLocal _inUpdate = new(() => false); - private readonly delegate* unmanaged[Stdcall] _original; diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index 99436e4..e08ab44 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -23,6 +23,21 @@ public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManage : (nint.Zero, Vector2.Zero, true); } + public (nint, Vector2, bool) GetIcon(BonusItem item, BonusItemFlag slot) + { + if (item.Icon.Id != 0 && TryLoadIcon(item.Icon.Id, out var ret)) + return (ret.ImGuiHandle, new Vector2(ret.Width, ret.Height), false); + + var idx = slot.ToIndex(); + if (idx == uint.MaxValue) + return (nint.Zero, Vector2.Zero, true); + + idx += 12; + return idx < 13 && _slotIcons[idx] != null + ? (_slotIcons[idx]!.ImGuiHandle, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) + : (nint.Zero, Vector2.Zero, true); + } + public void Dispose() { for (var i = 0; i < _slotIcons.Length; ++i) @@ -34,9 +49,9 @@ public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManage private static IDalamudTextureWrap?[] CreateSlotIcons(IUiBuilder uiBuilder) { - var ret = new IDalamudTextureWrap?[12]; + var ret = new IDalamudTextureWrap?[13]; - using var uldWrapper = uiBuilder.LoadUld("ui/uld/ArmouryBoard.uld"); + using var uldWrapper = uiBuilder.LoadUld("ui/uld/Character.uld"); if (!uldWrapper.Valid) { @@ -44,33 +59,37 @@ public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManage return ret; } - SetIcon(EquipSlot.Head, 1); - SetIcon(EquipSlot.Body, 2); - SetIcon(EquipSlot.Hands, 3); - SetIcon(EquipSlot.Legs, 5); - SetIcon(EquipSlot.Feet, 6); - SetIcon(EquipSlot.Ears, 8); - SetIcon(EquipSlot.Neck, 9); - SetIcon(EquipSlot.Wrists, 10); - SetIcon(EquipSlot.RFinger, 11); - SetIcon(EquipSlot.MainHand, 0); - SetIcon(EquipSlot.OffHand, 7); + SetIcon(EquipSlot.Head, 19); + SetIcon(EquipSlot.Body, 20); + SetIcon(EquipSlot.Hands, 21); + SetIcon(EquipSlot.Legs, 23); + SetIcon(EquipSlot.Feet, 24); + SetIcon(EquipSlot.Ears, 25); + SetIcon(EquipSlot.Neck, 26); + SetIcon(EquipSlot.Wrists, 27); + SetIcon(EquipSlot.RFinger, 28); + SetIcon(EquipSlot.MainHand, 17); + SetIcon(EquipSlot.OffHand, 18); + Set(BonusItemFlag.Glasses.ToName(), (int) BonusItemFlag.Glasses.ToIndex() + 12, 55); ret[EquipSlot.LFinger.ToIndex()] = ret[EquipSlot.RFinger.ToIndex()]; return ret; - void SetIcon(EquipSlot slot, int index) + void Set(string name, int slot, int index) { try { - ret[slot.ToIndex()] = uldWrapper.LoadTexturePart("ui/uld/ArmouryBoard_hr1.tex", index)!; + ret[slot] = uldWrapper.LoadTexturePart("ui/uld/Character_hr1.tex", index)!; } catch (Exception ex) { - Glamourer.Log.Error($"Could not get empty slot texture for {slot.ToName()}, icon will be left empty. " + Glamourer.Log.Error($"Could not get empty slot texture for {name}, icon will be left empty. " + $"This may be because of incompatible mods affecting your character screen interface:\n{ex}"); - ret[slot.ToIndex()] = null; + ret[slot] = null; } } + + void SetIcon(EquipSlot slot, int index) + => Set(slot.ToName(), (int)slot.ToIndex(), index); } } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index a4a1df4..2af6437 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -306,6 +306,8 @@ public class StateApplier( public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, ColorRow? value, bool force) { + // TODO fix when working + return; if (!force && !_config.UseAdvancedDyes) return; @@ -338,6 +340,8 @@ public class StateApplier( public unsafe void ChangeMaterialValues(ActorData data, in StateMaterialManager materials, bool force) { + // TODO: fix when working + return; if (!force && !_config.UseAdvancedDyes) return; @@ -383,6 +387,11 @@ public class StateApplier( ChangeCustomize(actors, state.ModelData.Customize); foreach (var slot in EquipSlotExtensions.EqdpSlots) ChangeArmor(actors, slot, state.ModelData.Armor(slot), !state.Sources[slot, false].IsIpc(), state.ModelData.IsHatVisible()); + foreach (var slot in BonusExtensions.AllFlags) + { + var item = state.ModelData.BonusItem(slot); + ChangeBonusItem(actors, slot, item.ModelId, item.Variant); + } var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index c39fd31..c627564 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -175,7 +175,7 @@ public class StateEditor( var @new = state.ModelData.Parameters[flag]; var actors = Applier.ChangeParameters(state, flag, settings.Source.RequiresChange()); Glamourer.Log.Verbose( - $"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); + $"Set {flag} in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Parameter, settings.Source, state, actors, (old, @new, flag)); } diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs index a569499..e1c4ea0 100644 --- a/Glamourer/State/StateIndex.cs +++ b/Glamourer/State/StateIndex.cs @@ -110,7 +110,9 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators new StateIndex(ParamHairSpecular), CustomizeParameterFlag.HairHighlight => new StateIndex(ParamHairHighlight), CustomizeParameterFlag.LeftEye => new StateIndex(ParamLeftEye), + CustomizeParameterFlag.LeftScleraIntensity => new StateIndex(ParamLeftScleraIntensity), CustomizeParameterFlag.RightEye => new StateIndex(ParamRightEye), + CustomizeParameterFlag.RightScleraIntensity => new StateIndex(ParamRightScleraIntensity), CustomizeParameterFlag.FeatureColor => new StateIndex(ParamFeatureColor), CustomizeParameterFlag.FacePaintUvMultiplier => new StateIndex(ParamFacePaintUvMultiplier), CustomizeParameterFlag.FacePaintUvOffset => new StateIndex(ParamFacePaintUvOffset), @@ -199,8 +201,10 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators CustomizeParameterFlag.HairSpecular, ParamHairHighlight => CustomizeParameterFlag.HairHighlight, ParamLeftEye => CustomizeParameterFlag.LeftEye, + ParamLeftScleraIntensity => CustomizeParameterFlag.LeftScleraIntensity, ParamRightEye => CustomizeParameterFlag.RightEye, + ParamRightScleraIntensity => CustomizeParameterFlag.RightScleraIntensity, ParamFeatureColor => CustomizeParameterFlag.FeatureColor, ParamFacePaintUvMultiplier => CustomizeParameterFlag.FacePaintUvMultiplier, ParamFacePaintUvOffset => CustomizeParameterFlag.FacePaintUvOffset, @@ -320,103 +326,6 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators -1, }; - public object? GetValue(in DesignData data) - { - return Value switch - { - EquipHead => data.Item(EquipSlot.Head), - EquipBody => data.Item(EquipSlot.Body), - EquipHands => data.Item(EquipSlot.Hands), - EquipLegs => data.Item(EquipSlot.Legs), - EquipFeet => data.Item(EquipSlot.Feet), - EquipEars => data.Item(EquipSlot.Ears), - EquipNeck => data.Item(EquipSlot.Neck), - EquipWrist => data.Item(EquipSlot.Wrists), - EquipRFinger => data.Item(EquipSlot.RFinger), - EquipLFinger => data.Item(EquipSlot.LFinger), - EquipMainhand => data.Item(EquipSlot.MainHand), - EquipOffhand => data.Item(EquipSlot.OffHand), - - StainHead => data.Stain(EquipSlot.Head), - StainBody => data.Stain(EquipSlot.Body), - StainHands => data.Stain(EquipSlot.Hands), - StainLegs => data.Stain(EquipSlot.Legs), - StainFeet => data.Stain(EquipSlot.Feet), - StainEars => data.Stain(EquipSlot.Ears), - StainNeck => data.Stain(EquipSlot.Neck), - StainWrist => data.Stain(EquipSlot.Wrists), - StainRFinger => data.Stain(EquipSlot.RFinger), - StainLFinger => data.Stain(EquipSlot.LFinger), - StainMainhand => data.Stain(EquipSlot.MainHand), - StainOffhand => data.Stain(EquipSlot.OffHand), - - CustomizeRace => data.Customize[CustomizeIndex.Race], - CustomizeGender => data.Customize[CustomizeIndex.Gender], - CustomizeBodyType => data.Customize[CustomizeIndex.BodyType], - CustomizeHeight => data.Customize[CustomizeIndex.Height], - CustomizeClan => data.Customize[CustomizeIndex.Clan], - CustomizeFace => data.Customize[CustomizeIndex.Face], - CustomizeHairstyle => data.Customize[CustomizeIndex.Hairstyle], - CustomizeHighlights => data.Customize[CustomizeIndex.Highlights], - CustomizeSkinColor => data.Customize[CustomizeIndex.SkinColor], - CustomizeEyeColorRight => data.Customize[CustomizeIndex.EyeColorRight], - CustomizeHairColor => data.Customize[CustomizeIndex.HairColor], - CustomizeHighlightsColor => data.Customize[CustomizeIndex.HighlightsColor], - CustomizeFacialFeature1 => data.Customize[CustomizeIndex.FacialFeature1], - CustomizeFacialFeature2 => data.Customize[CustomizeIndex.FacialFeature2], - CustomizeFacialFeature3 => data.Customize[CustomizeIndex.FacialFeature3], - CustomizeFacialFeature4 => data.Customize[CustomizeIndex.FacialFeature4], - CustomizeFacialFeature5 => data.Customize[CustomizeIndex.FacialFeature5], - CustomizeFacialFeature6 => data.Customize[CustomizeIndex.FacialFeature6], - CustomizeFacialFeature7 => data.Customize[CustomizeIndex.FacialFeature7], - CustomizeLegacyTattoo => data.Customize[CustomizeIndex.LegacyTattoo], - CustomizeTattooColor => data.Customize[CustomizeIndex.TattooColor], - CustomizeEyebrows => data.Customize[CustomizeIndex.Eyebrows], - CustomizeEyeColorLeft => data.Customize[CustomizeIndex.EyeColorLeft], - CustomizeEyeShape => data.Customize[CustomizeIndex.EyeShape], - CustomizeSmallIris => data.Customize[CustomizeIndex.SmallIris], - CustomizeNose => data.Customize[CustomizeIndex.Nose], - CustomizeJaw => data.Customize[CustomizeIndex.Jaw], - CustomizeMouth => data.Customize[CustomizeIndex.Mouth], - CustomizeLipstick => data.Customize[CustomizeIndex.Lipstick], - CustomizeLipColor => data.Customize[CustomizeIndex.LipColor], - CustomizeMuscleMass => data.Customize[CustomizeIndex.MuscleMass], - CustomizeTailShape => data.Customize[CustomizeIndex.TailShape], - CustomizeBustSize => data.Customize[CustomizeIndex.BustSize], - CustomizeFacePaint => data.Customize[CustomizeIndex.FacePaint], - CustomizeFacePaintReversed => data.Customize[CustomizeIndex.FacePaintReversed], - CustomizeFacePaintColor => data.Customize[CustomizeIndex.FacePaintColor], - - MetaWetness => data.GetMeta(MetaIndex.Wetness), - MetaHatState => data.GetMeta(MetaIndex.HatState), - MetaVisorState => data.GetMeta(MetaIndex.VisorState), - MetaWeaponState => data.GetMeta(MetaIndex.WeaponState), - MetaModelId => data.ModelId, - - CrestHead => data.Crest(CrestFlag.Head), - CrestBody => data.Crest(CrestFlag.Body), - CrestOffhand => data.Crest(CrestFlag.OffHand), - - ParamSkinDiffuse => data.Parameters[CustomizeParameterFlag.SkinDiffuse], - ParamMuscleTone => data.Parameters[CustomizeParameterFlag.MuscleTone], - ParamSkinSpecular => data.Parameters[CustomizeParameterFlag.SkinSpecular], - ParamLipDiffuse => data.Parameters[CustomizeParameterFlag.LipDiffuse], - ParamHairDiffuse => data.Parameters[CustomizeParameterFlag.HairDiffuse], - ParamHairSpecular => data.Parameters[CustomizeParameterFlag.HairSpecular], - ParamHairHighlight => data.Parameters[CustomizeParameterFlag.HairHighlight], - ParamLeftEye => data.Parameters[CustomizeParameterFlag.LeftEye], - ParamRightEye => data.Parameters[CustomizeParameterFlag.RightEye], - ParamFeatureColor => data.Parameters[CustomizeParameterFlag.FeatureColor], - ParamFacePaintUvMultiplier => data.Parameters[CustomizeParameterFlag.FacePaintUvMultiplier], - ParamFacePaintUvOffset => data.Parameters[CustomizeParameterFlag.FacePaintUvOffset], - ParamDecalColor => data.Parameters[CustomizeParameterFlag.DecalColor], - - BonusItemGlasses => data.BonusItem(BonusItemFlag.Glasses), - - _ => null, - }; - } - private static string GetName(EquipFlag flag) { var slot = flag.ToSlot(out var stain); diff --git a/Penumbra.GameData b/Penumbra.GameData index 8928015..491b619 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 8928015f38f951810a9a6fbb44fb4a0cb9a712dd +Subproject commit 491b61916951b7192bb2354d725363340ea153b5 From 717f9eb39390fac7400bb6d2ed0ce2340bf2f790 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jul 2024 22:10:35 +0200 Subject: [PATCH 401/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 491b619..67109fa 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 491b61916951b7192bb2354d725363340ea153b5 +Subproject commit 67109fa9e89d5ff5c9f93705208db92e836e9ef4 From 303001fed0528e733a01d16b0f4ec25dc8b8fc5b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jul 2024 22:57:37 +0200 Subject: [PATCH 402/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 67109fa..45f2c90 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 67109fa9e89d5ff5c9f93705208db92e836e9ef4 +Subproject commit 45f2c901b3a0131eaee18b3520184baeb0d1049d From d3824d6a23aa733ff42739a9b1fccf0cb20e8a41 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jul 2024 22:59:56 +0200 Subject: [PATCH 403/786] Fix some hrothgar gender change remainders. --- Glamourer/Services/CustomizeService.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Glamourer/Services/CustomizeService.cs b/Glamourer/Services/CustomizeService.cs index 6505846..99c2b78 100644 --- a/Glamourer/Services/CustomizeService.cs +++ b/Glamourer/Services/CustomizeService.cs @@ -35,11 +35,10 @@ public sealed class CustomizeService( } if (applyWhich.HasFlag(CustomizeFlag.Gender)) - if (ret.Race is not Race.Hrothgar || newValues.Gender is not Gender.Female) - { - changed |= ChangeGender(ref ret, newValues.Gender); - applied |= CustomizeFlag.Gender; - } + { + changed |= ChangeGender(ref ret, newValues.Gender); + applied |= CustomizeFlag.Gender; + } if (applyWhich.HasFlag(CustomizeFlag.BodyType)) { @@ -91,7 +90,7 @@ public sealed class CustomizeService( /// Returns whether a gender is valid for the given race. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public bool IsGenderValid(Race race, Gender gender) - => race is Race.Hrothgar ? gender == Gender.Male : CustomizeManager.Genders.Contains(gender); + => CustomizeManager.Genders.Contains(gender); /// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] From c75315f0f780480e07f97ed2dbba7c48b4d113de Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jul 2024 23:01:27 +0200 Subject: [PATCH 404/786] Update Repo. --- repo.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/repo.json b/repo.json index c5ef399..2b7305c 100644 --- a/repo.json +++ b/repo.json @@ -21,7 +21,8 @@ "TestingAssemblyVersion": "1.2.3.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 10, + "DalamudApiLevel": 9, + "TestingDalamudApiLevel": 10, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1, From a5f35dbd17a80de4f11f139a2851184a13ad0cb1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jul 2024 23:30:02 +0200 Subject: [PATCH 405/786] Revert legacy tattoo changes. --- .../Customization/CustomizationDrawer.Icon.cs | 29 ++++++++++++------- .../Gui/Customization/CustomizationDrawer.cs | 29 ++++++++++++++----- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index 8631481..baedc05 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,4 +1,5 @@ -using Glamourer.GameData; +using Dalamud.Interface.Textures.TextureWraps; +using Glamourer.GameData; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; @@ -197,16 +198,24 @@ public partial class CustomizationDrawer var face = _set.DataByValue(CustomizeIndex.Face, _customize.Face, out _, _customize.Face) < 0 ? _set.Faces[0].Value : _customize.Face; foreach (var (featureIdx, idx) in options.WithIndex()) { - using var id = SetId(featureIdx); - var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero; - var feature = _set.Data(featureIdx, 0, face); - var icon = featureIdx == CustomizeIndex.LegacyTattoo - ? _legacyTattoo ?? _service.Manager.GetIcon(feature.IconId) - : _service.Manager.GetIcon(feature.IconId); - var hasIcon = icon.TryGetWrap(out var wrap, out _); + using var id = SetId(featureIdx); + var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero; + var feature = _set.Data(featureIdx, 0, face); + bool hasIcon; + IDalamudTextureWrap? wrap; + var icon = _service.Manager.GetIcon(feature.IconId); + if (featureIdx is CustomizeIndex.LegacyTattoo) + { + wrap = _legacyTattoo; + hasIcon = wrap != null; + } + else + { + hasIcon = icon.TryGetWrap(out wrap, out _); + } + if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, - (int)ImGui.GetStyle().FramePadding.X, - Vector4.Zero, enabled ? Vector4.One : _redTint)) + (int)ImGui.GetStyle().FramePadding.X, Vector4.Zero, enabled ? Vector4.One : _redTint)) { _customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max); Changed |= _currentFlag; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 384307c..a7fcda7 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -1,14 +1,13 @@ -using Dalamud.Interface.Internal; -using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Plugin; +using Dalamud.Plugin.Services; using Glamourer.GameData; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; -using OtterGui.Classes; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -16,15 +15,16 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Customization; public partial class CustomizationDrawer( - TextureCache textureCache, + ITextureProvider textures, CustomizeService _service, CodeService _codes, Configuration _config, FavoriteManager _favorites, HeightService _heightService) + : IDisposable { - private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); - private readonly ISharedImmediateTexture? _legacyTattoo = GetLegacyTattooIcon(textureCache); + private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); + private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(textures); private Exception? _terminate; @@ -49,6 +49,9 @@ public partial class CustomizationDrawer( private float _raceSelectorWidth; private bool _withApply; + public void Dispose() + => _legacyTattoo?.Dispose(); + public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw) { _withApply = false; @@ -189,6 +192,16 @@ public partial class CustomizationDrawer( _raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X; } - private static ISharedImmediateTexture? GetLegacyTattooIcon(TextureCache icons) - => icons.TextureProvider.GetFromManifestResource(Assembly.GetExecutingAssembly(), "Glamourer.LegacyTattoo.raw"); + private static IDalamudTextureWrap? GetLegacyTattooIcon(ITextureProvider textures) + { + using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw"); + if (resource == null) + return null; + + var rawImage = new byte[resource.Length]; + var length = resource.Read(rawImage, 0, (int)resource.Length); + return length == resource.Length + ? textures.CreateFromRaw(RawImageSpecification.Rgba32(192, 192), rawImage, "Glamourer.LegacyTattoo") + : null; + } } From 51c7fc83dd6e7a85eea6a6dde25a7299fa57f9fd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jul 2024 23:34:45 +0200 Subject: [PATCH 406/786] Woops. --- Glamourer/Glamourer.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 3251543..9a1b95b 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -49,7 +49,6 @@ $(AppData)\XIVLauncher\addon\Hooks\dev\ - H:\Projects\FFPlugins\Dalamud\bin\Release\ From 25491adbe36177dbff1a9d51994cf822533ef592 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 16 Jul 2024 23:42:39 +0200 Subject: [PATCH 407/786] Rename Sclera to Limbal --- Glamourer/GameData/CustomizeParameterData.cs | 36 +++++++++---------- Glamourer/GameData/CustomizeParameterFlag.cs | 12 +++---- .../DebugTab/AdvancedCustomizationDrawer.cs | 4 +-- Glamourer/State/StateIndex.cs | 16 ++++----- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs index 09ce65a..97d8222 100644 --- a/Glamourer/GameData/CustomizeParameterData.cs +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -12,9 +12,9 @@ public struct CustomizeParameterData public Vector3 HairSpecular; public Vector3 HairHighlight; public Vector3 LeftEye; - public float LeftScleraIntensity; + public float LeftLimbalIntensity; public Vector3 RightEye; - public float RightScleraIntensity; + public float RightLimbalIntensity; public Vector3 FeatureColor; public float FacePaintUvMultiplier; public float FacePaintUvOffset; @@ -35,9 +35,9 @@ public struct CustomizeParameterData CustomizeParameterFlag.HairSpecular => new CustomizeParameterValue(HairSpecular), CustomizeParameterFlag.HairHighlight => new CustomizeParameterValue(HairHighlight), CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye), - CustomizeParameterFlag.LeftScleraIntensity => new CustomizeParameterValue(LeftScleraIntensity), + CustomizeParameterFlag.LeftLimbalIntensity => new CustomizeParameterValue(LeftLimbalIntensity), CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye), - CustomizeParameterFlag.RightScleraIntensity => new CustomizeParameterValue(RightScleraIntensity), + CustomizeParameterFlag.RightLimbalIntensity => new CustomizeParameterValue(RightLimbalIntensity), CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(FeatureColor), CustomizeParameterFlag.DecalColor => new CustomizeParameterValue(DecalColor), CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(FacePaintUvMultiplier), @@ -61,9 +61,9 @@ public struct CustomizeParameterData CustomizeParameterFlag.HairSpecular => SetIfDifferent(ref HairSpecular, value.InternalTriple), CustomizeParameterFlag.HairHighlight => SetIfDifferent(ref HairHighlight, value.InternalTriple), CustomizeParameterFlag.LeftEye => SetIfDifferent(ref LeftEye, value.InternalTriple), - CustomizeParameterFlag.LeftScleraIntensity => SetIfDifferent(ref LeftScleraIntensity, value.Single), + CustomizeParameterFlag.LeftLimbalIntensity => SetIfDifferent(ref LeftLimbalIntensity, value.Single), CustomizeParameterFlag.RightEye => SetIfDifferent(ref RightEye, value.InternalTriple), - CustomizeParameterFlag.RightScleraIntensity => SetIfDifferent(ref RightScleraIntensity, value.Single), + CustomizeParameterFlag.RightLimbalIntensity => SetIfDifferent(ref RightLimbalIntensity, value.Single), CustomizeParameterFlag.FeatureColor => SetIfDifferent(ref FeatureColor, value.InternalTriple), CustomizeParameterFlag.DecalColor => SetIfDifferent(ref DecalColor, value.InternalQuadruple), CustomizeParameterFlag.FacePaintUvMultiplier => SetIfDifferent(ref FacePaintUvMultiplier, value.Single), @@ -83,20 +83,20 @@ public struct CustomizeParameterData _ => new CustomizeParameterValue(SkinDiffuse, MuscleTone).XivQuadruple, }; - parameters.LeftColor = (flags & (CustomizeParameterFlag.LeftEye | CustomizeParameterFlag.LeftScleraIntensity)) switch + parameters.LeftColor = (flags & (CustomizeParameterFlag.LeftEye | CustomizeParameterFlag.LeftLimbalIntensity)) switch { 0 => parameters.LeftColor, CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple, - CustomizeParameterFlag.LeftScleraIntensity => parameters.LeftColor with { W = LeftScleraIntensity }, - _ => new CustomizeParameterValue(LeftEye, LeftScleraIntensity).XivQuadruple, + CustomizeParameterFlag.LeftLimbalIntensity => parameters.LeftColor with { W = LeftLimbalIntensity }, + _ => new CustomizeParameterValue(LeftEye, LeftLimbalIntensity).XivQuadruple, }; - parameters.RightColor = (flags & (CustomizeParameterFlag.RightEye | CustomizeParameterFlag.RightScleraIntensity)) switch + parameters.RightColor = (flags & (CustomizeParameterFlag.RightEye | CustomizeParameterFlag.RightLimbalIntensity)) switch { 0 => parameters.RightColor, CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye, parameters.RightColor.W).XivQuadruple, - CustomizeParameterFlag.RightScleraIntensity => parameters.RightColor with { W = RightScleraIntensity }, - _ => new CustomizeParameterValue(RightEye, RightScleraIntensity).XivQuadruple, + CustomizeParameterFlag.RightLimbalIntensity => parameters.RightColor with { W = RightLimbalIntensity }, + _ => new CustomizeParameterValue(RightEye, RightLimbalIntensity).XivQuadruple, }; if (flags.HasFlag(CustomizeParameterFlag.SkinSpecular)) @@ -165,11 +165,11 @@ public struct CustomizeParameterData case CustomizeParameterFlag.FacePaintUvOffset: GetUvOffsetWrite(ref parameters) = FacePaintUvOffset; break; - case CustomizeParameterFlag.LeftScleraIntensity: - parameters.LeftColor.W = LeftScleraIntensity; + case CustomizeParameterFlag.LeftLimbalIntensity: + parameters.LeftColor.W = LeftLimbalIntensity; break; - case CustomizeParameterFlag.RightScleraIntensity: - parameters.RightColor.W = RightScleraIntensity; + case CustomizeParameterFlag.RightLimbalIntensity: + parameters.RightColor.W = RightLimbalIntensity; break; } } @@ -187,9 +187,9 @@ public struct CustomizeParameterData HairSpecular = new CustomizeParameterValue(parameter.HairFresnelValue0).InternalTriple, HairHighlight = new CustomizeParameterValue(parameter.MeshColor).InternalTriple, LeftEye = new CustomizeParameterValue(parameter.LeftColor).InternalTriple, - LeftScleraIntensity = new CustomizeParameterValue(parameter.LeftColor.W).Single, + LeftLimbalIntensity = new CustomizeParameterValue(parameter.LeftColor.W).Single, RightEye = new CustomizeParameterValue(parameter.RightColor).InternalTriple, - RightScleraIntensity = new CustomizeParameterValue(parameter.RightColor.W).Single, + RightLimbalIntensity = new CustomizeParameterValue(parameter.RightColor.W).Single, FeatureColor = new CustomizeParameterValue(parameter.OptionColor).InternalTriple, DecalColor = FromParameter(decal), }; diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index aabcdae..009cfc6 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -16,8 +16,8 @@ public enum CustomizeParameterFlag : ushort FacePaintUvMultiplier = 0x0400, FacePaintUvOffset = 0x0800, DecalColor = 0x1000, - LeftScleraIntensity = 0x2000, - RightScleraIntensity = 0x4000, + LeftLimbalIntensity = 0x2000, + RightLimbalIntensity = 0x4000, } public static class CustomizeParameterExtensions @@ -31,8 +31,8 @@ public static class CustomizeParameterExtensions public const CustomizeParameterFlag RgbaQuadruples = CustomizeParameterFlag.DecalColor | CustomizeParameterFlag.LipDiffuse; public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone - | CustomizeParameterFlag.LeftScleraIntensity - | CustomizeParameterFlag.RightScleraIntensity; + | CustomizeParameterFlag.LeftLimbalIntensity + | CustomizeParameterFlag.RightLimbalIntensity; public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier; @@ -67,8 +67,8 @@ public static class CustomizeParameterExtensions CustomizeParameterFlag.FacePaintUvMultiplier => "Multiplier for Face Paint", CustomizeParameterFlag.FacePaintUvOffset => "Offset of Face Paint", CustomizeParameterFlag.DecalColor => "Face Paint Color", - CustomizeParameterFlag.LeftScleraIntensity => "Left Sclera Intensity", - CustomizeParameterFlag.RightScleraIntensity => "Right Sclera Intensity", + CustomizeParameterFlag.LeftLimbalIntensity => "Left Limbal Ring Intensity", + CustomizeParameterFlag.RightLimbalIntensity => "Right Limbal Ring Intensity", _ => string.Empty, }; } diff --git a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs index 4d6a2cd..6f6d27a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs @@ -117,11 +117,11 @@ public unsafe class AdvancedCustomizationDrawer(ObjectManager objects) : IGameDa 24 => "LeftEye.R"u8, 25 => "LeftEye.G"u8, 26 => "LeftEye.B"u8, - 27 => "LeftSclera"u8, + 27 => "LeftLimbal"u8, 28 => "RightEye.R"u8, 29 => "RightEye.G"u8, 30 => "RightEye.B"u8, - 31 => "RightSclera"u8, + 31 => "RightLimbal"u8, 32 => "Feature.R"u8, 33 => "Feature.G"u8, 34 => "Feature.B"u8, diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs index e1c4ea0..0ac52ec 100644 --- a/Glamourer/State/StateIndex.cs +++ b/Glamourer/State/StateIndex.cs @@ -110,9 +110,9 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators new StateIndex(ParamHairSpecular), CustomizeParameterFlag.HairHighlight => new StateIndex(ParamHairHighlight), CustomizeParameterFlag.LeftEye => new StateIndex(ParamLeftEye), - CustomizeParameterFlag.LeftScleraIntensity => new StateIndex(ParamLeftScleraIntensity), + CustomizeParameterFlag.LeftLimbalIntensity => new StateIndex(ParamLeftLimbalIntensity), CustomizeParameterFlag.RightEye => new StateIndex(ParamRightEye), - CustomizeParameterFlag.RightScleraIntensity => new StateIndex(ParamRightScleraIntensity), + CustomizeParameterFlag.RightLimbalIntensity => new StateIndex(ParamRightLimbalIntensity), CustomizeParameterFlag.FeatureColor => new StateIndex(ParamFeatureColor), CustomizeParameterFlag.FacePaintUvMultiplier => new StateIndex(ParamFacePaintUvMultiplier), CustomizeParameterFlag.FacePaintUvOffset => new StateIndex(ParamFacePaintUvOffset), @@ -201,10 +201,10 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators CustomizeParameterFlag.HairSpecular, ParamHairHighlight => CustomizeParameterFlag.HairHighlight, ParamLeftEye => CustomizeParameterFlag.LeftEye, - ParamLeftScleraIntensity => CustomizeParameterFlag.LeftScleraIntensity, + ParamLeftLimbalIntensity => CustomizeParameterFlag.LeftLimbalIntensity, ParamRightEye => CustomizeParameterFlag.RightEye, - ParamRightScleraIntensity => CustomizeParameterFlag.RightScleraIntensity, + ParamRightLimbalIntensity => CustomizeParameterFlag.RightLimbalIntensity, ParamFeatureColor => CustomizeParameterFlag.FeatureColor, ParamFacePaintUvMultiplier => CustomizeParameterFlag.FacePaintUvMultiplier, ParamFacePaintUvOffset => CustomizeParameterFlag.FacePaintUvOffset, From 951c1670583d18a3c1ad03c0db09a1a84581853d Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 16 Jul 2024 21:45:00 +0000 Subject: [PATCH 408/786] [CI] Updating repo.json for testing_1.3.0.0 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 2b7305c..a958f36 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.2.3.3", + "TestingAssemblyVersion": "1.3.0.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a807c9088529215e076fba25e929c1fc73608138 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jul 2024 00:29:53 +0200 Subject: [PATCH 409/786] Disable the currently outdated preparecolorset hook. --- Glamourer/Interop/Material/PrepareColorSet.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index cdbff11..6595c8f 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -21,9 +21,10 @@ public sealed unsafe class PrepareColorSet MaterialManager = 0, } + // TODO enable when working public PrepareColorSet(HookManager hooks) : base("Prepare Color Set ") - => _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); + => _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, false); private readonly Task> _task; From 3484a29f7af5373120bbdad3c79ffdf69a2bf289 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jul 2024 00:30:16 +0200 Subject: [PATCH 410/786] Disable shine parameter application rules. --- Glamourer/GameData/CustomizeParameterFlag.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index 009cfc6..0c11d48 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -36,7 +36,7 @@ public static class CustomizeParameterExtensions public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier; - public static readonly IReadOnlyList AllFlags = [.. Enum.GetValues()]; + public static readonly IReadOnlyList AllFlags = [.. Enum.GetValues().Where(f => All.HasFlag(f))]; public static readonly IReadOnlyList RgbaFlags = AllFlags.Where(f => RgbaQuadruples.HasFlag(f)).ToArray(); public static readonly IReadOnlyList RgbFlags = AllFlags.Where(f => RgbTriples.HasFlag(f)).ToArray(); public static readonly IReadOnlyList PercentageFlags = AllFlags.Where(f => Percentages.HasFlag(f)).ToArray(); From b3818a90dfabe6c975c7afdcb23ab57161d72dd6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jul 2024 02:01:14 +0200 Subject: [PATCH 411/786] Fix design migrations and cma import. --- Glamourer/Designs/DesignBase64Migration.cs | 23 ++++++++++++---------- Glamourer/Interop/CharaFile/CmaFile.cs | 4 ++-- Penumbra.GameData | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index d6d0886..a60c527 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -95,8 +95,8 @@ public class DesignBase64Migration fixed (byte* ptr = bytes) { - var cur = (CharacterWeapon*)(ptr + 30); - var eq = (CharacterArmor*)(cur + 2); + var cur = (LegacyCharacterWeapon*)(ptr + 30); + var eq = (LegacyCharacterArmor*)(cur + 2); if (!humans.IsHuman(data.ModelId)) { @@ -116,7 +116,7 @@ public class DesignBase64Migration } data.SetItem(slot, item); - data.SetStain(slot, mdl.Stains); + data.SetStain(slot, mdl.Stain); } var main = cur[0].Skeleton.Id == 0 @@ -129,7 +129,7 @@ public class DesignBase64Migration } data.SetItem(EquipSlot.MainHand, main); - data.SetStain(EquipSlot.MainHand, cur[0].Stains); + data.SetStain(EquipSlot.MainHand, cur[0].Stain); EquipItem off; // Fist weapon hack @@ -140,7 +140,7 @@ public class DesignBase64Migration if (gauntlet.Valid) { data.SetItem(EquipSlot.Hands, gauntlet); - data.SetStain(EquipSlot.Hands, cur[0].Stains); + data.SetStain(EquipSlot.Hands, cur[0].Stain); } } else @@ -157,12 +157,13 @@ public class DesignBase64Migration } data.SetItem(EquipSlot.OffHand, off); - data.SetStain(EquipSlot.OffHand, cur[1].Stains); + data.SetStain(EquipSlot.OffHand, cur[1].Stain); return data; } } - public static unsafe string CreateOldBase64(in DesignData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, MetaFlag meta, bool writeProtected, float alpha = 1.0f) + public static unsafe string CreateOldBase64(in DesignData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, MetaFlag meta, + bool writeProtected, float alpha = 1.0f) { var data = stackalloc byte[Base64SizeV4]; data[0] = 5; @@ -185,10 +186,12 @@ public class DesignBase64Migration | (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0) | (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0)); save.Customize.Write(data + 4); - ((CharacterWeapon*)(data + 30))[0] = save.Item(EquipSlot.MainHand).Weapon(save.Stain(EquipSlot.MainHand)); - ((CharacterWeapon*)(data + 30))[1] = save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand)); + ((LegacyCharacterWeapon*)(data + 30))[0] = + new LegacyCharacterWeapon(save.Item(EquipSlot.MainHand).Weapon(save.Stain(EquipSlot.MainHand))); + ((LegacyCharacterWeapon*)(data + 30))[1] = + new LegacyCharacterWeapon(save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand))); foreach (var slot in EquipSlotExtensions.EqdpSlots) - ((CharacterArmor*)(data + 44))[slot.ToIndex()] = save.Item(slot).Armor(save.Stain(slot)); + ((LegacyCharacterArmor*)(data + 44))[slot.ToIndex()] = new LegacyCharacterArmor(save.Item(slot).Armor(save.Stain(slot))); *(ushort*)(data + 84) = 1; // IsSet. *(float*)(data + 86) = 1f; data[90] = (byte)((save.IsHatVisible() ? 0x00 : 0x01) diff --git a/Glamourer/Interop/CharaFile/CmaFile.cs b/Glamourer/Interop/CharaFile/CmaFile.cs index da3fb43..2e06588 100644 --- a/Glamourer/Interop/CharaFile/CmaFile.cs +++ b/Glamourer/Interop/CharaFile/CmaFile.cs @@ -58,10 +58,10 @@ public sealed class CmaFile if (idx * 4 + 3 >= byteData.Length) continue; - var armor = ((CharacterArmor*)ptr)[idx]; + var armor = ((LegacyCharacterArmor*)ptr)[idx]; var item = items.Identify(slot, armor.Set, armor.Variant); data.SetItem(slot, item); - data.SetStain(slot, armor.Stains); + data.SetStain(slot, armor.Stain); } data.Customize.Read(ptr); diff --git a/Penumbra.GameData b/Penumbra.GameData index 45f2c90..c25ea7b 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 45f2c901b3a0131eaee18b3520184baeb0d1049d +Subproject commit c25ea7b19a6db37dd36e12b9a7a71f72a192ab57 From 1725dbb583648d9314dff4240dcb4ae00890a145 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 17 Jul 2024 00:03:10 +0000 Subject: [PATCH 412/786] [CI] Updating repo.json for testing_1.3.0.1 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index a958f36..ff24a47 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.0", + "TestingAssemblyVersion": "1.3.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 124656c22e2c4e2cc0e7d2997c74c61f0d5a66a7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jul 2024 02:36:01 +0200 Subject: [PATCH 413/786] Fix advanced customize. --- Glamourer/GameData/CustomizeParameterData.cs | 26 +++++++++++++++----- Glamourer/GameData/CustomizeParameterFlag.cs | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs index 97d8222..84d3e6a 100644 --- a/Glamourer/GameData/CustomizeParameterData.cs +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -73,7 +73,7 @@ public struct CustomizeParameterData } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public readonly unsafe void Apply(ref CustomizeParameter parameters, CustomizeParameterFlag flags = CustomizeParameterExtensions.All) + public readonly void Apply(ref CustomizeParameter parameters, CustomizeParameterFlag flags = CustomizeParameterExtensions.All) { parameters.SkinColor = (flags & (CustomizeParameterFlag.SkinDiffuse | CustomizeParameterFlag.MuscleTone)) switch { @@ -102,11 +102,25 @@ public struct CustomizeParameterData if (flags.HasFlag(CustomizeParameterFlag.SkinSpecular)) parameters.SkinFresnelValue0 = new CustomizeParameterValue(SkinSpecular).XivQuadruple; if (flags.HasFlag(CustomizeParameterFlag.HairDiffuse)) - parameters.MainColor = new CustomizeParameterValue(HairDiffuse).XivTriple; + { + // Vector3 is 0x10 byte for some reason. + var triple = new CustomizeParameterValue(HairDiffuse).XivTriple; + parameters.MainColor.X = triple.X; + parameters.MainColor.Y = triple.Y; + parameters.MainColor.Z = triple.Z; + } + if (flags.HasFlag(CustomizeParameterFlag.HairSpecular)) parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple; if (flags.HasFlag(CustomizeParameterFlag.HairHighlight)) - parameters.MeshColor = new CustomizeParameterValue(HairHighlight).XivTriple; + { + // Vector3 is 0x10 byte for some reason. + var triple = new CustomizeParameterValue(HairHighlight).XivTriple; + parameters.MeshColor.X = triple.X; + parameters.MeshColor.Y = triple.Y; + parameters.MeshColor.Z = triple.Z; + } + if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvMultiplier)) GetUvMultiplierWrite(ref parameters) = FacePaintUvMultiplier; if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvOffset)) @@ -125,7 +139,7 @@ public struct CustomizeParameterData } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public readonly unsafe void ApplySingle(ref CustomizeParameter parameters, CustomizeParameterFlag flag) + public readonly void ApplySingle(ref CustomizeParameter parameters, CustomizeParameterFlag flag) { switch (flag) { @@ -174,7 +188,7 @@ public struct CustomizeParameterData } } - public static unsafe CustomizeParameterData FromParameters(in CustomizeParameter parameter, in DecalParameters decal) + public static CustomizeParameterData FromParameters(in CustomizeParameter parameter, in DecalParameters decal) => new() { FacePaintUvOffset = GetUvOffset(parameter), @@ -194,7 +208,7 @@ public struct CustomizeParameterData DecalColor = FromParameter(decal), }; - public static unsafe CustomizeParameterValue FromParameter(in CustomizeParameter parameter, CustomizeParameterFlag flag) + public static CustomizeParameterValue FromParameter(in CustomizeParameter parameter, CustomizeParameterFlag flag) => flag switch { CustomizeParameterFlag.SkinDiffuse => new CustomizeParameterValue(parameter.SkinColor), diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index 0c11d48..3ef4cd4 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -23,7 +23,7 @@ public enum CustomizeParameterFlag : ushort public static class CustomizeParameterExtensions { // Speculars are not available anymore. - public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x1FDB; + public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x7FDB; public const CustomizeParameterFlag RgbTriples = All & ~(RgbaQuadruples | Percentages | Values); From 608ab7beb92cd22c1fce5ba3607a5a6bc82ab6b1 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 17 Jul 2024 13:05:02 +0000 Subject: [PATCH 414/786] [CI] Updating repo.json for testing_1.3.0.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index ff24a47..922cd9d 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.1", + "TestingAssemblyVersion": "1.3.0.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From dae3fbc901f1c85eb6f7a3211531235c530e3754 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Jul 2024 18:35:34 +0200 Subject: [PATCH 415/786] Update single-setter for 0x10 Vector3, too. --- Glamourer/GameData/CustomizeParameterData.cs | 12 ++++++++++-- Glamourer/GameData/CustomizeParameterFlag.cs | 2 +- Penumbra.GameData | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Glamourer/GameData/CustomizeParameterData.cs b/Glamourer/GameData/CustomizeParameterData.cs index 84d3e6a..3a04938 100644 --- a/Glamourer/GameData/CustomizeParameterData.cs +++ b/Glamourer/GameData/CustomizeParameterData.cs @@ -156,13 +156,21 @@ public struct CustomizeParameterData parameters.LipColor = new CustomizeParameterValue(LipDiffuse).XivQuadruple; break; case CustomizeParameterFlag.HairDiffuse: - parameters.MainColor = new CustomizeParameterValue(HairDiffuse).XivTriple; + // Vector3 is 0x10 byte for some reason. + var triple1 = new CustomizeParameterValue(HairDiffuse).XivTriple; + parameters.MainColor.X = triple1.X; + parameters.MainColor.Y = triple1.Y; + parameters.MainColor.Z = triple1.Z; break; case CustomizeParameterFlag.HairSpecular: parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple; break; case CustomizeParameterFlag.HairHighlight: - parameters.MeshColor = new CustomizeParameterValue(HairHighlight).XivTriple; + // Vector3 is 0x10 byte for some reason. + var triple2 = new CustomizeParameterValue(HairHighlight).XivTriple; + parameters.MeshColor.X = triple2.X; + parameters.MeshColor.Y = triple2.Y; + parameters.MeshColor.Z = triple2.Z; break; case CustomizeParameterFlag.LeftEye: parameters.LeftColor = new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple; diff --git a/Glamourer/GameData/CustomizeParameterFlag.cs b/Glamourer/GameData/CustomizeParameterFlag.cs index 3ef4cd4..ff804d4 100644 --- a/Glamourer/GameData/CustomizeParameterFlag.cs +++ b/Glamourer/GameData/CustomizeParameterFlag.cs @@ -63,7 +63,7 @@ public static class CustomizeParameterExtensions CustomizeParameterFlag.HairHighlight => "Hair Highlights", CustomizeParameterFlag.LeftEye => "Left Eye Color", CustomizeParameterFlag.RightEye => "Right Eye Color", - CustomizeParameterFlag.FeatureColor => "Tattoo Color", + CustomizeParameterFlag.FeatureColor => "Feature Color", CustomizeParameterFlag.FacePaintUvMultiplier => "Multiplier for Face Paint", CustomizeParameterFlag.FacePaintUvOffset => "Offset of Face Paint", CustomizeParameterFlag.DecalColor => "Face Paint Color", diff --git a/Penumbra.GameData b/Penumbra.GameData index c25ea7b..1236ae5 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit c25ea7b19a6db37dd36e12b9a7a71f72a192ab57 +Subproject commit 1236ae5cd1fd81d4f115789b48977a7ea3294332 From de9fb1fd9fef5047614923ac53867ba2b4ef354d Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 17 Jul 2024 16:41:59 +0000 Subject: [PATCH 416/786] [CI] Updating repo.json for testing_1.3.0.3 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 922cd9d..63922e6 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.2", + "TestingAssemblyVersion": "1.3.0.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 2f95a4ea34ea5bae25af3068f48d17ea07e57ff5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 19 Jul 2024 17:27:30 +0200 Subject: [PATCH 417/786] Update Advanced Dyes. --- Glamourer/Designs/DesignConverter.cs | 38 ++++++--- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 78 +++++++++++-------- Glamourer/Gui/Materials/MaterialDrawer.cs | 4 +- .../Material/LiveColorTablePreviewer.cs | 2 +- Glamourer/Interop/Material/MaterialManager.cs | 7 +- Glamourer/Interop/Material/MaterialService.cs | 10 +-- .../Interop/Material/MaterialValueIndex.cs | 22 +++++- Glamourer/Interop/Material/PrepareColorSet.cs | 67 ++++++++++++---- Glamourer/Interop/Material/UpdateColorSets.cs | 25 ++++++ Glamourer/State/StateApplier.cs | 4 - 10 files changed, 180 insertions(+), 77 deletions(-) create mode 100644 Glamourer/Interop/Material/UpdateColorSets.cs diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index c3235ab..2ebcea0 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -209,25 +209,43 @@ public class DesignConverter( } private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials, - EquipFlag equipFlags = EquipFlagExtensions.All) + EquipFlag equipFlags = EquipFlagExtensions.All, BonusItemFlag bonusFlags = BonusExtensions.All) { foreach (var (key, value) in materials.Values) { var idx = MaterialValueIndex.FromKey(key); - if (idx.RowIndex >= ColorTable.NumUsedRows) + if (idx.RowIndex >= ColorTable.NumRows) continue; if (idx.MaterialIndex >= MaterialService.MaterialsPerModel) continue; - var slot = idx.DrawObject switch + switch (idx.DrawObject) { - MaterialValueIndex.DrawObjectType.Human => idx.SlotIndex < 10 ? ((uint)idx.SlotIndex).ToEquipSlot() : EquipSlot.Unknown, - MaterialValueIndex.DrawObjectType.Mainhand when idx.SlotIndex == 0 => EquipSlot.MainHand, - MaterialValueIndex.DrawObjectType.Offhand when idx.SlotIndex == 0 => EquipSlot.OffHand, - _ => EquipSlot.Unknown, - }; - if (slot is EquipSlot.Unknown || (slot.ToBothFlags() & equipFlags) == 0) - continue; + case MaterialValueIndex.DrawObjectType.Mainhand when idx.SlotIndex == 0: + if ((equipFlags & (EquipFlag.Mainhand | EquipFlag.MainhandStain)) == 0) + continue; + + break; + case MaterialValueIndex.DrawObjectType.Offhand when idx.SlotIndex == 0: + if ((equipFlags & (EquipFlag.Offhand | EquipFlag.OffhandStain)) == 0) + continue; + + break; + case MaterialValueIndex.DrawObjectType.Human: + if (idx.SlotIndex < 10) + { + if ((((uint)idx.SlotIndex).ToEquipSlot().ToBothFlags() & equipFlags) == 0) + continue; + } + else if (idx.SlotIndex >= 16) + { + if (((idx.SlotIndex - 16u).ToBonusSlot() & bonusFlags) == 0) + continue; + } + + break; + default: continue; + } manager.AddOrUpdateValue(idx, value.Convert()); } diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 173109b..56f4d24 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -1,7 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using Glamourer.Designs; using Glamourer.Interop.Material; @@ -10,6 +9,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Widgets; using Penumbra.GameData.Enums; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; @@ -29,6 +29,9 @@ public sealed unsafe class AdvancedDyePopup( private byte _selectedMaterial = byte.MaxValue; private bool _anyChanged; + private const int RowsPerPage = 16; + private int _rowOffset; + private bool ShouldBeDrawn() { if (!config.UseAdvancedDyes) @@ -51,8 +54,6 @@ public sealed unsafe class AdvancedDyePopup( private void DrawButton(MaterialValueIndex index) { - // TODO fix when working - return; if (!config.UseAdvancedDyes) return; @@ -78,8 +79,8 @@ public sealed unsafe class AdvancedDyePopup( private (string Path, string GamePath) ResourceName(MaterialValueIndex index) { - var materialHandle = (MaterialResourceHandle*)_actor.Model.AsCharacterBase->MaterialsSpan[ - index.MaterialIndex + index.SlotIndex * MaterialService.MaterialsPerModel].Value; + var materialHandle = + _actor.Model.AsCharacterBase->Materials()[index.MaterialIndex + index.SlotIndex * MaterialService.MaterialsPerModel].Value; var model = _actor.Model.AsCharacterBase->ModelsSpan[index.SlotIndex].Value; var modelHandle = model == null ? null : model->ModelResourceHandle; var path = materialHandle == null @@ -129,17 +130,30 @@ public sealed unsafe class AdvancedDyePopup( if ((tab.Success || select is ImGuiTabItemFlags.SetSelected) && available) { _selectedMaterial = i; + DrawToggle(); DrawTable(index, table); } } + } - using (ImRaii.PushFont(UiBuilder.IconFont)) + private void DrawToggle() + { + var buttonWidth = new Vector2(ImGui.GetContentRegionAvail().X / 2, 0); + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + using (ImRaii.Disabled(_rowOffset == 0)) { - if (ImGui.TabItemButton($"{FontAwesomeIcon.Times.ToIconString()} ", ImGuiTabItemFlags.NoTooltip)) - _drawIndex = null; + if (ToggleButton.ButtonEx("Row 1-16 ", buttonWidth, ImGuiButtonFlags.MouseButtonLeft, ImDrawFlags.RoundCornersLeft)) + _rowOffset = 0; } - ImGuiUtil.HoverTooltip("Close the advanced dye window."); + ImGui.SameLine(0, 0); + + + using (ImRaii.Disabled(_rowOffset == RowsPerPage)) + { + if (ToggleButton.ButtonEx("Row 17-32", buttonWidth, ImGuiButtonFlags.MouseButtonLeft, ImDrawFlags.RoundCornersRight)) + _rowOffset = RowsPerPage; + } } private void DrawContent(ReadOnlySpan> textures) @@ -169,7 +183,7 @@ public sealed unsafe class AdvancedDyePopup( } var size = new Vector2(7 * ImGui.GetFrameHeight() + 3 * ImGui.GetStyle().ItemInnerSpacing.X + 300 * ImGuiHelpers.GlobalScale, - 18 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + 2 * ImGui.GetStyle().ItemSpacing.Y); + 19 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + 3 * ImGui.GetStyle().ItemSpacing.Y); ImGui.SetNextWindowSize(size); var window = ImGui.Begin("###Glamourer Advanced Dyes", flags); @@ -197,12 +211,16 @@ public sealed unsafe class AdvancedDyePopup( private void DrawTable(MaterialValueIndex materialIndex, in ColorTable table) { + if (!materialIndex.Valid) + return; + using var disabled = ImRaii.Disabled(_state.IsLocked); _anyChanged = false; - for (byte i = 0; i < ColorTable.NumUsedRows; ++i) + for (byte i = 0; i < RowsPerPage; ++i) { - var index = materialIndex with { RowIndex = i }; - ref var row = ref table[i]; + var actualI = (byte)(i + _rowOffset); + var index = materialIndex with { RowIndex = actualI }; + ref var row = ref table[actualI]; DrawRow(ref row, index, table); } @@ -222,7 +240,7 @@ public sealed unsafe class AdvancedDyePopup( ImGui.AlignTextToFramePadding(); using (ImRaii.PushFont(UiBuilder.MonoFont)) { - ImGui.TextUnformatted("All Color Rows"); + ImGui.TextUnformatted("All Color Rows (1-32)"); } var spacing = ImGui.GetStyle().ItemInnerSpacing.X; @@ -247,7 +265,7 @@ 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 < ColorTable.NumUsedRows; ++i) + for (byte i = 0; i < ColorTable.NumRows; ++i) stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = i }, ApplySettings.Game); } @@ -259,9 +277,13 @@ public sealed unsafe class AdvancedDyePopup( { var internalRow = new ColorRow(row); var slot = index.ToEquipSlot(); - var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand - ? _state.ModelData.Weapon(slot) - : _state.ModelData.Armor(slot).ToWeapon(0); + var weapon = slot switch + { + EquipSlot.MainHand => _state.ModelData.Weapon(EquipSlot.MainHand), + EquipSlot.OffHand => _state.ModelData.Weapon(EquipSlot.OffHand), + EquipSlot.Unknown => _state.ModelData.BonusItem((index.SlotIndex - 16u).ToBonusSlot()).ToArmor().ToWeapon(0), + _ => _state.ModelData.Armor(slot).ToWeapon(0), + }; value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual); } else @@ -327,11 +349,11 @@ public sealed unsafe class AdvancedDyePopup( private struct LabelStruct { - private fixed byte _label[12]; + private fixed byte _label[5]; public ImRaii.IEndObject TabItem(byte materialIndex, ImGuiTabItemFlags flags) { - _label[10] = (byte)('1' + materialIndex); + _label[4] = (byte)('A' + materialIndex); fixed (byte* ptr = _label) { return ImRaii.TabItem(ptr, flags | ImGuiTabItemFlags.NoTooltip); @@ -340,17 +362,11 @@ public sealed unsafe class AdvancedDyePopup( public LabelStruct() { - _label[0] = (byte)'M'; - _label[1] = (byte)'a'; - _label[2] = (byte)'t'; - _label[3] = (byte)'e'; - _label[4] = (byte)'r'; - _label[5] = (byte)'i'; - _label[6] = (byte)'a'; - _label[7] = (byte)'l'; - _label[8] = (byte)' '; - _label[9] = (byte)'#'; - _label[11] = 0; + _label[0] = (byte)'M'; + _label[1] = (byte)'a'; + _label[2] = (byte)'t'; + _label[3] = (byte)' '; + _label[5] = 0; } } } diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index e7a061f..445184d 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -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, ColorTable.NumUsedRows, "Row #%i")) + if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, ColorTable.NumRows, "Row #%i")) { - _newRowIdx = Math.Clamp(_newRowIdx, 1, ColorTable.NumUsedRows); + _newRowIdx = Math.Clamp(_newRowIdx, 1, ColorTable.NumRows); _newKey = _newKey with { RowIndex = (byte)(_newRowIdx - 1) }; } diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index a9f3a74..1df85f6 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -78,7 +78,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable } else { - for (var i = 0; i < ColorTable.NumUsedRows; ++i) + for (var i = 0; i < ColorTable.NumRows; ++i) { table[i].Diffuse = diffuse; table[i].Emissive = emissive; diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 5f2b553..fe786d7 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -38,10 +38,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable public void Dispose() => _event.Unsubscribe(OnPrepareColorSet); - private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainId stain, ref nint ret) + private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainIds stain, ref nint ret) { - // TODO fix when working - return; if (!_config.UseAdvancedDyes) return; @@ -50,7 +48,6 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable var (slotId, materialId) = FindMaterial(characterBase, material); if (!validType - || slotId > 9 || type is not MaterialValueIndex.DrawObjectType.Human && slotId > 0 || !actor.Identifier(_actors, out var identifier) || !_stateManager.TryGetValue(identifier, out var state)) @@ -62,7 +59,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable if (values.Length == 0) return; - if (!PrepareColorSet.TryGetColorTable(characterBase, material, stain, out var baseColorSet)) + if (!PrepareColorSet.TryGetColorTable(material, stain, out var baseColorSet)) return; var drawData = type switch diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index b2626be..3e972a8 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -10,7 +10,7 @@ namespace Glamourer.Interop.Material; public static unsafe class MaterialService { public const int TextureWidth = 8; - public const int TextureHeight = ColorTable.NumUsedRows; + public const int TextureHeight = ColorTable.NumRows; public const int MaterialsPerModel = 10; public static bool GenerateNewColorTable(in ColorTable colorTable, out Texture* texture) @@ -41,10 +41,10 @@ public static unsafe class MaterialService return null; var index = modelSlot * MaterialsPerModel + materialSlot; - if (index < 0 || index >= model.AsCharacterBase->ColorTableTexturesSpan.Length) + if (index < 0 || index >= model.AsCharacterBase->ColorTableTextures().Length) return null; - var texture = (Texture**)Unsafe.AsPointer(ref model.AsCharacterBase->ColorTableTexturesSpan[index]); + var texture = (Texture**)Unsafe.AsPointer(ref model.AsCharacterBase->ColorTableTextures()[index]); return texture; } @@ -59,10 +59,10 @@ public static unsafe class MaterialService return null; var index = modelSlot * MaterialsPerModel + materialSlot; - if (index < 0 || index >= model.AsCharacterBase->MaterialsSpan.Length) + if (index < 0 || index >= model.AsCharacterBase->Materials().Length) return null; - var material = (MaterialResourceHandle*)model.AsCharacterBase->MaterialsSpan[index].Value; + var material = model.AsCharacterBase->Materials()[index].Value; if (material == null || material->ColorTable == null) return null; diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 8101c05..5c44e21 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -1,4 +1,6 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using Newtonsoft.Json; using Penumbra.GameData.Enums; @@ -79,13 +81,13 @@ public readonly record struct MaterialValueIndex( { if (!TryGetModel(actor, out var model) || SlotIndex >= model.AsCharacterBase->SlotCount - || model.AsCharacterBase->ColorTableTexturesSpan.Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel) + || model.AsCharacterBase->ColorTableTextures().Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel) { textures = []; return false; } - textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(SlotIndex * MaterialService.MaterialsPerModel, + textures = model.AsCharacterBase->ColorTableTextures().Slice(SlotIndex * MaterialService.MaterialsPerModel, MaterialService.MaterialsPerModel); return true; } @@ -139,7 +141,7 @@ public readonly record struct MaterialValueIndex( public static bool ValidateSlot(DrawObjectType type, byte slotIndex) => type switch { - DrawObjectType.Human => slotIndex < 14, + DrawObjectType.Human => slotIndex < 18, DrawObjectType.Mainhand => slotIndex == 0, DrawObjectType.Offhand => slotIndex == 0, _ => false, @@ -149,7 +151,7 @@ public readonly record struct MaterialValueIndex( => materialIndex < MaterialService.MaterialsPerModel; public static bool ValidateRow(byte rowIndex) - => rowIndex < ColorTable.NumUsedRows; + => rowIndex < ColorTable.NumRows; private static uint ToKey(DrawObjectType type, byte slotIndex, byte materialIndex, byte rowIndex) { @@ -174,6 +176,8 @@ public readonly record struct MaterialValueIndex( DrawObjectType.Human when SlotIndex == 11 => $"BodySlot.Face.ToString() Material #{MaterialIndex + 1} Row #{RowIndex + 1}", DrawObjectType.Human when SlotIndex == 12 => $"{BodySlot.Tail} / {BodySlot.Ear} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", DrawObjectType.Human when SlotIndex == 13 => $"Connectors Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Human when SlotIndex == 16 => $"{BonusItemFlag.Glasses.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Human when SlotIndex == 17 => $"{BonusItemFlag.UnkSlot.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", DrawObjectType.Mainhand when SlotIndex == 0 => $"{EquipSlot.MainHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", DrawObjectType.Offhand when SlotIndex == 0 => $"{EquipSlot.OffHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", _ => $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", @@ -189,3 +193,13 @@ public readonly record struct MaterialValueIndex( => FromKey(serializer.Deserialize(reader), out var value) ? value : throw new Exception($"Invalid material key {value.Key}."); } } + +// TODO Remove when fixed in CS. +public static class ColorTableExtension +{ + public static unsafe Span> Materials(this ref CharacterBase character) + => new(character.Materials, character.SlotCount * MaterialService.MaterialsPerModel); + + public static unsafe Span> ColorTableTextures(this ref CharacterBase character) + => new(character.ColorTableTextures, character.SlotCount * MaterialService.MaterialsPerModel); +} diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 6595c8f..b8e31c2 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -13,18 +13,22 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Material; public sealed unsafe class PrepareColorSet - : EventWrapperPtr12Ref34, IHookService + : EventWrapperPtr12Ref34, IHookService { + private readonly UpdateColorSets _updateColorSets; + public enum Priority { /// MaterialManager = 0, } - // TODO enable when working - public PrepareColorSet(HookManager hooks) + public PrepareColorSet(HookManager hooks, UpdateColorSets updateColorSets) : base("Prepare Color Set ") - => _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, false); + { + _updateColorSets = updateColorSets; + _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); + } private readonly Task> _task; @@ -43,20 +47,25 @@ public sealed unsafe class PrepareColorSet public bool Finished => _task.IsCompletedSuccessfully; - private delegate Texture* Delegate(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId); + private delegate Texture* Delegate(MaterialResourceHandle* material, StainId stainId1, StainId stainId2); - private Texture* Detour(CharacterBase* characterBase, MaterialResourceHandle* material, StainId stainId) + private Texture* Detour(MaterialResourceHandle* material, StainId stainId1, StainId stainId2) { - Glamourer.Log.Excessive($"[{Name}] Triggered with 0x{(nint)characterBase:X} 0x{(nint)material:X} {stainId.Id}."); - var ret = nint.Zero; - Invoke(characterBase, material, ref stainId, ref ret); + Glamourer.Log.Excessive($"[{Name}] Triggered with 0x{(nint)material:X} {stainId1.Id} {stainId2.Id}."); + var characterBase = _updateColorSets.Get(); + if (!characterBase.IsCharacterBase) + return _task.Result.Original(material, stainId1, stainId2); + + var ret = nint.Zero; + var stainIds = new StainIds(stainId1, stainId2); + Invoke(characterBase.AsCharacterBase, material, ref stainIds, ref ret); if (ret != nint.Zero) return (Texture*)ret; - return _task.Result.Original(characterBase, material, stainId); + return _task.Result.Original(material, stainIds.Stain1, stainIds.Stain2); } - public static bool TryGetColorTable(CharacterBase* characterBase, MaterialResourceHandle* material, StainIds stainIds, + public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds, out ColorTable table) { if (material->ColorTable == null) @@ -66,9 +75,17 @@ public sealed unsafe class PrepareColorSet } var newTable = *(ColorTable*)material->ColorTable; - // TODO - //if (stainIds.Stain1.Id != 0 || stainIds.Stain2.Id != 0) - // characterBase->ReadStainingTemplate(material, stainId.Id, (Half*)(&newTable)); + if (GetDyeTable(material, out var dyeTable)) + { + if (stainIds.Stain1.Id != 0) + ((delegate* unmanaged)MaterialResourceHandle.MemberFunctionPointers + .ReadStainingTemplate)(material, dyeTable, stainIds.Stain1.Id, (Half*)(&newTable), 0); + + if (stainIds.Stain2.Id != 0) + ((delegate* unmanaged)MaterialResourceHandle.MemberFunctionPointers + .ReadStainingTemplate)(material, dyeTable, stainIds.Stain2.Id, (Half*)(&newTable), 1); + } + table = newTable; return true; } @@ -87,7 +104,7 @@ public sealed unsafe class PrepareColorSet return false; } - return TryGetColorTable(model.AsCharacterBase, handle, GetStains(), out table); + return TryGetColorTable(handle, GetStains(), out table); StainIds GetStains() { @@ -105,4 +122,24 @@ public sealed unsafe class PrepareColorSet } } } + + /// Get the correct dye table for a material. + private static bool GetDyeTable(MaterialResourceHandle* material, out ushort* ptr) + { + ptr = null; + if (material->AdditionalDataSize is 0 || material->AdditionalData is null) + return false; + + var flags1 = material->AdditionalData[0]; + if ((flags1 & 0xF0) is 0) + { + ptr = (ushort*)material + 0x100; + return true; + } + + var flags2 = material->AdditionalData[1]; + var offset = 4 * (1 << (flags1 >> 4)) * (1 << (flags2 & 0x0F)); + ptr = (ushort*)material->DataSet + offset; + return true; + } } diff --git a/Glamourer/Interop/Material/UpdateColorSets.cs b/Glamourer/Interop/Material/UpdateColorSets.cs new file mode 100644 index 0000000..a96c60f --- /dev/null +++ b/Glamourer/Interop/Material/UpdateColorSets.cs @@ -0,0 +1,25 @@ +using OtterGui.Services; +using Penumbra.GameData; +using Penumbra.GameData.Interop; + +namespace Glamourer.Interop.Material; + +public sealed class UpdateColorSets : FastHook +{ + public delegate void Delegate(Model model, uint unk); + + private readonly ThreadLocal _updatingModel = new(); + + public UpdateColorSets(HookManager hooks) + => Task = hooks.CreateHook("Update Color Sets", Sigs.UpdateColorSets, Detour, true); + + private void Detour(Model model, uint unk) + { + _updatingModel.Value = model; + Task.Result.Original(model, unk); + _updatingModel.Value = nint.Zero; + } + + public Model Get() + => _updatingModel.Value; +} diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 2af6437..66b83fb 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -306,8 +306,6 @@ public class StateApplier( public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, ColorRow? value, bool force) { - // TODO fix when working - return; if (!force && !_config.UseAdvancedDyes) return; @@ -340,8 +338,6 @@ public class StateApplier( public unsafe void ChangeMaterialValues(ActorData data, in StateMaterialManager materials, bool force) { - // TODO: fix when working - return; if (!force && !_config.UseAdvancedDyes) return; From f66961661697b358eb3550c124c9ad1facdb3d9e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 19 Jul 2024 17:28:57 +0200 Subject: [PATCH 418/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 1236ae5..a304713 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 1236ae5cd1fd81d4f115789b48977a7ea3294332 +Subproject commit a30471344f971cc42828006dfa07d37317598a29 From ed329ec989afe55732d6c6d88c5e153b4d98b657 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 19 Jul 2024 15:30:58 +0000 Subject: [PATCH 419/786] [CI] Updating repo.json for testing_1.3.0.4 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 63922e6..38c9040 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.3", + "TestingAssemblyVersion": "1.3.0.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 3ad67f661a64454fc8e92a46236f0acfcecaa90c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 20 Jul 2024 21:41:43 +0200 Subject: [PATCH 420/786] Add Hrothgar face hacks to race changing fixing of values. --- Glamourer/Services/CustomizeService.cs | 24 +++++++++++++++++++++++- Penumbra.GameData | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Glamourer/Services/CustomizeService.cs b/Glamourer/Services/CustomizeService.cs index 99c2b78..74f0b5b 100644 --- a/Glamourer/Services/CustomizeService.cs +++ b/Glamourer/Services/CustomizeService.cs @@ -238,7 +238,29 @@ public sealed class CustomizeService( private static CustomizeFlag FixValues(CustomizeSet set, ref CustomizeArray customize) { CustomizeFlag flags = 0; - foreach (var idx in CustomizationExtensions.AllBasic) + + // Hrothgar face hack. + if (customize.Race is Race.Hrothgar) + { + if (customize.Face.Value is < 5) + { + customize.Face += 4; + flags |= CustomizeFlag.Face; + } + } + else if (customize.Face.Value is > 4 and < 9) + { + customize.Face -= 4; + flags |= CustomizeFlag.Face; + } + + if (ValidateCustomizeValue(set, customize.Face, CustomizeIndex.Face, customize.Face, out var fixedFace, false).Length > 0) + { + customize.Face = fixedFace; + flags |= CustomizeFlag.Face; + } + + foreach (var idx in CustomizationExtensions.AllBasicWithoutFace) { if (set.IsAvailable(idx)) { diff --git a/Penumbra.GameData b/Penumbra.GameData index a304713..da99d9b 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit a30471344f971cc42828006dfa07d37317598a29 +Subproject commit da99d9b2b3c51b2bbeb40226c692dff2cbcd5cbc From a885411a8c22d441ff70f3cda2bb928f264f8722 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 20 Jul 2024 21:47:00 +0200 Subject: [PATCH 421/786] Fix saving of favorites. --- Glamourer/Unlocks/FavoriteManager.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index f4576c6..229b8e6 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -94,28 +94,34 @@ public class FavoriteManager : ISavable using var j = new JsonTextWriter(writer); j.Formatting = Formatting.Indented; j.WriteStartObject(); + j.WritePropertyName(nameof(LoadIntermediary.Version)); j.WriteValue(CurrentVersion); + j.WritePropertyName(nameof(LoadIntermediary.FavoriteItems)); j.WriteStartArray(); foreach (var item in _favorites) j.WriteValue(item.Id); j.WriteEndArray(); + j.WritePropertyName(nameof(LoadIntermediary.FavoriteColors)); j.WriteStartArray(); foreach (var stain in _favoriteColors) j.WriteValue(stain.Id); j.WriteEndArray(); + j.WritePropertyName(nameof(LoadIntermediary.FavoriteHairStyles)); j.WriteStartArray(); foreach (var hairStyle in _favoriteHairStyles) j.WriteValue(hairStyle.ToValue()); j.WriteEndArray(); - j.WriteStartArray(); + j.WritePropertyName(nameof(LoadIntermediary.FavoriteBonusItems)); + j.WriteStartArray(); foreach (var item in _favoriteBonusItems) j.WriteValue(item.Id); j.WriteEndArray(); + j.WriteEndObject(); } From 55f2053fe67654fae3c4e89b288c232ad6e10042 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 20 Jul 2024 22:01:21 +0200 Subject: [PATCH 422/786] Fix BodyType not applying. --- Glamourer/Designs/ApplicationCollection.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Glamourer/Designs/ApplicationCollection.cs b/Glamourer/Designs/ApplicationCollection.cs index b31ff2e..0fd18f0 100644 --- a/Glamourer/Designs/ApplicationCollection.cs +++ b/Glamourer/Designs/ApplicationCollection.cs @@ -7,7 +7,7 @@ namespace Glamourer.Designs; public record struct ApplicationCollection( EquipFlag Equip, BonusItemFlag BonusItem, - CustomizeFlag Customize, + CustomizeFlag CustomizeRaw, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) @@ -15,10 +15,10 @@ public record struct ApplicationCollection( public static readonly ApplicationCollection All = new(EquipFlagExtensions.All, BonusExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All); - public static readonly ApplicationCollection None = new(0, 0, 0, 0, 0, 0); + public static readonly ApplicationCollection None = new(0, 0, CustomizeFlag.BodyType, 0, 0, 0); public static readonly ApplicationCollection Equipment = new(EquipFlagExtensions.All, BonusExtensions.All, - 0, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState); + CustomizeFlag.BodyType, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState); public static readonly ApplicationCollection Customizations = new(0, 0, CustomizeFlagExtensions.AllRelevant, 0, CustomizeParameterExtensions.All, MetaFlag.Wetness); @@ -35,6 +35,12 @@ public record struct ApplicationCollection( (false, true) => Customizations, }; + public CustomizeFlag Customize + { + get => CustomizeRaw; + set => CustomizeRaw = value | CustomizeFlag.BodyType; + } + public void RemoveEquip() { Equip = 0; @@ -51,7 +57,7 @@ public record struct ApplicationCollection( } public ApplicationCollection Restrict(ApplicationCollection old) - => new(old.Equip & Equip, old.BonusItem & BonusItem, old.Customize & Customize, old.Crest & Crest, + => new(old.Equip & Equip, old.BonusItem & BonusItem, (old.Customize & Customize) | CustomizeFlag.BodyType, old.Crest & Crest, old.Parameters & Parameters, old.Meta & Meta); public ApplicationCollection CloneSecure() From 94b7ea2d9da66789c6bf36bf148c8f977be18ef7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 20 Jul 2024 23:42:04 +0200 Subject: [PATCH 423/786] Fix changed data offset for weapon. --- Glamourer/Interop/Material/MaterialManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index fe786d7..e695a90 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -198,11 +198,11 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// private static CharacterWeapon GetTempSlot(Weapon* weapon) { - // TODO: Fix offset - var changedData = *(void**)((byte*)weapon + 0x918); + var changedData = *(void**)((byte*)weapon + 0xA00); if (changedData == null) return new CharacterWeapon(weapon->ModelSetId, weapon->SecondaryId, (Variant)weapon->Variant, StainIds.FromWeapon(*weapon)); - return new CharacterWeapon(weapon->ModelSetId, *(SecondaryId*)changedData, ((Variant*)changedData)[2], new StainIds(((StainId*)changedData)[3], ((StainId*)changedData)[4])); + return new CharacterWeapon(weapon->ModelSetId, *(SecondaryId*)changedData, ((Variant*)changedData)[2], + new StainIds(((StainId*)changedData)[3], ((StainId*)changedData)[4])); } } From a7f36da3f5d79aaff722442ca94870b40efaebb7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 20 Jul 2024 23:42:29 +0200 Subject: [PATCH 424/786] Fix statesource overwriting data for characterweapon. --- Glamourer/Interop/Material/MaterialValueManager.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 897f1bf..d5ac877 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -176,7 +176,6 @@ public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert) } } -[StructLayout(LayoutKind.Explicit)] public struct MaterialValueState( in ColorRow game, in ColorRow model, @@ -187,17 +186,10 @@ public struct MaterialValueState( : this(gameRow, modelRow, armor.ToWeapon(0), source) { } - [FieldOffset(0)] - public ColorRow Game = game; - - [FieldOffset(44)] - public ColorRow Model = model; - - [FieldOffset(88)] + public ColorRow Game = game; + public ColorRow Model = model; public readonly CharacterWeapon DrawData = drawData; - - [FieldOffset(95)] - public readonly StateSource Source = source; + public readonly StateSource Source = source; public readonly bool EqualGame(in ColorRow rhsRow, CharacterWeapon rhsData) => DrawData.Skeleton == rhsData.Skeleton From b1c1cf0f992b2ae90a8898fc1c6242b4b57dd58b Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 20 Jul 2024 21:44:33 +0000 Subject: [PATCH 425/786] [CI] Updating repo.json for testing_1.3.0.5 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 38c9040..65b5414 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.4", + "TestingAssemblyVersion": "1.3.0.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.4/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From b2bb50b3d9b60a9de95773215327a85994f54f25 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 22 Jul 2024 16:04:16 +0200 Subject: [PATCH 426/786] Maybe fix an issue with monk fist weapons again. --- Glamourer/State/StateListener.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index f82b2fc..0568ce7 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -322,7 +322,11 @@ public class StateListener : IDisposable // Fist weapon gauntlet hack. if (slot is EquipSlot.OffHand && weapon.Variant == 0 && weapon.Weapon.Id != 0 && _lastFistOffhand.Weapon.Id != 0) - weapon = _lastFistOffhand; + { + Glamourer.Log.Excessive($"Applying stored fist weapon offhand {_lastFistOffhand}."); + weapon = _lastFistOffhand; + _lastFistOffhand = CharacterWeapon.Empty; + } if (!actor.Identifier(_actors, out var identifier) || !_manager.TryGetValue(identifier, out var state)) @@ -372,8 +376,11 @@ public class StateListener : IDisposable // Fist Weapon Offhand hack. if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651) + { _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, weapon.Stains); + Glamourer.Log.Excessive($"Storing fist weapon offhand {_lastFistOffhand}."); + } _funModule.ApplyFunToWeapon(actor, ref weapon, slot); } From d256702005d32e0272435398cfb2f997165fceeb Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 22 Jul 2024 14:06:34 +0000 Subject: [PATCH 427/786] [CI] Updating repo.json for testing_1.3.0.6 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 65b5414..c05c338 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.5", + "TestingAssemblyVersion": "1.3.0.6", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.6/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From fb2b676ff0c7685398822d1e71c905defa28eac9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 26 Jul 2024 15:06:29 +0200 Subject: [PATCH 428/786] Remove no longer needed helpers. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 4 ++-- Glamourer/Interop/Material/MaterialService.cs | 8 ++++---- .../Interop/Material/MaterialValueIndex.cs | 18 +++--------------- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 56f4d24..2521f83 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -1,6 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using Glamourer.Designs; using Glamourer.Interop.Material; @@ -79,8 +80,7 @@ public sealed unsafe class AdvancedDyePopup( private (string Path, string GamePath) ResourceName(MaterialValueIndex index) { - var materialHandle = - _actor.Model.AsCharacterBase->Materials()[index.MaterialIndex + index.SlotIndex * MaterialService.MaterialsPerModel].Value; + var materialHandle = (MaterialResourceHandle*)_actor.Model.AsCharacterBase->MaterialsSpan[index.MaterialIndex + index.SlotIndex * MaterialService.MaterialsPerModel].Value; var model = _actor.Model.AsCharacterBase->ModelsSpan[index.SlotIndex].Value; var modelHandle = model == null ? null : model->ModelResourceHandle; var path = materialHandle == null diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index 3e972a8..5adaf4d 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -41,10 +41,10 @@ public static unsafe class MaterialService return null; var index = modelSlot * MaterialsPerModel + materialSlot; - if (index < 0 || index >= model.AsCharacterBase->ColorTableTextures().Length) + if (index < 0 || index >= model.AsCharacterBase->ColorTableTexturesSpan.Length) return null; - var texture = (Texture**)Unsafe.AsPointer(ref model.AsCharacterBase->ColorTableTextures()[index]); + var texture = (Texture**)Unsafe.AsPointer(ref model.AsCharacterBase->ColorTableTexturesSpan[index]); return texture; } @@ -59,10 +59,10 @@ public static unsafe class MaterialService return null; var index = modelSlot * MaterialsPerModel + materialSlot; - if (index < 0 || index >= model.AsCharacterBase->Materials().Length) + if (index < 0 || index >= model.AsCharacterBase->MaterialsSpan.Length) return null; - var material = model.AsCharacterBase->Materials()[index].Value; + var material = (MaterialResourceHandle*) model.AsCharacterBase->MaterialsSpan[index].Value; if (material == null || material->ColorTable == null) return null; diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 5c44e21..5104713 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -1,6 +1,4 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using Newtonsoft.Json; using Penumbra.GameData.Enums; @@ -81,13 +79,13 @@ public readonly record struct MaterialValueIndex( { if (!TryGetModel(actor, out var model) || SlotIndex >= model.AsCharacterBase->SlotCount - || model.AsCharacterBase->ColorTableTextures().Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel) + || model.AsCharacterBase->ColorTableTexturesSpan.Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel) { textures = []; return false; } - textures = model.AsCharacterBase->ColorTableTextures().Slice(SlotIndex * MaterialService.MaterialsPerModel, + textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(SlotIndex * MaterialService.MaterialsPerModel, MaterialService.MaterialsPerModel); return true; } @@ -192,14 +190,4 @@ public readonly record struct MaterialValueIndex( JsonSerializer serializer) => FromKey(serializer.Deserialize(reader), out var value) ? value : throw new Exception($"Invalid material key {value.Key}."); } -} - -// TODO Remove when fixed in CS. -public static class ColorTableExtension -{ - public static unsafe Span> Materials(this ref CharacterBase character) - => new(character.Materials, character.SlotCount * MaterialService.MaterialsPerModel); - - public static unsafe Span> ColorTableTextures(this ref CharacterBase character) - => new(character.ColorTableTextures, character.SlotCount * MaterialService.MaterialsPerModel); -} +} \ No newline at end of file From 60302c37cd866fbe252b371d84082f74b75487b6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 26 Jul 2024 15:06:51 +0200 Subject: [PATCH 429/786] Fix minion placement. --- Glamourer/Interop/ScalingService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index f7b8f08..16b9a37 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -74,7 +74,8 @@ public unsafe class ScalingService : IDisposable private void PlaceMinionDetour(Companion* companion) { - var owner = (Actor)((nint*)companion)[0x386]; + // TODO Update CS + var owner = (Actor)((nint*)companion)[0x45C]; if (!owner.IsCharacter) { _placeMinionHook.Original(companion); From ff96e519636f7cc1c14f544562fe11f193fc3cd5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 26 Jul 2024 15:35:25 +0200 Subject: [PATCH 430/786] Maybe fix another issue with monk offhands. --- Glamourer/State/StateListener.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 0568ce7..34c9421 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -47,10 +47,11 @@ public class StateListener : IDisposable private readonly CrestService _crestService; private readonly ICondition _condition; + private readonly Dictionary _fistOffhands = []; + private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid; private ActorState? _creatingState; private ActorState? _customizeState; - private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty; public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, EquipSlotUpdating equipSlotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, @@ -321,11 +322,13 @@ public class StateListener : IDisposable return; // Fist weapon gauntlet hack. - if (slot is EquipSlot.OffHand && weapon.Variant == 0 && weapon.Weapon.Id != 0 && _lastFistOffhand.Weapon.Id != 0) + if (slot is EquipSlot.OffHand + && weapon.Variant == 0 + && weapon.Weapon.Id != 0 + && _fistOffhands.TryGetValue(actor, out var lastFistOffhand)) { - Glamourer.Log.Excessive($"Applying stored fist weapon offhand {_lastFistOffhand}."); - weapon = _lastFistOffhand; - _lastFistOffhand = CharacterWeapon.Empty; + Glamourer.Log.Information($"Applying stored fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); + weapon = lastFistOffhand; } if (!actor.Identifier(_actors, out var identifier) @@ -377,9 +380,10 @@ public class StateListener : IDisposable // Fist Weapon Offhand hack. if (slot is EquipSlot.MainHand && weapon.Skeleton.Id is > 1600 and < 1651) { - _lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, + lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, weapon.Stains); - Glamourer.Log.Excessive($"Storing fist weapon offhand {_lastFistOffhand}."); + _fistOffhands[actor] = lastFistOffhand; + Glamourer.Log.Excessive($"Storing fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); } _funModule.ApplyFunToWeapon(actor, ref weapon, slot); From 029cf12bed3029eb255c99a22b6c8bb5ad0695fe Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 26 Jul 2024 13:37:25 +0000 Subject: [PATCH 431/786] [CI] Updating repo.json for testing_1.3.0.7 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index c05c338..cf79426 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.6", + "TestingAssemblyVersion": "1.3.0.7", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.6/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.7/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 1bee5c680b389b0a023ab1ebec2f8ec29835c2e7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 27 Jul 2024 13:59:32 +0200 Subject: [PATCH 432/786] Add Facewear to application rules. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 60 +++++++++++++++------ 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index b20e00d..3fa8e74 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -14,6 +14,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Enums; using static Glamourer.Gui.Tabs.HeaderDrawer; @@ -250,11 +251,15 @@ public class DesignPanel using (var _ = ImRaii.Group()) { DrawCustomizeApplication(); - ImGui.NewLine(); + ImUtf8.IconDummy(); DrawCrestApplication(); - ImGui.NewLine(); + ImUtf8.IconDummy(); if (_config.UseAdvancedParameters) + { DrawMetaApplication(); + ImUtf8.IconDummy(); + DrawBonusSlotApplication(); + } } ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); @@ -287,24 +292,38 @@ public class DesignPanel EquipSlot.OffHand, }); - ImGui.NewLine(); + ImUtf8.IconDummy(); ApplyEquip("Armor", ApplicationTypeExtensions.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots); - ImGui.NewLine(); + ImUtf8.IconDummy(); ApplyEquip("Accessories", ApplicationTypeExtensions.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots); - ImGui.NewLine(); + ImUtf8.IconDummy(); ApplyEquip("Dyes", ApplicationTypeExtensions.StainFlags, true, EquipSlotExtensions.FullSlots); - ImGui.NewLine(); + ImUtf8.IconDummy(); if (_config.UseAdvancedParameters) + { DrawParameterApplication(); + } else + { DrawMetaApplication(); + ImUtf8.IconDummy(); + DrawBonusSlotApplication(); + } } } + private static readonly IReadOnlyList MetaLabels = + [ + "Apply Wetness", + "Apply Hat Visibility", + "Apply Visor State", + "Apply Weapon Visibility", + ]; + private void DrawMetaApplication() { using var id = ImRaii.PushId("Meta"); @@ -312,15 +331,7 @@ public class DesignPanel var flags = (uint)_selector.Selected!.Application.Meta; var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); - var labels = new[] - { - "Apply Wetness", - "Apply Hat Visibility", - "Apply Visor State", - "Apply Weapon Visibility", - }; - - foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(labels)) + foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(MetaLabels)) { var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selector.Selected!.DoApplyMeta(index); if (ImGui.Checkbox(label, ref apply) || bigChange) @@ -328,6 +339,25 @@ public class DesignPanel } } + private static readonly IReadOnlyList BonusSlotLabels = + [ + "Apply Facewear", + ]; + + private void DrawBonusSlotApplication() + { + using var id = ImUtf8.PushId("Bonus"u8); + var flags = _selector.Selected!.Application.BonusItem; + var bigChange = BonusExtensions.AllFlags.Count > 1 && ImUtf8.Checkbox("Apply All Bonus Slots"u8, ref flags, BonusExtensions.All); + foreach (var (index, label) in BonusExtensions.AllFlags.Zip(BonusSlotLabels)) + { + var apply = bigChange ? flags.HasFlag(index) : _selector.Selected!.DoApplyBonusItem(index); + if (ImUtf8.Checkbox(label, ref apply) || bigChange) + _manager.ChangeApplyBonusItem(_selector.Selected!, index, apply); + } + } + + private void DrawParameterApplication() { using var id = ImRaii.PushId("Parameter"); From 1e0b7fdfce62e770da391708f1338358ba738a00 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 28 Jul 2024 01:33:37 +0200 Subject: [PATCH 433/786] Improve respecting of design write protection. --- Glamourer/Gui/Customization/CustomizationDrawer.Color.cs | 6 ++++-- Glamourer/Gui/Materials/MaterialDrawer.cs | 9 +++++---- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 7 ++++++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index fb50f55..4d34a05 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -22,8 +22,10 @@ public partial class CustomizationDrawer using (_ = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0)) { if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.None, _framedIconSize)) + { ImGui.OpenPopup(ColorPickerPopupName); - else if (current >= 0 && CaptureMouseWheel(ref current, 0, _currentCount)) + } + else if (current >= 0 && !_locked && CaptureMouseWheel(ref current, 0, _currentCount)) { var data = _set.Data(_currentIndex, current, _customize.Face); UpdateValue(data.Value); @@ -70,7 +72,7 @@ public partial class CustomizationDrawer for (var i = 0; i < _currentCount; ++i) { var custom = _set.Data(_currentIndex, i, _customize[CustomizeIndex.Face]); - if (ImGui.ColorButton(custom.Value.ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color))) + if (ImGui.ColorButton(custom.Value.ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)) && !_locked) { UpdateValue(custom.Value); ImGui.CloseCurrentPopup(); diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 445184d..50cfb36 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -27,7 +27,7 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) public void Draw(Design design) { - var available = ImGui.GetContentRegionAvail().X; + var available = ImGui.GetContentRegionAvail().X; _spacing = ImGui.GetStyle().ItemInnerSpacing.X; _buttonSize = new Vector2(ImGui.GetFrameHeight()); var colorWidth = 4 * _buttonSize.X @@ -64,6 +64,7 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) ImGui.SameLine(0, _spacing); PasteButton(design, key); ImGui.SameLine(0, _spacing); + using var disabled = ImRaii.Disabled(design.WriteProtected()); EnabledToggle(design, key, value.Enabled); ImGui.SameLine(0, _spacing); DrawRow(design, key, value.Value, value.Revert); @@ -103,7 +104,7 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) var deleteEnabled = _config.DeleteDesignModifier.IsActive(); if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), _buttonSize, $"Delete this color row.{(deleteEnabled ? string.Empty : $"\nHold {_config.DeleteDesignModifier} to delete.")}", - !deleteEnabled, true)) + !deleteEnabled || design.WriteProtected(), true)) return; _designManager.ChangeMaterialValue(design, index, null); @@ -121,7 +122,7 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) private void PasteButton(Design design, MaterialValueIndex index) { if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), _buttonSize, - "Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet, true)) + "Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet || design.WriteProtected(), true)) _designManager.ChangeMaterialValue(design, index, ColorRowClipboard.Row); } @@ -154,7 +155,7 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); var exists = design.GetMaterialDataRef().TryGetValue(_newKey, out _); if (ImGuiUtil.DrawDisabledButton("Add New Row", Vector2.Zero, - exists ? "The selected advanced dye row already exists." : "Add the selected advanced dye row.", exists, false)) + exists ? "The selected advanced dye row already exists." : "Add the selected advanced dye row.", exists || design.WriteProtected())) _designManager.ChangeMaterialValue(design, _newKey, ColorRow.Empty); } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 3fa8e74..1a072cb 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -248,6 +248,8 @@ public class DesignPanel if (!h) return; + using var disabled = ImRaii.Disabled(_selector.Selected!.WriteProtected()); + using (var _ = ImRaii.Group()) { DrawCustomizeApplication(); @@ -548,7 +550,7 @@ public class DesignPanel => panel._selector.Selected != null; protected override bool Disabled - => !panel._manager.CanUndo(panel._selector.Selected); + => !panel._manager.CanUndo(panel._selector.Selected) || (panel._selector.Selected?.WriteProtected() ?? true); protected override string Description => "Undo the last change if you accidentally overwrote your design with a different one."; @@ -604,6 +606,9 @@ public class DesignPanel protected override string Description => "Overwrite this design with your character's current state."; + + protected override bool Disabled + => panel._selector.Selected?.WriteProtected() ?? true; protected override FontAwesomeIcon Icon => FontAwesomeIcon.UserEdit; From e87b2165416a1897b9218be6fe4537c674d172dd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 28 Jul 2024 14:11:56 +0200 Subject: [PATCH 434/786] Rename material stuff. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 40 ++++++++++++++------- Glamourer/Gui/Materials/MaterialDrawer.cs | 2 +- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 2521f83..61eea83 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -80,7 +80,9 @@ public sealed unsafe class AdvancedDyePopup( private (string Path, string GamePath) ResourceName(MaterialValueIndex index) { - var materialHandle = (MaterialResourceHandle*)_actor.Model.AsCharacterBase->MaterialsSpan[index.MaterialIndex + index.SlotIndex * MaterialService.MaterialsPerModel].Value; + var materialHandle = + (MaterialResourceHandle*)_actor.Model.AsCharacterBase->MaterialsSpan[ + index.MaterialIndex + index.SlotIndex * MaterialService.MaterialsPerModel].Value; var model = _actor.Model.AsCharacterBase->ModelsSpan[index.SlotIndex].Value; var modelHandle = model == null ? null : model->ModelResourceHandle; var path = materialHandle == null @@ -140,18 +142,19 @@ public sealed unsafe class AdvancedDyePopup( { var buttonWidth = new Vector2(ImGui.GetContentRegionAvail().X / 2, 0); using var font = ImRaii.PushFont(UiBuilder.MonoFont); - using (ImRaii.Disabled(_rowOffset == 0)) + using var hoverColor = ImRaii.PushColor(ImGuiCol.ButtonHovered, ImGui.GetColorU32(ImGuiCol.TabHovered)); + + using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(_rowOffset == 0 ? ImGuiCol.TabActive : ImGuiCol.Tab))) { - if (ToggleButton.ButtonEx("Row 1-16 ", buttonWidth, ImGuiButtonFlags.MouseButtonLeft, ImDrawFlags.RoundCornersLeft)) + if (ToggleButton.ButtonEx("Row Pairs 1-8 ", buttonWidth, ImGuiButtonFlags.MouseButtonLeft, ImDrawFlags.RoundCornersLeft)) _rowOffset = 0; } ImGui.SameLine(0, 0); - - using (ImRaii.Disabled(_rowOffset == RowsPerPage)) + using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(_rowOffset == RowsPerPage ? ImGuiCol.TabActive : ImGuiCol.Tab))) { - if (ToggleButton.ButtonEx("Row 17-32", buttonWidth, ImGuiButtonFlags.MouseButtonLeft, ImDrawFlags.RoundCornersRight)) + if (ToggleButton.ButtonEx("Row Pairs 9-16", buttonWidth, ImGuiButtonFlags.MouseButtonLeft, ImDrawFlags.RoundCornersRight)) _rowOffset = RowsPerPage; } } @@ -182,9 +185,14 @@ public sealed unsafe class AdvancedDyePopup( flags |= ImGuiWindowFlags.NoMove; } - var size = new Vector2(7 * ImGui.GetFrameHeight() + 3 * ImGui.GetStyle().ItemInnerSpacing.X + 300 * ImGuiHelpers.GlobalScale, - 19 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + 3 * ImGui.GetStyle().ItemSpacing.Y); - ImGui.SetNextWindowSize(size); + var width = 7 * ImGui.GetFrameHeight() // Buttons + + 3 * ImGui.GetStyle().ItemSpacing.X // around text + + 7 * ImGui.GetStyle().ItemInnerSpacing.X + + 200 * ImGuiHelpers.GlobalScale // Drags + + 7 * UiBuilder.MonoFont.GetCharAdvance(' ') // Row + + 2 * ImGui.GetStyle().WindowPadding.X; + var height = 19 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + 3 * ImGui.GetStyle().ItemSpacing.Y; + ImGui.SetNextWindowSize(new Vector2(width, height)); var window = ImGui.Begin("###Glamourer Advanced Dyes", flags); try @@ -240,11 +248,11 @@ public sealed unsafe class AdvancedDyePopup( ImGui.AlignTextToFramePadding(); using (ImRaii.PushFont(UiBuilder.MonoFont)) { - ImGui.TextUnformatted("All Color Rows (1-32)"); + ImGui.TextUnformatted("All Color Row Pairs (1-16)"); } var spacing = ImGui.GetStyle().ItemInnerSpacing.X; - ImGui.SameLine(ImGui.GetWindowSize().X - 3 * buttonSize.X - 3 * spacing); + ImGui.SameLine(ImGui.GetWindowSize().X - 3 * buttonSize.X - 2 * spacing - ImGui.GetStyle().WindowPadding.X); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this table to your clipboard.", false, true)) ColorRowClipboard.Table = table; @@ -302,7 +310,9 @@ public sealed unsafe class AdvancedDyePopup( ImGui.AlignTextToFramePadding(); using (ImRaii.PushFont(UiBuilder.MonoFont)) { - ImGui.TextUnformatted($"Row {index.RowIndex + 1:D2}"); + var rowIndex = index.RowIndex / 2 + 1; + var rowSuffix = (index.RowIndex & 1) == 0 ? 'A' : 'B'; + ImGui.TextUnformatted($"Row {rowIndex,2}{rowSuffix}"); } ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); @@ -313,18 +323,22 @@ public sealed unsafe class AdvancedDyePopup( ImGui.SameLine(0, spacing.X); applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular, v => value.Model.Specular = v, "S"); + ImGui.SameLine(0, spacing.X); applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive, v => value.Model.Emissive = v, "E"); + ImGui.SameLine(0, spacing.X); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") && value.Model.GlossStrength > 0; ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + ImGui.SameLine(0, spacing.X); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS"); + applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f%% SS"); ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + ImGui.SameLine(0, spacing.X); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", false, true)) diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 50cfb36..95be750 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -200,7 +200,7 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); ImGui.SameLine(0, _spacing); ImGui.SetNextItemWidth(SpecularStrengthWidth * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Specular Strength", ref tmp.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS"); + applied |= ImGui.DragFloat("##Specular Strength", ref tmp.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f%% SS"); ImGuiUtil.HoverTooltip("Change the specular strength for this row."); if (applied) _designManager.ChangeMaterialValue(design, index, tmp); From d0d518ddc23ba6e7bc130536fe98eb10e07e265c Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 28 Jul 2024 12:14:04 +0000 Subject: [PATCH 435/786] [CI] Updating repo.json for testing_1.3.0.8 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index cf79426..0c672d6 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.7", + "TestingAssemblyVersion": "1.3.0.8", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.7/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.8/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 6446309bd79eadb2fbdbb48c06c148fe01868252 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 30 Jul 2024 20:23:59 +0200 Subject: [PATCH 436/786] Allow drag&drop on stains. --- Glamourer/Api/ApiHelpers.cs | 1 - Glamourer/Gui/Equipment/EquipmentDrawer.cs | 28 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index a54a0ec..25af774 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -1,6 +1,5 @@ using Glamourer.Api.Enums; using Glamourer.Designs; -using Glamourer.GameData; using Glamourer.State; using OtterGui; using OtterGui.Log; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index e65981e..83c560d 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -9,6 +9,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Text; +using OtterGui.Text.EndObjects; using OtterGui.Widgets; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -35,6 +36,8 @@ public class EquipmentDrawer private float _requiredComboWidthUnscaled; private float _requiredComboWidth; + private Stain? _draggedStain; + public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures, Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes) { @@ -512,6 +515,10 @@ public class EquipmentDrawer var change = small ? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) : _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, width); + + if (!change) + DrawStainDragDrop(data, index, stain, found); + if (index < data.CurrentStains.Count - 1) ImUtf8.SameLineInner(); @@ -526,6 +533,27 @@ public class EquipmentDrawer } } + private void DrawStainDragDrop(in EquipDrawData data, int index, Stain stain, bool found) + { + if (found) + { + using var dragSource = ImUtf8.DragDropSource(); + if (dragSource.Success) + { + if (DragDropSource.SetPayload("stainDragDrop"u8)) + _draggedStain = stain; + ImUtf8.Text($"Dragging {stain.Name}..."); + } + } + + using var dragTarget = ImUtf8.DragDropTarget(); + if (dragTarget.IsDropping("stainDragDrop"u8) && _draggedStain.HasValue) + { + data.SetStains(data.CurrentStains.With(index, _draggedStain.Value.RowIndex)); + _draggedStain = null; + } + } + private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open) { Debug.Assert(data.Slot.IsEquipment() || data.Slot.IsAccessory(), $"Called {nameof(DrawItem)} on {data.Slot}."); From 9d569266b5bf8f5a7745f6fcf8deecdc5971897f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 31 Jul 2024 16:58:33 +0200 Subject: [PATCH 437/786] Improve job filter in unlocks tab. --- Glamourer.Api | 2 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 50 ++++++++++++++++---- OtterGui | 2 +- Penumbra.GameData | 2 +- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index ca00339..663702b 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit ca003395306791b9e595683c47824b4718385311 +Subproject commit 663702b57303368b3a897e9c6eae4b34b4339534 diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 600546f..558c4df 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -9,6 +9,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Table; +using OtterGui.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -23,16 +24,16 @@ public class UnlockTable : Table, IDisposable : base("ItemUnlockTable", new ItemList(items), new FavoriteColumn(favorites, @event) { Label = "F" }, new NameColumn(textures, tooltip) { Label = "Item Name..." }, - new SlotColumn() { Label = "Equip Slot" }, - new TypeColumn() { Label = "Item Type..." }, + new SlotColumn { Label = "Equip Slot" }, + new TypeColumn { Label = "Item Type..." }, new UnlockDateColumn(itemUnlocks) { Label = "Unlocked" }, - new ItemIdColumn() { Label = "Item Id..." }, + new ItemIdColumn { Label = "Item Id..." }, new ModelDataColumn(items) { Label = "Model Data..." }, new JobColumn(jobs) { Label = "Jobs" }, - new RequiredLevelColumn() { Label = "Level..." }, - new DyableColumn() { Label = "Dye" }, - new CrestColumn() { Label = "Crest" }, - new TradableColumn() { Label = "Trade" } + new RequiredLevelColumn { Label = "Level..." }, + new DyableColumn { Label = "Dye" }, + new CrestColumn { Label = "Crest" }, + new TradableColumn { Label = "Trade" } ) { _event = @event; @@ -334,11 +335,42 @@ public class UnlockTable : Table, IDisposable public JobColumn(JobService jobs) { _jobs = jobs; - _values = _jobs.Jobs.Values.Skip(1).Select(j => j.Flag).ToArray(); - _names = _jobs.Jobs.Values.Skip(1).Select(j => j.Abbreviation).ToArray(); + _values = _jobs.Jobs.Ordered.Select(j => j.Flag).ToArray(); + _names = _jobs.Jobs.Ordered.Select(j => j.Abbreviation).ToArray(); AllFlags = _values.Aggregate((l, r) => l | r); _filterValue = AllFlags; Flags &= ~ImGuiTableColumnFlags.NoResize; + ComboFlags |= ImGuiComboFlags.HeightLargest; + } + + protected override bool DrawCheckbox(int idx, out bool ret) + { + var job = _jobs.Jobs.Ordered[idx]; + var color = job.Role switch + { + Job.JobRole.Tank => 0xFFFFD0D0, + Job.JobRole.Melee => 0xFFD0D0FF, + Job.JobRole.RangedPhysical => 0xFFD0FFFF, + Job.JobRole.RangedMagical => 0xFFFFD0FF, + Job.JobRole.Healer => 0xFFD0FFD0, + Job.JobRole.Crafter => 0xFF808080, + Job.JobRole.Gatherer => 0xFFD0D0D0, + _ => ImGui.GetColorU32(ImGuiCol.Text), + }; + using var c = ImRaii.PushColor(ImGuiCol.Text, color); + var r = base.DrawCheckbox(idx, out ret); + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _filterValue = job.Flag & _filterValue; + ret = true; + r = true; + } + + ImUtf8.HoverTooltip("Right-Click to disable all other jobs."u8); + + if (idx < _names.Length - 1 && idx % 2 == 0) + ImGui.SameLine(ImGui.GetFrameHeight() * 4); + return r; } protected override void SetValue(JobFlag value, bool enable) diff --git a/OtterGui b/OtterGui index c2738e1..3a14692 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit c2738e1d42974cddbe5a31238c6ed236a831d17d +Subproject commit 3a14692e38708ca9f18652898e6f5c8cc87b517b diff --git a/Penumbra.GameData b/Penumbra.GameData index da99d9b..cd40d49 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit da99d9b2b3c51b2bbeb40226c692dff2cbcd5cbc +Subproject commit cd40d497f4791e946c0fd4bd5663da578f7680ed From f6434cbc619926cfba0fa0970ab5f4be1e6010d9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 31 Jul 2024 17:06:30 +0200 Subject: [PATCH 438/786] Rename stains. --- Glamourer/Designs/DesignEditor.cs | 2 +- Glamourer/Events/DesignChanged.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index 4a104ca..f68424c 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -196,7 +196,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stains}."); - DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stains, slot)); + DesignChanged.Invoke(DesignChanged.Type.Stains, design, (oldStain, stains, slot)); } /// diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 199c7d4..a8f35d5 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -68,8 +68,8 @@ public sealed class DesignChanged() /// An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand, the new offhand (if any) and the new gauntlets (if any). [(EquipItem, EquipItem, EquipItem, EquipItem?, EquipItem?)]. Weapon, - /// An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. - Stain, + /// An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainIds, StainIds, EquipSlot)]. + Stains, /// An existing design had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. Crest, From d8085dc02298fe3a735ed26409c2a19e78411530 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 31 Jul 2024 19:33:23 +0200 Subject: [PATCH 439/786] Introduce history. --- Glamourer.Api | 2 +- Glamourer/Api/StateApi.cs | 7 +- Glamourer/Automation/AutoDesignManager.cs | 3 +- Glamourer/Designs/DesignEditor.cs | 32 +-- Glamourer/Designs/DesignFileSystem.cs | 7 +- Glamourer/Designs/DesignManager.cs | 46 +++-- .../Designs/History/DesignTransaction.cs | 181 +++++++++++++++++ Glamourer/Designs/History/EditorHistory.cs | 191 ++++++++++++++++++ Glamourer/Designs/History/Transaction.cs | 113 +++++++++++ Glamourer/Designs/Links/DesignLinkManager.cs | 3 +- Glamourer/Events/DesignChanged.cs | 77 +++---- Glamourer/Events/StateChanged.cs | 13 +- Glamourer/Gui/DesignCombo.cs | 7 +- Glamourer/Gui/Equipment/EquipDrawData.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 25 ++- .../DesignTab/DesignFileSystemSelector.cs | 3 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 7 +- Glamourer/State/StateEditor.cs | 67 ++++-- 19 files changed, 682 insertions(+), 106 deletions(-) create mode 100644 Glamourer/Designs/History/DesignTransaction.cs create mode 100644 Glamourer/Designs/History/EditorHistory.cs create mode 100644 Glamourer/Designs/History/Transaction.cs diff --git a/Glamourer.Api b/Glamourer.Api index 663702b..4bad56d 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 663702b57303368b3a897e9c6eae4b34b4339534 +Subproject commit 4bad56d610132b419335b89896e1f387b9ba2039 diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 203f1b0..385cf3e 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -2,6 +2,7 @@ using Glamourer.Api.Enums; using Glamourer.Automation; using Glamourer.Designs; +using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.Interop.Structs; using Glamourer.State; @@ -41,13 +42,13 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable _objects = objects; _stateChanged = stateChanged; _gPose = gPose; - _stateChanged.Subscribe(OnStateChange, Events.StateChanged.Priority.GlamourerIpc); + _stateChanged.Subscribe(OnStateChanged, Events.StateChanged.Priority.GlamourerIpc); _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.GlamourerIpc); } public void Dispose() { - _stateChanged.Unsubscribe(OnStateChange); + _stateChanged.Unsubscribe(OnStateChanged); _gPose.Unsubscribe(OnGPoseChange); } @@ -330,7 +331,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnGPoseChange(bool gPose) => GPoseChanged?.Invoke(gPose); - private void OnStateChange(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, object? _5) + private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { if (StateChanged != null) foreach (var actor in actors.Objects) diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index da9b014..474afdd 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -1,6 +1,7 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; +using Glamourer.Designs.History; using Glamourer.Designs.Special; using Glamourer.Events; using Glamourer.Interop; @@ -620,7 +621,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos } } - private void OnDesignChange(DesignChanged.Type type, Design design, object? data) + private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? _) { if (type is not DesignChanged.Type.Deleted) return; diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index f68424c..df0e9ed 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -1,3 +1,4 @@ +using Glamourer.Designs.History; using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; @@ -72,7 +73,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx)); + DesignChanged.Invoke(DesignChanged.Type.Customize, design, new CustomizeTransaction(idx, oldValue, value)); } /// @@ -88,7 +89,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; Glamourer.Log.Debug($"Changed entire customize with resulting flags {applied} and {changed}."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.EntireCustomize, design, (oldCustomize, applied, changed)); + DesignChanged.Invoke(DesignChanged.Type.EntireCustomize, design, new EntireCustomizeTransaction(changed, oldCustomize, newCustomize)); } /// @@ -103,7 +104,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.Parameter, design, (old, @new, flag)); + DesignChanged.Invoke(DesignChanged.Type.Parameter, design, new ParameterTransaction(flag, old, @new)); } /// @@ -122,11 +123,14 @@ public class DesignEditor( if (!ChangeMainhandPeriphery(design, currentMain, currentOff, item, out var newOff, out var newGauntlets)) return; + var currentGauntlets = design.DesignData.Item(EquipSlot.Hands); design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug( $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId})."); - DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff, newGauntlets)); + DesignChanged.Invoke(DesignChanged.Type.Weapon, design, + new WeaponTransaction(currentMain, currentOff, currentGauntlets, item, newOff ?? currentOff, + newGauntlets ?? currentGauntlets)); return; } case EquipSlot.OffHand: @@ -139,11 +143,13 @@ public class DesignEditor( if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item)) return; + var currentGauntlets = design.DesignData.Item(EquipSlot.Hands); design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug( $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId})."); - DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item)); + DesignChanged.Invoke(DesignChanged.Type.Weapon, design, + new WeaponTransaction(currentMain, currentOff, currentGauntlets, currentMain, item, currentGauntlets)); return; } default: @@ -159,7 +165,7 @@ public class DesignEditor( Glamourer.Log.Debug( $"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId})."); SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.Equip, design, (old, item, slot)); + DesignChanged.Invoke(DesignChanged.Type.Equip, design, new EquipTransaction(slot, old, item)); return; } } @@ -179,7 +185,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set {slot} bonus item to {item}."); - DesignChanged.Invoke(DesignChanged.Type.BonusItem, design, (oldItem, item, slot)); + DesignChanged.Invoke(DesignChanged.Type.BonusItem, design, new BonusItemTransaction(slot, oldItem, item)); } /// @@ -196,7 +202,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stains}."); - DesignChanged.Invoke(DesignChanged.Type.Stains, design, (oldStain, stains, slot)); + DesignChanged.Invoke(DesignChanged.Type.Stains, design, new StainTransaction(slot, oldStain, stains)); } /// @@ -219,7 +225,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}."); - DesignChanged.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot)); + DesignChanged.Invoke(DesignChanged.Type.Crest, design, new CrestTransaction(slot, oldCrest, crest)); } /// @@ -232,7 +238,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set value of {metaIndex} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value)); + DesignChanged.Invoke(DesignChanged.Type.Other, design, new MetaTransaction(metaIndex, !value, value)); } public void ChangeMaterialRevert(Design design, MaterialValueIndex index, bool revert) @@ -245,7 +251,7 @@ public class DesignEditor( Glamourer.Log.Debug($"Changed advanced dye value for {index} to {(revert ? "Revert." : "no longer Revert.")}"); design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.MaterialRevert, design, index); + DesignChanged.Invoke(DesignChanged.Type.MaterialRevert, design, new MaterialRevertTransaction(index, !revert, revert)); } public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row) @@ -280,7 +286,7 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.DelaySave(design); - DesignChanged.Invoke(DesignChanged.Type.Material, design, (oldValue, row, index)); + DesignChanged.Invoke(DesignChanged.Type.Material, design, new MaterialTransaction(index, oldValue.Value, row)); } public void ChangeApplyMaterialValue(Design design, MaterialValueIndex index, bool value) @@ -293,7 +299,7 @@ public class DesignEditor( Glamourer.Log.Debug($"Changed application of advanced dye for {index} to {value}."); design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); - DesignChanged.Invoke(DesignChanged.Type.ApplyMaterial, design, index); + DesignChanged.Invoke(DesignChanged.Type.ApplyMaterial, design, new ApplicationTransaction(index, !value, value)); } diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index f154684..e985e32 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -1,4 +1,5 @@ using Dalamud.Interface.ImGuiNotification; +using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.Services; using Newtonsoft.Json; @@ -92,13 +93,13 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable _saveService.QueueSave(this); } - private void OnDesignChange(DesignChanged.Type type, Design design, object? data) + private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? data) { switch (type) { case DesignChanged.Type.Created: var parent = Root; - if (data is string path) + if ((data as CreationTransaction?)?.Path is { } path) try { parent = FindOrCreateAllFolders(path); @@ -119,7 +120,7 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable case DesignChanged.Type.ReloadedAll: Reload(); return; - case DesignChanged.Type.Renamed when data is string oldName: + case DesignChanged.Type.Renamed when (data as RenameTransaction?)?.Old is { } oldName: if (!FindLeaf(design, out var leaf2)) return; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 97058a9..7eaad9d 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -1,4 +1,5 @@ using Dalamud.Utility; +using Glamourer.Designs.History; using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; @@ -107,7 +108,7 @@ public sealed class DesignManager : DesignEditor Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier}."); SaveService.ImmediateSave(design); - DesignChanged.Invoke(DesignChanged.Type.Created, design, path); + DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path)); return design; } @@ -127,7 +128,7 @@ public sealed class DesignManager : DesignEditor Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design."); SaveService.ImmediateSave(design); - DesignChanged.Invoke(DesignChanged.Type.Created, design, path); + DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path)); return design; } @@ -147,7 +148,7 @@ public sealed class DesignManager : DesignEditor Glamourer.Log.Debug( $"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}."); SaveService.ImmediateSave(design); - DesignChanged.Invoke(DesignChanged.Type.Created, design, path); + DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path)); return design; } @@ -176,7 +177,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.Renamed, design, oldName); + DesignChanged.Invoke(DesignChanged.Type.Renamed, design, new RenameTransaction(oldName, newName)); } /// Change the description of a design. @@ -190,7 +191,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Changed description of design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription); + DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, new DescriptionTransaction(oldDescription, description)); } /// Change the associated color of a design. @@ -204,7 +205,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Changed color of design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.ChangedColor, design, oldColor); + DesignChanged.Invoke(DesignChanged.Type.ChangedColor, design, new DesignColorTransaction(oldColor, newColor)); } /// Add a new tag to a design. The tags remain sorted. @@ -218,7 +219,7 @@ public sealed class DesignManager : DesignEditor var idx = design.Tags.IndexOf(tag); SaveService.QueueSave(design); Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, (tag, idx)); + DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx)); } /// Remove a tag from a design by its index. @@ -232,7 +233,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.RemovedTag, design, (oldTag, tagIdx)); + DesignChanged.Invoke(DesignChanged.Type.RemovedTag, design, new TagRemovedTransaction(oldTag, tagIdx)); } /// Rename a tag from a design by its index. The tags stay sorted. @@ -247,7 +248,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags."); - DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx)); + DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.IndexOf(newTag))); } /// Add an associated mod to a design. @@ -259,7 +260,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} to design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings)); + DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings)); } /// Remove an associated mod from a design. @@ -271,17 +272,18 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Removed associated mod {mod.DirectoryName} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings)); + DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, new ModRemovedTransaction(mod, settings)); } /// Add or update an associated mod to a design. public void UpdateMod(Design design, Mod mod, ModSettings settings) { + var oldSettings = design.AssociatedMods[mod]; design.AssociatedMods[mod] = settings; design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Updated (or added) associated mod {mod.DirectoryName} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings)); + DesignChanged.Invoke(DesignChanged.Type.UpdatedMod, design, new ModUpdatedTransaction(mod, oldSettings, settings)); } /// Set the write protection status of a design. @@ -292,7 +294,7 @@ public sealed class DesignManager : DesignEditor SaveService.QueueSave(design); Glamourer.Log.Debug($"Set design {design.Identifier} to {(value ? "no longer be " : string.Empty)} write-protected."); - DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, value); + DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, null); } /// Set the quick design bar display status of a design. @@ -305,7 +307,7 @@ public sealed class DesignManager : DesignEditor SaveService.QueueSave(design); 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); + DesignChanged.Invoke(DesignChanged.Type.QuickDesignBar, design, null); } #endregion @@ -332,7 +334,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of customization {idx.ToDefaultName()} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyCustomize, design, idx); + DesignChanged.Invoke(DesignChanged.Type.ApplyCustomize, design, new ApplicationTransaction(idx, !value, value)); } /// Change whether to apply a specific equipment piece. @@ -344,7 +346,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, slot); + DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, new ApplicationTransaction((slot, false), !value, value)); } /// Change whether to apply a specific equipment piece. @@ -356,11 +358,11 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {slot} bonus item to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyBonusItem, design, slot); + DesignChanged.Invoke(DesignChanged.Type.ApplyBonusItem, design, new ApplicationTransaction(slot, !value, value)); } /// Change whether to apply a specific stain. - public void ChangeApplyStain(Design design, EquipSlot slot, bool value) + public void ChangeApplyStains(Design design, EquipSlot slot, bool value) { if (!design.SetApplyStain(slot, value)) return; @@ -368,7 +370,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyStain, design, slot); + DesignChanged.Invoke(DesignChanged.Type.ApplyStain, design, new ApplicationTransaction((slot, true), !value, value)); } /// Change whether to apply a specific crest visibility. @@ -380,7 +382,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyCrest, design, slot); + DesignChanged.Invoke(DesignChanged.Type.ApplyCrest, design, new ApplicationTransaction(slot, !value, value)); } /// Change the application value of one of the meta flags. @@ -392,7 +394,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {metaIndex} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value)); + DesignChanged.Invoke(DesignChanged.Type.Other, design, new ApplicationTransaction(metaIndex, !value, value)); } /// Change the application value of a customize parameter. @@ -404,7 +406,7 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of parameter {flag} to {value}."); - DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, flag); + DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, new ApplicationTransaction(flag, !value, value)); } #endregion diff --git a/Glamourer/Designs/History/DesignTransaction.cs b/Glamourer/Designs/History/DesignTransaction.cs new file mode 100644 index 0000000..98e3eec --- /dev/null +++ b/Glamourer/Designs/History/DesignTransaction.cs @@ -0,0 +1,181 @@ +using Glamourer.GameData; +using Glamourer.Interop.Material; +using Glamourer.Interop.Penumbra; +using Penumbra.GameData.Enums; + +namespace Glamourer.Designs.History; + +/// Only Designs. Can not be reverted. +public readonly record struct CreationTransaction(string Name, string? Path) + : ITransaction +{ + public ITransaction? Merge(ITransaction other) + => null; + + public void Revert(IDesignEditor editor, object data) + { } +} + +/// Only Designs. +public readonly record struct RenameTransaction(string Old, string New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is RenameTransaction other ? new RenameTransaction(other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).Rename((Design)data, Old); +} + +/// Only Designs. +public readonly record struct DescriptionTransaction(string Old, string New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is DescriptionTransaction other ? new DescriptionTransaction(other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).ChangeDescription((Design)data, Old); +} + +/// Only Designs. +public readonly record struct DesignColorTransaction(string Old, string New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is DesignColorTransaction other ? new DesignColorTransaction(other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).ChangeColor((Design)data, Old); +} + +/// Only Designs. +public readonly record struct TagAddedTransaction(string New, int Index) + : ITransaction +{ + public ITransaction? Merge(ITransaction other) + => null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).RemoveTag((Design)data, Index); +} + +/// Only Designs. +public readonly record struct TagRemovedTransaction(string Old, int Index) + : ITransaction +{ + public ITransaction? Merge(ITransaction other) + => null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).AddTag((Design)data, Old); +} + +/// Only Designs. +public readonly record struct TagChangedTransaction(string Old, string New, int IndexOld, int IndexNew) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is TagChangedTransaction other && other.IndexNew == IndexOld + ? new TagChangedTransaction(other.Old, New, other.IndexOld, IndexNew) + : null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).RenameTag((Design)data, IndexNew, Old); +} + +/// Only Designs. +public readonly record struct ModAddedTransaction(Mod Mod, ModSettings Settings) + : ITransaction +{ + public ITransaction? Merge(ITransaction other) + => null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).RemoveMod((Design)data, Mod); +} + +/// Only Designs. +public readonly record struct ModRemovedTransaction(Mod Mod, ModSettings Settings) + : ITransaction +{ + public ITransaction? Merge(ITransaction other) + => null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).AddMod((Design)data, Mod, Settings); +} + +/// Only Designs. +public readonly record struct ModUpdatedTransaction(Mod Mod, ModSettings Old, ModSettings New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is ModUpdatedTransaction other && Mod == other.Mod ? new ModUpdatedTransaction(Mod, other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).UpdateMod((Design)data, Mod, Old); +} + +/// Only Designs. +public readonly record struct MaterialTransaction(MaterialValueIndex Index, ColorRow? Old, ColorRow? New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is MaterialTransaction other && Index == other.Index ? new MaterialTransaction(Index, other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).ChangeMaterialValue((Design)data, Index, Old); +} + +/// Only Designs. +public readonly record struct MaterialRevertTransaction(MaterialValueIndex Index, bool Old, bool New) + : ITransaction +{ + public ITransaction? Merge(ITransaction other) + => null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).ChangeMaterialRevert((Design)data, Index, Old); +} + +/// Only Designs. +public readonly record struct ApplicationTransaction(object Index, bool Old, bool New) + : ITransaction +{ + public ITransaction? Merge(ITransaction other) + => null; + + public void Revert(IDesignEditor editor, object data) + { + var manager = (DesignManager)editor; + var design = (Design)data; + switch (Index) + { + case CustomizeIndex idx: + manager.ChangeApplyCustomize(design, idx, Old); + break; + case (EquipSlot slot, true): + manager.ChangeApplyStains(design, slot, Old); + break; + case (EquipSlot slot, _): + manager.ChangeApplyItem(design, slot, Old); + break; + case BonusItemFlag slot: + manager.ChangeApplyBonusItem(design, slot, Old); + break; + case CrestFlag slot: + manager.ChangeApplyCrest(design, slot, Old); + break; + case MetaIndex slot: + manager.ChangeApplyMeta(design, slot, Old); + break; + case CustomizeParameterFlag slot: + manager.ChangeApplyParameter(design, slot, Old); + break; + case MaterialValueIndex slot: + manager.ChangeApplyMaterialValue(design, slot, Old); + break; + } + } +} diff --git a/Glamourer/Designs/History/EditorHistory.cs b/Glamourer/Designs/History/EditorHistory.cs new file mode 100644 index 0000000..6382b94 --- /dev/null +++ b/Glamourer/Designs/History/EditorHistory.cs @@ -0,0 +1,191 @@ +using Glamourer.Api.Enums; +using Glamourer.Events; +using Glamourer.Interop.Structs; +using Glamourer.State; +using OtterGui.Services; + +namespace Glamourer.Designs.History; + +public class EditorHistory : IDisposable, IService +{ + public const int MaxUndo = 16; + + private sealed class Queue : IReadOnlyList + { + private DateTime _lastAdd = DateTime.UtcNow; + + private readonly ITransaction[] _data = new ITransaction[MaxUndo]; + public int Offset { get; private set; } + public int Count { get; private set; } + + public void Add(ITransaction transaction) + { + if (!TryMerge(transaction)) + { + if (Count == MaxUndo) + { + _data[Offset] = transaction; + Offset = (Offset + 1) % MaxUndo; + } + else + { + if (Offset > 0) + { + _data[(Count + Offset) % MaxUndo] = transaction; + ++Count; + } + else + { + _data[Count] = transaction; + ++Count; + } + } + } + + _lastAdd = DateTime.UtcNow; + } + + private bool TryMerge(ITransaction newTransaction) + { + if (Count == 0) + return false; + + var time = DateTime.UtcNow; + if (time - _lastAdd > TimeSpan.FromMilliseconds(250)) + return false; + + var lastIdx = (Offset + Count - 1) % MaxUndo; + if (newTransaction.Merge(_data[lastIdx]) is not { } transaction) + return false; + + _data[lastIdx] = transaction; + return true; + } + + public ITransaction? RemoveLast() + { + if (Count == 0) + return null; + + --Count; + var idx = (Offset + Count) % MaxUndo; + return _data[idx]; + } + + public IEnumerator GetEnumerator() + { + var end = Offset + (Offset + Count) % MaxUndo; + for (var i = Offset; i < end; ++i) + yield return _data[i]; + + end = Count - end; + for (var i = 0; i < end; ++i) + yield return _data[i]; + } + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public ITransaction this[int index] + => index < 0 || index >= Count + ? throw new IndexOutOfRangeException() + : _data[(Offset + index) % MaxUndo]; + } + + private readonly DesignEditor _designEditor; + private readonly StateEditor _stateEditor; + private readonly DesignChanged _designChanged; + private readonly StateChanged _stateChanged; + + private readonly Dictionary _stateEntries = []; + private readonly Dictionary _designEntries = []; + + private bool _undoMode; + + public EditorHistory(DesignManager designEditor, StateManager stateEditor, DesignChanged designChanged, StateChanged stateChanged) + { + _designEditor = designEditor; + _stateEditor = stateEditor; + _designChanged = designChanged; + _stateChanged = stateChanged; + + _designChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.EditorHistory); + _stateChanged.Subscribe(OnStateChanged, StateChanged.Priority.EditorHistory); + } + + public void Dispose() + { + _designChanged.Unsubscribe(OnDesignChanged); + _stateChanged.Unsubscribe(OnStateChanged); + } + + public bool CanUndo(ActorState state) + => _stateEntries.TryGetValue(state, out var list) && list.Count > 0; + + public bool CanUndo(Design design) + => _designEntries.TryGetValue(design, out var list) && list.Count > 0; + + public bool Undo(ActorState state) + { + if (!_stateEntries.TryGetValue(state, out var list) || list.Count == 0) + return false; + + _undoMode = true; + list.RemoveLast()!.Revert(_stateEditor, state); + _undoMode = false; + return true; + } + + public bool Undo(Design design) + { + if (!_designEntries.TryGetValue(design, out var list) || list.Count == 0) + return false; + + _undoMode = true; + list.RemoveLast()!.Revert(_designEditor, design); + _undoMode = false; + return true; + } + + + private void AddStateTransaction(ActorState state, ITransaction transaction) + { + if (!_stateEntries.TryGetValue(state, out var list)) + { + list = new Queue(); + _stateEntries.Add(state, list); + } + + list.Add(transaction); + } + + private void AddDesignTransaction(Design design, ITransaction transaction) + { + if (!_designEntries.TryGetValue(design, out var list)) + { + list = new Queue(); + _designEntries.Add(design, list); + } + + list.Add(transaction); + } + + + private void OnStateChanged(StateChangeType type, StateSource source, ActorState state, ActorData actors, ITransaction? data) + { + if (_undoMode || source is not StateSource.Manual) + return; + + if (data is not null) + AddStateTransaction(state, data); + } + + private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? data) + { + if (_undoMode) + return; + + if (data is not null) + AddDesignTransaction(design, data); + } +} diff --git a/Glamourer/Designs/History/Transaction.cs b/Glamourer/Designs/History/Transaction.cs new file mode 100644 index 0000000..ac310b0 --- /dev/null +++ b/Glamourer/Designs/History/Transaction.cs @@ -0,0 +1,113 @@ +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using Glamourer.GameData; + +namespace Glamourer.Designs.History; + +public interface ITransaction +{ + public ITransaction? Merge(ITransaction other); + public void Revert(IDesignEditor editor, object data); +} + +public readonly record struct CustomizeTransaction(CustomizeIndex Slot, CustomizeValue Old, CustomizeValue New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is CustomizeTransaction other && Slot == other.Slot ? new CustomizeTransaction(Slot, other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => editor.ChangeCustomize(data, Slot, Old, ApplySettings.Manual); +} + +public readonly record struct EntireCustomizeTransaction(CustomizeFlag Apply, CustomizeArray Old, CustomizeArray New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is EntireCustomizeTransaction other ? new EntireCustomizeTransaction(Apply | other.Apply, other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => editor.ChangeEntireCustomize(data, Old, Apply, ApplySettings.Manual); +} + +public readonly record struct EquipTransaction(EquipSlot Slot, EquipItem Old, EquipItem New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is EquipTransaction other && Slot == other.Slot ? new EquipTransaction(Slot, other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => editor.ChangeItem(data, Slot, Old, ApplySettings.Manual); +} + +public readonly record struct BonusItemTransaction(BonusItemFlag Slot, BonusItem Old, BonusItem New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is BonusItemTransaction other && Slot == other.Slot ? new BonusItemTransaction(Slot, other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => editor.ChangeBonusItem(data, Slot, Old, ApplySettings.Manual); +} + +public readonly record struct WeaponTransaction( + EquipItem OldMain, + EquipItem OldOff, + EquipItem OldGauntlets, + EquipItem NewMain, + EquipItem NewOff, + EquipItem NewGauntlets) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is WeaponTransaction other + ? new WeaponTransaction(other.OldMain, other.OldOff, other.OldGauntlets, NewMain, NewOff, NewGauntlets) + : null; + + public void Revert(IDesignEditor editor, object data) + { + editor.ChangeItem(data, EquipSlot.MainHand, OldMain, ApplySettings.Manual); + editor.ChangeItem(data, EquipSlot.OffHand, OldOff, ApplySettings.Manual); + editor.ChangeItem(data, EquipSlot.Hands, OldGauntlets, ApplySettings.Manual); + } +} + +public readonly record struct StainTransaction(EquipSlot Slot, StainIds Old, StainIds New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is StainTransaction other && Slot == other.Slot ? new StainTransaction(Slot, other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => editor.ChangeStains(data, Slot, Old, ApplySettings.Manual); +} + +public readonly record struct CrestTransaction(CrestFlag Slot, bool Old, bool New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is CrestTransaction other && Slot == other.Slot ? new CrestTransaction(Slot, other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => editor.ChangeCrest(data, Slot, Old, ApplySettings.Manual); +} + +public readonly record struct ParameterTransaction(CustomizeParameterFlag Slot, CustomizeParameterValue Old, CustomizeParameterValue New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => older is ParameterTransaction other && Slot == other.Slot ? new ParameterTransaction(Slot, other.Old, New) : null; + + public void Revert(IDesignEditor editor, object data) + => editor.ChangeCustomizeParameter(data, Slot, Old, ApplySettings.Manual); +} + +public readonly record struct MetaTransaction(MetaIndex Slot, bool Old, bool New) + : ITransaction +{ + public ITransaction? Merge(ITransaction older) + => null; + + public void Revert(IDesignEditor editor, object data) + => editor.ChangeMetaState(data, Slot, Old, ApplySettings.Manual); +} diff --git a/Glamourer/Designs/Links/DesignLinkManager.cs b/Glamourer/Designs/Links/DesignLinkManager.cs index 76d9c9a..df1f147 100644 --- a/Glamourer/Designs/Links/DesignLinkManager.cs +++ b/Glamourer/Designs/Links/DesignLinkManager.cs @@ -1,4 +1,5 @@ using Glamourer.Automation; +using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.Services; using OtterGui.Services; @@ -67,7 +68,7 @@ public sealed class DesignLinkManager : IService, IDisposable _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); } - private void OnDesignChanged(DesignChanged.Type type, Design deletedDesign, object? _) + private void OnDesignChanged(DesignChanged.Type type, Design deletedDesign, ITransaction? _) { if (type is not DesignChanged.Type.Deleted) return; diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index a8f35d5..6c2ba8a 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -1,4 +1,5 @@ using Glamourer.Designs; +using Glamourer.Designs.History; using Glamourer.Gui; using OtterGui.Classes; @@ -13,113 +14,116 @@ namespace Glamourer.Events; /// /// public sealed class DesignChanged() - : EventWrapper(nameof(DesignChanged)) + : EventWrapper(nameof(DesignChanged)) { public enum Type { - /// A new design was created. Data is a potential path to move it to [string?]. + /// A new design was created. Created, - /// An existing design was deleted. Data is null. + /// An existing design was deleted. Deleted, - /// Invoked on full reload. Design and Data are null. + /// Invoked on full reload. ReloadedAll, - /// An existing design was renamed. Data is the prior name [string]. + /// An existing design was renamed. Renamed, - /// An existing design had its description changed. Data is the prior description [string]. + /// An existing design had its description changed. ChangedDescription, - /// An existing design had its associated color changed. Data is the prior color [string]. + /// An existing design had its associated color changed. ChangedColor, - /// An existing design had a new tag added. Data is the new tag and the index it was added at [(string, int)]. + /// An existing design had a new tag added. AddedTag, - /// An existing design had an existing tag removed. Data is the removed tag and the index it had before removal [(string, int)]. + /// An existing design had an existing tag removed. RemovedTag, - /// An existing design had an existing tag renamed. Data is the old name of the tag, the new name of the tag, and the index it had before being resorted [(string, string, int)]. + /// An existing design had an existing tag renamed. ChangedTag, - /// An existing design had a new associated mod added. Data is the Mod and its Settings [(Mod, ModSettings)]. + /// An existing design had a new associated mod added. AddedMod, - /// An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. + /// An existing design had an existing associated mod removed. RemovedMod, - /// An existing design had a link to a different design added, removed or moved. Data is null. + /// An existing design had an existing associated mod updated. + UpdatedMod, + + /// An existing design had a link to a different design added, removed or moved. ChangedLink, - /// An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. + /// An existing design had a customization changed. Customize, - /// An existing design had its entire customize array changed. Data is the old array, the applied flags and the changed flags. [(CustomizeArray, CustomizeFlag, CustomizeFlag)]. + /// An existing design had its entire customize array changed. EntireCustomize, - /// An existing design had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. + /// An existing design had an equipment piece changed. Equip, - /// An existing design had a bonus item changed. Data is the old value, the new value and the slot [(BonusItem, BonusItem, BonusItemFlag)]. + /// An existing design had a bonus item changed. BonusItem, - /// An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand, the new offhand (if any) and the new gauntlets (if any). [(EquipItem, EquipItem, EquipItem, EquipItem?, EquipItem?)]. + /// An existing design had its weapons changed. Weapon, - /// An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainIds, StainIds, EquipSlot)]. + /// An existing design had a stain changed. Stains, - /// An existing design had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. + /// An existing design had a crest visibility changed. Crest, - /// An existing design had a customize parameter changed. Data is the old value, the new value and the flag [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. + /// An existing design had a customize parameter changed. Parameter, - /// An existing design had an advanced dye row added, changed, or deleted. Data is the old value, the new value and the index [(ColorRow?, ColorRow?, MaterialValueIndex)]. + /// An existing design had an advanced dye row added, changed, or deleted. Material, - /// An existing design had an advanced dye rows Revert state changed. Data is the index [MaterialValueIndex]. + /// An existing design had an advanced dye rows Revert state changed. 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]. + /// An existing design changed whether a specific customization is applied. ApplyCustomize, - /// An existing design changed whether a specific equipment piece is applied. Data is the slot of the equipment [EquipSlot]. + /// An existing design changed whether a specific equipment piece is applied. ApplyEquip, - /// An existing design changed whether a specific bonus item is applied. Data is the slot of the item [BonusItemFlag]. + /// An existing design changed whether a specific bonus item is applied. ApplyBonusItem, - /// An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. + /// An existing design changed whether a specific stain is applied. ApplyStain, - /// An existing design changed whether a specific crest visibility is applied. Data is the slot of the equipment [EquipSlot]. + /// An existing design changed whether a specific crest visibility is applied. ApplyCrest, - /// An existing design changed whether a specific customize parameter is applied. Data is the flag for the parameter [CustomizeParameterFlag]. + /// An existing design changed whether a specific customize parameter is applied. ApplyParameter, - /// An existing design changed whether an advanced dye row is applied. Data is the index [MaterialValueIndex]. + /// An existing design changed whether an advanced dye row is applied. ApplyMaterial, - /// An existing design changed its write protection status. Data is the new value [bool]. + /// An existing design changed its write protection status. WriteProtection, - /// An existing design changed its display status for the quick design bar. Data is the new value [bool]. + /// An existing design changed its display status for the quick design bar. QuickDesignBar, - /// An existing design changed one of the meta flags. Data is the flag, whether it was about their applying and the new value [(MetaFlag, bool, bool)]. + /// An existing design changed one of the meta flags. Other, } public enum Priority { - /// + /// DesignLinkManager = 1, /// @@ -131,7 +135,10 @@ public sealed class DesignChanged() /// DesignFileSystemSelector = -1, - /// + /// DesignCombo = -2, + + /// + EditorHistory = -1000, } } diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index 641665c..c704195 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -1,4 +1,5 @@ using Glamourer.Api.Enums; +using Glamourer.Designs.History; using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Classes; @@ -15,11 +16,17 @@ namespace Glamourer.Events; /// /// public sealed class StateChanged() - : EventWrapper(nameof(StateChanged)) + : EventWrapper(nameof(StateChanged)) { public enum Priority { - GlamourerIpc = int.MinValue, + /// + GlamourerIpc = int.MinValue, + + /// PenumbraAutoRedraw = 0, + + /// + EditorHistory = -1000, } -} \ No newline at end of file +} diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 4bd18ab..dccfa44 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -2,6 +2,7 @@ using Dalamud.Interface.Utility.Raii; using Glamourer.Automation; using Glamourer.Designs; +using Glamourer.Designs.History; using Glamourer.Designs.Special; using Glamourer.Events; using ImGuiNET; @@ -29,7 +30,7 @@ public abstract class DesignComboBase : FilterComboCache "Undo the last change."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Undo; + + public override bool Visible + => panel._state != null; + + protected override bool Disabled + => (panel._state?.IsLocked ?? true) || !panel._editorHistory.CanUndo(panel._state); + + protected override void OnClick() + => panel._editorHistory.Undo(panel._state!); + } + private sealed class LockedButton(ActorPanel panel) : HeaderDrawer.Button { protected override string Description diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 11147c8..ea117c5 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -2,6 +2,7 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin.Services; using Glamourer.Designs; +using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.Services; using ImGuiNET; @@ -178,7 +179,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector _config.OpenFoldersByDefault; - private void OnDesignChange(DesignChanged.Type type, Design design, object? oldData) + private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? _) { switch (type) { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 1a072cb..07eb432 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -277,7 +277,7 @@ public class DesignPanel { var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : _selector.Selected!.DoApplyStain(slot); if (ImGui.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange) - _manager.ChangeApplyStain(_selector.Selected!, slot, apply); + _manager.ChangeApplyStains(_selector.Selected!, slot, apply); } else foreach (var slot in slots) diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index 72fb554..fbe0d9d 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin.Services; using Glamourer.Api.Enums; +using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.Interop.Structs; using Glamourer.State; @@ -30,21 +31,21 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService _stateChanged = stateChanged; _penumbra.ModSettingChanged += OnModSettingChange; _framework.Update += OnFramework; - _stateChanged.Subscribe(OnStateChange, StateChanged.Priority.PenumbraAutoRedraw); + _stateChanged.Subscribe(OnStateChanged, StateChanged.Priority.PenumbraAutoRedraw); } public void Dispose() { _penumbra.ModSettingChanged -= OnModSettingChange; _framework.Update -= OnFramework; - _stateChanged.Unsubscribe(OnStateChange); + _stateChanged.Unsubscribe(OnStateChanged); } private readonly ConcurrentQueue<(ActorState, Action, int)> _actions = []; private readonly ConcurrentSet _skips = []; private DateTime _frame; - private void OnStateChange(StateChangeType type, StateSource source, ActorState state, ActorData _1, object? _2) + private void OnStateChanged(StateChangeType type, StateSource source, ActorState state, ActorData _1, ITransaction? _2) { if (type is StateChangeType.Design && source.IsIpc()) _skips.TryAdd(state); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index c627564..95c118d 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,5 +1,6 @@ using Glamourer.Api.Enums; using Glamourer.Designs; +using Glamourer.Designs.History; using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; @@ -39,7 +40,7 @@ public class StateEditor( var actors = Applier.ForceRedraw(state, source.RequiresChange()); Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Model, source, state, actors, (old, modelId)); + StateChanged.Invoke(StateChangeType.Model, source, state, actors, null); } /// @@ -52,7 +53,7 @@ public class StateEditor( var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Customize, settings.Source, state, actors, (old, value, idx)); + StateChanged.Invoke(StateChangeType.Customize, settings.Source, state, actors, new CustomizeTransaction(idx, old, value)); } /// @@ -65,7 +66,8 @@ public class StateEditor( var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.EntireCustomize, settings.Source, state, actors, (old, applied)); + StateChanged.Invoke(StateChangeType.EntireCustomize, settings.Source, state, actors, + new EntireCustomizeTransaction(applied, old, customizeInput)); } /// @@ -86,7 +88,25 @@ public class StateEditor( 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")}.]"); - StateChanged.Invoke(type, settings.Source, state, actors, (old, item, slot)); + + if (type is StateChangeType.Equip) + { + StateChanged.Invoke(type, settings.Source, state, actors, new EquipTransaction(slot, old, item)); + } + else if (slot is EquipSlot.MainHand) + { + var oldOff = state.ModelData.Item(EquipSlot.OffHand); + var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); + StateChanged.Invoke(type, settings.Source, state, actors, + new WeaponTransaction(old, oldOff, oldGauntlets, item, oldOff, oldGauntlets)); + } + else + { + var oldMain = state.ModelData.Item(EquipSlot.MainHand); + var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); + StateChanged.Invoke(type, settings.Source, state, actors, + new WeaponTransaction(oldMain, old, oldGauntlets, oldMain, item, oldGauntlets)); + } } public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default) @@ -98,7 +118,7 @@ public class StateEditor( var actors = Applier.ChangeBonusItem(state, slot, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.BonusItem, settings.Source, state, actors, (old, item, slot)); + StateChanged.Invoke(StateChangeType.BonusItem, settings.Source, state, actors, new BonusItemTransaction(slot, old, item)); } /// @@ -131,8 +151,26 @@ public class StateEditor( Glamourer.Log.Verbose( $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot)); - StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, (oldStains, stains!.Value, slot)); + if (type is StateChangeType.Equip) + { + StateChanged.Invoke(type, settings.Source, state, actors, new EquipTransaction(slot, old, item!.Value)); + } + else if (slot is EquipSlot.MainHand) + { + var oldOff = state.ModelData.Item(EquipSlot.OffHand); + var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); + StateChanged.Invoke(type, settings.Source, state, actors, + new WeaponTransaction(old, oldOff, oldGauntlets, item!.Value, oldOff, oldGauntlets)); + } + else + { + var oldMain = state.ModelData.Item(EquipSlot.MainHand); + var oldGauntlets = state.ModelData.Item(EquipSlot.Hands); + StateChanged.Invoke(type, settings.Source, state, actors, + new WeaponTransaction(oldMain, old, oldGauntlets, oldMain, item!.Value, oldGauntlets)); + } + + StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, oldStains, stains!.Value)); } /// @@ -145,7 +183,7 @@ public class StateEditor( var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, (old, stains, slot)); + StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, old, stains)); } /// @@ -158,7 +196,7 @@ public class StateEditor( var actors = Applier.ChangeCrests(state, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Crest, settings.Source, state, actors, (old, crest, slot)); + StateChanged.Invoke(StateChangeType.Crest, settings.Source, state, actors, new CrestTransaction(slot, old, crest)); } /// @@ -176,7 +214,7 @@ public class StateEditor( var actors = Applier.ChangeParameters(state, flag, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set {flag} in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Parameter, settings.Source, state, actors, (old, @new, flag)); + StateChanged.Invoke(StateChangeType.Parameter, settings.Source, state, actors, new ParameterTransaction(flag, old, @new)); } public void ChangeMaterialValue(object data, MaterialValueIndex index, in MaterialValueState newValue, ApplySettings settings) @@ -188,7 +226,8 @@ public class StateEditor( var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index)); + StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, + new MaterialTransaction(index, oldValue, newValue.Game)); } public void ResetMaterialValue(object data, MaterialValueIndex index, ApplySettings settings) @@ -200,7 +239,7 @@ public class StateEditor( var actors = Applier.ChangeMaterialValue(state, index, true); Glamourer.Log.Verbose( $"Reset material value in state {state.Identifier.Incognito(null)} to game value. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, index); + StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, new MaterialTransaction(index, null, null)); } /// @@ -213,7 +252,7 @@ public class StateEditor( var actors = Applier.ChangeMetaState(state, index, settings.Source.RequiresChange()); Glamourer.Log.Verbose( $"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Other, settings.Source, state, actors, (old, value, MetaIndex.HatState)); + StateChanged.Invoke(StateChangeType.Other, settings.Source, state, actors, new MetaTransaction(index, old, value)); } /// @@ -377,7 +416,7 @@ public class StateEditor( Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, mergedDesign.Design); + StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later return; From ab771fd0105e76e937b4c5df6fc5e92a15818ba9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 31 Jul 2024 20:57:50 +0200 Subject: [PATCH 440/786] Add Undo to design panel. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 36 +++++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 07eb432..e11a032 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -4,10 +4,12 @@ using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.System.Framework; using Glamourer.Automation; using Glamourer.Designs; +using Glamourer.Designs.History; using Glamourer.GameData; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Materials; +using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Interop; using Glamourer.State; using ImGuiNET; @@ -38,6 +40,7 @@ public class DesignPanel private readonly CustomizeParameterDrawer _parameterDrawer; private readonly DesignLinkDrawer _designLinkDrawer; private readonly MaterialDrawer _materials; + private readonly EditorHistory _history; private readonly Button[] _leftButtons; private readonly Button[] _rightButtons; @@ -56,7 +59,8 @@ public class DesignPanel MultiDesignPanel multiDesignPanel, CustomizeParameterDrawer parameterDrawer, DesignLinkDrawer designLinkDrawer, - MaterialDrawer materials) + MaterialDrawer materials, + EditorHistory history) { _selector = selector; _customizationDrawer = customizationDrawer; @@ -73,12 +77,14 @@ public class DesignPanel _parameterDrawer = parameterDrawer; _designLinkDrawer = designLinkDrawer; _materials = materials; + _history = history; _leftButtons = [ new SetFromClipboardButton(this), - new UndoButton(this), + new DesignUndoButton(this), new ExportToClipboardButton(this), new ApplyCharacterButton(this), + new UndoButton(this), ]; _rightButtons = [ @@ -544,7 +550,7 @@ public class DesignPanel } } - private sealed class UndoButton(DesignPanel panel) : Button + private sealed class DesignUndoButton(DesignPanel panel) : Button { public override bool Visible => panel._selector.Selected != null; @@ -553,10 +559,10 @@ public class DesignPanel => !panel._manager.CanUndo(panel._selector.Selected) || (panel._selector.Selected?.WriteProtected() ?? true); protected override string Description - => "Undo the last change if you accidentally overwrote your design with a different one."; + => "Undo the last time you applied an entire design onto this design, if you accidentally overwrote your design with a different one."; protected override FontAwesomeIcon Icon - => FontAwesomeIcon.Undo; + => FontAwesomeIcon.SyncAlt; protected override void OnClick() { @@ -606,7 +612,7 @@ public class DesignPanel protected override string Description => "Overwrite this design with your character's current state."; - + protected override bool Disabled => panel._selector.Selected?.WriteProtected() ?? true; @@ -632,4 +638,22 @@ public class DesignPanel } } } + + private sealed class UndoButton(DesignPanel panel) : Button + { + protected override string Description + => "Undo the last change."; + + protected override FontAwesomeIcon Icon + => FontAwesomeIcon.Undo; + + public override bool Visible + => panel._selector.Selected != null; + + protected override bool Disabled + => (panel._selector.Selected?.WriteProtected() ?? true) || !panel._history.CanUndo(panel._selector.Selected); + + protected override void OnClick() + => panel._history.Undo(panel._selector.Selected!); + } } From ad79d9cc079adb820e7c5febc01f9795ea6fd60e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 1 Aug 2024 17:06:10 +0200 Subject: [PATCH 441/786] Set focus on detached advanced dyes when opening or toggling with buttons. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 61eea83..a9ab805 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -29,6 +29,7 @@ public sealed unsafe class AdvancedDyePopup( private Actor _actor; private byte _selectedMaterial = byte.MaxValue; private bool _anyChanged; + private bool _forceFocus; private const int RowsPerPage = 16; private int _rowOffset; @@ -70,6 +71,7 @@ public sealed unsafe class AdvancedDyePopup( if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Palette.ToIconString(), new Vector2(ImGui.GetFrameHeight()), string.Empty, false, true)) { + _forceFocus = true; _selectedMaterial = byte.MaxValue; _drawIndex = isOpen ? null : index; } @@ -195,6 +197,12 @@ public sealed unsafe class AdvancedDyePopup( ImGui.SetNextWindowSize(new Vector2(width, height)); var window = ImGui.Begin("###Glamourer Advanced Dyes", flags); + if (ImGui.IsWindowAppearing() || _forceFocus) + { + ImGui.SetWindowFocus(); + _forceFocus = false; + } + try { if (window) From e516fd93189fba87f39b3f79e31d161dc6fc6b55 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 1 Aug 2024 15:08:33 +0000 Subject: [PATCH 442/786] [CI] Updating repo.json for testing_1.3.0.9 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 0c672d6..ac8b996 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.8", + "TestingAssemblyVersion": "1.3.0.9", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.8/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.9/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 5460ffd0a07ecf8e11f996ff18a327da8210ae35 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 2 Aug 2024 17:02:28 +0200 Subject: [PATCH 443/786] Update world sets. --- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 8 +- Glamourer/State/WorldSets.cs | 96 ++++++++++++++++---- 2 files changed, 85 insertions(+), 19 deletions(-) diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 558c4df..9651e85 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -357,8 +357,12 @@ public class UnlockTable : Table, IDisposable Job.JobRole.Gatherer => 0xFFD0D0D0, _ => ImGui.GetColorU32(ImGuiCol.Text), }; - using var c = ImRaii.PushColor(ImGuiCol.Text, color); - var r = base.DrawCheckbox(idx, out ret); + bool r; + using (ImRaii.PushColor(ImGuiCol.Text, color)) + { + r = base.DrawCheckbox(idx, out ret); + } + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { _filterValue = job.Flag & _filterValue; diff --git a/Glamourer/State/WorldSets.cs b/Glamourer/State/WorldSets.cs index 6a497ea..314934b 100644 --- a/Glamourer/State/WorldSets.cs +++ b/Glamourer/State/WorldSets.cs @@ -70,6 +70,8 @@ public class WorldSets (CharacterWeapon.Int(2601, 13, 01), CharacterWeapon.Int(2651, 13, 1)), // DNC, High Steel Chakrams (CharacterWeapon.Int(2802, 13, 01), CharacterWeapon.Empty), // RPR, Deepgold War Scythe (CharacterWeapon.Int(2702, 08, 01), CharacterWeapon.Empty), // SGE, Stonegold Milpreves + (CharacterWeapon.Int(3101, 04, 03), CharacterWeapon.Int(3151, 04, 3)), // VPR, High Durium Twinfangs + (CharacterWeapon.Int(2901, 25, 01), CharacterWeapon.Int(2951, 25, 1)), // PCT, Chocobo Brush }; private static readonly (FunEquipSet.Group, CharacterWeapon, CharacterWeapon)[] _50Artifact = @@ -115,6 +117,8 @@ public class WorldSets (FunEquipSet.Group.FullSet(204, 4), CharacterWeapon.Int(2601, 13, 1), CharacterWeapon.Int(2651, 13, 1)), // DNC, Softstepper, High Steel Chakrams (new FunEquipSet.Group(206, 7, 303, 3, 23, 109, 303, 3, 262, 7), CharacterWeapon.Int(2802, 13, 1), CharacterWeapon.Empty), // RPR, Muzhik, Deepgold War Scythe (new FunEquipSet.Group(20, 46, 289, 6, 342, 3, 120, 9, 342, 3), CharacterWeapon.Int(2702, 08, 1), CharacterWeapon.Empty), // SGE, Bookwyrm, Stonegold Milpreves + (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 288, 5), CharacterWeapon.Int(3101, 06, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs + (new FunEquipSet.Group(595, 3, 372, 3, 290, 6, 315, 3, 290, 6), CharacterWeapon.Int(2901, 02, 1), CharacterWeapon.Int(2951, 2, 1)), // PCT, Painter's, Round Brush }; private static readonly (FunEquipSet.Group, CharacterWeapon, CharacterWeapon)[] _60Artifact = @@ -160,6 +164,8 @@ public class WorldSets (FunEquipSet.Group.FullSet(204, 4), CharacterWeapon.Int(2601, 13, 1), CharacterWeapon.Int(2651, 13, 01)), // DNC, Softstepper, High Steel Chakrams (new FunEquipSet.Group(206, 7, 303, 3, 23, 109, 303, 3, 262, 7), CharacterWeapon.Int(2802, 13, 1), CharacterWeapon.Empty), // RPR, Muzhik, Deepgold War Scythe (new FunEquipSet.Group(20, 46, 289, 6, 342, 3, 120, 9, 342, 3), CharacterWeapon.Int(2702, 08, 1), CharacterWeapon.Empty), // SGE, Bookwyrm, Stonegold Milpreves + (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 288, 5), CharacterWeapon.Int(3101, 06, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs + (new FunEquipSet.Group(595, 3, 372, 3, 290, 6, 315, 3, 290, 6), CharacterWeapon.Int(2901, 02, 1), CharacterWeapon.Int(2951, 2, 1)), // PCT, Painter's, Round Brush }; private static readonly (FunEquipSet.Group, CharacterWeapon, CharacterWeapon)[] _70Artifact = @@ -205,6 +211,8 @@ public class WorldSets (FunEquipSet.Group.FullSet(204, 4), CharacterWeapon.Int(2601, 13, 1), CharacterWeapon.Int(2651, 13, 01)), // DNC, Softstepper, High Steel Chakrams (new FunEquipSet.Group(206, 7, 303, 3, 23, 109, 303, 3, 262, 7), CharacterWeapon.Int(2802, 13, 1), CharacterWeapon.Empty), // RPR, Muzhik, Deepgold War Scythe (new FunEquipSet.Group(20, 46, 289, 6, 342, 3, 120, 9, 342, 3), CharacterWeapon.Int(2702, 08, 1), CharacterWeapon.Empty), // SGE, Bookwyrm, Stonegold Milpreves + (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 288, 5), CharacterWeapon.Int(3101, 06, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs + (new FunEquipSet.Group(595, 3, 372, 3, 290, 6, 315, 3, 290, 6), CharacterWeapon.Int(2901, 02, 1), CharacterWeapon.Int(2951, 2, 1)), // PCT, Painter's, Round Brush }; private static readonly (FunEquipSet.Group, CharacterWeapon, CharacterWeapon)[] _80Artifact = @@ -250,6 +258,8 @@ public class WorldSets (FunEquipSet.Group.FullSet(543, 1), CharacterWeapon.Int(2601, 001, 1), CharacterWeapon.Int(2651, 01, 001)), // DNC, Dancer, Krishna (new FunEquipSet.Group(206, 7, 303, 3, 23, 109, 303, 3, 262, 7), CharacterWeapon.Int(2802, 013, 1), CharacterWeapon.Empty), // RPR, Harvester's, Demon Slicer (new FunEquipSet.Group(20, 46, 289, 6, 342, 3, 120, 9, 342, 3), CharacterWeapon.Int(2702, 008, 1), CharacterWeapon.Empty), // SGE, Therapeute's, Horkos + (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 288, 5), CharacterWeapon.Int(3101, 6, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs + (new FunEquipSet.Group(595, 3, 372, 3, 290, 6, 315, 3, 290, 6), CharacterWeapon.Int(2901, 2, 1), CharacterWeapon.Int(2951, 2, 1)), // PCT, Painter's, Round Brush }; private static readonly (FunEquipSet.Group, CharacterWeapon, CharacterWeapon)[] _90Artifact = @@ -295,29 +305,80 @@ public class WorldSets (FunEquipSet.Group.FullSet(694, 1), CharacterWeapon.Int(2607, 001, 1), CharacterWeapon.Int(2657, 001, 001)), // DNC, Etoile, Terpsichore (FunEquipSet.Group.FullSet(695, 1), CharacterWeapon.Int(2801, 001, 1), CharacterWeapon.Empty), // RPR, Reaper, Death Sickle (FunEquipSet.Group.FullSet(696, 1), CharacterWeapon.Int(2701, 006, 1), CharacterWeapon.Empty), // SGE, Didact, Hagneia + (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 288, 5), CharacterWeapon.Int(3101, 006, 1), CharacterWeapon.Int(3151, 006, 001)), // VPR, Snakebite, Twinfangs + (new FunEquipSet.Group(595, 3, 372, 3, 290, 6, 315, 3, 290, 6), CharacterWeapon.Int(2901, 002, 1), CharacterWeapon.Int(2951, 002, 001)), // PCT, Painter's, Round Brush + }; + + private static readonly (FunEquipSet.Group, CharacterWeapon, CharacterWeapon)[] _100Artifact = + { + (FunEquipSet.Group.FullSet(000, 0), CharacterWeapon.Empty, CharacterWeapon.Empty), // ADV, Nothing + (FunEquipSet.Group.FullSet(772, 1), CharacterWeapon.Int(0220, 002, 01), CharacterWeapon.Int(0114, 002, 001)), // GLA, Caballarius, Clarent, Galahad + (FunEquipSet.Group.FullSet(773, 1), CharacterWeapon.Int(0338, 001, 01), CharacterWeapon.Int(0388, 001, 001)), // PGL, Hesychast's, Suwaiyas + (FunEquipSet.Group.FullSet(774, 1), CharacterWeapon.Int(0417, 001, 01), CharacterWeapon.Empty), // MRD, Agoge, Ferocity + (FunEquipSet.Group.FullSet(775, 1), CharacterWeapon.Int(0528, 001, 01), CharacterWeapon.Empty), // LNC, Heavensbound, Gae Assail + (FunEquipSet.Group.FullSet(776, 1), CharacterWeapon.Int(0635, 001, 01), CharacterWeapon.Int(0698, 130, 001)), // ARC, Bihu, Gastraphetes + (FunEquipSet.Group.FullSet(777, 1), CharacterWeapon.Int(0831, 002, 01), CharacterWeapon.Empty), // CNJ, Theophany, Xoanon + (FunEquipSet.Group.FullSet(778, 1), CharacterWeapon.Int(1033, 002, 01), CharacterWeapon.Empty), // THM, Archmage's, Gridarvor + (FunEquipSet.Group.FullSet(791, 1), CharacterWeapon.Int(5004, 001, 16), CharacterWeapon.Int(5041, 001, 016)), // CRP, Millrise + (FunEquipSet.Group.FullSet(792, 1), CharacterWeapon.Int(5103, 001, 01), CharacterWeapon.Int(5141, 001, 017)), // BSM, Forgerise + (FunEquipSet.Group.FullSet(793, 1), CharacterWeapon.Int(5201, 011, 01), CharacterWeapon.Int(5241, 001, 017)), // ARM, Hammerrise + (FunEquipSet.Group.FullSet(794, 1), CharacterWeapon.Int(5301, 011, 01), CharacterWeapon.Int(5341, 001, 001)), // GSM, Gemrise + (FunEquipSet.Group.FullSet(795, 1), CharacterWeapon.Int(5405, 001, 01), CharacterWeapon.Int(5441, 001, 016)), // LTW, Hiderise + (FunEquipSet.Group.FullSet(796, 1), CharacterWeapon.Int(5503, 001, 01), CharacterWeapon.Int(5571, 001, 001)), // WVR, Boltrise + (FunEquipSet.Group.FullSet(797, 1), CharacterWeapon.Int(5603, 008, 01), CharacterWeapon.Int(5641, 001, 017)), // ALC, Cauldronrise + (FunEquipSet.Group.FullSet(798, 1), CharacterWeapon.Int(5701, 012, 01), CharacterWeapon.Int(5741, 001, 017)), // CUL, Galleyrise + (FunEquipSet.Group.FullSet(799, 1), CharacterWeapon.Int(7004, 001, 01), CharacterWeapon.Int(7051, 001, 017)), // MIN, Minerise + (FunEquipSet.Group.FullSet(800, 1), CharacterWeapon.Int(7101, 012, 01), CharacterWeapon.Int(7151, 001, 017)), // BTN, Fieldrise + (FunEquipSet.Group.FullSet(801, 1), CharacterWeapon.Int(7202, 001, 01), CharacterWeapon.Int(7255, 001, 001)), // FSH, Tacklerise + (FunEquipSet.Group.FullSet(772, 1), CharacterWeapon.Int(0220, 002, 01), CharacterWeapon.Int(0114, 002, 001)), // PLD, Caballarius, Clarent, Galahad + (FunEquipSet.Group.FullSet(773, 1), CharacterWeapon.Int(0338, 001, 01), CharacterWeapon.Int(0388, 001, 001)), // MNK, Hesychast's, Suwaiyas + (FunEquipSet.Group.FullSet(774, 1), CharacterWeapon.Int(0417, 001, 01), CharacterWeapon.Empty), // WAR, Agoge, Ferocity + (FunEquipSet.Group.FullSet(775, 1), CharacterWeapon.Int(0528, 001, 01), CharacterWeapon.Empty), // DRG, Heavensbound, Gae Assail + (FunEquipSet.Group.FullSet(776, 1), CharacterWeapon.Int(0635, 001, 01), CharacterWeapon.Int(0698, 130, 001)), // BRD, Bihu, Gastraphetes + (FunEquipSet.Group.FullSet(777, 1), CharacterWeapon.Int(0831, 002, 01), CharacterWeapon.Empty), // WHM, Theophany, Xoanon + (FunEquipSet.Group.FullSet(778, 1), CharacterWeapon.Int(1033, 002, 01), CharacterWeapon.Empty), // BLM, Archmage's, Gridarvor + (FunEquipSet.Group.FullSet(779, 1), CharacterWeapon.Int(1752, 001, 01), CharacterWeapon.Empty), // ACN, Glyphic, The Grand Grimoire + (FunEquipSet.Group.FullSet(779, 1), CharacterWeapon.Int(1752, 001, 01), CharacterWeapon.Empty), // SMN, Glyphic, The Grand Grimoire + (FunEquipSet.Group.FullSet(780, 1), CharacterWeapon.Int(1753, 001, 01), CharacterWeapon.Empty), // SCH, Pedagogy, Eclecticism + (FunEquipSet.Group.FullSet(781, 1), CharacterWeapon.Int(1801, 128, 01), CharacterWeapon.Int(1851, 128, 001)), // ROG, Momochi, Shiranui + (FunEquipSet.Group.FullSet(781, 1), CharacterWeapon.Int(1801, 128, 01), CharacterWeapon.Int(1851, 128, 001)), // NIN, Momochi, Shiranui + (FunEquipSet.Group.FullSet(783, 1), CharacterWeapon.Int(2026, 002, 01), CharacterWeapon.Int(2099, 001, 001)), // MCH, Forerider's, Sthalmann Special + (FunEquipSet.Group.FullSet(782, 1), CharacterWeapon.Int(1519, 002, 01), CharacterWeapon.Empty), // DRK, Fallen's, Maleficus + (FunEquipSet.Group.FullSet(784, 1), CharacterWeapon.Int(2136, 082, 01), CharacterWeapon.Int(2199, 001, 188)), // AST, Ephemerist's, Metis + (FunEquipSet.Group.FullSet(785, 1), CharacterWeapon.Int(2215, 001, 01), CharacterWeapon.Int(2259, 003, 001)), // SAM, Sakonji, Kogarasumaru + (FunEquipSet.Group.FullSet(786, 1), CharacterWeapon.Int(2301, 097, 01), CharacterWeapon.Int(2380, 001, 001)), // RDM, Roseblood, Colada + (FunEquipSet.Group.FullSet(811, 1), CharacterWeapon.Int(2401, 005, 01), CharacterWeapon.Empty), // BLU, Phantasmal, Blue-eyes + (FunEquipSet.Group.FullSet(787, 1), CharacterWeapon.Int(2501, 064, 01), CharacterWeapon.Empty), // GNB, Bastion's, Chastiefol + (FunEquipSet.Group.FullSet(788, 1), CharacterWeapon.Int(2611, 002, 01), CharacterWeapon.Int(2661, 002, 001)), // DNC, Horos, Soma + (FunEquipSet.Group.FullSet(790, 1), CharacterWeapon.Int(2816, 001, 01), CharacterWeapon.Empty), // RPR, Assassin's, Vendetta + (FunEquipSet.Group.FullSet(789, 1), CharacterWeapon.Int(2701, 026, 01), CharacterWeapon.Empty), // SGE, Metanoia, Asklepian + (FunEquipSet.Group.FullSet(840, 1), CharacterWeapon.Int(3101, 001, 01), CharacterWeapon.Int(3151, 001, 001)), // VPR, Viper's, Sargatanas + (FunEquipSet.Group.FullSet(841, 1), CharacterWeapon.Int(2901, 001, 01), CharacterWeapon.Int(2951, 001, 001)), // PCT, Pictomancer's, Angel Brush }; // @formatter:on private (FunEquipSet.Group, CharacterWeapon, CharacterWeapon)? GetGroup(byte level, byte job, Race race, Gender gender, Random rng) { - const int weight50 = 200; - const int weight60 = 500; - const int weight70 = 700; - const int weight80 = 800; - const int weight90 = 900; - const int weight100 = 1000; + const int weight50 = 200; + const int weight60 = 500; + const int weight70 = 700; + const int weight80 = 800; + const int weight90 = 900; + const int weight100 = 1000; + const int weight110 = 1100; if (job >= StarterWeapons.Length) return null; var maxWeight = level switch { - < 50 => weight50, - < 60 => weight60, - < 70 => weight70, - < 80 => weight80, - < 90 => weight90, - _ => weight100, + < 50 => weight50, + < 60 => weight60, + < 70 => weight70, + < 80 => weight80, + < 90 => weight90, + < 100 => weight100, + _ => weight110, }; var weight = rng.Next(0, maxWeight + 1); @@ -332,11 +393,12 @@ public class WorldSets var list = weight switch { - < weight60 => _50Artifact, - < weight70 => _60Artifact, - < weight80 => _70Artifact, - < weight90 => _80Artifact, - _ => _90Artifact, + < weight60 => _50Artifact, + < weight70 => _60Artifact, + < weight80 => _70Artifact, + < weight90 => _80Artifact, + < weight100 => _90Artifact, + _ => _100Artifact, }; Glamourer.Log.Verbose($"Chose weight {weight}/{maxWeight} for World set [Character: {level} {job} {race} {gender}]."); From 3a63a1b22d5a051f89fc469f62154f2c5ccaf30e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 3 Aug 2024 02:25:53 +0200 Subject: [PATCH 444/786] Add some codes. --- Glamourer/GameData/NpcCustomizeSet.cs | 4 +- Glamourer/Services/CodeService.cs | 57 ++++++++---- Glamourer/State/FunModule.cs | 127 +++++++++++++++++++++++++- 3 files changed, 165 insertions(+), 23 deletions(-) diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index dbf74cc..3c683a5 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -78,8 +78,8 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList }; // Event NPCs have a reference to NpcEquip but also contain the appearance in their own row. - // Prefer the NpcEquip reference if it is set, otherwise use the own. - if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip) + // Prefer the NpcEquip reference if it is set and the own does not appear to be set, otherwise use the own. + if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip && row is { ModelBody: 0, ModelLegs: 0 }) ApplyNpcEquip(ref ret, equip); else ApplyNpcEquip(ref ret, row); diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 61339e1..5ab0d8f 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -30,13 +30,19 @@ public class CodeService Elephants = 0x020000, Crown = 0x040000, Dolphins = 0x080000, + Face = 0x100000, + Manderville = 0x200000, + Smiles = 0x400000, } 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; + public const CodeFlag DyeCodes = + CodeFlag.Clown | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins; + + public const CodeFlag GearCodes = + CodeFlag.Emperor | CodeFlag.World | CodeFlag.Elephants | CodeFlag.Dolphins; public const CodeFlag RaceCodes = CodeFlag.OopsHyur | CodeFlag.OopsElezen @@ -46,6 +52,8 @@ public class CodeService | CodeFlag.OopsAuRa | CodeFlag.OopsHrothgar; + public const CodeFlag FullCodes = CodeFlag.Face | CodeFlag.Manderville | CodeFlag.Smiles; + public const CodeFlag SizeCodes = CodeFlag.Dwarf | CodeFlag.Giant; private CodeFlag _enabled; @@ -161,26 +169,29 @@ public class CodeService private static CodeFlag GetMutuallyExclusive(CodeFlag flag) => flag switch { - CodeFlag.Clown => DyeCodes & ~CodeFlag.Clown, - CodeFlag.Emperor => GearCodes & ~CodeFlag.Emperor, - CodeFlag.Individual => 0, - CodeFlag.Dwarf => SizeCodes & ~CodeFlag.Dwarf, - CodeFlag.Giant => SizeCodes & ~CodeFlag.Giant, - CodeFlag.OopsHyur => RaceCodes & ~CodeFlag.OopsHyur, - CodeFlag.OopsElezen => RaceCodes & ~CodeFlag.OopsElezen, - CodeFlag.OopsLalafell => RaceCodes & ~CodeFlag.OopsLalafell, - CodeFlag.OopsMiqote => RaceCodes & ~CodeFlag.OopsMiqote, - CodeFlag.OopsRoegadyn => RaceCodes & ~CodeFlag.OopsRoegadyn, - CodeFlag.OopsAuRa => RaceCodes & ~CodeFlag.OopsAuRa, - CodeFlag.OopsHrothgar => RaceCodes & ~CodeFlag.OopsHrothgar, - CodeFlag.OopsViera => RaceCodes & ~CodeFlag.OopsViera, + CodeFlag.Clown => (FullCodes | DyeCodes) & ~CodeFlag.Clown, + CodeFlag.Emperor => (FullCodes | GearCodes) & ~CodeFlag.Emperor, + CodeFlag.Individual => FullCodes, + CodeFlag.Dwarf => (FullCodes | SizeCodes) & ~CodeFlag.Dwarf, + CodeFlag.Giant => (FullCodes | SizeCodes) & ~CodeFlag.Giant, + CodeFlag.OopsHyur => (FullCodes | RaceCodes) & ~CodeFlag.OopsHyur, + CodeFlag.OopsElezen => (FullCodes | RaceCodes) & ~CodeFlag.OopsElezen, + CodeFlag.OopsLalafell => (FullCodes | RaceCodes) & ~CodeFlag.OopsLalafell, + CodeFlag.OopsMiqote => (FullCodes | RaceCodes) & ~CodeFlag.OopsMiqote, + CodeFlag.OopsRoegadyn => (FullCodes | RaceCodes) & ~CodeFlag.OopsRoegadyn, + CodeFlag.OopsAuRa => (FullCodes | RaceCodes) & ~CodeFlag.OopsAuRa, + CodeFlag.OopsHrothgar => (FullCodes | RaceCodes) & ~CodeFlag.OopsHrothgar, + CodeFlag.OopsViera => (FullCodes | RaceCodes) & ~CodeFlag.OopsViera, CodeFlag.Artisan => 0, - CodeFlag.SixtyThree => 0, + CodeFlag.SixtyThree => FullCodes, CodeFlag.Shirts => 0, - CodeFlag.World => (DyeCodes | GearCodes) & ~CodeFlag.World, - CodeFlag.Elephants => (DyeCodes | GearCodes) & ~CodeFlag.Elephants, - CodeFlag.Crown => 0, - CodeFlag.Dolphins => (DyeCodes | GearCodes) & ~CodeFlag.Dolphins, + CodeFlag.World => (FullCodes | DyeCodes | GearCodes) & ~CodeFlag.World, + CodeFlag.Elephants => (FullCodes | DyeCodes | GearCodes) & ~CodeFlag.Elephants, + CodeFlag.Crown => FullCodes, + CodeFlag.Dolphins => (FullCodes | DyeCodes | GearCodes) & ~CodeFlag.Dolphins, + CodeFlag.Face => (FullCodes | RaceCodes | SizeCodes | GearCodes | DyeCodes | CodeFlag.Crown | CodeFlag.SixtyThree) & ~CodeFlag.Face, + CodeFlag.Manderville => (FullCodes | RaceCodes | SizeCodes | GearCodes | DyeCodes | CodeFlag.Crown | CodeFlag.SixtyThree) & ~CodeFlag.Manderville, + CodeFlag.Smiles => (FullCodes | RaceCodes | SizeCodes | GearCodes | DyeCodes | CodeFlag.Crown | CodeFlag.SixtyThree) & ~CodeFlag.Smiles, _ => 0, }; @@ -207,6 +218,9 @@ public class CodeService CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ], CodeFlag.Crown => [ 0x43, 0x8E, 0x34, 0x56, 0x24, 0xC9, 0xC6, 0xDE, 0x2A, 0x68, 0x3A, 0x5D, 0xF5, 0x8E, 0xCB, 0xEF, 0x0D, 0x4D, 0x5B, 0xDC, 0x23, 0xF9, 0xF9, 0xBD, 0xD9, 0x60, 0xAD, 0x53, 0xC5, 0xA0, 0x33, 0xC4 ], 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 ], + CodeFlag.Face => [ 0xCA, 0x97, 0x81, 0x12, 0xCA, 0x1B, 0xBD, 0xCA, 0xFA, 0xC2, 0x31, 0xB3, 0x9A, 0x23, 0xDC, 0x4D, 0xA7, 0x86, 0xEF, 0xF8, 0x14, 0x7C, 0x4E, 0x72, 0xB9, 0x80, 0x77, 0x85, 0xAF, 0xEE, 0x48, 0xBB ], + CodeFlag.Manderville => [ 0x3E, 0x23, 0xE8, 0x16, 0x00, 0x39, 0x59, 0x4A, 0x33, 0x89, 0x4F, 0x65, 0x64, 0xE1, 0xB1, 0x34, 0x8B, 0xBD, 0x7A, 0x00, 0x88, 0xD4, 0x2C, 0x4A, 0xCB, 0x73, 0xEE, 0xAE, 0xD5, 0x9C, 0x00, 0x9D ], + CodeFlag.Smiles => [ 0x2E, 0x7D, 0x2C, 0x03, 0xA9, 0x50, 0x7A, 0xE2, 0x65, 0xEC, 0xF5, 0xB5, 0x35, 0x68, 0x85, 0xA5, 0x33, 0x93, 0xA2, 0x02, 0x9D, 0x24, 0x13, 0x94, 0x99, 0x72, 0x65, 0xA1, 0xA2, 0x5A, 0xEF, 0xC6 ], _ => [], }; @@ -233,6 +247,9 @@ public class CodeService 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."), + CodeFlag.Face => (false, 3, ",,!", string.Empty, "Enable a debugging mode for the UI. Not really useful."), + CodeFlag.Manderville => (false, 3, ",,!", string.Empty, "Enable a debugging mode for the UI. Not really useful."), + CodeFlag.Smiles => (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/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index a9ce8e2..f07ff93 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; +using Glamourer.GameData; using Glamourer.Gui; using Glamourer.Services; using ImGuiNET; @@ -35,6 +36,7 @@ public unsafe class FunModule : IDisposable private readonly DesignConverter _designConverter; private readonly DesignManager _designManager; private readonly ObjectManager _objects; + private readonly NpcCustomizeSet _npcs; private readonly StainId[] _stains; public FestivalType CurrentFestival { get; private set; } = FestivalType.None; @@ -68,7 +70,7 @@ public unsafe class FunModule : IDisposable public FunModule(CodeService codes, CustomizeService customizations, ItemManager items, Configuration config, GenericPopupWindow popupWindow, StateManager stateManager, ObjectManager objects, DesignConverter designConverter, - DesignManager designManager) + DesignManager designManager, NpcCustomizeSet npcs) { _codes = codes; _customizations = customizations; @@ -79,6 +81,7 @@ public unsafe class FunModule : IDisposable _objects = objects; _designConverter = designConverter; _designManager = designManager; + _npcs = npcs; _rng = new Random(); _stains = _items.Stains.Keys.Prepend((StainId)0).ToArray(); ResetFestival(); @@ -102,6 +105,15 @@ public unsafe class FunModule : IDisposable return; } + switch (_codes.Masked(CodeService.FullCodes)) + { + case CodeService.CodeFlag.Face: + case CodeService.CodeFlag.Manderville: + case CodeService.CodeFlag.Smiles: + KeepOldArmor(actor, slot, ref armor); + return; + } + if (_codes.Enabled(CodeService.CodeFlag.Crown) && actor.OnlineStatus is OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor && slot.IsEquipment()) @@ -130,11 +142,124 @@ public unsafe class FunModule : IDisposable } } + private sealed class PrioritizedList : List<(T Item, int Priority)> + { + private int _cumulative; + + public PrioritizedList(params (T Item, int Priority)[] list) + { + if (list.Length == 0) + return; + + AddRange(list.Where(p => p.Priority > 0).OrderByDescending(p => p.Priority).Select(p => (p.Item, _cumulative += p.Priority))); + } + + public T GetRandom(Random rng) + { + var val = rng.Next(0, _cumulative); + foreach (var (item, priority) in this) + { + if (val < priority) + return item; + } + + // Should never happen. + return this[^1].Item1; + } + } + + private static readonly PrioritizedList MandervilleMale = new + ( + (1008264, 30), // Hildi + (1008731, 10), // Hildi, slightly damaged + (1011668, 3), // Zombi + (1016617, 5), // Hildi, heavily damaged + (1042518, 1), // Hildi of Light + (1006339, 2), // Godbert, naked + (1008734, 10), // Godbert, shorts + (1015921, 5), // Godbert, ripped + (1041606, 5), // Godbert, only shorts + (1041605, 5), // Godbert, summer + (1024501, 30), // Godbert, fully clothed + (1045184, 3), // Godbrand + (1044749, 1) // Brandihild + ); + + private static readonly PrioritizedList MandervilleFemale = new + ( + (1025669, 5), // Hildi, Geisha + (1025670, 2), // Hildi, makeup, black + (1042477, 2), // Hildi, makeup, white + (1016798, 20), // Julyan, Winter + (1011707, 30), // Julyan + (1005714, 20), // Nashu + (1025668, 5), // Nashu, Kimono + (1025674, 5), // Nashu, fancy + (1042486, 30), // Nashu, inspector + (1017263, 3), // Gigi + (1017263, 1) // Gigi, buff + ); + + private static readonly PrioritizedList Smile = new + ( + (1046504, 75), // Normal + (1046501, 20), // Hat + (1050613, 4), // Armor + (1047625, 1) // Elephant + ); + + private static readonly PrioritizedList FaceMale = new + ( + (1016136, 35), // Gerolt + (1032667, 2), // Gerolt, Suit + (1030519, 35), // Grenoldt + (1030519, 20), // Grenoldt, Short + (1046262, 2), // Grenoldt, Suit + (1048084, 15) // Genolt + ); + + private static readonly PrioritizedList FaceFemale = new + ( + (1013713, 10), // Rowena, Togi + (1018496, 30), // Rowena, Poncho + (1032668, 2), // Rowena, Gown + (1042857, 10), // Rowena, Hannish + (1046255, 10), // Mowen, Miner + (1046263, 2), // Mowen, Gown + (1027544, 30), // Mowen, Bustle + (1049088, 15) // Rhodina + ); + + private bool ApplyFullCode(Actor actor, Span armor, ref CustomizeArray customize) + { + var id = _codes.Masked(CodeService.FullCodes) switch + { + CodeService.CodeFlag.Face when customize.Gender is Gender.Female && actor.Index != 0 => FaceFemale.GetRandom(_rng), + CodeService.CodeFlag.Face when actor.Index != 0 => FaceFemale.GetRandom(_rng), + CodeService.CodeFlag.Smiles => Smile.GetRandom(_rng), + CodeService.CodeFlag.Manderville when customize.Gender is Gender.Female => MandervilleFemale.GetRandom(_rng), + CodeService.CodeFlag.Manderville => MandervilleMale.GetRandom(_rng), + _ => (NpcId)0, + }; + + if (id.Id == 0 || !_npcs.FindFirst(n => n.Id == id, out var npc)) + return false; + + customize = npc.Customize; + var idx = 0; + foreach (ref var a in armor) + a = npc.Equip[idx++]; + return true; + } + public void ApplyFunOnLoad(Actor actor, Span armor, ref CustomizeArray customize) { if (!ValidFunTarget(actor)) return; + if (ApplyFullCode(actor, armor, ref customize)) + return; + // First set the race, if any. SetRace(ref customize); // Now apply the gender. From 34caf29b321d7811439abf51786a67886350a73f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 4 Aug 2024 12:38:04 +0200 Subject: [PATCH 445/786] Add more Corgis to Glamourer. --- Glamourer/Configuration.cs | 2 ++ .../Customization/CustomizationDrawer.Simple.cs | 12 +++++++----- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 16 +++++++++------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index d0b3f98..11c45a8 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -21,6 +21,8 @@ public enum HeightDisplayType Metre, Wrong, WrongFoot, + Corgi, + OlympicPool, } public class Configuration : IPluginConfiguration, ISavable diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 5bb98c9..2e5524f 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -43,11 +43,13 @@ public partial class CustomizationDrawer var heightString = _config.HeightDisplayType switch { - 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}'')", - _ => FormattableString.Invariant($"({height})"), + 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}'')", + HeightDisplayType.Corgi => FormattableString.Invariant($"({height * 100 / 40.0:F1} Corgis)"), + HeightDisplayType.OlympicPool => FormattableString.Invariant($"({height / 3.0:F3} Pools)"), + _ => FormattableString.Invariant($"({height})"), }; ImGui.TextUnformatted(heightString); } diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index f38d81d..1540696 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -401,14 +401,16 @@ public class SettingsTab( ImGuiUtil.HoverTooltip(tt); } - private string HeightDisplayTypeName(HeightDisplayType type) + private static 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, + HeightDisplayType.None => "Do Not Display", + HeightDisplayType.Centimetre => "Centimetres (000.0 cm)", + HeightDisplayType.Metre => "Metres (0.00 m)", + HeightDisplayType.Wrong => "Inches (00.0 in)", + HeightDisplayType.WrongFoot => "Feet (0'00'')", + HeightDisplayType.Corgi => "Corgis (0.0 Corgis)", + HeightDisplayType.OlympicPool => "Olympic-size swimming Pools (0.000 Pools)", + _ => string.Empty, }; } From f69915dcb3079ca49bd2a9f57c30a634f96d7a9a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 4 Aug 2024 12:38:38 +0200 Subject: [PATCH 446/786] Remove some warnings. --- Glamourer/Gui/DesignQuickBar.cs | 5 ++++- Glamourer/Interop/ObjectManager.cs | 2 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 16 +++++++++------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index c3829fa..31ca45e 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -70,6 +70,9 @@ public sealed class DesignQuickBar : Window, IDisposable IsOpen = _config.Ephemeral.ShowDesignQuickBar && _config.QdbButtons != 0; } + public override bool DrawConditions() + => _objects.Player.Valid; + public override void PreDraw() { Flags = GetFlags; @@ -112,7 +115,7 @@ public sealed class DesignQuickBar : Window, IDisposable ImGui.SameLine(); DrawApplyButton(buttonSize); } - + DrawRevertButton(buttonSize); DrawRevertEquipButton(buttonSize); DrawRevertCustomizeButton(buttonSize); diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index f8f5813..b185f4a 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -40,7 +40,7 @@ public class ObjectManager( return false; _identifierUpdate = LastUpdate; - World = (ushort)(this[0].Valid ? this[0].HomeWorld : 0); + World = (ushort)(Player.Valid ? Player.HomeWorld : 0); _identifiers.Clear(); _allWorldIdentifiers.Clear(); _nonOwnedIdentifiers.Clear(); diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index a204a1c..801f211 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -6,6 +6,7 @@ using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.UI; using Glamourer.GameData; using Glamourer.Events; +using Glamourer.Interop; using Glamourer.Services; using Lumina.Excel.GeneratedSheets; using Penumbra.GameData; @@ -15,10 +16,10 @@ namespace Glamourer.Unlocks; public class CustomizeUnlockManager : IDisposable, ISavable { - private readonly SaveService _saveService; - private readonly IClientState _clientState; - private readonly ObjectUnlocked _event; - + private readonly SaveService _saveService; + private readonly IClientState _clientState; + private readonly ObjectUnlocked _event; + private readonly ObjectManager _objects; private readonly Dictionary _unlocked = new(); public readonly IReadOnlyDictionary Unlockable; @@ -27,12 +28,13 @@ public class CustomizeUnlockManager : IDisposable, ISavable => _unlocked; public CustomizeUnlockManager(SaveService saveService, CustomizeService customizations, IDataManager gameData, - IClientState clientState, ObjectUnlocked @event, IGameInteropProvider interop) + IClientState clientState, ObjectUnlocked @event, IGameInteropProvider interop, ObjectManager objects) { interop.InitializeFromAttributes(this); _saveService = saveService; _clientState = clientState; _event = @event; + _objects = objects; Unlockable = CreateUnlockableCustomizations(customizations, gameData); Load(); _setUnlockLinkValueHook.Enable(); @@ -94,7 +96,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable /// Scan and update all unlockable customizations for their current game state. public unsafe void Scan() { - if (_clientState.LocalPlayer == null) + if (!_objects.Player.Valid) return; Glamourer.Log.Debug("[UnlockManager] Scanning for new unlocked customizations."); @@ -128,7 +130,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable } private delegate void SetUnlockLinkValueDelegate(nint uiState, uint data, byte value); - + [Signature(Sigs.SetUnlockLinkValue, DetourName = nameof(SetUnlockLinkValueDetour))] private readonly Hook _setUnlockLinkValueHook = null!; From 9f04ee7695e168b691cbdd7e33ecbb60f0e02948 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 4 Aug 2024 12:39:01 +0200 Subject: [PATCH 447/786] Fix visor state toggle. --- Glamourer/Interop/MetaService.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs index 062986b..1dc4c1a 100644 --- a/Glamourer/Interop/MetaService.cs +++ b/Glamourer/Interop/MetaService.cs @@ -13,7 +13,7 @@ public unsafe class MetaService : IDisposable private readonly VisorStateChanged _visorEvent; private delegate void HideHatGearDelegate(DrawDataContainer* drawData, uint id, byte value); - private delegate void HideWeaponsDelegate(DrawDataContainer* drawData, bool value); + private delegate void HideWeaponsDelegate(DrawDataContainer* drawData, byte value); private readonly Hook _hideHatGearHook; private readonly Hook _hideWeaponsHook; @@ -61,7 +61,7 @@ public unsafe class MetaService : IDisposable if (!actor.IsCharacter) return; - _hideWeaponsHook.Original(&actor.AsCharacter->DrawData, !value); + _hideWeaponsHook.Original(&actor.AsCharacter->DrawData, (byte)(value ? 0 : 1)); } private void HideHatDetour(DrawDataContainer* drawData, uint id, byte value) @@ -80,21 +80,21 @@ public unsafe class MetaService : IDisposable _hideHatGearHook.Original(drawData, id, value); } - private void HideWeaponsDetour(DrawDataContainer* drawData, bool value) + private void HideWeaponsDetour(DrawDataContainer* drawData, byte value) { Actor actor = drawData->OwnerObject; - value = !value; - _weaponEvent.Invoke(actor, ref value); - value = !value; + var v = value == 0; + _weaponEvent.Invoke(actor, ref v); Glamourer.Log.Verbose($"[MetaService] Hide Weapon triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}."); - _hideWeaponsHook.Original(drawData, value); + _hideWeaponsHook.Original(drawData, (byte)(v ? 0 : 1)); } - private void ToggleVisorDetour(DrawDataContainer* drawData, bool value) + private void ToggleVisorDetour(DrawDataContainer* drawData, byte value) { Actor actor = drawData->OwnerObject; - _visorEvent.Invoke(actor.Model, true, ref value); + var v = value != 0; + _visorEvent.Invoke(actor.Model, true, ref v); Glamourer.Log.Verbose($"[MetaService] Toggle Visor triggered with 0x{(nint)drawData:X} {value} for {actor.Utf8Name}."); - _toggleVisorHook.Original(drawData, value); + _toggleVisorHook.Original(drawData, (byte)(v ? 1 : 0)); } } From e2ffcea0b2e2764309e6601fb5481c55ac2c10f3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 4 Aug 2024 12:40:00 +0200 Subject: [PATCH 448/786] Fix some warnings. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index cd40d49..a62d62a 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit cd40d497f4791e946c0fd4bd5663da578f7680ed +Subproject commit a62d62a8532de528f43515233ea44892cdb974b5 From 68bffba3cd95c70136f1b882f5c1b569c40cd0fc Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 4 Aug 2024 14:14:10 +0200 Subject: [PATCH 449/786] Update for submodules. --- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 8 ++++---- Penumbra.Api | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index 6e4774b..c74d6a3 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -1,5 +1,4 @@ -using Dalamud; -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -8,6 +7,7 @@ using Glamourer.State; using ImGuiNET; using OtterGui.Raii; using Penumbra.Api.Enums; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -174,7 +174,7 @@ public sealed class PenumbraChangedItemTooltip : IDisposable CreateTooltip(item, "[Glamourer] ", false); return; case ChangedItemType.Customization: - var (race, gender, index, value) = ChangedItemExtensions.Split(id); + var (race, gender, index, value) = IdentifiedCustomization.Split(id); if (!_objects.Player.Model.IsHuman) return; @@ -218,7 +218,7 @@ public sealed class PenumbraChangedItemTooltip : IDisposable ApplyItem(state, item); return; case ChangedItemType.Customization: - var (race, gender, index, value) = ChangedItemExtensions.Split(id); + var (race, gender, index, value) = IdentifiedCustomization.Split(id); var customize = state.ModelData.Customize; if (CheckGenderRace(customize, race, gender) && VerifyValue(customize, index, value)) _stateManager.ChangeCustomize(state, index, value, ApplySettings.Manual); diff --git a/Penumbra.Api b/Penumbra.Api index f4c6144..759a8e9 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit f4c6144ca2012b279e6d8aa52b2bef6cc2ba32d9 +Subproject commit 759a8e9dc50b3453cdb7c3cba76de7174c94aba0 From 6115d24775882e5dc1b970002b475f1b7d309d6c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 4 Aug 2024 14:36:00 +0200 Subject: [PATCH 450/786] Some changes for codes. --- Glamourer/State/FunModule.cs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index f07ff93..5d436c0 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -105,14 +105,15 @@ public unsafe class FunModule : IDisposable return; } - switch (_codes.Masked(CodeService.FullCodes)) - { - case CodeService.CodeFlag.Face: - case CodeService.CodeFlag.Manderville: - case CodeService.CodeFlag.Smiles: - KeepOldArmor(actor, slot, ref armor); - return; - } + if (actor.Index < ObjectIndex.CutsceneStart) + switch (_codes.Masked(CodeService.FullCodes)) + { + case CodeService.CodeFlag.Face when actor.Index != 0: + case CodeService.CodeFlag.Smiles: + case CodeService.CodeFlag.Manderville: + KeepOldArmor(actor, slot, ref armor); + return; + } if (_codes.Enabled(CodeService.CodeFlag.Crown) && actor.OnlineStatus is OnlineStatus.PvEMentor or OnlineStatus.PvPMentor or OnlineStatus.TradeMentor @@ -170,6 +171,7 @@ public unsafe class FunModule : IDisposable private static readonly PrioritizedList MandervilleMale = new ( + //(0000000, 400), // Nothing (1008264, 30), // Hildi (1008731, 10), // Hildi, slightly damaged (1011668, 3), // Zombi @@ -187,6 +189,7 @@ public unsafe class FunModule : IDisposable private static readonly PrioritizedList MandervilleFemale = new ( + //(0000000, 400), // Nothing (1025669, 5), // Hildi, Geisha (1025670, 2), // Hildi, makeup, black (1042477, 2), // Hildi, makeup, white @@ -210,6 +213,7 @@ public unsafe class FunModule : IDisposable private static readonly PrioritizedList FaceMale = new ( + //(0000000, 700), // Nothing (1016136, 35), // Gerolt (1032667, 2), // Gerolt, Suit (1030519, 35), // Grenoldt @@ -220,6 +224,7 @@ public unsafe class FunModule : IDisposable private static readonly PrioritizedList FaceFemale = new ( + //(0000000, 400), // Nothing (1013713, 10), // Rowena, Togi (1018496, 30), // Rowena, Poncho (1032668, 2), // Rowena, Gown @@ -232,13 +237,16 @@ public unsafe class FunModule : IDisposable private bool ApplyFullCode(Actor actor, Span armor, ref CustomizeArray customize) { + if (actor.Index >= ObjectIndex.CutsceneStart) + return false; + var id = _codes.Masked(CodeService.FullCodes) switch { CodeService.CodeFlag.Face when customize.Gender is Gender.Female && actor.Index != 0 => FaceFemale.GetRandom(_rng), - CodeService.CodeFlag.Face when actor.Index != 0 => FaceFemale.GetRandom(_rng), + CodeService.CodeFlag.Face when actor.Index != 0 => FaceMale.GetRandom(_rng), CodeService.CodeFlag.Smiles => Smile.GetRandom(_rng), CodeService.CodeFlag.Manderville when customize.Gender is Gender.Female => MandervilleFemale.GetRandom(_rng), - CodeService.CodeFlag.Manderville => MandervilleMale.GetRandom(_rng), + CodeService.CodeFlag.Manderville => MandervilleMale.GetRandom(_rng), _ => (NpcId)0, }; From 1cc7c2f0cd6a4bc738044a63227c9c6e7ff19848 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 5 Aug 2024 14:48:43 +0200 Subject: [PATCH 451/786] Add editable mod state and priority. --- Glamourer/Api/ApiHelpers.cs | 11 ++-- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 57 ++++++++++--------- OtterGui | 2 +- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index 25af774..ed58500 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -54,10 +54,10 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM internal static DesignBase.FlagRestrictionResetter Restrict(DesignBase design, ApplyFlag flags) => (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) switch { - ApplyFlag.Equipment => design.TemporarilyRestrictApplication(ApplicationCollection.Equipment), - ApplyFlag.Customization => design.TemporarilyRestrictApplication(ApplicationCollection.Customizations), + ApplyFlag.Equipment => design.TemporarilyRestrictApplication(ApplicationCollection.Equipment), + ApplyFlag.Customization => design.TemporarilyRestrictApplication(ApplicationCollection.Customizations), ApplyFlag.Equipment | ApplyFlag.Customization => design.TemporarilyRestrictApplication(ApplicationCollection.All), - _ => design.TemporarilyRestrictApplication(ApplicationCollection.None), + _ => design.TemporarilyRestrictApplication(ApplicationCollection.None), }; [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -112,7 +112,10 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM { sb.Append(arguments[2 * i]); sb.Append(" = "); - sb.Append(arguments[2 * i + 1]); + if (arguments[2 * i + 1] is IEnumerable e) + sb.Append($"[{string.Join(',', e)}]"); + else + sb.Append(arguments[2 * i + 1]); sb.Append(", "); } diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 9db8c19..3ebf78a 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -8,6 +8,9 @@ using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Text; +using OtterGui.Text.Widget; +using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.DesignTab; @@ -92,10 +95,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ImGui.TableSetupColumn("##Buttons", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight() * 3 + ImGui.GetStyle().ItemInnerSpacing.X * 2); - ImGui.TableSetupColumn("Mod 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("Applym").X); + ImGui.TableSetupColumn("Mod 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("Applym").X); ImGui.TableHeadersRow(); Mod? removedMod = null; @@ -124,20 +127,15 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect removedMod = null; updatedMod = null; ImGui.TableNextColumn(); - 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)) + if (ImUtf8.IconButton(FontAwesomeIcon.Trash, "Delete this mod from associations."u8)) removedMod = mod; - ImGui.SameLine(0, spacing); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, - "Copy this mod setting to clipboard.", false, true)) + ImUtf8.SameLineInner(); + if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Copy this mod setting to clipboard."u8)) _copy = [(mod, settings)]; - ImGui.SameLine(0, spacing); - ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.RedoAlt.ToIconString(), buttonSize, - "Update the settings of this mod association.", false, true); + ImUtf8.SameLineInner(); + ImUtf8.IconButton(FontAwesomeIcon.RedoAlt, "Update the settings of this mod association."u8); if (ImGui.IsItemHovered()) { var newSettings = penumbra.GetModSettings(mod); @@ -145,16 +143,16 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect updatedMod = (mod, newSettings); using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale); - using var tt = ImRaii.Tooltip(); + using var tt = ImUtf8.Tooltip(); ImGui.Separator(); var namesDifferent = mod.Name != mod.DirectoryName; ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0)); using (ImRaii.Group()) { if (namesDifferent) - ImGui.TextUnformatted("Directory Name"); - ImGui.TextUnformatted("Enabled"); - ImGui.TextUnformatted("Priority"); + ImUtf8.Text("Directory Name"u8); + ImUtf8.Text("Enabled"u8); + ImUtf8.Text("Priority"u8); ModCombo.DrawSettingsLeft(newSettings); } @@ -162,27 +160,30 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect using (ImRaii.Group()) { if (namesDifferent) - ImGui.TextUnformatted(mod.DirectoryName); - ImGui.TextUnformatted(newSettings.Enabled.ToString()); - ImGui.TextUnformatted(newSettings.Priority.ToString()); + ImUtf8.Text(mod.DirectoryName); + + ImUtf8.Text(newSettings.Enabled.ToString()); + ImUtf8.Text(newSettings.Priority.ToString()); ModCombo.DrawSettingsRight(newSettings); } } ImGui.TableNextColumn(); - - if (ImGui.Selectable($"{mod.Name}##name")) + + if (ImUtf8.Selectable($"{mod.Name}##name")) penumbra.OpenModPage(mod); 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)) - { - ImGuiUtil.Center((settings.Enabled ? FontAwesomeIcon.Check : FontAwesomeIcon.Times).ToIconString()); - } + var enabled = settings.Enabled; + if (TwoStateCheckbox.Instance.Draw("##Enabled"u8, ref enabled)) + updatedMod = (mod, settings with { Enabled = enabled }); ImGui.TableNextColumn(); - ImGuiUtil.RightAlign(settings.Priority.ToString()); + var priority = settings.Priority; + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImUtf8.InputScalarOnDeactivated("##Priority"u8, ref priority)) + updatedMod = (mod, settings with { Priority = priority }); ImGui.TableNextColumn(); if (ImGuiUtil.DrawDisabledButton("Apply", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, !penumbra.Available)) diff --git a/OtterGui b/OtterGui index 3a14692..51bab6d 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 3a14692e38708ca9f18652898e6f5c8cc87b517b +Subproject commit 51bab6dd1bd7d98cc468e8122f410e1c79e3c92d From 5bca4b91184b7400a2098e1802a2ea8db577aac6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 Aug 2024 17:38:53 +0200 Subject: [PATCH 452/786] Do not apply Nothing-shield for swords periphery. --- Glamourer/Designs/DesignEditor.cs | 2 +- Glamourer/State/StateEditor.cs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index df0e9ed..5328f03 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -360,7 +360,7 @@ public class DesignEditor( newOff = o; } - else if (!_forceFullItemOff && Config.ChangeEntireItem) + else if (!_forceFullItemOff && Config.ChangeEntireItem && newMain.Type is not FullEquipType.Sword) // Skip applying shields. { var defaultOffhand = Items.GetDefaultOffhand(newMain); if (Items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o)) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 95c118d..50479b2 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -456,6 +456,10 @@ public class StateEditor( return; var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); + // Do not change Shields to nothing. + if (mh.Type is FullEquipType.Sword) + return; + var offhand = newMainhand != null ? Items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand); var stains = newStains ?? state.ModelData.Stain(EquipSlot.MainHand); if (offhand.Valid) From 2e52030c31bd710d11dc3208f8a49f9ed849209b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 Aug 2024 17:39:34 +0200 Subject: [PATCH 453/786] Fix issue with adding mods from clipboard --- Glamourer/Designs/DesignManager.cs | 17 +++++++++++++---- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 16 ++++++++++++---- Penumbra.GameData | 2 +- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 7eaad9d..a31ba83 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -248,7 +248,8 @@ public sealed class DesignManager : DesignEditor design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags."); - DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.IndexOf(newTag))); + DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, + new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.IndexOf(newTag))); } /// Add an associated mod to a design. @@ -278,12 +279,20 @@ public sealed class DesignManager : DesignEditor /// Add or update an associated mod to a design. public void UpdateMod(Design design, Mod mod, ModSettings settings) { - var oldSettings = design.AssociatedMods[mod]; + var hasOldSettings = design.AssociatedMods.TryGetValue(mod, out var oldSettings); design.AssociatedMods[mod] = settings; design.LastEdit = DateTimeOffset.UtcNow; SaveService.QueueSave(design); - Glamourer.Log.Debug($"Updated (or added) associated mod {mod.DirectoryName} from design {design.Identifier}."); - DesignChanged.Invoke(DesignChanged.Type.UpdatedMod, design, new ModUpdatedTransaction(mod, oldSettings, settings)); + if (hasOldSettings) + { + Glamourer.Log.Debug($"Updated associated mod {mod.DirectoryName} from design {design.Identifier}."); + DesignChanged.Invoke(DesignChanged.Type.UpdatedMod, design, new ModUpdatedTransaction(mod, oldSettings, settings)); + } + else + { + Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} from design {design.Identifier}."); + DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings)); + } } /// Set the write protection status of a design. diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 3ebf78a..1f1e1ad 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -10,11 +10,10 @@ using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Text.Widget; -using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.DesignTab; -public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager) +public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager, Configuration config) { private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log); private (Mod, ModSettings)[]? _copy; @@ -127,8 +126,17 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect removedMod = null; updatedMod = null; ImGui.TableNextColumn(); - if (ImUtf8.IconButton(FontAwesomeIcon.Trash, "Delete this mod from associations."u8)) - removedMod = mod; + var canDelete = config.DeleteDesignModifier.IsActive(); + if (canDelete) + { + if (ImUtf8.IconButton(FontAwesomeIcon.Trash, "Delete this mod from associations."u8)) + removedMod = mod; + } + else + { + ImUtf8.IconButton(FontAwesomeIcon.Trash, $"Delete this mod from associations.\nHold {config.DeleteDesignModifier} to delete.", + disabled: true); + } ImUtf8.SameLineInner(); if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Copy this mod setting to clipboard."u8)) diff --git a/Penumbra.GameData b/Penumbra.GameData index a62d62a..ad6973c 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit a62d62a8532de528f43515233ea44892cdb974b5 +Subproject commit ad6973c559b4c05aa3c3735ec7b53cc0f5d2f203 From 61cb46a298ca25ab9ecfd4c4110d86cde94c1f53 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 Aug 2024 18:25:00 +0200 Subject: [PATCH 454/786] Update for GameData update. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 13 ++++--- Glamourer/Gui/Materials/ColorRowClipboard.cs | 4 +-- Glamourer/Interop/Material/DirectXService.cs | 19 +++++----- .../Material/LiveColorTablePreviewer.cs | 14 ++++---- Glamourer/Interop/Material/MaterialManager.cs | 2 +- Glamourer/Interop/Material/MaterialService.cs | 8 ++--- .../Interop/Material/MaterialValueManager.cs | 35 ++++++++++--------- Glamourer/Interop/Material/PrepareColorSet.cs | 9 +++-- Penumbra.GameData | 2 +- 9 files changed, 57 insertions(+), 49 deletions(-) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index a9ab805..3ba2a1b 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -102,10 +102,12 @@ public sealed unsafe class AdvancedDyePopup( if (!bar) return; + var table = new ColorTable.Table(); for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) { var index = _drawIndex!.Value with { MaterialIndex = i }; - var available = index.TryGetTexture(textures, out var texture) && directX.TryGetColorTable(*texture, out var table); + var available = index.TryGetTexture(textures, out var texture) && directX.TryGetColorTable(*texture, out table); + if (index == preview.LastValueIndex with { RowIndex = 0 }) table = preview.LastOriginalColorTable; @@ -225,7 +227,7 @@ public sealed unsafe class AdvancedDyePopup( DrawWindow(textures); } - private void DrawTable(MaterialValueIndex materialIndex, in ColorTable table) + private void DrawTable(MaterialValueIndex materialIndex, ColorTable.Table table) { if (!materialIndex.Valid) return; @@ -244,7 +246,7 @@ public sealed unsafe class AdvancedDyePopup( DrawAllRow(materialIndex, table); } - private void DrawAllRow(MaterialValueIndex materialIndex, in ColorTable table) + private void DrawAllRow(MaterialValueIndex materialIndex, in ColorTable.Table table) { using var id = ImRaii.PushId(100); var buttonSize = new Vector2(ImGui.GetFrameHeight()); @@ -267,8 +269,9 @@ public sealed unsafe class AdvancedDyePopup( ImGui.SameLine(0, spacing); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize, "Import an exported table from your clipboard onto this table.", !ColorRowClipboard.IsTableSet, true)) - foreach (var (row, idx) in ColorRowClipboard.Table.WithIndex()) + for (var idx = 0; idx < ColorTable.NumRows; ++idx) { + var row = ColorRowClipboard.Table[idx]; var internalRow = new ColorRow(row); var slot = materialIndex.ToEquipSlot(); var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand @@ -285,7 +288,7 @@ public sealed unsafe class AdvancedDyePopup( stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = i }, ApplySettings.Game); } - private void DrawRow(ref ColorTable.Row row, MaterialValueIndex index, in ColorTable table) + private void DrawRow(ref ColorTableRow row, MaterialValueIndex index, in ColorTable.Table 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 4213cc5..f7fac1d 100644 --- a/Glamourer/Gui/Materials/ColorRowClipboard.cs +++ b/Glamourer/Gui/Materials/ColorRowClipboard.cs @@ -6,13 +6,13 @@ namespace Glamourer.Gui.Materials; public static class ColorRowClipboard { private static ColorRow _row; - private static ColorTable _table; + private static ColorTable.Table _table; public static bool IsSet { get; private set; } public static bool IsTableSet { get; private set; } - public static ColorTable Table + public static ColorTable.Table Table { get => _table; set diff --git a/Glamourer/Interop/Material/DirectXService.cs b/Glamourer/Interop/Material/DirectXService.cs index 3316491..a809a34 100644 --- a/Glamourer/Interop/Material/DirectXService.cs +++ b/Glamourer/Interop/Material/DirectXService.cs @@ -15,13 +15,13 @@ namespace Glamourer.Interop.Material; public unsafe class DirectXService(IFramework framework) : IService { private readonly object _lock = new(); - private readonly ConcurrentDictionary _textures = []; + 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 ColorTable.Table 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 (ColorTable.Table* 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 ColorTable.Table 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 ColorTable.Table table) { if (texture == null) { @@ -92,6 +92,7 @@ public unsafe class DirectXService(IFramework framework) : IService } catch { + table = default; return false; } } @@ -114,7 +115,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 ColorTable.Table GetTextureData(ID3D11Texture2D1 resource, MappedSubresource map) { var desc = resource.Description1; @@ -133,14 +134,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 ColorTable.Table 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(ColorTable.Table) != expectedSize || height != MaterialService.TextureHeight) return default; - var ret = new ColorTable(); + var ret = new ColorTable.Table(); 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 1df85f6..94cb9ca 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -13,11 +13,11 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable private readonly DirectXService _directXService; public MaterialValueIndex LastValueIndex { get; private set; } = MaterialValueIndex.Invalid; - public ColorTable LastOriginalColorTable { get; private set; } + public ColorTable.Table LastOriginalColorTable { get; private set; } private MaterialValueIndex _valueIndex = MaterialValueIndex.Invalid; private ObjectIndex _lastObjectIndex = ObjectIndex.AnyIndex; private ObjectIndex _objectIndex = ObjectIndex.AnyIndex; - private ColorTable _originalColorTable; + private ColorTable.Table _originalColorTable; public LiveColorTablePreviewer(global::Penumbra.GameData.Interop.ObjectManager objects, IFramework framework, DirectXService directXService) { @@ -73,15 +73,15 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable var table = LastOriginalColorTable; if (_valueIndex.RowIndex != byte.MaxValue) { - table[_valueIndex.RowIndex].Diffuse = diffuse; - table[_valueIndex.RowIndex].Emissive = emissive; + table[_valueIndex.RowIndex].DiffuseColor = (HalfColor)diffuse; + table[_valueIndex.RowIndex].EmissiveColor = (HalfColor)emissive; } else { for (var i = 0; i < ColorTable.NumRows; ++i) { - table[i].Diffuse = diffuse; - table[i].Emissive = emissive; + table[i].DiffuseColor = (HalfColor)diffuse; + table[i].EmissiveColor = (HalfColor)emissive; } } @@ -92,7 +92,7 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable _objectIndex = ObjectIndex.AnyIndex; } - public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, ColorTable table) + public void OnHover(MaterialValueIndex index, ObjectIndex objectIndex, in ColorTable.Table 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 e695a90..3e0e35b 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -75,7 +75,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 ColorTable colorTable) + ref ColorTable.Table colorTable) { var deleteList = _deleteList.Value!; deleteList.Clear(); diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index 5adaf4d..f7ffe0f 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -13,7 +13,7 @@ public static unsafe class MaterialService public const int TextureHeight = ColorTable.NumRows; public const int MaterialsPerModel = 10; - public static bool GenerateNewColorTable(in ColorTable colorTable, out Texture* texture) + public static bool GenerateNewColorTable(in ColorTable.Table 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 (ColorTable.Table* 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 ColorTable.Table* 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 (ColorTable.Table*)material->ColorTable; } } diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index d5ac877..79338a0 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -21,8 +21,9 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa public float SpecularStrength = specularStrength; public float GlossStrength = glossStrength; - public ColorRow(in ColorTable.Row row) - : this(Root(row.Diffuse), Root(row.Specular), Root(row.Emissive), row.SpecularStrength, row.GlossStrength) + public ColorRow(in ColorTableRow row) + : this(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor), (float)row.SheenRate, + (float)row.Metalness) { } public readonly bool NearEqual(in ColorRow rhs) @@ -44,39 +45,39 @@ 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 ColorTable.Row row) + public readonly bool Apply(ref ColorTableRow row) { var ret = false; var d = Square(Diffuse); - if (!row.Diffuse.NearEqual(d)) + if (!((Vector3)row.DiffuseColor).NearEqual(d)) { - row.Diffuse = d; - ret = true; + row.DiffuseColor = (HalfColor)d; + ret = true; } var s = Square(Specular); - if (!row.Specular.NearEqual(s)) + if (!((Vector3)row.SpecularColor).NearEqual(s)) { - row.Specular = s; - ret = true; + row.SpecularColor = (HalfColor)s; + ret = true; } var e = Square(Emissive); - if (!row.Emissive.NearEqual(e)) + if (!((Vector3)row.EmissiveColor).NearEqual(e)) { - row.Emissive = e; - ret = true; + row.EmissiveColor = (HalfColor)e; + ret = true; } - if (!row.SpecularStrength.NearEqual(SpecularStrength)) + if (!((float)row.SheenRate).NearEqual(SpecularStrength)) { - row.SpecularStrength = SpecularStrength; - ret = true; + row.SheenRate = (Half)SpecularStrength; + ret = true; } - if (!row.GlossStrength.NearEqual(GlossStrength)) + if (!((float)row.Metalness).NearEqual(GlossStrength)) { - row.GlossStrength = GlossStrength; + row.Metalness = (Half)GlossStrength; ret = true; } diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index b8e31c2..6d65a6b 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -66,7 +66,7 @@ public sealed unsafe class PrepareColorSet } public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds, - out ColorTable table) + out ColorTable.Table table) { if (material->ColorTable == null) { @@ -74,7 +74,7 @@ public sealed unsafe class PrepareColorSet return false; } - var newTable = *(ColorTable*)material->ColorTable; + var newTable = *(ColorTable.Table*)material->ColorTable; if (GetDyeTable(material, out var dyeTable)) { if (stainIds.Stain1.Id != 0) @@ -91,11 +91,14 @@ public sealed unsafe class PrepareColorSet } /// Assumes the actor is valid. - public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out ColorTable table) + public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out ColorTable.Table table) { var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex; if (!index.TryGetModel(actor, out var model)) + { + table = default; return false; + } var handle = (MaterialResourceHandle*)model.AsCharacterBase->Materials[idx]; if (handle == null) diff --git a/Penumbra.GameData b/Penumbra.GameData index ad6973c..016da3c 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit ad6973c559b4c05aa3c3735ec7b53cc0f5d2f203 +Subproject commit 016da3c2219a3dbe4c2841ae0d1305ae0b2ad60f From a1b455d9a599bacd48c161a160f6662d7bc615f7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 7 Aug 2024 17:55:23 +0200 Subject: [PATCH 455/786] Update advanced dyes. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 50 ++++++++++----- Glamourer/Interop/Material/MaterialManager.cs | 14 +++-- .../Interop/Material/MaterialValueIndex.cs | 61 +++++++++++++++++-- .../Interop/Material/MaterialValueManager.cs | 47 ++++++++++---- Glamourer/Interop/Material/PrepareColorSet.cs | 13 +++- Glamourer/State/StateApplier.cs | 18 +++--- OtterGui | 2 +- 7 files changed, 157 insertions(+), 48 deletions(-) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 3ba2a1b..b857bf8 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -1,6 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using Glamourer.Designs; @@ -27,6 +28,7 @@ public sealed unsafe class AdvancedDyePopup( private MaterialValueIndex? _drawIndex; private ActorState _state = null!; private Actor _actor; + private ColorRow.Mode _mode; private byte _selectedMaterial = byte.MaxValue; private bool _anyChanged; private bool _forceFocus; @@ -96,7 +98,7 @@ public sealed unsafe class AdvancedDyePopup( return (path, gamePath); } - private void DrawTabBar(ReadOnlySpan> textures, ref bool firstAvailable) + private void DrawTabBar(ReadOnlySpan> textures, ReadOnlySpan> materials, ref bool firstAvailable) { using var bar = ImRaii.TabBar("tabs"); if (!bar) @@ -105,8 +107,9 @@ public sealed unsafe class AdvancedDyePopup( var table = new ColorTable.Table(); for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) { - var index = _drawIndex!.Value with { MaterialIndex = i }; - var available = index.TryGetTexture(textures, out var texture) && directX.TryGetColorTable(*texture, out table); + var index = _drawIndex!.Value with { MaterialIndex = i }; + var available = index.TryGetTexture(textures, materials, out var texture, out _mode) + && directX.TryGetColorTable(*texture, out table); if (index == preview.LastValueIndex with { RowIndex = 0 }) @@ -163,16 +166,16 @@ public sealed unsafe class AdvancedDyePopup( } } - private void DrawContent(ReadOnlySpan> textures) + private void DrawContent(ReadOnlySpan> textures, ReadOnlySpan> materials) { var firstAvailable = true; - DrawTabBar(textures, ref firstAvailable); + DrawTabBar(textures, materials, ref firstAvailable); if (firstAvailable) ImGui.TextUnformatted("No Editable Materials available."); } - private void DrawWindow(ReadOnlySpan> textures) + private void DrawWindow(ReadOnlySpan> textures, ReadOnlySpan> materials) { var flags = ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoCollapse @@ -208,7 +211,7 @@ public sealed unsafe class AdvancedDyePopup( try { if (window) - DrawContent(textures); + DrawContent(textures, materials); } finally { @@ -223,8 +226,8 @@ public sealed unsafe class AdvancedDyePopup( if (!ShouldBeDrawn()) return; - if (_drawIndex!.Value.TryGetTextures(actor, out var textures)) - DrawWindow(textures); + if (_drawIndex!.Value.TryGetTextures(actor, out var textures, out var materials)) + DrawWindow(textures, materials); } private void DrawTable(MaterialValueIndex materialIndex, ColorTable.Table table) @@ -340,15 +343,30 @@ public sealed unsafe class AdvancedDyePopup( v => value.Model.Emissive = v, "E"); ImGui.SameLine(0, spacing.X); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") - && value.Model.GlossStrength > 0; - ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + if (_mode is not ColorRow.Mode.Dawntrail) + { + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") + && value.Model.GlossStrength > 0; + ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + } + else + { + ImGui.Dummy(new Vector2(100 * ImGuiHelpers.GlobalScale, 0)); + } ImGui.SameLine(0, spacing.X); - ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f%% SS"); - ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + if (_mode is not ColorRow.Mode.Dawntrail) + { + ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); + applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, + "%.3f%% SS"); + ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + } + else + { + ImGui.Dummy(new Vector2(100 * ImGuiHelpers.GlobalScale, 0)); + } ImGui.SameLine(0, spacing.X); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", false, diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 3e0e35b..7f13c2d 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -1,5 +1,6 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using FFXIVClientStructs.Havok.Animation.Rig; using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; @@ -67,7 +68,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId), _ => GetTempSlot((Weapon*)characterBase), }; - UpdateMaterialValues(state, values, drawData, ref baseColorSet); + var mode = PrepareColorSet.GetMode(material); + UpdateMaterialValues(state, values, drawData, ref baseColorSet, mode); if (MaterialService.GenerateNewColorTable(baseColorSet, out var texture)) ret = (nint)texture; @@ -75,7 +77,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 ColorTable.Table colorTable) + ref ColorTable.Table colorTable, ColorRow.Mode mode) { var deleteList = _deleteList.Value!; deleteList.Clear(); @@ -86,17 +88,17 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable ref var row = ref colorTable[idx.RowIndex]; var newGame = new ColorRow(row); if (materialValue.EqualGame(newGame, drawData)) - materialValue.Model.Apply(ref row); + materialValue.Model.Apply(ref row, mode); else switch (materialValue.Source) { case StateSource.Pending: - materialValue.Model.Apply(ref row); + materialValue.Model.Apply(ref row, mode); state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.Manual), out _); break; case StateSource.IpcPending: - materialValue.Model.Apply(ref row); + materialValue.Model.Apply(ref row, mode); state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, StateSource.IpcManual), out _); break; @@ -106,7 +108,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable break; case StateSource.Fixed: case StateSource.IpcFixed: - materialValue.Model.Apply(ref row); + materialValue.Model.Apply(ref row, mode); state.Materials.UpdateValue(idx, new MaterialValueState(newGame, materialValue.Model, drawData, materialValue.Source), out _); break; diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 5104713..30f2e68 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -1,9 +1,11 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using Newtonsoft.Json; using Penumbra.GameData.Enums; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; +using CsMaterial = FFXIVClientStructs.FFXIV.Client.Graphics.Render.Material; namespace Glamourer.Interop.Material; @@ -75,21 +77,39 @@ public readonly record struct MaterialValueIndex( return model.IsCharacterBase; } + public unsafe bool TryGetTextures(Actor actor, out ReadOnlySpan> textures, out ReadOnlySpan> materials) + { + if (!TryGetModel(actor, out var model) + || SlotIndex >= model.AsCharacterBase->SlotCount + || model.AsCharacterBase->ColorTableTexturesSpan.Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel) + { + textures = []; + materials = []; + return false; + } + + var from = SlotIndex * MaterialService.MaterialsPerModel; + textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(from, MaterialService.MaterialsPerModel); + materials = model.AsCharacterBase->MaterialsSpan.Slice(from, MaterialService.MaterialsPerModel); + return true; + } + public unsafe bool TryGetTextures(Actor actor, out ReadOnlySpan> textures) { if (!TryGetModel(actor, out var model) || SlotIndex >= model.AsCharacterBase->SlotCount || model.AsCharacterBase->ColorTableTexturesSpan.Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel) { - textures = []; + textures = []; return false; } - textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(SlotIndex * MaterialService.MaterialsPerModel, - MaterialService.MaterialsPerModel); + var from = SlotIndex * MaterialService.MaterialsPerModel; + textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(from, MaterialService.MaterialsPerModel); return true; } + public unsafe bool TryGetTexture(Actor actor, out Texture** texture) { if (TryGetTextures(actor, out var textures)) @@ -99,6 +119,38 @@ public readonly record struct MaterialValueIndex( return false; } + public unsafe bool TryGetTexture(Actor actor, out Texture** texture, out ColorRow.Mode mode) + { + if (TryGetTextures(actor, out var textures, out var materials)) + return TryGetTexture(textures, materials, out texture, out mode); + + mode = ColorRow.Mode.Dawntrail; + texture = null; + return false; + } + + public unsafe bool TryGetTexture(ReadOnlySpan> textures, ReadOnlySpan> materials, + out Texture** texture, out ColorRow.Mode mode) + { + mode = MaterialIndex >= materials.Length + ? ColorRow.Mode.Dawntrail + : PrepareColorSet.GetMode((MaterialResourceHandle*)materials[MaterialIndex].Value); + + + if (MaterialIndex >= textures.Length || textures[MaterialIndex].Value == null) + { + texture = null; + return false; + } + + fixed (Pointer* ptr = textures) + { + texture = (Texture**)ptr + MaterialIndex; + } + + return true; + } + public unsafe bool TryGetTexture(ReadOnlySpan> textures, out Texture** texture) { if (MaterialIndex >= textures.Length || textures[MaterialIndex].Value == null) @@ -115,6 +167,7 @@ public readonly record struct MaterialValueIndex( return true; } + public static MaterialValueIndex FromKey(uint key) => new(key); @@ -190,4 +243,4 @@ public readonly record struct MaterialValueIndex( JsonSerializer serializer) => FromKey(serializer.Deserialize(reader), out var value) ? value : throw new Exception($"Invalid material key {value.Key}."); } -} \ No newline at end of file +} diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 79338a0..cb3a7e2 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -13,6 +13,12 @@ namespace Glamourer.Interop.Material; /// Values are not squared. public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength) { + public enum Mode + { + Legacy, + Dawntrail, + } + public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 0, 0); public Vector3 Diffuse = diffuse; @@ -22,8 +28,9 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa public float GlossStrength = glossStrength; public ColorRow(in ColorTableRow row) - : this(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor), (float)row.SheenRate, - (float)row.Metalness) + : this(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor), + (float)row.LegacySpecularStrength(), + (float)row.LegacyGloss()) { } public readonly bool NearEqual(in ColorRow rhs) @@ -45,7 +52,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 ColorTableRow row) + public readonly bool Apply(ref ColorTableRow row, Mode mode) { var ret = false; var d = Square(Diffuse); @@ -69,22 +76,40 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa ret = true; } - if (!((float)row.SheenRate).NearEqual(SpecularStrength)) + if (mode is Mode.Legacy) { - row.SheenRate = (Half)SpecularStrength; - ret = true; - } + if (!((float)row.LegacySpecularStrength()).NearEqual(SpecularStrength)) + { + row.LegacySpecularStrengthWrite() = (Half)SpecularStrength; + ret = true; + } - if (!((float)row.Metalness).NearEqual(GlossStrength)) - { - row.Metalness = (Half)GlossStrength; - ret = true; + if (!((float)row.LegacyGloss()).NearEqual(GlossStrength)) + { + row.LegacyGlossWrite() = (Half)GlossStrength; + ret = true; + } } return ret; } } +internal static class ColorTableRowExtensions +{ + internal static Half LegacySpecularStrength(this in ColorTableRow row) + => row[7]; + + internal static Half LegacyGloss(this in ColorTableRow row) + => row[3]; + + internal static ref Half LegacySpecularStrengthWrite(this ref ColorTableRow row) + => ref row[7]; + + internal static ref Half LegacyGlossWrite(this ref ColorTableRow row) + => ref row[3]; +} + [JsonConverter(typeof(Converter))] public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert) { diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 6d65a6b..b44246b 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -91,11 +91,12 @@ public sealed unsafe class PrepareColorSet } /// Assumes the actor is valid. - public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out ColorTable.Table table) + public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out ColorTable.Table table, out ColorRow.Mode mode) { var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex; if (!index.TryGetModel(actor, out var model)) { + mode = ColorRow.Mode.Dawntrail; table = default; return false; } @@ -103,10 +104,12 @@ public sealed unsafe class PrepareColorSet var handle = (MaterialResourceHandle*)model.AsCharacterBase->Materials[idx]; if (handle == null) { + mode = ColorRow.Mode.Dawntrail; table = default; return false; } + mode = GetMode(handle); return TryGetColorTable(handle, GetStains(), out table); StainIds GetStains() @@ -126,6 +129,14 @@ public sealed unsafe class PrepareColorSet } } + /// Get the shader mode of the material. + public static ColorRow.Mode GetMode(MaterialResourceHandle* handle) + => handle == null + ? ColorRow.Mode.Dawntrail + : handle->ShpkNameSpan.SequenceEqual("characterlegacy.shpk"u8) + ? ColorRow.Mode.Legacy + : ColorRow.Mode.Dawntrail; + /// Get the correct dye table for a material. private static bool GetDyeTable(MaterialResourceHandle* material, out ushort* ptr) { diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 66b83fb..d6d5bde 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -75,7 +75,7 @@ public class StateApplier( } } - /// + /// public ActorData ChangeCustomize(ActorState state, bool apply) { var data = GetData(state); @@ -177,7 +177,7 @@ public class StateApplier( } } - /// + /// public ActorData ChangeStain(ActorState state, EquipSlot slot, bool apply) { var data = GetData(state); @@ -197,7 +197,7 @@ public class StateApplier( ChangeOffhand(data, item, stains); } - /// + /// public ActorData ChangeWeapon(ActorState state, EquipSlot slot, bool apply, bool onlyGPose) { var data = GetData(state); @@ -229,7 +229,7 @@ public class StateApplier( } /// Change a meta state. - public unsafe void ChangeMetaState(ActorData data, MetaIndex index, bool value) + public void ChangeMetaState(ActorData data, MetaIndex index, bool value) { switch (index) { @@ -311,15 +311,15 @@ public class StateApplier( foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) { - if (!index.TryGetTexture(actor, out var texture)) + if (!index.TryGetTexture(actor, out var texture, out var mode)) continue; if (!_directX.TryGetColorTable(*texture, out var table)) continue; if (value.HasValue) - value.Value.Apply(ref table[index.RowIndex]); - else if (PrepareColorSet.TryGetColorTable(actor, index, out var baseTable)) + value.Value.Apply(ref table[index.RowIndex], mode); + else if (PrepareColorSet.TryGetColorTable(actor, index, out var baseTable, out _)) table[index.RowIndex] = baseTable[index.RowIndex]; else continue; @@ -353,11 +353,11 @@ public class StateApplier( if (!mainKey.TryGetTexture(actor, out var texture)) continue; - if (!PrepareColorSet.TryGetColorTable(actor, mainKey, out var table)) + if (!PrepareColorSet.TryGetColorTable(actor, mainKey, out var table, out var mode)) continue; foreach (var (key, value) in values) - value.Model.Apply(ref table[key.RowIndex]); + value.Model.Apply(ref table[key.RowIndex], mode); _directX.ReplaceColorTable(texture, table); } diff --git a/OtterGui b/OtterGui index 51bab6d..d9486ae 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 51bab6dd1bd7d98cc468e8122f410e1c79e3c92d +Subproject commit d9486ae54b5a4b61cf74f79ed27daa659eb1ce5b From 1c8d01bdd3fc2bb22961c4665aa79787a91d4c26 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 7 Aug 2024 17:57:49 +0200 Subject: [PATCH 456/786] For later. --- Glamourer/Services/CodeService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 5ab0d8f..73d27b4 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -218,9 +218,9 @@ public class CodeService CodeFlag.Elephants => [ 0x9F, 0x4C, 0xCF, 0x6D, 0xC4, 0x01, 0x31, 0x46, 0x02, 0x05, 0x31, 0xED, 0xED, 0xB2, 0x66, 0x29, 0x31, 0x09, 0x1E, 0xE7, 0x47, 0xDE, 0x7B, 0x03, 0xB0, 0x3C, 0x06, 0x76, 0x26, 0x91, 0xDF, 0xB2 ], CodeFlag.Crown => [ 0x43, 0x8E, 0x34, 0x56, 0x24, 0xC9, 0xC6, 0xDE, 0x2A, 0x68, 0x3A, 0x5D, 0xF5, 0x8E, 0xCB, 0xEF, 0x0D, 0x4D, 0x5B, 0xDC, 0x23, 0xF9, 0xF9, 0xBD, 0xD9, 0x60, 0xAD, 0x53, 0xC5, 0xA0, 0x33, 0xC4 ], 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 ], - CodeFlag.Face => [ 0xCA, 0x97, 0x81, 0x12, 0xCA, 0x1B, 0xBD, 0xCA, 0xFA, 0xC2, 0x31, 0xB3, 0x9A, 0x23, 0xDC, 0x4D, 0xA7, 0x86, 0xEF, 0xF8, 0x14, 0x7C, 0x4E, 0x72, 0xB9, 0x80, 0x77, 0x85, 0xAF, 0xEE, 0x48, 0xBB ], - CodeFlag.Manderville => [ 0x3E, 0x23, 0xE8, 0x16, 0x00, 0x39, 0x59, 0x4A, 0x33, 0x89, 0x4F, 0x65, 0x64, 0xE1, 0xB1, 0x34, 0x8B, 0xBD, 0x7A, 0x00, 0x88, 0xD4, 0x2C, 0x4A, 0xCB, 0x73, 0xEE, 0xAE, 0xD5, 0x9C, 0x00, 0x9D ], - CodeFlag.Smiles => [ 0x2E, 0x7D, 0x2C, 0x03, 0xA9, 0x50, 0x7A, 0xE2, 0x65, 0xEC, 0xF5, 0xB5, 0x35, 0x68, 0x85, 0xA5, 0x33, 0x93, 0xA2, 0x02, 0x9D, 0x24, 0x13, 0x94, 0x99, 0x72, 0x65, 0xA1, 0xA2, 0x5A, 0xEF, 0xC6 ], + CodeFlag.Face => [ 0xCA, 0x97, 0x81, 0x12, 0xCA, 0x1B, 0xBD, 0xCA, 0xFA, 0xC2, 0x31, 0xB3, 0x9B, 0x23, 0xDC, 0x4D, 0xA7, 0x86, 0xEF, 0xF8, 0x14, 0x7C, 0x4E, 0x72, 0xB9, 0x80, 0x77, 0x85, 0xAF, 0xEE, 0x48, 0xBB ], + CodeFlag.Manderville => [ 0x3E, 0x23, 0xE8, 0x16, 0x00, 0x39, 0x59, 0x4A, 0x33, 0x89, 0x4F, 0x65, 0x65, 0xE1, 0xB1, 0x34, 0x8B, 0xBD, 0x7A, 0x00, 0x88, 0xD4, 0x2C, 0x4A, 0xCB, 0x73, 0xEE, 0xAE, 0xD5, 0x9C, 0x00, 0x9D ], + CodeFlag.Smiles => [ 0x2E, 0x7D, 0x2C, 0x03, 0xA9, 0x50, 0x7A, 0xE2, 0x65, 0xEC, 0xF5, 0xB5, 0x36, 0x68, 0x85, 0xA5, 0x33, 0x93, 0xA2, 0x02, 0x9D, 0x24, 0x13, 0x94, 0x99, 0x72, 0x65, 0xA1, 0xA2, 0x5A, 0xEF, 0xC6 ], _ => [], }; From d594082165afae0dd7e47f90081c73e7c2922d51 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 7 Aug 2024 15:59:50 +0000 Subject: [PATCH 457/786] [CI] Updating repo.json for testing_1.3.0.10 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index ac8b996..fb27eac 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.9", + "TestingAssemblyVersion": "1.3.0.10", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.9/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.10/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From d7074c5791de557344334b7679d87d3c7f52f725 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 9 Aug 2024 16:01:17 +0200 Subject: [PATCH 458/786] Improve Gloss/Specular display --- Glamourer/Designs/Design.cs | 6 ++-- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 37 ++++++++++++++++++--- Glamourer/Gui/Materials/MaterialDrawer.cs | 5 +-- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 05ee948..8e4d366 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -228,10 +228,10 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn if (array == null) return; - foreach (var obj in array.OfType()) + foreach (var jObj in array.OfType()) { - var identifier = obj["Design"]?.ToObject() ?? throw new ArgumentNullException("Design"); - var type = (ApplicationType)(obj["Type"]?.ToObject() ?? 0); + var identifier = jObj["Design"]?.ToObject() ?? throw new ArgumentNullException(nameof(design)); + var type = (ApplicationType)(jObj["Type"]?.ToObject() ?? 0); linkLoader.AddObject(design, new LinkData(identifier, type, order)); } } diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index b857bf8..9991aae 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -11,6 +11,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; using OtterGui.Widgets; using Penumbra.GameData.Enums; using Penumbra.GameData.Files.MaterialStructs; @@ -346,8 +347,7 @@ public sealed unsafe class AdvancedDyePopup( if (_mode is not ColorRow.Mode.Dawntrail) { ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") - && value.Model.GlossStrength > 0; + applied |= DragGloss(ref value.Model.GlossStrength); ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); } else @@ -359,8 +359,7 @@ public sealed unsafe class AdvancedDyePopup( if (_mode is not ColorRow.Mode.Dawntrail) { ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, - "%.3f%% SS"); + applied |= DragSpecularStrength(ref value.Model.SpecularStrength); ImGuiUtil.HoverTooltip("Change the specular strength for this row."); } else @@ -388,6 +387,36 @@ public sealed unsafe class AdvancedDyePopup( stateManager.ChangeMaterialValue(_state, index, value, ApplySettings.Manual); } + public static bool DragGloss(ref float value) + { + var tmp = value; + var minValue = ImGui.GetIO().KeyCtrl ? 0f : (float)Half.Epsilon; + if (!ImUtf8.DragScalar("##Gloss"u8, ref tmp, "%.1f G"u8, 0.001f, minValue, Math.Max(0.01f, 0.005f * value), ImGuiSliderFlags.AlwaysClamp)) + return false; + + var tmp2 = Math.Clamp(tmp, minValue, (float)Half.MaxValue); + if (tmp2 == value) + return false; + + value = tmp2; + return true; + } + + public static bool DragSpecularStrength(ref float value) + { + var tmp = value * 100f; + if (!ImUtf8.DragScalar("##SpecularStrength"u8, ref tmp, "%.0f%% SS"u8, 0f, (float)Half.MaxValue * 100f, 0.05f, + ImGuiSliderFlags.AlwaysClamp)) + return false; + + var tmp2 = Math.Clamp(tmp, 0f, (float)Half.MaxValue * 100f) / 100f; + if (tmp2 == value) + return false; + + value = tmp2; + return true; + } + private LabelStruct _label = new(); private struct LabelStruct diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 95be750..3fcdbe1 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -1,6 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; +using FFXIVClientStructs.FFXIV.Common.Lua; using Glamourer.Designs; using Glamourer.Interop.Material; using ImGuiNET; @@ -196,11 +197,11 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", row.Emissive, v => tmp.Emissive = v, "E"); ImGui.SameLine(0, _spacing); ImGui.SetNextItemWidth(GlossWidth * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Gloss", ref tmp.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G"); + applied |= AdvancedDyePopup.DragGloss(ref tmp.GlossStrength); ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); ImGui.SameLine(0, _spacing); ImGui.SetNextItemWidth(SpecularStrengthWidth * ImGuiHelpers.GlobalScale); - applied |= ImGui.DragFloat("##Specular Strength", ref tmp.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f%% SS"); + applied |= AdvancedDyePopup.DragSpecularStrength(ref tmp.SpecularStrength); ImGuiUtil.HoverTooltip("Change the specular strength for this row."); if (applied) _designManager.ChangeMaterialValue(design, index, tmp); From 5cd224b164d92a47a4f03b5def720c99fa881277 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 9 Aug 2024 16:01:44 +0200 Subject: [PATCH 459/786] Add design migration for inverted gloss and specular, and a backup before doing that. --- Glamourer/Configuration.cs | 6 +- Glamourer/Designs/Design.cs | 75 ++++++++++++++++++-- Glamourer/Designs/DesignConverter.cs | 11 +-- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Services/ConfigMigrationService.cs | 11 +++ 5 files changed, 91 insertions(+), 14 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 11c45a8..165e20d 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -143,10 +143,10 @@ public class Configuration : IPluginConfiguration, ISavable public static class Constants { - public const int CurrentVersion = 6; + public const int CurrentVersion = 7; public static readonly ISortMode[] ValidSortModes = - { + [ ISortMode.FoldersFirst, ISortMode.Lexicographical, new DesignFileSystem.CreationDate(), @@ -159,7 +159,7 @@ public class Configuration : IPluginConfiguration, ISavable ISortMode.InverseFoldersLast, ISortMode.InternalOrder, ISortMode.InverseInternalOrder, - }; + ]; } /// Convert SortMode Types to their name. diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 8e4d366..fb18873 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.GameData.Structs; +using Notification = OtterGui.Classes.Notification; namespace Glamourer.Designs; @@ -34,7 +35,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn } // Metadata - public new const int FileVersion = 1; + public new const int FileVersion = 2; public Guid Identifier { get; internal init; } public DateTimeOffset CreationDate { get; internal init; } @@ -143,17 +144,81 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn #region Deserialization - public static Design LoadDesign(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) + public static Design LoadDesign(SaveService saveService, CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) { var version = json["FileVersion"]?.ToObject() ?? 0; return version switch { - FileVersion => LoadDesignV1(customizations, items, linkLoader, json), + 1 => LoadDesignV1(saveService, customizations, items, linkLoader, json), + FileVersion => LoadDesignV2(customizations, items, linkLoader, json), _ => throw new Exception("The design to be loaded has no valid Version."), }; } - private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) + /// The values for gloss and specular strength were switched. Swap them for all appropriate designs. + private static Design LoadDesignV1(SaveService saveService, CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) + { + var design = LoadDesignV2(customizations, items, linkLoader, json); + var materialDesignData = design.GetMaterialDataRef(); + if (materialDesignData.Values.Count == 0) + return design; + + var materialData = materialDesignData.Clone(); + // Guesstimate whether to migrate material rows: + // Update 1.3.0.10 released at that time, so any design last updated before that can be migrated. + if (design.LastEdit <= new DateTime(2024, 8, 7, 16, 0, 0, DateTimeKind.Utc)) + { + Migrate("because it was saved the wrong way around before 1.3.0.10, and this design was not changed since that release."); + } + else + { + var hasNegativeGloss = false; + var hasNonPositiveGloss = false; + var specularLarger = 0; + foreach (var (key, value) in materialData.GetValues(MaterialValueIndex.Min(), MaterialValueIndex.Max())) + { + hasNegativeGloss |= value.Value.GlossStrength < 0; + hasNonPositiveGloss |= value.Value.GlossStrength <= 0; + if (value.Value.SpecularStrength > value.Value.GlossStrength) + ++specularLarger; + } + + // If there is any negative gloss, this is wrong and can be migrated. + if (hasNegativeGloss) + Migrate("because it had a negative Gloss value, which is not supported and thus probably outdated."); + // If there is any non-positive Gloss and some specular values that are larger, it is probably wrong and can be migrated. + else if (hasNonPositiveGloss && specularLarger > 0) + Migrate("because it had a zero Gloss value, and at least one Specular Strength larger than the Gloss, which is unusual."); + // If most of the specular strengths are larger, it is probably wrong and can be migrated. + else if (specularLarger > materialData.Values.Count / 2) + Migrate("because most of its Specular Strength values were larger than the Gloss values, which is unusual."); + } + + return design; + + void Migrate(string reason) + { + materialDesignData.Clear(); + foreach (var (key, value) in materialData.GetValues(MaterialValueIndex.Min(), MaterialValueIndex.Max())) + { + var gloss = Math.Clamp(value.Value.SpecularStrength, 0, (float)Half.MaxValue); + var specularStrength = Math.Clamp(value.Value.GlossStrength, 0, (float)Half.MaxValue); + var colorRow = value.Value with + { + GlossStrength = gloss, + SpecularStrength = specularStrength, + }; + materialDesignData.AddOrUpdateValue(MaterialValueIndex.FromKey(key), value with { Value = colorRow }); + } + + Glamourer.Messager.AddMessage(new Notification( + $"Swapped Gloss and Specular Strength in {materialDesignData.Values.Count} Rows in design {design.Incognito} {reason}", + NotificationType.Info)); + saveService.Save(SaveType.ImmediateSync,design); + } + } + + private static Design LoadDesignV2(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) { var creationDate = json["CreationDate"]?.ToObject() ?? throw new ArgumentNullException("CreationDate"); @@ -183,7 +248,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn static string[] ParseTags(JObject json) { - var tags = json["Tags"]?.ToObject() ?? Array.Empty(); + var tags = json["Tags"]?.ToObject() ?? []; return tags.OrderBy(t => t).Distinct().ToArray(); } } diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 2ebcea0..058b023 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -13,6 +13,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Designs; public class DesignConverter( + SaveService saveService, ItemManager _items, DesignManager _designs, CustomizeService _customize, @@ -69,7 +70,7 @@ public class DesignConverter( try { var ret = jObject["Identifier"] != null - ? Design.LoadDesign(_customize, _items, _linkLoader, jObject) + ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObject) : DesignBase.LoadDesignBase(_customize, _items, jObject); if (!customize) @@ -100,7 +101,7 @@ public class DesignConverter( case (byte)'{': var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes)); ret = jObj1["Identifier"] != null - ? Design.LoadDesign(_customize, _items, _linkLoader, jObj1) + ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj1) : DesignBase.LoadDesignBase(_customize, _items, jObj1); break; case 1: @@ -115,7 +116,7 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 3); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) + ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } @@ -126,7 +127,7 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 5); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) + ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } @@ -136,7 +137,7 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 6); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) + ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index a31ba83..63c98c0 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -53,7 +53,7 @@ public sealed class DesignManager : DesignEditor { var text = File.ReadAllText(f.FullName); var data = JObject.Parse(text); - var design = Design.LoadDesign(Customizations, Items, linkLoader, data); + var design = Design.LoadDesign(SaveService, Customizations, Items, linkLoader, data); designs.Value!.Add((design, f.FullName)); } catch (Exception ex) diff --git a/Glamourer/Services/ConfigMigrationService.cs b/Glamourer/Services/ConfigMigrationService.cs index 88eaf69..3f997c9 100644 --- a/Glamourer/Services/ConfigMigrationService.cs +++ b/Glamourer/Services/ConfigMigrationService.cs @@ -23,9 +23,20 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator MigrateV2To4(); MigrateV4To5(); MigrateV5To6(); + MigrateV6To7(); AddColors(config, true); } + private void MigrateV6To7() + { + if (_config.Version > 6) + return; + + // Do not actually change anything in the config, just create a backup before designs are migrated. + backupService.CreateMigrationBackup("pre_gloss_specular_migration"); + _config.Version = 7; + } + private void MigrateV5To6() { if (_config.Version > 5) From 2282f3f87ae9e8e003249f58815f6b537a8078fd Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 9 Aug 2024 14:04:22 +0000 Subject: [PATCH 460/786] [CI] Updating repo.json for testing_1.3.0.11 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index fb27eac..cc7adf9 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.10", + "TestingAssemblyVersion": "1.3.0.11", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.10/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.11/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From af58a52a596021289f3295661617660f1ef1913c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 9 Aug 2024 16:27:12 +0200 Subject: [PATCH 461/786] Update display of saved material values. --- .../Interop/Material/MaterialValueIndex.cs | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 30f2e68..712b0d5 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -100,12 +100,12 @@ public readonly record struct MaterialValueIndex( || SlotIndex >= model.AsCharacterBase->SlotCount || model.AsCharacterBase->ColorTableTexturesSpan.Length < (SlotIndex + 1) * MaterialService.MaterialsPerModel) { - textures = []; + textures = []; return false; } var from = SlotIndex * MaterialService.MaterialsPerModel; - textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(from, MaterialService.MaterialsPerModel); + textures = model.AsCharacterBase->ColorTableTexturesSpan.Slice(from, MaterialService.MaterialsPerModel); return true; } @@ -220,20 +220,27 @@ public readonly record struct MaterialValueIndex( public override string ToString() => DrawObject switch { - DrawObjectType.Invalid => "Invalid", - DrawObjectType.Human when SlotIndex < 10 => - $"{((uint)SlotIndex).ToEquipSlot().ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - DrawObjectType.Human when SlotIndex == 10 => $"BodySlot.Hair.ToString() Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - DrawObjectType.Human when SlotIndex == 11 => $"BodySlot.Face.ToString() Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - DrawObjectType.Human when SlotIndex == 12 => $"{BodySlot.Tail} / {BodySlot.Ear} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - DrawObjectType.Human when SlotIndex == 13 => $"Connectors Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - DrawObjectType.Human when SlotIndex == 16 => $"{BonusItemFlag.Glasses.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - DrawObjectType.Human when SlotIndex == 17 => $"{BonusItemFlag.UnkSlot.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - DrawObjectType.Mainhand when SlotIndex == 0 => $"{EquipSlot.MainHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - DrawObjectType.Offhand when SlotIndex == 0 => $"{EquipSlot.OffHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", - _ => $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Invalid => "Invalid", + DrawObjectType.Human when SlotIndex < 10 => $"{((uint)SlotIndex).ToEquipSlot().ToName()} {MaterialString()} {RowString()}", + DrawObjectType.Human when SlotIndex == 10 => $"{BodySlot.Hair} {MaterialString()} {RowString()}", + DrawObjectType.Human when SlotIndex == 11 => $"{BodySlot.Face} {MaterialString()} {RowString()}", + DrawObjectType.Human when SlotIndex == 12 => $"{BodySlot.Tail} / {BodySlot.Ear} {MaterialString()} {RowString()}", + DrawObjectType.Human when SlotIndex == 13 => $"Connectors {MaterialString()} {RowString()}", + DrawObjectType.Human when SlotIndex == 16 => $"{BonusItemFlag.Glasses.ToName()} {MaterialString()} {RowString()}", + DrawObjectType.Human when SlotIndex == 17 => $"{BonusItemFlag.UnkSlot.ToName()} {MaterialString()} {RowString()}", + DrawObjectType.Mainhand when SlotIndex == 0 => $"{EquipSlot.MainHand.ToName()} {MaterialString()} {RowString()}", + DrawObjectType.Offhand when SlotIndex == 0 => $"{EquipSlot.OffHand.ToName()} {MaterialString()} {RowString()}", + _ => $"{DrawObject} Slot {SlotIndex} {MaterialString()} {RowString()}", }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string MaterialString() + => $"Material {(char)(MaterialIndex + 'A')}"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string RowString() + => $"Row {RowIndex / 2 + 1}{(char)(RowIndex % 2 + 'A')}"; + private class Converter : JsonConverter { public override void WriteJson(JsonWriter writer, MaterialValueIndex value, JsonSerializer serializer) From edc54203b5b1c8113fddf2344c2d0cac804b9a79 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 9 Aug 2024 22:09:01 +0200 Subject: [PATCH 462/786] Fix display and initial values of design color rows. --- Glamourer/Gui/Materials/MaterialDrawer.cs | 87 +++++++++---------- .../Interop/Material/MaterialValueManager.cs | 2 +- 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 3fcdbe1..dec1aae 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -1,12 +1,12 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; -using FFXIVClientStructs.FFXIV.Common.Lua; using Glamourer.Designs; using Glamourer.Interop.Material; using ImGuiNET; using OtterGui; using OtterGui.Services; +using OtterGui.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Gui; @@ -28,13 +28,13 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) public void Draw(Design design) { - var available = ImGui.GetContentRegionAvail().X; + var available = ImGui.GetContentRegionAvail().X; _spacing = ImGui.GetStyle().ItemInnerSpacing.X; _buttonSize = new Vector2(ImGui.GetFrameHeight()); var colorWidth = 4 * _buttonSize.X + (GlossWidth + SpecularStrengthWidth) * ImGuiHelpers.GlobalScale + 6 * _spacing - + ImGui.CalcTextSize("Revert").X; + + ImUtf8.CalcTextSize("Revert"u8).X; if (available > 1.95 * colorWidth) DrawSingleRow(design); else @@ -44,9 +44,9 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) private void DrawName(MaterialValueIndex index) { - using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale).Push(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); - using var color = ImRaii.PushColor(ImGuiCol.Border, ImGui.GetColorU32(ImGuiCol.Text)); - ImGuiUtil.DrawTextButton(index.ToString(), new Vector2((GlossWidth + SpecularStrengthWidth) * ImGuiHelpers.GlobalScale + _spacing, 0), 0); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); + ImUtf8.TextFramed(index.ToString(), 0, new Vector2((GlossWidth + SpecularStrengthWidth) * ImGuiHelpers.GlobalScale + _spacing, 0), + borderColor: ImGui.GetColorU32(ImGuiCol.Text)); } private void DrawSingleRow(Design design) @@ -55,11 +55,11 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) { using var id = ImRaii.PushId(i); var (idx, value) = design.Materials[i]; - var key = MaterialValueIndex.FromKey(idx); + var key = MaterialValueIndex.FromKey(idx); DrawName(key); ImGui.SameLine(0, _spacing); - DeleteButton(design, key, ref i); + DeleteButton(design, key, ref i); ImGui.SameLine(0, _spacing); CopyButton(value.Value); ImGui.SameLine(0, _spacing); @@ -91,7 +91,7 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) PasteButton(design, key); ImGui.SameLine(0, _spacing); EnabledToggle(design, key, value.Enabled); - + DrawRow(design, key, value.Value, value.Revert); ImGui.SameLine(0, _spacing); @@ -103,9 +103,9 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) private void DeleteButton(Design design, MaterialValueIndex index, ref int idx) { var deleteEnabled = _config.DeleteDesignModifier.IsActive(); - if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), _buttonSize, - $"Delete this color row.{(deleteEnabled ? string.Empty : $"\nHold {_config.DeleteDesignModifier} to delete.")}", - !deleteEnabled || design.WriteProtected(), true)) + if (!ImUtf8.IconButton(FontAwesomeIcon.Trash, + $"Delete this color row.{(deleteEnabled ? string.Empty : $"\nHold {_config.DeleteDesignModifier} to delete.")}", disabled: + !deleteEnabled || design.WriteProtected())) return; _designManager.ChangeMaterialValue(design, index, null); @@ -114,31 +114,29 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) private void CopyButton(in ColorRow row) { - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), _buttonSize, "Export this row to your clipboard.", - false, - true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Export this row to your clipboard."u8)) ColorRowClipboard.Row = row; } private void PasteButton(Design design, MaterialValueIndex index) { - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), _buttonSize, - "Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet || design.WriteProtected(), true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported row from your clipboard onto this row."u8, + disabled: !ColorRowClipboard.IsSet || design.WriteProtected())) _designManager.ChangeMaterialValue(design, index, ColorRowClipboard.Row); } private void EnabledToggle(Design design, MaterialValueIndex index, bool enabled) { - if (ImGui.Checkbox("Enabled", ref enabled)) + if (ImUtf8.Checkbox("Enabled"u8, ref enabled)) _designManager.ChangeApplyMaterialValue(design, index, enabled); } private void RevertToggle(Design design, MaterialValueIndex index, bool revert) { - if (ImGui.Checkbox("Revert", ref revert)) + if (ImUtf8.Checkbox("Revert"u8, ref revert)) _designManager.ChangeMaterialRevert(design, index, revert); - ImGuiUtil.HoverTooltip( - "If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."); + ImUtf8.HoverTooltip( + "If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."u8); } public void DrawNew(Design design) @@ -149,41 +147,38 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) MaterialIndex = (byte)_newMaterialIdx, RowIndex = (byte)_newRowIdx, }; - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImUtf8.SameLineInner(); DrawMaterialIdxDrag(); - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImUtf8.SameLineInner(); DrawRowIdxDrag(); - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImUtf8.SameLineInner(); var exists = design.GetMaterialDataRef().TryGetValue(_newKey, out _); - if (ImGuiUtil.DrawDisabledButton("Add New Row", Vector2.Zero, - exists ? "The selected advanced dye row already exists." : "Add the selected advanced dye row.", exists || design.WriteProtected())) + if (ImUtf8.ButtonEx("Add New Row"u8, + exists ? "The selected advanced dye row already exists."u8 : "Add the selected advanced dye row."u8, Vector2.Zero, + exists || design.WriteProtected())) _designManager.ChangeMaterialValue(design, _newKey, ColorRow.Empty); } private void DrawMaterialIdxDrag() { - _newMaterialIdx += 1; - ImGui.SetNextItemWidth(ImGui.CalcTextSize("Material #000").X); - if (ImGui.DragInt("##Material", ref _newMaterialIdx, 0.01f, 1, MaterialService.MaterialsPerModel, "Material #%i")) + ImGui.SetNextItemWidth(ImUtf8.CalcTextSize("Material AA"u8).X); + var format = $"Material {(char)('A' + _newMaterialIdx)}"; + if (ImUtf8.DragScalar("##Material"u8, ref _newMaterialIdx, format, 0, MaterialService.MaterialsPerModel - 1, 0.01f)) { - _newMaterialIdx = Math.Clamp(_newMaterialIdx, 1, MaterialService.MaterialsPerModel); - _newKey = _newKey with { MaterialIndex = (byte)(_newMaterialIdx - 1) }; + _newMaterialIdx = Math.Clamp(_newMaterialIdx, 0, MaterialService.MaterialsPerModel - 1); + _newKey = _newKey with { MaterialIndex = (byte)_newMaterialIdx }; } - - _newMaterialIdx -= 1; } private void DrawRowIdxDrag() { - _newRowIdx += 1; - ImGui.SetNextItemWidth(ImGui.CalcTextSize("Row #0000").X); - if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, ColorTable.NumRows, "Row #%i")) + ImGui.SetNextItemWidth(ImUtf8.CalcTextSize("Row 0000"u8).X); + var format = $"Row {_newRowIdx / 2 + 1}{(char)(_newRowIdx % 2 + 'A')}"; + if (ImUtf8.DragScalar("##Row"u8, ref _newRowIdx, format, 0, ColorTable.NumRows - 1, 0.01f)) { - _newRowIdx = Math.Clamp(_newRowIdx, 1, ColorTable.NumRows); - _newKey = _newKey with { RowIndex = (byte)(_newRowIdx - 1) }; + _newRowIdx = Math.Clamp(_newRowIdx, 0, ColorTable.NumRows - 1); + _newKey = _newKey with { RowIndex = (byte)_newRowIdx }; } - - _newRowIdx -= 1; } private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row, bool disabled) @@ -191,18 +186,18 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) var tmp = row; using var _ = ImRaii.Disabled(disabled); var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", row.Diffuse, v => tmp.Diffuse = v, "D"); - ImGui.SameLine(0, _spacing); + ImUtf8.SameLineInner(); applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", row.Specular, v => tmp.Specular = v, "S"); - ImGui.SameLine(0, _spacing); + ImUtf8.SameLineInner(); applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", row.Emissive, v => tmp.Emissive = v, "E"); - ImGui.SameLine(0, _spacing); + ImUtf8.SameLineInner(); ImGui.SetNextItemWidth(GlossWidth * ImGuiHelpers.GlobalScale); applied |= AdvancedDyePopup.DragGloss(ref tmp.GlossStrength); - ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); - ImGui.SameLine(0, _spacing); + ImUtf8.HoverTooltip("Change the gloss strength for this row."u8); + ImUtf8.SameLineInner(); ImGui.SetNextItemWidth(SpecularStrengthWidth * ImGuiHelpers.GlobalScale); applied |= AdvancedDyePopup.DragSpecularStrength(ref tmp.SpecularStrength); - ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + ImUtf8.HoverTooltip("Change the specular strength for this row."u8); if (applied) _designManager.ChangeMaterialValue(design, index, tmp); } diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index cb3a7e2..f1ec440 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -19,7 +19,7 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa Dawntrail, } - public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 0, 0); + public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 1f, 1f); public Vector3 Diffuse = diffuse; public Vector3 Specular = specular; From 3a07acbd05f7f2fdae71a1616be6160067cec1c0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 9 Aug 2024 23:50:59 +0200 Subject: [PATCH 463/786] 1.3.1.0 --- Glamourer/Gui/GlamourerChangelog.cs | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index e9173ec..cfa345f 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -34,6 +34,7 @@ public class GlamourerChangelog Add1_2_1_0(Changelog); AddDummy(Changelog); Add1_2_3_0(Changelog); + Add1_3_1_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -54,6 +55,47 @@ public class GlamourerChangelog } } + private static void Add1_3_1_0(Changelog log) + => log.NextVersion("Version 1.2.3.0") + .RegisterHighlight("Glamourer is now released for Dawntrail!") + .RegisterEntry("Added support for female Hrothgar.", 1) + .RegisterEntry("Added support for the Glasses slot.", 1) + .RegisterEntry("Added support for two dye slots.", 1) + .RegisterImportant( + "There were some issues with Advanced Dyes stored in Designs. When launching this update, Glamourer will try to migrate all your old designs into the new form.") + .RegisterEntry("Unfortunately, this is slightly based on guesswork and may cause false-positive migrations.", 1) + .RegisterEntry("In general, the values for Gloss and Specular Strength were swapped, so the migration swaps them back.", 1) + .RegisterEntry( + "In some cases this may not be correct, or the values stored were problematic to begin with and will now cause further issues.", + 1) + .RegisterImportant( + "If your designs lose their specular color, you need to verify that the Specular Strength is non-zero (usually in 0-100%).", 1) + .RegisterImportant( + "If your designs are extremely glossy and reflective, you need to verify that the Gloss value is greater than zero (usually a power of 2 >= 1, it should never be 0).", + 1) + .RegisterEntry( + "I am very sorry for the inconvenience but there is no way to salvage this sanely in all cases, especially with user-input values.", + 1) + .RegisterImportant( + "Any materials already using Dawntrails shaders will currently not be able to edit the Gloss or Specular Strength Values in Advanced Dyes.") + .RegisterImportant( + "Skin and Hair Shine from advanced customizations are not supported by the game any longer, so they are not displayed for the moment.") + .RegisterHighlight("All eyes now support Limbal rings (which use the Feature Color for their color).") + .RegisterHighlight("Dyes can now be dragged and dropped onto other dyes to replicate them.") + .RegisterEntry("The job filter in the Unlocks tab has been improved.") + .RegisterHighlight( + "Editing designs or actors now has a history and you can undo up to 16 of the last changes you made, separately per design or actor.") + .RegisterEntry( + "Some changes (like when a weapon applies its offhand) may count as multiple and have to be stepped back separately.", 1) + .RegisterEntry("You can now change the priority or enabled state of associated mods directly.") + .RegisterEntry("Glamourer now has a Support Info button akin to Penumbra's.") + .RegisterEntry("Glamourer now respects write protection on designs better.") + .RegisterEntry("The advanced dye window popup should now get focused when it is opening even in detached state.") + .RegisterHighlight("You can now display your characters height in Corgis or Olympic Swimming Pools.") + .RegisterEntry("Fixed some issues with advanced customizations and dyes applied via IPC. (1.2.3.2)") + .RegisterEntry( + "Glamourer now uses the last matching game object for advanced dyes instead of the first (mainly relevant for GPose). (1.2.3.1)"); + private static void Add1_2_3_0(Changelog log) => log.NextVersion("Version 1.2.3.0") .RegisterHighlight( From 9e0612509250c2a811ddbeb430e288b47891c4dd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 10 Aug 2024 00:57:10 +0200 Subject: [PATCH 464/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 016da3c..bf020eb 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 016da3c2219a3dbe4c2841ae0d1305ae0b2ad60f +Subproject commit bf020ebf5e4980f1814b336aabbaba5e2e00c362 From 5971592217e9f74662718a0e19014d69f05da1b8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 10 Aug 2024 11:52:12 +0200 Subject: [PATCH 465/786] Add BonusItem API. --- Glamourer.Api | 2 +- Glamourer/Api/IpcProviders.cs | 3 +- Glamourer/Api/ItemsApi.cs | 69 +++++++++++++++++++ Glamourer/Gui/GlamourerChangelog.cs | 1 + .../Tabs/DebugTab/IpcTester/ItemsIpcTester.cs | 21 +++++- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 10 +-- Penumbra.GameData | 2 +- 7 files changed, 100 insertions(+), 8 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 4bad56d..b1b90e6 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 4bad56d610132b419335b89896e1f387b9ba2039 +Subproject commit b1b90e6ecfeee76a12cb27793753fa87af21083f diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index efbc368..8639a22 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -35,7 +35,8 @@ public sealed class IpcProviders : IDisposable, IApiService (a, b, c, d, e, f) => (int)api.Items.SetItem(a, (ApiEquipSlot)b, c, [d], e, (ApplyFlag)f)), new FuncProvider(pi, IpcSubscribers.Legacy.SetItemName.Label, (a, b, c, d, e, f) => (int)api.Items.SetItemName(a, (ApiEquipSlot)b, c, [d], e, (ApplyFlag)f)), - + IpcSubscribers.SetBonusItem.Provider(pi, api.Items), + IpcSubscribers.SetBonusItemName.Provider(pi, api.Items), IpcSubscribers.GetState.Provider(pi, api.State), IpcSubscribers.GetStateName.Provider(pi, api.State), IpcSubscribers.GetStateBase64.Provider(pi, api.State), diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index a2e3533..a516b68 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -57,6 +57,64 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager 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); + } + + public GlamourerApiEc SetBonusItem(int objectIndex, ApiBonusSlot slot, ulong bonusItemId, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Slot", slot, "ID", bonusItemId, "Key", key, "Flags", flags); + if (!ResolveBonusItem(slot, bonusItemId, 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.ChangeBonusItem(state, item.Slot, item, settings); + ApiHelpers.Lock(state, key, flags); + return GlamourerApiEc.Success; + } + + public GlamourerApiEc SetBonusItemName(string playerName, ApiBonusSlot slot, ulong bonusItemId, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", playerName, "Slot", slot, "ID", bonusItemId, "Key", key, "Flags", flags); + if (!ResolveBonusItem(slot, bonusItemId, out var item)) + return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); + + var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key); + var anyHuman = false; + var anyFound = false; + var anyUnlocked = false; + foreach (var state in helpers.FindStates(playerName)) + { + anyFound = true; + if (!state.ModelData.IsHuman) + continue; + + anyHuman = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + stateManager.ChangeBonusItem(state, item.Slot, item, settings); + ApiHelpers.Lock(state, key, flags); + } + if (!anyFound) return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); @@ -79,4 +137,15 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager item = itemManager.Resolve(slot, id); return item.Valid; } + + private bool ResolveBonusItem(ApiBonusSlot apiSlot, ulong itemId, out BonusItem item) + { + var slot = apiSlot switch + { + ApiBonusSlot.Glasses => BonusItemFlag.Glasses, + _ => BonusItemFlag.Unknown, + }; + + return itemManager.IsBonusItemValid(slot, (BonusItemId)itemId, out item); + } } diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index cfa345f..ce0c5e0 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -91,6 +91,7 @@ public class GlamourerChangelog .RegisterEntry("Glamourer now has a Support Info button akin to Penumbra's.") .RegisterEntry("Glamourer now respects write protection on designs better.") .RegisterEntry("The advanced dye window popup should now get focused when it is opening even in detached state.") + .RegisterEntry("Added API and IPC for bonus items, i.e. the Glasses slot.") .RegisterHighlight("You can now display your characters height in Corgis or Olympic Swimming Pools.") .RegisterEntry("Fixed some issues with advanced customizations and dyes applied via IPC. (1.2.3.2)") .RegisterEntry( diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs index 5f9e748..1499fcb 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs @@ -19,7 +19,8 @@ public class ItemsIpcTester(IDalamudPluginInterface pluginInterface) : IUiServic private ApplyFlag _flags = ApplyFlagEx.DesignDefault; private CustomItemId _customItemId; private StainId _stainId; - private EquipSlot _slot = EquipSlot.Head; + private EquipSlot _slot = EquipSlot.Head; + private BonusItemFlag _bonusSlot = BonusItemFlag.Glasses; private GlamourerApiEc _lastError; public void Draw() @@ -47,6 +48,16 @@ public class ItemsIpcTester(IDalamudPluginInterface pluginInterface) : IUiServic if (ImGui.Button("Set##Name")) _lastError = new SetItemName(pluginInterface).Invoke(_gameObjectName, (ApiEquipSlot)_slot, _customItemId.Id, [_stainId.Id], _key, _flags); + + IpcTesterHelpers.DrawIntro(SetBonusItem.Label); + if (ImGui.Button("Set##BonusIdx")) + _lastError = new SetBonusItem(pluginInterface).Invoke(_gameObjectIndex, ToApi(_bonusSlot), _customItemId.Id, _key, + _flags); + + IpcTesterHelpers.DrawIntro(SetBonusItemName.Label); + if (ImGui.Button("Set##BonusName")) + _lastError = new SetBonusItemName(pluginInterface).Invoke(_gameObjectName, ToApi(_bonusSlot), _customItemId.Id, _key, + _flags); } private void DrawItemInput() @@ -57,6 +68,7 @@ public class ItemsIpcTester(IDalamudPluginInterface pluginInterface) : IUiServic if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp)) _customItemId = tmp; EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot, width); + BonusSlotCombo.Draw("Bonus Slot", string.Empty, ref _bonusSlot, width); var value = (int)_stainId.Id; ImGui.SetNextItemWidth(width); if (ImGui.InputInt("Stain ID", ref value, 1, 3)) @@ -65,4 +77,11 @@ public class ItemsIpcTester(IDalamudPluginInterface pluginInterface) : IUiServic _stainId = (StainId)value; } } + + private static ApiBonusSlot ToApi(BonusItemFlag slot) + => slot switch + { + BonusItemFlag.Glasses => ApiBonusSlot.Glasses, + _ => ApiBonusSlot.Unknown, + }; } diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index d1624f6..363cbcc 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -7,6 +7,7 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using ImGuiClip = OtterGui.ImGuiClip; @@ -200,11 +201,12 @@ public class UnlockOverview( using var tt = ImRaii.Tooltip(); if (size.X >= iconSize.X && size.Y >= iconSize.Y) ImGui.Image(icon, size); - ImGui.TextUnformatted(item.Name); - ImGui.TextUnformatted($"{item.Slot.ToName()}"); - ImGui.TextUnformatted($"{item.ModelId.Id}-{item.Variant.Id}"); + ImUtf8.Text(item.Name); + ImUtf8.Text($"{item.Slot.ToName()}"); + ImUtf8.Text($"{item.Id.Id}"); + ImUtf8.Text($"{item.ModelId.Id}-{item.Variant.Id}"); // TODO - ImGui.TextUnformatted("Always Unlocked"); // : $"Unlocked on {time:g}" : "Not Unlocked."); + ImUtf8.Text("Always Unlocked"); // : $"Unlocked on {time:g}" : "Not Unlocked."); // TODO //tooltip.CreateTooltip(item, string.Empty, false); } diff --git a/Penumbra.GameData b/Penumbra.GameData index bf020eb..3a65ed1 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit bf020ebf5e4980f1814b336aabbaba5e2e00c362 +Subproject commit 3a65ed1c86a2d5fd5794ff5c0559b02fc25d7224 From 6a46a410f7cdac0d6054204782e7219f7ac61359 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 10 Aug 2024 11:57:44 +0200 Subject: [PATCH 466/786] Update Penumbra API, increment API version. --- Glamourer/Api/GlamourerApi.cs | 2 +- Penumbra.Api | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index b9e21fd..24ed840 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -6,7 +6,7 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; - public const int CurrentApiVersionMinor = 2; + public const int CurrentApiVersionMinor = 3; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); diff --git a/Penumbra.Api b/Penumbra.Api index 759a8e9..552246e 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 759a8e9dc50b3453cdb7c3cba76de7174c94aba0 +Subproject commit 552246e595ffab2aaba2c75f578d564f8938fc9a From 3880c1870b03d3609c617a683bfa1791d8b31fe5 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 10 Aug 2024 10:02:21 +0000 Subject: [PATCH 467/786] [CI] Updating repo.json for 1.3.1.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index cc7adf9..828a685 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.2.3.3", - "TestingAssemblyVersion": "1.3.0.11", + "AssemblyVersion": "1.3.1.0", + "TestingAssemblyVersion": "1.3.1.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.2.3.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.0.11/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 76cdacf80fb4cd05e55643863c629cc6c520146b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 10 Aug 2024 12:24:21 +0200 Subject: [PATCH 468/786] Update non-testing Dalamud API Level --- Glamourer/Gui/GlamourerChangelog.cs | 2 +- repo.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index ce0c5e0..a69a4c4 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -56,7 +56,7 @@ public class GlamourerChangelog } private static void Add1_3_1_0(Changelog log) - => log.NextVersion("Version 1.2.3.0") + => log.NextVersion("Version 1.3.1.0") .RegisterHighlight("Glamourer is now released for Dawntrail!") .RegisterEntry("Added support for female Hrothgar.", 1) .RegisterEntry("Added support for the Glasses slot.", 1) diff --git a/repo.json b/repo.json index 828a685..a065ebc 100644 --- a/repo.json +++ b/repo.json @@ -21,7 +21,7 @@ "TestingAssemblyVersion": "1.3.1.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 9, + "DalamudApiLevel": 10, "TestingDalamudApiLevel": 10, "IsHide": "False", "IsTestingExclusive": "False", From 38a489d7f08a7c5e56c5b309a7aae41b3f9efdc1 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 10 Aug 2024 10:27:38 +0000 Subject: [PATCH 469/786] [CI] Updating repo.json for 1.3.1.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index a065ebc..5be3916 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.1.0", - "TestingAssemblyVersion": "1.3.1.0", + "AssemblyVersion": "1.3.1.1", + "TestingAssemblyVersion": "1.3.1.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a7c5d513d4438a7d6024c35d7f9d917e01c9602d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 13 Aug 2024 15:27:32 +0200 Subject: [PATCH 470/786] Maybe fix weapon hidden state when leaving gpose or changing zones. --- Glamourer/Interop/MetaService.cs | 2 ++ Glamourer/Interop/ScalingService.cs | 2 +- Glamourer/State/StateApplier.cs | 9 +++++++-- Glamourer/State/StateEditor.cs | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs index 1dc4c1a..1c79cec 100644 --- a/Glamourer/Interop/MetaService.cs +++ b/Glamourer/Interop/MetaService.cs @@ -61,7 +61,9 @@ public unsafe class MetaService : IDisposable if (!actor.IsCharacter) return; + var old = actor.AsCharacter->DrawData.IsWeaponHidden; _hideWeaponsHook.Original(&actor.AsCharacter->DrawData, (byte)(value ? 0 : 1)); + actor.AsCharacter->DrawData.IsWeaponHidden = old; } private void HideHatDetour(DrawDataContainer* drawData, uint id, byte value) diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 16b9a37..c2eb32f 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -59,7 +59,7 @@ public unsafe class ScalingService : IDisposable private void SetupOrnamentDetour(Ornament* ornament, uint* unk1, float* unk2) { - var character = ornament->Character.GetParentCharacter(); + var character = ornament->GetParentCharacter(); if (character == null) { _setupOrnamentHook.Original(ornament, unk1, unk2); diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index d6d5bde..ed6f907 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -247,8 +247,13 @@ public class StateApplier( } case MetaIndex.WeaponState: { - foreach (var actor in data.Objects.Where(a => a.IsCharacter)) - _metaService.SetWeaponState(actor, value); + // Only apply to the GPose character because otherwise we get some weird incompatibility when leaving GPose. + if (_objects.IsInGPose) + foreach (var actor in data.Objects.Where(a => a.IsGPoseOrCutscene)) + _metaService.SetWeaponState(actor, value); + else + foreach (var actor in data.Objects.Where(a => a.IsCharacter)) + _metaService.SetWeaponState(actor, value); return; } case MetaIndex.VisorState: diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 50479b2..c7867e1 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -251,7 +251,7 @@ public class StateEditor( var actors = Applier.ChangeMetaState(state, index, settings.Source.RequiresChange()); Glamourer.Log.Verbose( - $"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); + $"Set {index.ToName()} in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Other, settings.Source, state, actors, new MetaTransaction(index, old, value)); } From fc6604fd5a6765cca0ee128e6e1223d9b84f2136 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 15 Aug 2024 15:58:04 +0200 Subject: [PATCH 471/786] Remove Artisan code. --- .../Gui/Customization/CustomizationDrawer.cs | 31 ----- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 118 +----------------- Glamourer/Services/CodeService.cs | 5 +- 3 files changed, 2 insertions(+), 152 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index a7fcda7..6b6cc0a 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -1,7 +1,5 @@ using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; -using Dalamud.Interface.Utility; -using Dalamud.Plugin; using Dalamud.Plugin.Services; using Glamourer.GameData; using Glamourer.Services; @@ -17,7 +15,6 @@ namespace Glamourer.Gui.Customization; public partial class CustomizationDrawer( ITextureProvider textures, CustomizeService _service, - CodeService _codes, Configuration _config, FavoriteManager _favorites, HeightService _heightService) @@ -119,9 +116,6 @@ public partial class CustomizationDrawer( try { - if (_codes.Enabled(CodeService.CodeFlag.Artisan)) - return DrawArtisan(); - DrawRaceGenderSelector(); DrawBodyType(); @@ -156,31 +150,6 @@ public partial class CustomizationDrawer( } } - private unsafe bool DrawArtisan() - { - for (var i = 0; i < CustomizeArray.Size; ++i) - { - using var id = ImRaii.PushId(i); - int value = _customize.Data[i]; - ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt(string.Empty, ref value, 0, 0)) - { - var newValue = (byte)Math.Clamp(value, 0, byte.MaxValue); - if (newValue != _customize.Data[i]) - foreach (var flag in Enum.GetValues()) - { - var (j, _) = flag.ToByteAndMask(); - if (j == i) - Changed |= flag.ToFlag(); - } - - _customize.Data[i] = newValue; - } - } - - return Changed != 0; - } - private void UpdateSizes() { _spacing = ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemInnerSpacing.X }; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 83c560d..0844626 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -27,7 +27,6 @@ public class EquipmentDrawer private readonly ItemCombo[] _itemCombo; private readonly BonusItemCombo[] _bonusItemCombo; private readonly Dictionary _weaponCombo; - private readonly CodeService _codes; private readonly TextureService _textures; private readonly Configuration _config; private readonly GPoseService _gPose; @@ -38,11 +37,10 @@ public class EquipmentDrawer private Stain? _draggedStain; - public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures, + public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures, Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes) { _items = items; - _codes = codes; _textures = textures; _config = config; _gPose = gPose; @@ -99,8 +97,6 @@ public class EquipmentDrawer if (_config.SmallEquip) DrawEquipSmall(equipDrawData); - else if (!equipDrawData.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan)) - DrawEquipArtisan(equipDrawData); else DrawEquipNormal(equipDrawData); } @@ -137,8 +133,6 @@ public class EquipmentDrawer if (_config.SmallEquip) DrawWeaponsSmall(mainhand, offhand, allWeapons); - else if (!mainhand.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan)) - DrawWeaponsArtisan(mainhand, offhand); else DrawWeaponsNormal(mainhand, offhand, allWeapons); } @@ -186,116 +180,6 @@ public class EquipmentDrawer return change; } - #region Artisan - - private void DrawEquipArtisan(EquipDrawData data) - { - DrawStainArtisan(data); - ImGui.SameLine(); - DrawArmorArtisan(data); - if (!data.DisplayApplication) - return; - - ImGui.SameLine(); - DrawApply(data); - ImGui.SameLine(); - DrawApplyStain(data); - } - - private void DrawWeaponsArtisan(in EquipDrawData mainhand, in EquipDrawData offhand) - { - using (var _ = ImRaii.PushId(0)) - { - DrawStainArtisan(mainhand); - ImGui.SameLine(); - DrawWeapon(mainhand); - } - - using (var _ = ImRaii.PushId(1)) - { - DrawStainArtisan(offhand); - ImGui.SameLine(); - DrawWeapon(offhand); - } - - return; - - void DrawWeapon(in EquipDrawData current) - { - int setId = current.CurrentItem.PrimaryId.Id; - int type = current.CurrentItem.SecondaryId.Id; - int variant = current.CurrentItem.Variant.Id; - ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##setId", ref setId, 0, 0)) - { - var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue); - if (newSetId.Id != current.CurrentItem.PrimaryId.Id) - current.SetItem(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant)); - } - - ImGui.SameLine(); - ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##type", ref type, 0, 0)) - { - var newType = (SecondaryId)Math.Clamp(type, 0, ushort.MaxValue); - if (newType.Id != current.CurrentItem.SecondaryId.Id) - current.SetItem(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant)); - } - - ImGui.SameLine(); - ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##variant", ref variant, 0, 0)) - { - var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue); - if (newVariant.Id != current.CurrentItem.Variant.Id) - current.SetItem(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, current.CurrentItem.SecondaryId, - newVariant)); - } - } - } - - /// Draw an input for stain that can set arbitrary values instead of choosing valid stains. - private static void DrawStainArtisan(EquipDrawData data) - { - foreach (var (stain, index) in data.CurrentStains.WithIndex()) - { - using var id = ImUtf8.PushId(index); - int stainId = stain.Id; - ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); - if (!ImGui.InputInt("##stain", ref stainId, 0, 0)) - return; - - var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue); - if (newStainId != stain.Id) - data.SetStains(data.CurrentStains.With(index, newStainId)); - } - } - - /// Draw an input for armor that can set arbitrary values instead of choosing items. - private void DrawArmorArtisan(EquipDrawData data) - { - int setId = data.CurrentItem.PrimaryId.Id; - int variant = data.CurrentItem.Variant.Id; - ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##setId", ref setId, 0, 0)) - { - var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue); - if (newSetId.Id != data.CurrentItem.PrimaryId.Id) - data.SetItem(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant)); - } - - ImGui.SameLine(); - ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); - if (ImGui.InputInt("##variant", ref variant, 0, 0)) - { - var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue); - if (newVariant != data.CurrentItem.Variant) - data.SetItem(_items.Identify(data.Slot, data.CurrentItem.PrimaryId, newVariant)); - } - } - - #endregion - #region Small private void DrawEquipSmall(in EquipDrawData equipDrawData) diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index 73d27b4..af2e88b 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -23,7 +23,7 @@ public class CodeService OopsAuRa = 0x000400, OopsHrothgar = 0x000800, OopsViera = 0x001000, - Artisan = 0x002000, + //Artisan = 0x002000, SixtyThree = 0x004000, Shirts = 0x008000, World = 0x010000, @@ -182,7 +182,6 @@ public class CodeService CodeFlag.OopsAuRa => (FullCodes | RaceCodes) & ~CodeFlag.OopsAuRa, CodeFlag.OopsHrothgar => (FullCodes | RaceCodes) & ~CodeFlag.OopsHrothgar, CodeFlag.OopsViera => (FullCodes | RaceCodes) & ~CodeFlag.OopsViera, - CodeFlag.Artisan => 0, CodeFlag.SixtyThree => FullCodes, CodeFlag.Shirts => 0, CodeFlag.World => (FullCodes | DyeCodes | GearCodes) & ~CodeFlag.World, @@ -211,7 +210,6 @@ public class CodeService CodeFlag.OopsAuRa => [ 0x69, 0x93, 0xAF, 0xE4, 0xB8, 0xEC, 0x5F, 0x40, 0xEB, 0x8A, 0x6F, 0xD1, 0x9B, 0xD9, 0x56, 0x0B, 0xEA, 0x64, 0x79, 0x9B, 0x54, 0xA1, 0x41, 0xED, 0xBC, 0x3E, 0x6E, 0x5C, 0xF1, 0x23, 0x60, 0xF8 ], CodeFlag.OopsHrothgar => [ 0x41, 0xEC, 0x65, 0x05, 0x8D, 0x20, 0x68, 0x5A, 0xB7, 0xEB, 0x92, 0x15, 0x43, 0xCF, 0x15, 0x05, 0x27, 0x51, 0xFE, 0x20, 0xC9, 0xB6, 0x2B, 0x84, 0xD9, 0x6A, 0x49, 0x5A, 0x5B, 0x7F, 0x2E, 0xE7 ], CodeFlag.OopsViera => [ 0x16, 0xFF, 0x63, 0x85, 0x1C, 0xF5, 0x34, 0x33, 0x67, 0x8C, 0x46, 0x8E, 0x3E, 0xE3, 0xA6, 0x94, 0xF9, 0x74, 0x47, 0xAA, 0xC7, 0x29, 0x59, 0x1F, 0x6C, 0x6E, 0xF2, 0xF5, 0x87, 0x24, 0x9E, 0x2B ], - CodeFlag.Artisan => [ 0xDE, 0x01, 0x32, 0x1E, 0x7F, 0x22, 0x80, 0x3D, 0x76, 0xDF, 0x74, 0x0E, 0xEC, 0x33, 0xD3, 0xF4, 0x1A, 0x98, 0x9E, 0x9D, 0x22, 0x5C, 0xAC, 0x3B, 0xFE, 0x0B, 0xC2, 0x13, 0xB9, 0x91, 0x24, 0x61 ], CodeFlag.SixtyThree => [ 0xA1, 0x65, 0x60, 0x99, 0xB0, 0x9F, 0xBF, 0xD7, 0x20, 0xC8, 0x29, 0xF6, 0x7B, 0x86, 0x27, 0xF5, 0xBE, 0xA9, 0x5B, 0xB0, 0x20, 0x5E, 0x57, 0x7B, 0xFF, 0xBC, 0x1E, 0x8C, 0x04, 0xF9, 0x35, 0xD3 ], CodeFlag.Shirts => [ 0xD1, 0x35, 0xD7, 0x18, 0xBE, 0x45, 0x42, 0xBD, 0x88, 0x77, 0x7E, 0xC4, 0x41, 0x06, 0x34, 0x4D, 0x71, 0x3A, 0xC5, 0xCC, 0xA4, 0x1B, 0x7D, 0x3F, 0x3B, 0x86, 0x07, 0xCB, 0x63, 0xD7, 0xF9, 0xDB ], CodeFlag.World => [ 0xFD, 0xA2, 0xD2, 0xBC, 0xD9, 0x8A, 0x7E, 0x2B, 0x52, 0xCB, 0x57, 0x6E, 0x3A, 0x2E, 0x30, 0xBA, 0x4E, 0xAE, 0x42, 0xEA, 0x5C, 0x57, 0xDF, 0x17, 0x37, 0x3C, 0xCE, 0x17, 0x42, 0x43, 0xAE, 0xD0 ], @@ -246,7 +244,6 @@ public class CodeService 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."), CodeFlag.Face => (false, 3, ",,!", string.Empty, "Enable a debugging mode for the UI. Not really useful."), CodeFlag.Manderville => (false, 3, ",,!", string.Empty, "Enable a debugging mode for the UI. Not really useful."), CodeFlag.Smiles => (false, 3, ",,!", string.Empty, "Enable a debugging mode for the UI. Not really useful."), From 773f526fba3e94008e393539ac910c85005ffcdc Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 15 Aug 2024 17:23:51 +0200 Subject: [PATCH 472/786] Improve handling for bonus items in designs. --- Glamourer/Designs/DesignBase.cs | 9 +++-- Glamourer/Designs/DesignData.cs | 2 +- Glamourer/Designs/DesignEditor.cs | 2 +- Glamourer/Gui/Materials/MaterialDrawer.cs | 2 ++ Glamourer/Services/ItemManager.cs | 40 ++++++++++++++++++++--- Penumbra.GameData | 2 +- 6 files changed, 45 insertions(+), 12 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 06e55d7..5c4996a 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -280,7 +280,7 @@ public class DesignBase var item = _designData.BonusItem(slot); ret[slot.ToString()] = new JObject() { - ["BonusId"] = item.Id.Id, + ["BonusId"] = item.CustomId.Id, ["Apply"] = DoApplyBonusItem(slot), }; } @@ -431,7 +431,7 @@ public class DesignBase protected static void LoadBonus(ItemManager items, DesignBase design, JToken? json) { - if (json is not JObject obj) + if (json is not JObject) { design.Application.BonusItem = 0; return; @@ -439,8 +439,7 @@ public class DesignBase foreach (var slot in BonusExtensions.AllFlags) { - var itemJson = json[slot.ToString()] as JObject; - if (itemJson == null) + if (json[slot.ToString()] is not JObject itemJson) { design.Application.BonusItem &= ~slot; design.GetDesignDataRef().SetBonusItem(slot, BonusItem.Empty(slot)); @@ -448,7 +447,7 @@ public class DesignBase } design.SetApplyBonusItem(slot, itemJson["Apply"]?.ToObject() ?? false); - var id = itemJson["BonusId"]?.ToObject() ?? 0; + var id = itemJson["BonusId"]?.ToObject() ?? 0; var item = items.Resolve(slot, id); design.GetDesignDataRef().SetBonusItem(slot, item); } diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 0a52861..28944b9 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -112,7 +112,7 @@ public unsafe struct DesignData => slot switch { // @formatter:off - BonusItemFlag.Glasses => new BonusItem(_nameGlasses, _iconIds[12], _bonusIds[0], _bonusModelIds[0], _bonusVariants[0], BonusItemFlag.Glasses), + BonusItemFlag.Glasses => Penumbra.GameData.Structs.BonusItem.FromIds(_bonusIds[0], _iconIds[12], _bonusModelIds[0], _bonusVariants[0], BonusItemFlag.Glasses, _nameGlasses), _ => Penumbra.GameData.Structs.BonusItem.Empty(slot), // @formatter:on }; diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index 5328f03..a3e86a5 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -175,7 +175,7 @@ public class DesignEditor( public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default) { var design = (Design)data; - if (!Items.IsBonusItemValid(slot, item.Id, out item)) + if (item.Slot != slot) return; var oldItem = design.DesignData.BonusItem(slot); diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index dec1aae..1b5e65a 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -139,6 +139,8 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) "If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."u8); } + public sealed class MaterialSlotCombo; + public void DrawNew(Design design) { if (EquipSlotCombo.Draw("##slot", "Choose a slot for an advanced dye row.", ref _newSlot)) diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 7b83199..8cfd387 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -132,16 +132,48 @@ public class ItemManager if (index == uint.MaxValue) return new BonusItem($"Invalid ({id.Id}-{variant})", 0, 0, id, variant, slot); - if (id.Id == 0) - return BonusItem.Empty(slot); + var item = ObjectIdentification.Identify(id, variant, slot) + .FirstOrDefault(BonusItem.FromIds(BonusItemId.Invalid, 0, id, variant, slot)); + if (item.Id != BonusItemId.Invalid) + return item; - return ObjectIdentification.Identify(id, variant, slot) - .FirstOrDefault(new BonusItem($"Invalid ({id.Id}-{variant})", 0, 0, id, variant, slot)); + if (slot is BonusItemFlag.Glasses) + { + var headItem = ObjectIdentification.Identify(id, 0, variant, EquipSlot.Head).FirstOrDefault(); + if (headItem.Valid) + return BonusItem.FromIds(BonusItemId.Invalid, headItem.IconId, id, variant, slot, $"{headItem.Name} ({EquipSlot.Head.ToName()}: {id}-{variant})"); + } + + return item; } public BonusItem Resolve(BonusItemFlag slot, BonusItemId id) => IsBonusItemValid(slot, id, out var item) ? item : new BonusItem($"Invalid ({id.Id})", 0, id, 0, 0, slot); + public BonusItem Resolve(BonusItemFlag slot, CustomItemId id) + { + // Only from early designs as migration. + if (!id.IsBonusItem) + { + IsBonusItemValid(slot, (BonusItemId)id.Id, out var item); + return item; + } + + if (!id.IsCustom) + { + if (IsBonusItemValid(slot, id.BonusItem, out var item)) + return item; + + return BonusItem.Empty(slot); + } + + var (model, variant, slot2) = id.SplitBonus; + if (slot != slot2) + return BonusItem.Empty(slot); + + return Identify(slot, model, variant); + } + /// Return the default offhand for a given mainhand, that is for both handed weapons, return the correct offhand part, and for everything else Nothing. public EquipItem GetDefaultOffhand(EquipItem mainhand) { diff --git a/Penumbra.GameData b/Penumbra.GameData index 3a65ed1..d9d4b28 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 3a65ed1c86a2d5fd5794ff5c0559b02fc25d7224 +Subproject commit d9d4b286d54ad4027a1e8a2bbf900ee79aefaa93 From 82536c75c9ca47722510ed0bd33c9c8a138f0f91 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 24 Aug 2024 20:40:02 +0200 Subject: [PATCH 473/786] Fix fun module RNG. --- Glamourer/State/FunModule.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 5d436c0..de39a53 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -340,14 +340,14 @@ public unsafe class FunModule : IDisposable private void SetRandomDye(ref CharacterArmor armor) { - var stainIdx = _rng.Next(0, _stains.Length - 1); + var stainIdx = _rng.Next(0, _stains.Length); armor.Stains = _stains[stainIdx]; } private void SetRandomItem(EquipSlot slot, ref CharacterArmor armor) { var list = _items.ItemData.ByType[slot.ToEquipType()]; - var rng = _rng.Next(0, list.Count - 1); + var rng = _rng.Next(0, list.Count); var item = list[rng]; armor.Set = item.PrimaryId; armor.Variant = item.Variant; @@ -385,7 +385,7 @@ public unsafe class FunModule : IDisposable { armor = slot switch { - EquipSlot.Body => DolphinBodies[_rng.Next(0, DolphinBodies.Count - 1)], + EquipSlot.Body => DolphinBodies[_rng.Next(0, DolphinBodies.Count)], EquipSlot.Head => new CharacterArmor(5040, 1, StainIds.None), _ => armor, }; @@ -440,7 +440,7 @@ public unsafe class FunModule : IDisposable if (index is CustomizeIndex.Face || !set.IsAvailable(index)) continue; - var valueIdx = _rng.Next(0, set.Count(index) - 1); + var valueIdx = _rng.Next(0, set.Count(index)); customize[index] = set.Data(index, valueIdx).Value; } } From 03043ba2c96791f2febf59ceed82fd487b7296a9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 24 Aug 2024 20:40:26 +0200 Subject: [PATCH 474/786] Fix reversion. --- Glamourer/State/StateManager.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 5033577..24b025f 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -311,7 +311,8 @@ public sealed class StateManager( foreach (var flag in CustomizationExtensions.All) state.Sources[flag] = StateSource.Game; - state.ModelData = state.BaseData; + state.ModelData.ModelId = state.BaseData.ModelId; + state.ModelData.Customize = state.BaseData.Customize; var actors = ActorData.Invalid; if (source is not StateSource.Game) actors = Applier.ChangeCustomize(state, true); @@ -335,6 +336,13 @@ public sealed class StateManager( } } + foreach (var slot in BonusExtensions.AllFlags) + { + state.Sources[slot] = StateSource.Game; + if (source is not StateSource.Game) + state.ModelData.SetBonusItem(slot, state.BaseData.BonusItem(slot)); + } + var actors = ActorData.Invalid; if (source is not StateSource.Game) { From 9a02ba298724c68766b4a3fae4afeb00d8a072cd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 31 Aug 2024 20:51:38 +0200 Subject: [PATCH 475/786] Add support for previewing non-existing items from Penumbra. --- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 28 +++++++++++++++++-- Glamourer/Interop/Penumbra/PenumbraService.cs | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index c74d6a3..c6f2fc4 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -1,4 +1,4 @@ -using Glamourer.Designs; +using Glamourer.Designs; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -168,12 +168,23 @@ public sealed class PenumbraChangedItemTooltip : IDisposable { case ChangedItemType.ItemOffhand: case ChangedItemType.Item: + { if (!_items.ItemData.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return; CreateTooltip(item, "[Glamourer] ", false); return; + } + case ChangedItemType.CustomArmor: + { + var (model, variant, slot) = IdentifiedItem.Split(id); + var item = _items.Identify(slot.ToSlot(), model, variant); + if (item.Valid) + CreateTooltip(item, "[Glamourer] ", false); + return; + } case ChangedItemType.Customization: + { var (race, gender, index, value) = IdentifiedCustomization.Split(id); if (!_objects.Player.Model.IsHuman) return; @@ -183,6 +194,7 @@ public sealed class PenumbraChangedItemTooltip : IDisposable ImGui.TextUnformatted("[Glamourer] Right-Click to apply to current actor."); return; + } } } @@ -212,17 +224,29 @@ public sealed class PenumbraChangedItemTooltip : IDisposable { case ChangedItemType.Item: case ChangedItemType.ItemOffhand: + { if (!_items.ItemData.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) return; ApplyItem(state, item); return; + } + case ChangedItemType.CustomArmor: + { + var (model, variant, slot) = IdentifiedItem.Split(id); + var item = _items.Identify(slot.ToSlot(), model, variant); + if (item.Valid) + ApplyItem(state, item); + return; + } case ChangedItemType.Customization: + { var (race, gender, index, value) = IdentifiedCustomization.Split(id); var customize = state.ModelData.Customize; if (CheckGenderRace(customize, race, gender) && VerifyValue(customize, index, value)) _stateManager.ChangeCustomize(state, index, value, ApplySettings.Manual); return; + } } } @@ -236,7 +260,7 @@ public sealed class PenumbraChangedItemTooltip : IDisposable else { var oldItem = state.ModelData.Item(slot); - if (oldItem.ItemId != item.ItemId) + if (oldItem.Id != item.Id) _lastItems[slot.ToIndex()] = oldItem; } } diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 85235e7..4f6c44a 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -31,7 +31,7 @@ public readonly record struct ModSettings(Dictionary> Setti => new(); } -public unsafe class PenumbraService : IDisposable +public class PenumbraService : IDisposable { public const int RequiredPenumbraBreakingVersion = 5; public const int RequiredPenumbraFeatureVersion = 0; diff --git a/Penumbra.Api b/Penumbra.Api index 552246e..97e9f42 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 552246e595ffab2aaba2c75f578d564f8938fc9a +Subproject commit 97e9f427406f82a59ddef764b44ecea654a51623 diff --git a/Penumbra.GameData b/Penumbra.GameData index d9d4b28..66bc00d 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d9d4b286d54ad4027a1e8a2bbf900ee79aefaa93 +Subproject commit 66bc00dc8517204e58c6515af5aec0ba6d196716 From f473abb99c9e721812d82ebdc7780d53602e68e8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Sep 2024 22:53:35 +0200 Subject: [PATCH 476/786] Fix chat log context menu try-on. --- Glamourer/Interop/ContextMenuService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 19a805f..eeee70a 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -12,7 +12,7 @@ namespace Glamourer.Interop; public class ContextMenuService : IDisposable { public const int ItemSearchContextItemId = 0x1738; - public const int ChatLogContextItemId = 0x948; + public const int ChatLogContextItemId = 0x950; private readonly ItemManager _items; private readonly IContextMenu _contextMenu; From 1f1b04bdfe21a3aea9090d0d95f057e3d5bd4582 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Sep 2024 23:34:29 +0200 Subject: [PATCH 477/786] Update actions. --- .github/workflows/release.yml | 2 +- .github/workflows/test_release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 821f20b..e22a656 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: - name: Archive run: Compress-Archive -Path Glamourer/bin/Release/* -DestinationPath Glamourer.zip - name: Upload a Build Artifact - uses: actions/upload-artifact@v2.2.1 + uses: actions/upload-artifact@v4 with: path: | ./Glamourer/bin/Release/* diff --git a/.github/workflows/test_release.yml b/.github/workflows/test_release.yml index 4435e39..b9d3672 100644 --- a/.github/workflows/test_release.yml +++ b/.github/workflows/test_release.yml @@ -37,7 +37,7 @@ jobs: - name: Archive run: Compress-Archive -Path Glamourer/bin/Debug/* -DestinationPath Glamourer.zip - name: Upload a Build Artifact - uses: actions/upload-artifact@v2.2.1 + uses: actions/upload-artifact@v4 with: path: | ./Glamourer/bin/Debug/* From be9c3eab4015015ec23970f2ff46c80e06ea9ec0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 21 Sep 2024 21:17:24 +0200 Subject: [PATCH 478/786] Add bonus item import from .chara files, fix small bugs when applying designs. --- Glamourer/Designs/DesignBase.cs | 13 +++++---- Glamourer/Designs/DesignEditor.cs | 6 ++++ Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 1 - Glamourer/Interop/CharaFile/CharaFile.cs | 31 +++++++++++++++++++++ Glamourer/Interop/ImportService.cs | 4 +-- 5 files changed, 46 insertions(+), 9 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 5c4996a..a8e9986 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -40,13 +40,14 @@ public class DesignBase } /// Used when importing .cma or .chara files. - internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) + internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags, BonusItemFlag bonusFlags) { - _designData = designData; - ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; - Application.Equip = equipFlags & EquipFlagExtensions.All; - Application.Meta = 0; - CustomizeSet = SetCustomizationSet(customize); + _designData = designData; + ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; + Application.Equip = equipFlags & EquipFlagExtensions.All; + Application.BonusItem = bonusFlags & BonusExtensions.All; + Application.Meta = 0; + CustomizeSet = SetCustomizationSet(customize); } internal DesignBase(DesignBase clone) diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index a3e86a5..a42971e 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -330,6 +330,12 @@ public class DesignEditor( _forceFullItemOff = false; + foreach (var slot in BonusExtensions.AllFlags) + { + if (other.DoApplyBonusItem(slot)) + ChangeBonusItem(design, slot, other.DesignData.BonusItem(slot)); + } + foreach (var slot in Enum.GetValues().Where(other.DoApplyCrest)) ChangeCrest(design, slot, other.DesignData.Crest(slot)); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index e11a032..5fb2d57 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -9,7 +9,6 @@ using Glamourer.GameData; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Materials; -using Glamourer.Gui.Tabs.ActorTab; using Glamourer.Interop; using Glamourer.State; using ImGuiNET; diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs index 4bf08cd..f956379 100644 --- a/Glamourer/Interop/CharaFile/CharaFile.cs +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -13,6 +13,7 @@ public sealed class CharaFile public DesignData Data = new(); public CustomizeFlag ApplyCustomize; public EquipFlag ApplyEquip; + public BonusItemFlag ApplyBonus; public static CharaFile ParseData(ItemManager items, string data, string? name = null) { @@ -24,6 +25,7 @@ public sealed class CharaFile ret.Name = jObj["Nickname"]?.ToObject() ?? name ?? "New Design"; ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize); ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data); + ret.ApplyBonus = ParseBonusItems(items, jObj, ref ret.Data); return ret; } @@ -45,6 +47,13 @@ public sealed class CharaFile return ret; } + private static BonusItemFlag ParseBonusItems(ItemManager items, JObject jObj, ref DesignData data) + { + BonusItemFlag ret = 0; + ParseBonus(items, jObj, "Glasses", "GlassesId", BonusItemFlag.Glasses, ref data, ref ret); + return ret; + } + private static void ParseWeapon(ItemManager items, JObject jObj, string property, EquipSlot slot, ref DesignData data, ref EquipFlag flags) { var jTok = jObj[property]; @@ -61,6 +70,8 @@ public sealed class CharaFile data.SetItem(slot, item); data.SetStain(slot, new StainIds(dye)); + if (slot is EquipSlot.MainHand) + data.SetItem(EquipSlot.OffHand, items.GetDefaultOffhand(item)); flags |= slot.ToFlag(); flags |= slot.ToStainFlag(); } @@ -84,6 +95,26 @@ public sealed class CharaFile flags |= slot.ToStainFlag(); } + private static void ParseBonus(ItemManager items, JObject jObj, string property, string subProperty, BonusItemFlag slot, + ref DesignData data, ref BonusItemFlag flags) + { + var id = jObj[property]?[subProperty]?.ToObject(); + if (id is null) + return; + + if (id is 0) + { + data.SetBonusItem(slot, BonusItem.Empty(slot)); + flags |= slot; + } + + if (!items.DictBonusItems.TryGetValue((BonusItemId)id.Value, out var item) || item.Slot != slot) + return; + + data.SetBonusItem(slot, item); + flags |= slot; + } + private static CustomizeFlag ParseCustomize(JObject jObj, ref CustomizeArray customize) { CustomizeFlag ret = 0; diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index 7dc3313..b9dfbe1 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -65,7 +65,7 @@ public class ImportService(CustomizeService _customizations, IDragDropManager _d var file = CharaFile.CharaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path)); name = file.Name; - design = new DesignBase(_customizations, file.Data, file.ApplyEquip, file.ApplyCustomize); + design = new DesignBase(_customizations, file.Data, file.ApplyEquip, file.ApplyCustomize, file.ApplyBonus); } catch (Exception ex) { @@ -95,7 +95,7 @@ public class ImportService(CustomizeService _customizations, IDragDropManager _d throw new Exception(); name = file.Name; - design = new DesignBase(_customizations, file.Data, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant); + design = new DesignBase(_customizations, file.Data, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, 0); } catch (Exception ex) { From c8cc375d4b56911976110b4f8e9fd3613f1337e8 Mon Sep 17 00:00:00 2001 From: Ottermandias <70807659+Ottermandias@users.noreply.github.com> Date: Sat, 5 Oct 2024 02:15:23 +0200 Subject: [PATCH 479/786] Update WorldSets.cs --- Glamourer/State/WorldSets.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Glamourer/State/WorldSets.cs b/Glamourer/State/WorldSets.cs index 314934b..958a2ed 100644 --- a/Glamourer/State/WorldSets.cs +++ b/Glamourer/State/WorldSets.cs @@ -117,7 +117,7 @@ public class WorldSets (FunEquipSet.Group.FullSet(204, 4), CharacterWeapon.Int(2601, 13, 1), CharacterWeapon.Int(2651, 13, 1)), // DNC, Softstepper, High Steel Chakrams (new FunEquipSet.Group(206, 7, 303, 3, 23, 109, 303, 3, 262, 7), CharacterWeapon.Int(2802, 13, 1), CharacterWeapon.Empty), // RPR, Muzhik, Deepgold War Scythe (new FunEquipSet.Group(20, 46, 289, 6, 342, 3, 120, 9, 342, 3), CharacterWeapon.Int(2702, 08, 1), CharacterWeapon.Empty), // SGE, Bookwyrm, Stonegold Milpreves - (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 288, 5), CharacterWeapon.Int(3101, 06, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs + (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 262, 8), CharacterWeapon.Int(3101, 06, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs (new FunEquipSet.Group(595, 3, 372, 3, 290, 6, 315, 3, 290, 6), CharacterWeapon.Int(2901, 02, 1), CharacterWeapon.Int(2951, 2, 1)), // PCT, Painter's, Round Brush }; @@ -164,7 +164,7 @@ public class WorldSets (FunEquipSet.Group.FullSet(204, 4), CharacterWeapon.Int(2601, 13, 1), CharacterWeapon.Int(2651, 13, 01)), // DNC, Softstepper, High Steel Chakrams (new FunEquipSet.Group(206, 7, 303, 3, 23, 109, 303, 3, 262, 7), CharacterWeapon.Int(2802, 13, 1), CharacterWeapon.Empty), // RPR, Muzhik, Deepgold War Scythe (new FunEquipSet.Group(20, 46, 289, 6, 342, 3, 120, 9, 342, 3), CharacterWeapon.Int(2702, 08, 1), CharacterWeapon.Empty), // SGE, Bookwyrm, Stonegold Milpreves - (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 288, 5), CharacterWeapon.Int(3101, 06, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs + (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 262, 8), CharacterWeapon.Int(3101, 06, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs (new FunEquipSet.Group(595, 3, 372, 3, 290, 6, 315, 3, 290, 6), CharacterWeapon.Int(2901, 02, 1), CharacterWeapon.Int(2951, 2, 1)), // PCT, Painter's, Round Brush }; @@ -211,7 +211,7 @@ public class WorldSets (FunEquipSet.Group.FullSet(204, 4), CharacterWeapon.Int(2601, 13, 1), CharacterWeapon.Int(2651, 13, 01)), // DNC, Softstepper, High Steel Chakrams (new FunEquipSet.Group(206, 7, 303, 3, 23, 109, 303, 3, 262, 7), CharacterWeapon.Int(2802, 13, 1), CharacterWeapon.Empty), // RPR, Muzhik, Deepgold War Scythe (new FunEquipSet.Group(20, 46, 289, 6, 342, 3, 120, 9, 342, 3), CharacterWeapon.Int(2702, 08, 1), CharacterWeapon.Empty), // SGE, Bookwyrm, Stonegold Milpreves - (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 288, 5), CharacterWeapon.Int(3101, 06, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs + (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 262, 8), CharacterWeapon.Int(3101, 06, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs (new FunEquipSet.Group(595, 3, 372, 3, 290, 6, 315, 3, 290, 6), CharacterWeapon.Int(2901, 02, 1), CharacterWeapon.Int(2951, 2, 1)), // PCT, Painter's, Round Brush }; @@ -258,7 +258,7 @@ public class WorldSets (FunEquipSet.Group.FullSet(543, 1), CharacterWeapon.Int(2601, 001, 1), CharacterWeapon.Int(2651, 01, 001)), // DNC, Dancer, Krishna (new FunEquipSet.Group(206, 7, 303, 3, 23, 109, 303, 3, 262, 7), CharacterWeapon.Int(2802, 013, 1), CharacterWeapon.Empty), // RPR, Harvester's, Demon Slicer (new FunEquipSet.Group(20, 46, 289, 6, 342, 3, 120, 9, 342, 3), CharacterWeapon.Int(2702, 008, 1), CharacterWeapon.Empty), // SGE, Therapeute's, Horkos - (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 288, 5), CharacterWeapon.Int(3101, 6, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs + (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 262, 8), CharacterWeapon.Int(3101, 6, 1), CharacterWeapon.Int(3151, 6, 1)), // VPR, Snakebite, Twinfangs (new FunEquipSet.Group(595, 3, 372, 3, 290, 6, 315, 3, 290, 6), CharacterWeapon.Int(2901, 2, 1), CharacterWeapon.Int(2951, 2, 1)), // PCT, Painter's, Round Brush }; @@ -305,7 +305,7 @@ public class WorldSets (FunEquipSet.Group.FullSet(694, 1), CharacterWeapon.Int(2607, 001, 1), CharacterWeapon.Int(2657, 001, 001)), // DNC, Etoile, Terpsichore (FunEquipSet.Group.FullSet(695, 1), CharacterWeapon.Int(2801, 001, 1), CharacterWeapon.Empty), // RPR, Reaper, Death Sickle (FunEquipSet.Group.FullSet(696, 1), CharacterWeapon.Int(2701, 006, 1), CharacterWeapon.Empty), // SGE, Didact, Hagneia - (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 288, 5), CharacterWeapon.Int(3101, 006, 1), CharacterWeapon.Int(3151, 006, 001)), // VPR, Snakebite, Twinfangs + (new FunEquipSet.Group(491, 3, 288, 5, 288, 5, 288, 5, 262, 8), CharacterWeapon.Int(3101, 006, 1), CharacterWeapon.Int(3151, 006, 001)), // VPR, Snakebite, Twinfangs (new FunEquipSet.Group(595, 3, 372, 3, 290, 6, 315, 3, 290, 6), CharacterWeapon.Int(2901, 002, 1), CharacterWeapon.Int(2951, 002, 001)), // PCT, Painter's, Round Brush }; From 9b5271bca4dd0ffeeb17ef72ea15f0cea465818a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 11:58:33 +0200 Subject: [PATCH 480/786] Update Gamedata. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 66bc00d..0dfe87d 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 66bc00dc8517204e58c6515af5aec0ba6d196716 +Subproject commit 0dfe87d59ea800e2bdb2b2f35680de79be2f5598 From 5da07381479442adc316023c34f4584087aa49bc Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 12:45:58 +0200 Subject: [PATCH 481/786] Add a debug rider for actors. --- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 43 +++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index e6cdbd3..14a9e11 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -16,6 +16,8 @@ using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Text; +using OtterGui.Text.HelperObjects; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -181,6 +183,7 @@ public class ActorPanel DrawCustomizationsHeader(); DrawEquipmentHeader(); DrawParameterHeader(); + DrawDebugData(); } private void DrawCustomizationsHeader() @@ -188,7 +191,7 @@ public class ActorPanel var header = _state!.ModelData.ModelId == 0 ? "Customization" : $"Customization (Model Id #{_state.ModelData.ModelId})###Customization"; - using var h = ImRaii.CollapsingHeader(header); + using var h = ImUtf8.CollapsingHeaderId(header); if (!h) return; @@ -201,7 +204,7 @@ public class ActorPanel private void DrawEquipmentHeader() { - using var h = ImRaii.CollapsingHeader("Equipment"); + using var h = ImUtf8.CollapsingHeaderId("Equipment"u8); if (!h) return; @@ -236,13 +239,47 @@ public class ActorPanel if (!_config.UseAdvancedParameters) return; - using var h = ImRaii.CollapsingHeader("Advanced Customizations"); + using var h = ImUtf8.CollapsingHeaderId("Advanced Customizations"u8); if (!h) return; _parameterDrawer.Draw(_stateManager, _state!); } + private unsafe void DrawDebugData() + { + if (!_config.DebugMode) + return; + + using var h = ImUtf8.CollapsingHeaderId("Debug Data"u8); + if (!h) + return; + + using var t = ImUtf8.Table("table"u8, 2, ImGuiTableFlags.SizingFixedFit); + if (!t) + return; + + ImUtf8.DrawTableColumn("Object Index"u8); + DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->ObjectIndex))}"); + ImUtf8.DrawTableColumn("Name ID"u8); + DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->GetNameId()))}"); + ImUtf8.DrawTableColumn("Base ID"u8); + DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->BaseId))}"); + ImUtf8.DrawTableColumn("Entity ID"u8); + DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->EntityId))}"); + ImUtf8.DrawTableColumn("Owner ID"u8); + DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->OwnerId))}"); + ImUtf8.DrawTableColumn("Game Object ID"u8); + DrawCopyColumn($"{string.Join(", ", _data.Objects.Select(d => d.AsObject->GetGameObjectId().ObjectId))}"); + + static void DrawCopyColumn(ref Utf8StringHandler text) + { + ImUtf8.DrawTableColumn(ref text); + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + ImUtf8.SetClipboardText(TextStringHandlerBuffer.Span); + } + } + private void DrawEquipmentMetaToggles() { using (_ = ImRaii.Group()) From 726eb52e4f42f61167da99d9efeffc12f34df7a8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 12:46:50 +0200 Subject: [PATCH 482/786] Update Gamedata. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 0dfe87d..737f90e 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 0dfe87d59ea800e2bdb2b2f35680de79be2f5598 +Subproject commit 737f90e236418d5c4269aef7b16dd1ab74f298fa From 816e88e0bdb14ba5135637d2f1c4f9e974b5036d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 12:51:35 +0200 Subject: [PATCH 483/786] Make the advanced dye window non-docking. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 9991aae..520d2c1 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -181,7 +181,8 @@ public sealed unsafe class AdvancedDyePopup( var flags = ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDecoration - | ImGuiWindowFlags.NoResize; + | ImGuiWindowFlags.NoResize + | ImGuiWindowFlags.NoDocking; // Set position to the right of the main window when attached // The downwards offset is implicit through child position. From 885063d38902d463fba7a2eb8299b951641650f3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 12:59:06 +0200 Subject: [PATCH 484/786] Make item combos search for entire model string. --- Glamourer/Gui/Equipment/ItemCombo.cs | 5 +++-- Glamourer/Gui/Equipment/WeaponCombo.cs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 7e298c0..1dac468 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -7,6 +7,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Raii; +using OtterGui.Text; using OtterGui.Widgets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -75,12 +76,12 @@ public sealed class ItemCombo : FilterComboCache var ret = ImGui.Selectable(name, selected); ImGui.SameLine(); using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080); - ImGuiUtil.RightAlign($"({obj.ModelString})"); + ImUtf8.TextRightAligned($"({obj.PrimaryId.Id}-{obj.Variant.Id})"); return ret; } protected override bool IsVisible(int globalIndex, LowerString filter) - => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString()); + => base.IsVisible(globalIndex, filter) || Items[globalIndex].ModelString.StartsWith(filter.Lower); protected override string ToString(EquipItem obj) => obj.Name; diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index f6531a2..67f09f5 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -5,6 +5,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Raii; +using OtterGui.Text; using OtterGui.Widgets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -69,12 +70,12 @@ public sealed class WeaponCombo : FilterComboCache 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})"); + ImUtf8.TextRightAligned($"({obj.PrimaryId.Id}-{obj.SecondaryId.Id}-{obj.Variant.Id})"); return ret; } protected override bool IsVisible(int globalIndex, LowerString filter) - => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString()); + => base.IsVisible(globalIndex, filter) || Items[globalIndex].ModelString.StartsWith(filter.Lower); protected override string ToString(EquipItem obj) => obj.Name; From 83e1476e7f7eb5c7d6ea733c750a0bba3152c6a9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 12:59:15 +0200 Subject: [PATCH 485/786] Make unlocks tab non-docking. --- Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs index 1c82add..c2e06e5 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs @@ -20,7 +20,8 @@ public class UnlocksTab : Window, ITab _overview = overview; _table = table; - IsOpen = false; + Flags |= ImGuiWindowFlags.NoDocking; + IsOpen = false; SizeConstraints = new WindowSizeConstraints() { MinimumSize = new Vector2(700, 675), From ce02c64655afd4951896e125c1c52570a4d944aa Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 13:01:28 +0200 Subject: [PATCH 486/786] Update Gamedata. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 737f90e..dd86daf 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 737f90e236418d5c4269aef7b16dd1ab74f298fa +Subproject commit dd86dafb88ca4c7b662938bbc1310729ba7f788d From 65e33d91aca3d228f0558dbe2b7bba8a5cb6e6e1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 15:03:58 +0200 Subject: [PATCH 487/786] 1.3.2.0 --- Glamourer/Gui/GlamourerChangelog.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index a69a4c4..77a5788 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -35,6 +35,7 @@ public class GlamourerChangelog AddDummy(Changelog); Add1_2_3_0(Changelog); Add1_3_1_0(Changelog); + Add1_3_2_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -55,6 +56,23 @@ public class GlamourerChangelog } } + private static void Add1_3_2_0(Changelog log) + => log.NextVersion("Version 1.3.2.0") + .RegisterEntry("Fixed an issue with weapon hiding when leaving GPose or changing zones.") + .RegisterEntry("Added support for unnamed items to be previewed from Penumbra.") + .RegisterEntry("Item combos filters now check if the model string starts with the current filter, instead of checking if the primary ID contains the current filter.") + .RegisterEntry("Improved the handling of bonus items (glasses) in designs.") + .RegisterEntry("Imported .chara files now import bonus items.") + .RegisterEntry("Added a Debug Data rider in the Actors tab that is visible if Debug Mode is enabled and (currently) contains some IDs.") + .RegisterEntry("Fixed bonus items not reverting correctly in some cases.") + .RegisterEntry("Fixed an issue with the RNG in cheat codes and events skipping some possible entries.") + .RegisterEntry("Fixed the chat log context menu for glamourer Try-On.") + .RegisterEntry("Fixed some issues with cheat code sets.") + .RegisterEntry("Made the popped out Advanced Dye Window and Unlocks Window non-docking as that caused issues when docked to the main Glamourer window.") + .RegisterEntry("Refreshed NPC name associations.") + .RegisterEntry("Removed a now useless cheat code.") + .RegisterEntry("Added API for Bonus Items. (1.3.1.1)"); + private static void Add1_3_1_0(Changelog log) => log.NextVersion("Version 1.3.1.0") .RegisterHighlight("Glamourer is now released for Dawntrail!") From ea8b23d3fdcf80eca69ed7d6b4362f90880e05c4 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 6 Oct 2024 13:05:46 +0000 Subject: [PATCH 488/786] [CI] Updating repo.json for 1.3.2.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 5be3916..f3ea329 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.1.1", - "TestingAssemblyVersion": "1.3.1.1", + "AssemblyVersion": "1.3.2.0", + "TestingAssemblyVersion": "1.3.2.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.1.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.2.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.2.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.2.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From d104b794aee21b91ba81e03865d60a5feeaf542e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 7 Oct 2024 18:34:52 +0200 Subject: [PATCH 489/786] Add owned NPC button for automation identifiers. --- Glamourer/Automation/AutoDesignApplier.cs | 9 ++++++++- Glamourer/Automation/AutoDesignManager.cs | 7 ++++--- .../Gui/Tabs/AutomationTab/IdentifierDrawer.cs | 9 +++++++++ Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 15 ++++++++++----- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index f90cb9b..22048a8 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -299,12 +299,19 @@ public sealed class AutoDesignApplier : IDisposable if (_manager.EnabledSets.TryGetValue(identifier, out set)) return true; - identifier = _actors.CreatePlayer(identifier.PlayerName, ushort.MaxValue); + identifier = _actors.CreatePlayer(identifier.PlayerName, WorldId.AnyWorld); return _manager.EnabledSets.TryGetValue(identifier, out set); case IdentifierType.Retainer: case IdentifierType.Npc: return _manager.EnabledSets.TryGetValue(identifier, out set); case IdentifierType.Owned: + if (_manager.EnabledSets.TryGetValue(identifier, out set)) + return true; + + identifier = _actors.CreateOwned(identifier.PlayerName, WorldId.AnyWorld, identifier.Kind, identifier.DataId); + if (_manager.EnabledSets.TryGetValue(identifier, out set)) + return true; + identifier = _actors.CreateNpc(identifier.Kind, identifier.DataId); return _manager.EnabledSets.TryGetValue(identifier, out set); default: diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 474afdd..a219376 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -570,12 +570,13 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos IdentifierType.Player => true, IdentifierType.Retainer => true, IdentifierType.Npc => true, + IdentifierType.Owned => true, _ => false, }; if (!validType) { - group = Array.Empty(); + group = []; return false; } @@ -602,6 +603,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos : ActorIdentifier.RetainerType.Bell).CreatePermanent(), ], IdentifierType.Npc => CreateNpcs(_actors, identifier), + IdentifierType.Owned => CreateNpcs(_actors, identifier), _ => [], }; @@ -616,8 +618,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos }; return table.Where(kvp => kvp.Value == name) .Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id, - identifier.Kind, - kvp.Key)).ToArray(); + identifier.Kind, kvp.Key)).ToArray(); } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs index 8498bc1..b197a1a 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs @@ -20,6 +20,7 @@ public class IdentifierDrawer public ActorIdentifier PlayerIdentifier { get; private set; } = ActorIdentifier.Invalid; public ActorIdentifier RetainerIdentifier { get; private set; } = ActorIdentifier.Invalid; public ActorIdentifier MannequinIdentifier { get; private set; } = ActorIdentifier.Invalid; + public ActorIdentifier OwnedIdentifier { get; private set; } = ActorIdentifier.Invalid; public IdentifierDrawer(ActorManager actors, DictWorld dictWorld, DictModelChara dictModelChara, DictBNpcNames bNpcNames, DictBNpc bNpc, HumanModelList humans) @@ -60,6 +61,9 @@ public class IdentifierDrawer public bool CanSetNpc => NpcIdentifier.IsValid; + public bool CanSetOwned + => OwnedIdentifier.IsValid; + private void UpdateIdentifiers() { if (ByteString.FromString(_characterName, out var byteName)) @@ -67,6 +71,11 @@ public class IdentifierDrawer PlayerIdentifier = _actors.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key); RetainerIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell); MannequinIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin); + + if (_humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc) + OwnedIdentifier = _actors.CreateOwned(byteName, _worldCombo.CurrentSelection.Key, _humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0]); + else + OwnedIdentifier = ActorIdentifier.Invalid; } NpcIdentifier = _humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 8d52a76..ab2b3d6 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -10,6 +10,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Log; using OtterGui.Raii; +using OtterGui.Text; using OtterGui.Widgets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -424,22 +425,26 @@ public class SetPanel( private void DrawIdentifierSelection(int setIndex) { - using var id = ImRaii.PushId("Identifiers"); + using var id = ImUtf8.PushId("Identifiers"u8); _identifierDrawer.DrawWorld(130); ImGui.SameLine(); _identifierDrawer.DrawName(200 - ImGui.GetStyle().ItemSpacing.X); _identifierDrawer.DrawNpcs(330); var buttonWidth = new Vector2(165 * ImGuiHelpers.GlobalScale - ImGui.GetStyle().ItemSpacing.X / 2, 0); - if (ImGuiUtil.DrawDisabledButton("Set to Character", buttonWidth, string.Empty, !_identifierDrawer.CanSetPlayer)) + if (ImUtf8.ButtonEx("Set to Character"u8, string.Empty, buttonWidth, !_identifierDrawer.CanSetPlayer)) _manager.ChangeIdentifier(setIndex, _identifierDrawer.PlayerIdentifier); ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton("Set to NPC", buttonWidth, string.Empty, !_identifierDrawer.CanSetNpc)) + if (ImUtf8.ButtonEx("Set to NPC"u8, string.Empty, buttonWidth, !_identifierDrawer.CanSetNpc)) _manager.ChangeIdentifier(setIndex, _identifierDrawer.NpcIdentifier); - if (ImGuiUtil.DrawDisabledButton("Set to Retainer", buttonWidth, string.Empty, !_identifierDrawer.CanSetRetainer)) + + if (ImUtf8.ButtonEx("Set to Retainer"u8, string.Empty, buttonWidth, !_identifierDrawer.CanSetRetainer)) _manager.ChangeIdentifier(setIndex, _identifierDrawer.RetainerIdentifier); ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton("Set to Mannequin", buttonWidth, string.Empty, !_identifierDrawer.CanSetRetainer)) + if (ImUtf8.ButtonEx("Set to Mannequin"u8, string.Empty, buttonWidth, !_identifierDrawer.CanSetRetainer)) _manager.ChangeIdentifier(setIndex, _identifierDrawer.MannequinIdentifier); + + if (ImUtf8.ButtonEx("Set to Owned NPC"u8, string.Empty, buttonWidth, !_identifierDrawer.CanSetOwned)) + _manager.ChangeIdentifier(setIndex, _identifierDrawer.OwnedIdentifier); } private sealed class JobGroupCombo(AutoDesignManager manager, JobService jobs, Logger log) From 8f53253bae474020a6cf4cccb6514af14fc1b718 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 7 Oct 2024 18:35:09 +0200 Subject: [PATCH 490/786] Add special filters to actor selector. --- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 62 +++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index e688bce..76f0ba4 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -1,11 +1,15 @@ -using Dalamud.Interface; +using System.Security.AccessControl; +using Dalamud.Interface; using Glamourer.Interop; using Glamourer.Interop.Structs; using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.ActorTab; @@ -25,6 +29,7 @@ public class ActorSelector(ObjectManager objects, ActorManager actors, Ephemeral private LowerString _actorFilter = LowerString.Empty; private Vector2 _defaultItemSpacing; + private WorldId _world; private float _width; public (ActorIdentifier Identifier, ActorData Data) Selection @@ -36,12 +41,43 @@ public class ActorSelector(ObjectManager objects, ActorManager actors, Ephemeral public void Draw(float width) { _width = width; - using var group = ImRaii.Group(); + using var group = ImUtf8.Group(); _defaultItemSpacing = ImGui.GetStyle().ItemSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) .Push(ImGuiStyleVar.FrameRounding, 0); ImGui.SetNextItemWidth(_width); LowerString.InputWithHint("##actorFilter", "Filter...", ref _actorFilter, 64); + if (ImGui.IsItemHovered()) + { + using var tt = ImUtf8.Tooltip(); + ImUtf8.Text("Filter for names containing the input."u8); + ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeight() / 2)); + ImUtf8.Text("Special filters are:"u8); + var color = ColorId.HeaderButtons.Value(); + ImUtf8.Text("

"u8, color); + ImGui.SameLine(0, 0); + ImUtf8.Text(": show only player characters."u8); + + ImUtf8.Text(""u8, color); + ImGui.SameLine(0, 0); + ImUtf8.Text(": show only owned game objects."u8); + + ImUtf8.Text(""u8, color); + ImGui.SameLine(0, 0); + ImUtf8.Text(": show only NPCs."u8); + + ImUtf8.Text(""u8, color); + ImGui.SameLine(0, 0); + ImUtf8.Text(": show only retainers."u8); + + ImUtf8.Text(""u8, color); + ImGui.SameLine(0, 0); + ImUtf8.Text(": show only special screen characters."u8); + + ImUtf8.Text(""u8, color); + ImGui.SameLine(0, 0); + ImUtf8.Text(": show only players from your world."u8); + } DrawSelector(); DrawSelectionButtons(); @@ -49,11 +85,12 @@ public class ActorSelector(ObjectManager objects, ActorManager actors, Ephemeral private void DrawSelector() { - using var child = ImRaii.Child("##Selector", new Vector2(_width, -ImGui.GetFrameHeight()), true); + using var child = ImUtf8.Child("##Selector"u8, new Vector2(_width, -ImGui.GetFrameHeight()), true); if (!child) return; objects.Update(); + _world = new WorldId(objects.Player.Valid ? objects.Player.HomeWorld : (ushort)0); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); var remainder = ImGuiClip.FilteredClippedDraw(objects.Identifiers, skips, CheckFilter, DrawSelectable); @@ -61,12 +98,22 @@ public class ActorSelector(ObjectManager objects, ActorManager actors, Ephemeral } private bool CheckFilter(KeyValuePair pair) - => _actorFilter.IsEmpty || pair.Value.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase); + => _actorFilter.Lower switch + { + "" => true, + "

" => pair.Key.Type is IdentifierType.Player, + "" => pair.Key.Type is IdentifierType.Owned, + "" => pair.Key.Type is IdentifierType.Npc, + "" => pair.Key.Type is IdentifierType.Retainer, + "" => pair.Key.Type is IdentifierType.Special, + "" => pair.Key.Type is IdentifierType.Player && pair.Key.HomeWorld == _world, + _ => _actorFilter.IsContained(pair.Value.Label), + }; private void DrawSelectable(KeyValuePair pair) { var equals = pair.Key.Equals(_identifier); - if (ImGui.Selectable(IncognitoMode ? pair.Key.Incognito(pair.Value.Label) : pair.Value.Label, equals) && !equals) + if (ImUtf8.Selectable(IncognitoMode ? pair.Key.Incognito(pair.Value.Label) : pair.Value.Label, equals) && !equals) _identifier = pair.Key.CreatePermanent(); } @@ -76,15 +123,14 @@ public class ActorSelector(ObjectManager objects, ActorManager actors, Ephemeral .Push(ImGuiStyleVar.FrameRounding, 0); var buttonWidth = new Vector2(_width / 2, 0); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth - , "Select the local player character.", !objects.Player, true)) + if (ImUtf8.IconButton(FontAwesomeIcon.UserCircle, "Select the local player character."u8, buttonWidth, !objects.Player)) _identifier = objects.Player.GetIdentifier(actors); ImGui.SameLine(); var (id, data) = objects.TargetData; var tt = data.Valid ? $"Select the current target {id} in the list." : id.IsValid ? $"The target {id} is not in the list." : "No target selected."; - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth, tt, objects.IsInGPose || !data.Valid, true)) + if (ImUtf8.IconButton(FontAwesomeIcon.HandPointer, tt, buttonWidth, objects.IsInGPose || !data.Valid)) _identifier = id; } } From 9d99d936aa56673412d5f82229b1fef9381c816d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 11 Oct 2024 18:18:33 +0200 Subject: [PATCH 491/786] Remove BonusItem and replace with normal EquipItems everywhere. --- Glamourer/Api/ItemsApi.cs | 6 +-- Glamourer/Designs/DesignBase.cs | 4 +- Glamourer/Designs/DesignData.cs | 16 +++--- Glamourer/Designs/DesignEditor.cs | 4 +- Glamourer/Designs/History/Transaction.cs | 2 +- Glamourer/Designs/IDesignEditor.cs | 2 +- Glamourer/Glamourer.cs | 12 +++++ Glamourer/Gui/Equipment/BonusDrawData.cs | 6 +-- Glamourer/Gui/Equipment/BonusItemCombo.cs | 19 +++---- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 4 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 2 +- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 2 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 8 +-- Glamourer/Gui/UiHelpers.cs | 25 +-------- Glamourer/Interop/CharaFile/CharaFile.cs | 4 +- Glamourer/Interop/UpdateSlotService.cs | 2 +- Glamourer/Services/ItemManager.cs | 37 +++++--------- Glamourer/Services/TextureService.cs | 4 +- Glamourer/State/InternalStateEditor.cs | 2 +- Glamourer/State/StateApplier.cs | 4 +- Glamourer/State/StateEditor.cs | 2 +- Glamourer/State/StateListener.cs | 8 +-- Glamourer/State/StateManager.cs | 2 +- Glamourer/Unlocks/FavoriteManager.cs | 51 ++++++++++++------- Penumbra.GameData | 2 +- 25 files changed, 112 insertions(+), 118 deletions(-) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index a516b68..fd174ca 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -85,7 +85,7 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key); - stateManager.ChangeBonusItem(state, item.Slot, item, settings); + stateManager.ChangeBonusItem(state, item.Type.ToBonus(), item, settings); ApiHelpers.Lock(state, key, flags); return GlamourerApiEc.Success; } @@ -111,7 +111,7 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager continue; anyUnlocked = true; - stateManager.ChangeBonusItem(state, item.Slot, item, settings); + stateManager.ChangeBonusItem(state, item.Type.ToBonus(), item, settings); ApiHelpers.Lock(state, key, flags); } @@ -138,7 +138,7 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return item.Valid; } - private bool ResolveBonusItem(ApiBonusSlot apiSlot, ulong itemId, out BonusItem item) + private bool ResolveBonusItem(ApiBonusSlot apiSlot, ulong itemId, out EquipItem item) { var slot = apiSlot switch { diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index a8e9986..30d4ddd 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -281,7 +281,7 @@ public class DesignBase var item = _designData.BonusItem(slot); ret[slot.ToString()] = new JObject() { - ["BonusId"] = item.CustomId.Id, + ["BonusId"] = item.Id.Id, ["Apply"] = DoApplyBonusItem(slot), }; } @@ -443,7 +443,7 @@ public class DesignBase if (json[slot.ToString()] is not JObject itemJson) { design.Application.BonusItem &= ~slot; - design.GetDesignDataRef().SetBonusItem(slot, BonusItem.Empty(slot)); + design.GetDesignDataRef().SetBonusItem(slot, EquipItem.BonusItemNothing(slot)); continue; } diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 28944b9..3e32eac 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -108,12 +108,12 @@ public unsafe struct DesignData } } - public readonly BonusItem BonusItem(BonusItemFlag slot) + public readonly EquipItem BonusItem(BonusItemFlag slot) => slot switch { // @formatter:off - BonusItemFlag.Glasses => Penumbra.GameData.Structs.BonusItem.FromIds(_bonusIds[0], _iconIds[12], _bonusModelIds[0], _bonusVariants[0], BonusItemFlag.Glasses, _nameGlasses), - _ => Penumbra.GameData.Structs.BonusItem.Empty(slot), + BonusItemFlag.Glasses => EquipItem.FromIds(_bonusIds[0], _iconIds[12], _bonusModelIds[0], 0, _bonusVariants[0], FullEquipType.Glasses, 0, _nameGlasses), + _ => EquipItem.BonusItemNothing(slot), // @formatter:on }; @@ -191,15 +191,15 @@ public unsafe struct DesignData return true; } - public bool SetBonusItem(BonusItemFlag slot, BonusItem item) + public bool SetBonusItem(BonusItemFlag slot, EquipItem item) { var index = slot.ToIndex(); if (index > NumBonusItems) return false; - _iconIds[NumEquipment + NumWeapons + index] = item.Icon.Id; - _bonusIds[index] = item.Id.Id; - _bonusModelIds[index] = item.ModelId.Id; + _iconIds[NumEquipment + NumWeapons + index] = item.IconId.Id; + _bonusIds[index] = item.Id.BonusItem.Id; + _bonusModelIds[index] = item.PrimaryId.Id; _bonusVariants[index] = item.Variant.Id; switch (index) { @@ -330,7 +330,7 @@ public unsafe struct DesignData public void SetDefaultBonusItems() { foreach (var slot in BonusExtensions.AllFlags) - SetBonusItem(slot, Penumbra.GameData.Structs.BonusItem.Empty(slot)); + SetBonusItem(slot, EquipItem.BonusItemNothing(slot)); } diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index a42971e..cfda047 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -172,10 +172,10 @@ public class DesignEditor( } /// - public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default) + public void ChangeBonusItem(object data, BonusItemFlag slot, EquipItem item, ApplySettings settings = default) { var design = (Design)data; - if (item.Slot != slot) + if (item.Type.ToBonus() != slot) return; var oldItem = design.DesignData.BonusItem(slot); diff --git a/Glamourer/Designs/History/Transaction.cs b/Glamourer/Designs/History/Transaction.cs index ac310b0..47b10bf 100644 --- a/Glamourer/Designs/History/Transaction.cs +++ b/Glamourer/Designs/History/Transaction.cs @@ -40,7 +40,7 @@ public readonly record struct EquipTransaction(EquipSlot Slot, EquipItem Old, Eq => editor.ChangeItem(data, Slot, Old, ApplySettings.Manual); } -public readonly record struct BonusItemTransaction(BonusItemFlag Slot, BonusItem Old, BonusItem New) +public readonly record struct BonusItemTransaction(BonusItemFlag Slot, EquipItem Old, EquipItem New) : ITransaction { public ITransaction? Merge(ITransaction older) diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index 9eefa63..935263b 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -65,7 +65,7 @@ public interface IDesignEditor => ChangeEquip(data, slot, item, null, settings); ///

Change a bonus item. - public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default); + public void ChangeBonusItem(object data, BonusItemFlag slot, EquipItem item, ApplySettings settings = default); /// Change the stain for any equipment piece. public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings = default) diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 84312d9..ee202d6 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -6,9 +6,12 @@ using Glamourer.Gui; using Glamourer.Interop; using Glamourer.Services; using Glamourer.State; +using OtterGui; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Files; namespace Glamourer; @@ -44,6 +47,15 @@ public class Glamourer : IDalamudPlugin _services.GetService(); // initialize commands. _services.GetService(); // initialize IPC. Log.Information($"Glamourer v{Version} loaded successfully."); + + //var text = File.ReadAllBytes(@"C:\FFXIVMods\PBDTest\files\human.pbd"); + //var pbd = new PbdFile(text); + //var roundtrip = pbd.Write(); + //File.WriteAllBytes(@"C:\FFXIVMods\PBDTest\files\Vanilla resaved save.pbd", roundtrip); + //var deformer = pbd.Deformers.FirstOrDefault(d => d.GenderRace is GenderRace.RoegadynFemale)!.RacialDeformer; + //deformer.DeformMatrices["ya_fukubu_phys"] = deformer.DeformMatrices["j_kosi"]; + //var aleks = pbd.Write(); + //File.WriteAllBytes(@"C:\FFXIVMods\PBDTest\files\rue.pbd", aleks); } catch { diff --git a/Glamourer/Gui/Equipment/BonusDrawData.cs b/Glamourer/Gui/Equipment/BonusDrawData.cs index d2a6b2e..9c023b8 100644 --- a/Glamourer/Gui/Equipment/BonusDrawData.cs +++ b/Glamourer/Gui/Equipment/BonusDrawData.cs @@ -20,7 +20,7 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) public readonly bool IsState => _object is ActorState; - public readonly void SetItem(BonusItem item) + public readonly void SetItem(EquipItem item) => _editor.ChangeBonusItem(_object, Slot, item, ApplySettings.Manual); public readonly void SetApplyItem(bool value) @@ -30,8 +30,8 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) manager.ChangeApplyBonusItem(design, Slot, value); } - public BonusItem CurrentItem = designData.BonusItem(slot); - public BonusItem GameItem = default; + public EquipItem CurrentItem = designData.BonusItem(slot); + public EquipItem GameItem = default; public bool CurrentApply; public static BonusDrawData FromDesign(DesignManager manager, Design design, BonusItemFlag slot) diff --git a/Glamourer/Gui/Equipment/BonusItemCombo.cs b/Glamourer/Gui/Equipment/BonusItemCombo.cs index ae8bf19..735a23f 100644 --- a/Glamourer/Gui/Equipment/BonusItemCombo.cs +++ b/Glamourer/Gui/Equipment/BonusItemCombo.cs @@ -13,11 +13,11 @@ using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public sealed class BonusItemCombo : FilterComboCache +public sealed class BonusItemCombo : FilterComboCache { private readonly FavoriteManager _favorites; public readonly string Label; - private BonusItemId _currentItem; + private CustomItemId _currentItem; private float _innerWidth; public PrimaryId CustomSetId { get; private set; } @@ -75,14 +75,14 @@ public sealed class BonusItemCombo : FilterComboCache var ret = ImGui.Selectable(name, selected); ImGui.SameLine(); using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080); - ImGuiUtil.RightAlign($"({obj.ModelId.Id}-{obj.Variant.Id})"); + ImGuiUtil.RightAlign($"({obj.PrimaryId.Id}-{obj.Variant.Id})"); return ret; } protected override bool IsVisible(int globalIndex, LowerString filter) - => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Id.ToString()); + => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString()); - protected override string ToString(BonusItem obj) + protected override string ToString(EquipItem obj) => obj.Name; private static string GetLabel(IDataManager gameData, BonusItemFlag slot) @@ -98,13 +98,10 @@ public sealed class BonusItemCombo : FilterComboCache }; } - private static List GetItems(FavoriteManager favorites, ItemManager items, BonusItemFlag slot) + private static List GetItems(FavoriteManager favorites, ItemManager items, BonusItemFlag slot) { - var nothing = BonusItem.Empty(slot); - if (slot is not BonusItemFlag.Glasses) - return [nothing]; - - return items.DictBonusItems.Values.OrderByDescending(favorites.Contains).ThenBy(i => i.Id.Id).Prepend(nothing).ToList(); + var nothing = EquipItem.BonusItemNothing(slot); + return items.ItemData.ByType[slot.ToEquipType()].OrderByDescending(favorites.Contains).ThenBy(i => i.Id.Id).Prepend(nothing).ToList(); } protected override void OnClosePopup() diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 0844626..38c8263 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -468,14 +468,14 @@ public class EquipmentDrawer UiHelpers.OpenCombo($"##{combo.Label}"); using var disabled = ImRaii.Disabled(data.Locked); - var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, + var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id.BonusItem, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); if (change) data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); - if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, BonusItem.Empty(data.Slot), out var item)) + if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, EquipItem.BonusItemNothing(data.Slot), out var item)) data.SetItem(item); } diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 520d2c1..0c2b11e 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -305,7 +305,7 @@ public sealed unsafe class AdvancedDyePopup( { EquipSlot.MainHand => _state.ModelData.Weapon(EquipSlot.MainHand), EquipSlot.OffHand => _state.ModelData.Weapon(EquipSlot.OffHand), - EquipSlot.Unknown => _state.ModelData.BonusItem((index.SlotIndex - 16u).ToBonusSlot()).ToArmor().ToWeapon(0), + EquipSlot.Unknown => _state.ModelData.BonusItem((index.SlotIndex - 16u).ToBonusSlot()).Armor().ToWeapon(0), // TODO: Handle better _ => _state.ModelData.Armor(slot).ToWeapon(0), }; value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual); diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index 7307f22..eeb6f3f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -248,7 +248,7 @@ public unsafe class ModelEvaluationPanel( { var glassesId = actor.GetBonusItem(slot); if (bonusItems.TryGetValue(glassesId, out var glasses)) - ImGuiUtil.DrawTableColumn($"{glasses.ModelId.Id},{glasses.Variant.Id} ({glassesId})"); + ImGuiUtil.DrawTableColumn($"{glasses.PrimaryId.Id},{glasses.Variant.Id} ({glassesId})"); else ImGuiUtil.DrawTableColumn($"{glassesId}"); } diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 363cbcc..efe2448 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -180,11 +180,11 @@ public class UnlockOverview( if (remainder > 0) ImGuiClip.DrawEndDummy(remainder, iconSize.Y + spacing.Y); - void DrawItem(BonusItem item) + void DrawItem(EquipItem item) { // TODO check unlocks var unlocked = true; - if (!textures.TryLoadIcon(item.Icon.Id, out var iconHandle)) + if (!textures.TryLoadIcon(item.IconId.Id, out var iconHandle)) return; var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); @@ -202,9 +202,9 @@ public class UnlockOverview( if (size.X >= iconSize.X && size.Y >= iconSize.Y) ImGui.Image(icon, size); ImUtf8.Text(item.Name); - ImUtf8.Text($"{item.Slot.ToName()}"); + ImUtf8.Text($"{item.Type.ToName()}"); ImUtf8.Text($"{item.Id.Id}"); - ImUtf8.Text($"{item.ModelId.Id}-{item.Variant.Id}"); + ImUtf8.Text($"{item.PrimaryId.Id}-{item.Variant.Id}"); // TODO ImUtf8.Text("Always Unlocked"); // : $"Unlocked on {time:g}" : "Not Unlocked."); // TODO diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 8499728..1ec79d7 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -44,9 +44,9 @@ public static class UiHelpers } } - public static void DrawIcon(this BonusItem item, TextureService textures, Vector2 size, BonusItemFlag slot) + public static void DrawIcon(this EquipItem item, TextureService textures, Vector2 size, BonusItemFlag slot) { - var isEmpty = item.ModelId.Id == 0; + var isEmpty = item.PrimaryId.Id == 0; var (ptr, textureSize, empty) = textures.GetIcon(item, slot); if (empty) { @@ -149,27 +149,6 @@ public static class UiHelpers } - public static bool DrawFavoriteStar(FavoriteManager favorites, BonusItem item) - { - var favorite = favorites.Contains(item); - var hovering = ImGui.IsMouseHoveringRect(ImGui.GetCursorScreenPos(), - ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight())); - - using var font = ImRaii.PushFont(UiBuilder.IconFont); - using var c = ImRaii.PushColor(ImGuiCol.Text, - hovering ? ColorId.FavoriteStarHovered.Value() : favorite ? ColorId.FavoriteStarOn.Value() : ColorId.FavoriteStarOff.Value()); - ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString()); - if (!ImGui.IsItemClicked()) - return false; - - if (favorite) - favorites.Remove(item); - else - favorites.TryAdd(item); - return true; - - } - public static bool DrawFavoriteStar(FavoriteManager favorites, StainId stain) { var favorite = favorites.Contains(stain); diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs index f956379..aabac2d 100644 --- a/Glamourer/Interop/CharaFile/CharaFile.cs +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -104,11 +104,11 @@ public sealed class CharaFile if (id is 0) { - data.SetBonusItem(slot, BonusItem.Empty(slot)); + data.SetBonusItem(slot, EquipItem.BonusItemNothing(slot)); flags |= slot; } - if (!items.DictBonusItems.TryGetValue((BonusItemId)id.Value, out var item) || item.Slot != slot) + if (!items.DictBonusItems.TryGetValue((BonusItemId)id.Value, out var item) || item.Type.ToBonus() != slot) return; data.SetBonusItem(slot, item); diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 55db36d..d1004e6 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -58,7 +58,7 @@ public unsafe class UpdateSlotService : IDisposable if (!_bonusItems.TryGetValue(id, out var glasses)) return; - var armor = new CharacterArmor(glasses.ModelId, glasses.Variant, StainIds.None); + var armor = new CharacterArmor(glasses.PrimaryId, glasses.Variant, StainIds.None); _flagBonusSlotForUpdateHook.Original(drawObject.Address, BonusItemFlag.Glasses.ToIndex(), &armor); } diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 8cfd387..6e55888 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -10,7 +10,7 @@ namespace Glamourer.Services; public class ItemManager { - public const string Nothing = "Nothing"; + public const string Nothing = EquipItem.Nothing; public const string SmallClothesNpc = "Smallclothes (NPC)"; public const ushort SmallClothesNpcModel = 9903; @@ -126,31 +126,20 @@ public class ItemManager } } - public BonusItem Identify(BonusItemFlag slot, PrimaryId id, Variant variant) + public EquipItem Identify(BonusItemFlag slot, PrimaryId id, Variant variant) { var index = slot.ToIndex(); if (index == uint.MaxValue) - return new BonusItem($"Invalid ({id.Id}-{variant})", 0, 0, id, variant, slot); + return new EquipItem($"Invalid ({id.Id}-{variant})", 0, 0, id, 0, variant, slot.ToEquipType(), 0, 0, 0); - var item = ObjectIdentification.Identify(id, variant, slot) - .FirstOrDefault(BonusItem.FromIds(BonusItemId.Invalid, 0, id, variant, slot)); - if (item.Id != BonusItemId.Invalid) - return item; - - if (slot is BonusItemFlag.Glasses) - { - var headItem = ObjectIdentification.Identify(id, 0, variant, EquipSlot.Head).FirstOrDefault(); - if (headItem.Valid) - return BonusItem.FromIds(BonusItemId.Invalid, headItem.IconId, id, variant, slot, $"{headItem.Name} ({EquipSlot.Head.ToName()}: {id}-{variant})"); - } - - return item; + return ObjectIdentification.Identify(id, variant, slot) + .FirstOrDefault(new EquipItem($"Invalid ({id.Id}-{variant})", 0, 0, id, 0, variant, slot.ToEquipType(), 0, 0, 0)); } - public BonusItem Resolve(BonusItemFlag slot, BonusItemId id) - => IsBonusItemValid(slot, id, out var item) ? item : new BonusItem($"Invalid ({id.Id})", 0, id, 0, 0, slot); + public EquipItem Resolve(BonusItemFlag slot, BonusItemId id) + => IsBonusItemValid(slot, id, out var item) ? item : new EquipItem($"Invalid ({id.Id})", id, 0, 0, 0, 0, slot.ToEquipType(), 0, 0, 0); - public BonusItem Resolve(BonusItemFlag slot, CustomItemId id) + public EquipItem Resolve(BonusItemFlag slot, CustomItemId id) { // Only from early designs as migration. if (!id.IsBonusItem) @@ -164,12 +153,12 @@ public class ItemManager if (IsBonusItemValid(slot, id.BonusItem, out var item)) return item; - return BonusItem.Empty(slot); + return EquipItem.BonusItemNothing(slot); } var (model, variant, slot2) = id.SplitBonus; if (slot != slot2) - return BonusItem.Empty(slot); + return EquipItem.BonusItemNothing(slot); return Identify(slot, model, variant); } @@ -213,12 +202,12 @@ public class ItemManager /// Returns whether a bonus item id represents a valid item for a slot and gives the item. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public bool IsBonusItemValid(BonusItemFlag slot, BonusItemId itemId, out BonusItem item) + public bool IsBonusItemValid(BonusItemFlag slot, BonusItemId itemId, out EquipItem item) { if (itemId.Id != 0) - return DictBonusItems.TryGetValue(itemId, out item) && slot == item.Slot; + return DictBonusItems.TryGetValue(itemId, out item) && slot == item.Type.ToBonus(); - item = BonusItem.Empty(slot); + item = new EquipItem(Nothing, (BonusItemId)0, 0, 0, 0, 0, slot.ToEquipType(), 0, 0, 0); return true; } diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index e08ab44..29c2343 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -23,9 +23,9 @@ public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManage : (nint.Zero, Vector2.Zero, true); } - public (nint, Vector2, bool) GetIcon(BonusItem item, BonusItemFlag slot) + public (nint, Vector2, bool) GetIcon(EquipItem item, BonusItemFlag slot) { - if (item.Icon.Id != 0 && TryLoadIcon(item.Icon.Id, out var ret)) + if (item.IconId.Id != 0 && TryLoadIcon(item.IconId.Id, out var ret)) return (ret.ImGuiHandle, new Vector2(ret.Width, ret.Height), false); var idx = slot.ToIndex(); diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs index 71af0aa..69051c2 100644 --- a/Glamourer/State/InternalStateEditor.cs +++ b/Glamourer/State/InternalStateEditor.cs @@ -152,7 +152,7 @@ public class InternalStateEditor( } /// Change a single bonus item. - public bool ChangeBonusItem(ActorState state, BonusItemFlag slot, BonusItem item, StateSource source, out BonusItem oldItem, uint key = 0) + public bool ChangeBonusItem(ActorState state, BonusItemFlag slot, EquipItem item, StateSource source, out EquipItem oldItem, uint key = 0) { oldItem = state.ModelData.BonusItem(slot); if (!state.CanUnlock(key)) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index ed6f907..6ba6c06 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -146,7 +146,7 @@ public class StateApplier( if (apply) { var item = state.ModelData.BonusItem(slot); - ChangeBonusItem(data, slot, item.ModelId, item.Variant); + ChangeBonusItem(data, slot, item.PrimaryId, item.Variant); } return data; @@ -391,7 +391,7 @@ public class StateApplier( foreach (var slot in BonusExtensions.AllFlags) { var item = state.ModelData.BonusItem(slot); - ChangeBonusItem(actors, slot, item.ModelId, item.Variant); + ChangeBonusItem(actors, slot, item.PrimaryId, item.Variant); } var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index c7867e1..633fc10 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -109,7 +109,7 @@ public class StateEditor( } } - public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default) + public void ChangeBonusItem(object data, BonusItemFlag slot, EquipItem item, ApplySettings settings = default) { var state = (ActorState)data; if (!Editor.ChangeBonusItem(state, slot, item, settings.Source, out var old, settings.Key)) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 34c9421..9c0dd67 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -250,11 +250,11 @@ public class StateListener : IDisposable else apply = true; if (apply) - item = state.ModelData.BonusItem(slot).ToArmor(); + item = state.ModelData.BonusItem(slot).Armor(); break; // Use current model data. case UpdateState.NoChange: - item = state.ModelData.BonusItem(slot).ToArmor(); + item = state.ModelData.BonusItem(slot).Armor(); break; case UpdateState.Transformed: break; } @@ -453,12 +453,12 @@ public class StateListener : IDisposable return UpdateState.NoChange; // The actor item does not correspond to the model item, thus the actor is transformed. - if (actorItem.ModelId != item.Set || actorItem.Variant != item.Variant) + if (actorItem.PrimaryId != item.Set || actorItem.Variant != item.Variant) return UpdateState.Transformed; var baseData = state.BaseData.BonusItem(slot); var change = UpdateState.NoChange; - if (baseData.Id != actorItem.Id || baseData.ModelId != item.Set || baseData.Variant != item.Variant) + if (baseData.Id != actorItem.Id || baseData.PrimaryId != item.Set || baseData.Variant != item.Variant) { var identified = _items.Identify(slot, item.Set, item.Variant); state.BaseData.SetBonusItem(slot, identified); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 24b025f..4ff232a 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -356,7 +356,7 @@ public sealed class StateManager( foreach (var slot in BonusExtensions.AllFlags) { var item = state.ModelData.BonusItem(slot); - Applier.ChangeBonusItem(actors, slot, item.ModelId, item.Variant); + Applier.ChangeBonusItem(actors, slot, item.PrimaryId, item.Variant); } var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; diff --git a/Glamourer/Unlocks/FavoriteManager.cs b/Glamourer/Unlocks/FavoriteManager.cs index 229b8e6..01a2507 100644 --- a/Glamourer/Unlocks/FavoriteManager.cs +++ b/Glamourer/Unlocks/FavoriteManager.cs @@ -12,7 +12,7 @@ public class FavoriteManager : ISavable private readonly record struct FavoriteHairStyle(Gender Gender, SubRace Race, CustomizeIndex Type, CustomizeValue Id) { public uint ToValue() - => (uint)Id.Value | ((uint)Type << 8) | ((uint)Race << 16) | ((uint)Gender << 24); + => Id.Value | ((uint)Type << 8) | ((uint)Race << 16) | ((uint)Gender << 24); public FavoriteHairStyle(uint value) : this((Gender)((value >> 24) & 0xFF), (SubRace)((value >> 16) & 0xFF), (CustomizeIndex)((value >> 8) & 0xFF), @@ -61,9 +61,9 @@ public class FavoriteManager : ISavable { case 1: _favorites.UnionWith(load!.FavoriteItems.Select(i => (ItemId)i)); - _favoriteColors.UnionWith(load!.FavoriteColors.Select(i => (StainId)i)); - _favoriteHairStyles.UnionWith(load!.FavoriteHairStyles.Select(t => new FavoriteHairStyle(t))); - _favoriteBonusItems.UnionWith(load!.FavoriteBonusItems.Select(b => new BonusItemId(b))); + _favoriteColors.UnionWith(load.FavoriteColors.Select(i => (StainId)i)); + _favoriteHairStyles.UnionWith(load.FavoriteHairStyles.Select(t => new FavoriteHairStyle(t))); + _favoriteBonusItems.UnionWith(load.FavoriteBonusItems.Select(b => new BonusItemId(b))); break; default: throw new Exception($"Unknown Version {load?.Version ?? 0}"); @@ -126,7 +126,12 @@ public class FavoriteManager : ISavable } public bool TryAdd(EquipItem item) - => TryAdd(item.ItemId); + { + if (item.Id.IsBonusItem) + return TryAdd(item.Id.BonusItem); + + return TryAdd(item.ItemId); + } public bool TryAdd(ItemId item) { @@ -137,18 +142,18 @@ public class FavoriteManager : ISavable return true; } - public bool TryAdd(StainId stain) + public bool TryAdd(BonusItemId item) { - if (stain.Id == 0 || !_favoriteColors.Add(stain)) + if (item.Id == 0 || !_favoriteBonusItems.Add(item)) return false; Save(); return true; } - public bool TryAdd(BonusItem bonusItem) + public bool TryAdd(StainId stain) { - if (bonusItem.Id == 0 || !_favoriteBonusItems.Add(bonusItem.Id)) + if (stain.Id == 0 || !_favoriteColors.Add(stain)) return false; Save(); @@ -165,7 +170,11 @@ public class FavoriteManager : ISavable } public bool Remove(EquipItem item) - => Remove(item.ItemId); + { + if (item.Id.IsBonusItem) + Remove(item.Id.BonusItem); + return Remove(item.ItemId); + } public bool Remove(ItemId item) { @@ -176,18 +185,18 @@ public class FavoriteManager : ISavable return true; } - public bool Remove(StainId stain) + public bool Remove(BonusItemId item) { - if (!_favoriteColors.Remove(stain)) + if (!_favoriteBonusItems.Remove(item)) return false; Save(); return true; } - public bool Remove(BonusItem bonusItem) + public bool Remove(StainId stain) { - if (!_favoriteBonusItems.Remove(bonusItem.Id)) + if (!_favoriteColors.Remove(stain)) return false; Save(); @@ -204,13 +213,21 @@ public class FavoriteManager : ISavable } public bool Contains(EquipItem item) - => _favorites.Contains(item.ItemId); + { + if (item.Id.IsBonusItem) + return _favoriteBonusItems.Contains(item.Id.BonusItem); + + return _favorites.Contains(item.ItemId); + } public bool Contains(StainId stain) => _favoriteColors.Contains(stain); - public bool Contains(BonusItem bonusItem) - => _favoriteBonusItems.Contains(bonusItem.Id); + public bool Contains(ItemId itemId) + => _favorites.Contains(itemId); + + public bool Contains(BonusItemId bonusItemId) + => _favoriteBonusItems.Contains(bonusItemId); public bool Contains(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value) => _favoriteHairStyles.Contains(new FavoriteHairStyle(gender, race, type, value)); diff --git a/Penumbra.GameData b/Penumbra.GameData index dd86daf..2f6acca 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit dd86dafb88ca4c7b662938bbc1310729ba7f788d +Subproject commit 2f6acca678b71203763ac4404c3f054747c14f75 From 210bca4c7c5e448dabf308d439fbe8b9cf99348a Mon Sep 17 00:00:00 2001 From: anya-hichu Date: Fri, 11 Oct 2024 19:55:05 +0200 Subject: [PATCH 492/786] Add ResetMaterials option to Design --- Glamourer/Automation/AutoDesignApplier.cs | 6 ++++-- Glamourer/Designs/Design.cs | 3 +++ Glamourer/Designs/DesignManager.cs | 11 +++++++++++ Glamourer/Designs/IDesignStandIn.cs | 2 ++ Glamourer/Designs/Links/DesignMerger.cs | 2 ++ Glamourer/Designs/Links/MergedDesign.cs | 1 + 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 +++++++ 11 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 22048a8..0e30244 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -154,7 +154,7 @@ public sealed class AutoDesignApplier : IDisposable { Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false, out var forcedRedraw); foreach (var actor in data.Objects) - _state.ReapplyState(actor, forcedRedraw,StateSource.Fixed); + _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); } } else if (_objects.TryGetValueAllWorld(id, out data) || _objects.TryGetValueNonOwned(id, out data)) @@ -286,7 +286,9 @@ public sealed class AutoDesignApplier : IDisposable var mergedDesign = _designMerger.Merge( 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)); + + var resetMaterials = mergedDesign.ResetMaterials; + _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, resetMaterials)); forcedRedraw = mergedDesign.ForcedRedraw; } diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index fb18873..2add91c 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -45,6 +45,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public string[] Tags { get; internal set; } = []; public int Index { get; internal set; } public bool ForcedRedraw { get; internal set; } + public bool ResetMaterials { get; internal set; } public bool QuickDesign { get; internal set; } = true; public string Color { get; internal set; } = string.Empty; public SortedList AssociatedMods { get; private set; } = []; @@ -102,6 +103,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn ["Name"] = Name.Text, ["Description"] = Description, ["ForcedRedraw"] = ForcedRedraw, + ["ResetMaterials"] = ResetMaterials, ["Color"] = Color, ["QuickDesign"] = QuickDesign, ["Tags"] = JArray.FromObject(Tags), @@ -244,6 +246,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn LoadLinks(linkLoader, json["Links"], design); design.Color = json["Color"]?.ToObject() ?? string.Empty; design.ForcedRedraw = json["ForcedRedraw"]?.ToObject() ?? false; + design.ResetMaterials = json["ResetMaterials"]?.ToObject() ?? false; return design; static string[] ParseTags(JObject json) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 63c98c0..8342898 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -334,6 +334,17 @@ public sealed class DesignManager : DesignEditor DesignChanged.Invoke(DesignChanged.Type.ForceRedraw, design, null); } + public void ChangeResetMaterials(Design design, bool resetMaterials) + { + if (design.ResetMaterials == resetMaterials) + return; + + design.ResetMaterials = resetMaterials; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set {design.Identifier} to {(resetMaterials ? "not" : string.Empty)} reset materials."); + DesignChanged.Invoke(DesignChanged.Type.ResetMaterials, 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 492bc6b..73a4863 100644 --- a/Glamourer/Designs/IDesignStandIn.cs +++ b/Glamourer/Designs/IDesignStandIn.cs @@ -25,4 +25,6 @@ public interface IDesignStandIn : IEquatable public bool ChangeData(object data); public bool ForcedRedraw { get; } + + public bool ResetMaterials { get; } } diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 9832ead..73e94c7 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -56,6 +56,8 @@ public class DesignMerger( ReduceMaterials(design, ret); if (design.ForcedRedraw) ret.ForcedRedraw = true; + if (design.ResetMaterials) + ret.ResetMaterials = true; } ApplyFixFlags(ret, fixFlags); diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index da6cb54..0fa148a 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -100,4 +100,5 @@ public sealed class MergedDesign public readonly SortedList AssociatedMods = []; public StateSources Sources = new(); public bool ForcedRedraw; + public bool ResetMaterials; } diff --git a/Glamourer/Designs/Special/QuickSelectedDesign.cs b/Glamourer/Designs/Special/QuickSelectedDesign.cs index c506f0a..476bd62 100644 --- a/Glamourer/Designs/Special/QuickSelectedDesign.cs +++ b/Glamourer/Designs/Special/QuickSelectedDesign.cs @@ -53,4 +53,7 @@ public class QuickSelectedDesign(QuickDesignCombo combo) : IDesignStandIn, IServ public bool ForcedRedraw => combo.Design?.ForcedRedraw ?? false; + + public bool ResetMaterials + => combo.Design?.ResetMaterials ?? false; } diff --git a/Glamourer/Designs/Special/RandomDesign.cs b/Glamourer/Designs/Special/RandomDesign.cs index 5fac61b..54ab4ab 100644 --- a/Glamourer/Designs/Special/RandomDesign.cs +++ b/Glamourer/Designs/Special/RandomDesign.cs @@ -81,4 +81,7 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn public bool ForcedRedraw => false; + + public bool ResetMaterials + => false; } diff --git a/Glamourer/Designs/Special/RevertDesign.cs b/Glamourer/Designs/Special/RevertDesign.cs index 023d5eb..affa203 100644 --- a/Glamourer/Designs/Special/RevertDesign.cs +++ b/Glamourer/Designs/Special/RevertDesign.cs @@ -45,4 +45,7 @@ public class RevertDesign : IDesignStandIn public bool ForcedRedraw => false; + + public bool ResetMaterials + => false; } diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 6c2ba8a..22169c8 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -90,6 +90,9 @@ public sealed class DesignChanged() /// An existing design had changed whether it always forces a redraw or not. ForceRedraw, + /// An existing design had changed whether it always reset materials or not. + ResetMaterials, + /// An existing design changed whether a specific customization is applied. ApplyCustomize, diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index e9fbcf4..fddf8a2 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -143,6 +143,13 @@ public class DesignDetailTab _manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw); ImGuiUtil.HoverTooltip("Set this design to always force a redraw when it is applied through any means."); + var resetMaterials = _selector.Selected!.ResetMaterials; + ImGuiUtil.DrawFrameColumn("Reset Materials"); + ImGui.TableNextColumn(); + if (ImGui.Checkbox("##ResetMaterials", ref resetMaterials)) + _manager.ChangeResetMaterials(_selector.Selected!, resetMaterials); + ImGuiUtil.HoverTooltip("Set this design to reset materials when it is applied through any means."); + ImGuiUtil.DrawFrameColumn("Color"); var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color; ImGui.TableNextColumn(); From cca2bf645fce57a1ad57915967bad260b0168367 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 12 Oct 2024 13:02:30 +0200 Subject: [PATCH 493/786] Fix PR. --- Glamourer/Automation/AutoDesignApplier.cs | 3 +- Glamourer/Designs/Design.cs | 78 ++++++++++--------- Glamourer/Designs/DesignEditor.cs | 4 +- Glamourer/Designs/DesignManager.cs | 10 +-- Glamourer/Designs/IDesignStandIn.cs | 2 +- Glamourer/Designs/Links/DesignMerger.cs | 4 +- Glamourer/Designs/Links/MergedDesign.cs | 2 +- .../Designs/Special/QuickSelectedDesign.cs | 4 +- Glamourer/Designs/Special/RandomDesign.cs | 2 +- Glamourer/Designs/Special/RevertDesign.cs | 4 +- Glamourer/Events/DesignChanged.cs | 4 +- .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 10 +-- Glamourer/State/StateEditor.cs | 2 +- 13 files changed, 65 insertions(+), 64 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 0e30244..fba1a58 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -287,8 +287,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); - var resetMaterials = mergedDesign.ResetMaterials; - _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, resetMaterials)); + _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false)); forcedRedraw = mergedDesign.ForcedRedraw; } diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 2add91c..c58d74f 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -37,19 +37,19 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn // Metadata public new const int FileVersion = 2; - public Guid Identifier { get; internal init; } - public DateTimeOffset CreationDate { get; internal init; } - public DateTimeOffset LastEdit { get; internal set; } - public LowerString Name { get; internal set; } = LowerString.Empty; - 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 ResetMaterials { get; internal set; } - public bool QuickDesign { get; internal set; } = true; - public string Color { get; internal set; } = string.Empty; - public SortedList AssociatedMods { get; private set; } = []; - public LinkContainer Links { get; private set; } = []; + public Guid Identifier { get; internal init; } + public DateTimeOffset CreationDate { get; internal init; } + public DateTimeOffset LastEdit { get; internal set; } + public LowerString Name { get; internal set; } = LowerString.Empty; + 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 ResetAdvancedDyes { get; internal set; } + public bool QuickDesign { get; internal set; } = true; + public string Color { get; internal set; } = string.Empty; + public SortedList AssociatedMods { get; private set; } = []; + public LinkContainer Links { get; private set; } = []; public string Incognito => Identifier.ToString()[..8]; @@ -96,25 +96,25 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn { var ret = new JObject() { - ["FileVersion"] = FileVersion, - ["Identifier"] = Identifier, - ["CreationDate"] = CreationDate, - ["LastEdit"] = LastEdit, - ["Name"] = Name.Text, - ["Description"] = Description, - ["ForcedRedraw"] = ForcedRedraw, - ["ResetMaterials"] = ResetMaterials, - ["Color"] = Color, - ["QuickDesign"] = QuickDesign, - ["Tags"] = JArray.FromObject(Tags), - ["WriteProtected"] = WriteProtected(), - ["Equipment"] = SerializeEquipment(), - ["Bonus"] = SerializeBonusItems(), - ["Customize"] = SerializeCustomize(), - ["Parameters"] = SerializeParameters(), - ["Materials"] = SerializeMaterials(), - ["Mods"] = SerializeMods(), - ["Links"] = Links.Serialize(), + ["FileVersion"] = FileVersion, + ["Identifier"] = Identifier, + ["CreationDate"] = CreationDate, + ["LastEdit"] = LastEdit, + ["Name"] = Name.Text, + ["Description"] = Description, + ["ForcedRedraw"] = ForcedRedraw, + ["ResetAdvancedDyes"] = ResetAdvancedDyes, + ["Color"] = Color, + ["QuickDesign"] = QuickDesign, + ["Tags"] = JArray.FromObject(Tags), + ["WriteProtected"] = WriteProtected(), + ["Equipment"] = SerializeEquipment(), + ["Bonus"] = SerializeBonusItems(), + ["Customize"] = SerializeCustomize(), + ["Parameters"] = SerializeParameters(), + ["Materials"] = SerializeMaterials(), + ["Mods"] = SerializeMods(), + ["Links"] = Links.Serialize(), }; return ret; } @@ -146,7 +146,8 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn #region Deserialization - public static Design LoadDesign(SaveService saveService, CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) + public static Design LoadDesign(SaveService saveService, CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, + JObject json) { var version = json["FileVersion"]?.ToObject() ?? 0; return version switch @@ -158,7 +159,8 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn } /// The values for gloss and specular strength were switched. Swap them for all appropriate designs. - private static Design LoadDesignV1(SaveService saveService, CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) + private static Design LoadDesignV1(SaveService saveService, CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, + JObject json) { var design = LoadDesignV2(customizations, items, linkLoader, json); var materialDesignData = design.GetMaterialDataRef(); @@ -216,7 +218,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn Glamourer.Messager.AddMessage(new Notification( $"Swapped Gloss and Specular Strength in {materialDesignData.Values.Count} Rows in design {design.Incognito} {reason}", NotificationType.Info)); - saveService.Save(SaveType.ImmediateSync,design); + saveService.Save(SaveType.ImmediateSync, design); } } @@ -244,9 +246,9 @@ 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.ForcedRedraw = json["ForcedRedraw"]?.ToObject() ?? false; - design.ResetMaterials = json["ResetMaterials"]?.ToObject() ?? false; + design.Color = json["Color"]?.ToObject() ?? string.Empty; + design.ForcedRedraw = json["ForcedRedraw"]?.ToObject() ?? false; + design.ResetAdvancedDyes = json["ResetAdvancedDyes"]?.ToObject() ?? false; return design; static string[] ParseTags(JObject json) diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index cfda047..448e373 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -304,8 +304,8 @@ public class DesignEditor( /// - public void ApplyDesign(object data, MergedDesign other, ApplySettings _ = default) - => ApplyDesign(data, other.Design); + public void ApplyDesign(object data, MergedDesign other, ApplySettings settings = default) + => ApplyDesign(data, other.Design, settings); /// public void ApplyDesign(object data, DesignBase other, ApplySettings _ = default) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 8342898..a9ea66a 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -334,15 +334,15 @@ public sealed class DesignManager : DesignEditor DesignChanged.Invoke(DesignChanged.Type.ForceRedraw, design, null); } - public void ChangeResetMaterials(Design design, bool resetMaterials) + public void ChangeResetAdvancedDyes(Design design, bool resetAdvancedDyes) { - if (design.ResetMaterials == resetMaterials) + if (design.ResetAdvancedDyes == resetAdvancedDyes) return; - design.ResetMaterials = resetMaterials; + design.ResetAdvancedDyes = resetAdvancedDyes; SaveService.QueueSave(design); - Glamourer.Log.Debug($"Set {design.Identifier} to {(resetMaterials ? "not" : string.Empty)} reset materials."); - DesignChanged.Invoke(DesignChanged.Type.ResetMaterials, design, null); + Glamourer.Log.Debug($"Set {design.Identifier} to {(resetAdvancedDyes ? "not" : string.Empty)} reset advanced dyes."); + DesignChanged.Invoke(DesignChanged.Type.ResetAdvancedDyes, design, null); } /// Change whether to apply a specific customize value. diff --git a/Glamourer/Designs/IDesignStandIn.cs b/Glamourer/Designs/IDesignStandIn.cs index 73a4863..fd76b4b 100644 --- a/Glamourer/Designs/IDesignStandIn.cs +++ b/Glamourer/Designs/IDesignStandIn.cs @@ -26,5 +26,5 @@ public interface IDesignStandIn : IEquatable public bool ForcedRedraw { get; } - public bool ResetMaterials { get; } + public bool ResetAdvancedDyes { get; } } diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 73e94c7..5767c7a 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -56,8 +56,8 @@ public class DesignMerger( ReduceMaterials(design, ret); if (design.ForcedRedraw) ret.ForcedRedraw = true; - if (design.ResetMaterials) - ret.ResetMaterials = true; + if (design.ResetAdvancedDyes) + ret.ResetAdvancedDyes = true; } ApplyFixFlags(ret, fixFlags); diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index 0fa148a..0748633 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -100,5 +100,5 @@ public sealed class MergedDesign public readonly SortedList AssociatedMods = []; public StateSources Sources = new(); public bool ForcedRedraw; - public bool ResetMaterials; + public bool ResetAdvancedDyes; } diff --git a/Glamourer/Designs/Special/QuickSelectedDesign.cs b/Glamourer/Designs/Special/QuickSelectedDesign.cs index 476bd62..1919929 100644 --- a/Glamourer/Designs/Special/QuickSelectedDesign.cs +++ b/Glamourer/Designs/Special/QuickSelectedDesign.cs @@ -54,6 +54,6 @@ public class QuickSelectedDesign(QuickDesignCombo combo) : IDesignStandIn, IServ public bool ForcedRedraw => combo.Design?.ForcedRedraw ?? false; - public bool ResetMaterials - => combo.Design?.ResetMaterials ?? false; + public bool ResetAdvancedDyes + => combo.Design?.ResetAdvancedDyes ?? false; } diff --git a/Glamourer/Designs/Special/RandomDesign.cs b/Glamourer/Designs/Special/RandomDesign.cs index 54ab4ab..bbb9b7d 100644 --- a/Glamourer/Designs/Special/RandomDesign.cs +++ b/Glamourer/Designs/Special/RandomDesign.cs @@ -82,6 +82,6 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn public bool ForcedRedraw => false; - public bool ResetMaterials + public bool ResetAdvancedDyes => false; } diff --git a/Glamourer/Designs/Special/RevertDesign.cs b/Glamourer/Designs/Special/RevertDesign.cs index affa203..5f8d8c6 100644 --- a/Glamourer/Designs/Special/RevertDesign.cs +++ b/Glamourer/Designs/Special/RevertDesign.cs @@ -46,6 +46,6 @@ public class RevertDesign : IDesignStandIn public bool ForcedRedraw => false; - public bool ResetMaterials - => false; + public bool ResetAdvancedDyes + => true; } diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 22169c8..8cd8f5c 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -90,8 +90,8 @@ public sealed class DesignChanged() /// An existing design had changed whether it always forces a redraw or not. ForceRedraw, - /// An existing design had changed whether it always reset materials or not. - ResetMaterials, + /// An existing design had changed whether it always resets advanced dyes or not. + ResetAdvancedDyes, /// An existing design changed whether a specific customization is applied. ApplyCustomize, diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index fddf8a2..fea444c 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -143,12 +143,12 @@ public class DesignDetailTab _manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw); ImGuiUtil.HoverTooltip("Set this design to always force a redraw when it is applied through any means."); - var resetMaterials = _selector.Selected!.ResetMaterials; - ImGuiUtil.DrawFrameColumn("Reset Materials"); + var resetMaterials = _selector.Selected!.ResetAdvancedDyes; + ImGuiUtil.DrawFrameColumn("Reset Advanced Dyes"); ImGui.TableNextColumn(); - if (ImGui.Checkbox("##ResetMaterials", ref resetMaterials)) - _manager.ChangeResetMaterials(_selector.Selected!, resetMaterials); - ImGuiUtil.HoverTooltip("Set this design to reset materials when it is applied through any means."); + if (ImGui.Checkbox("##ResetAdvancedDyes", ref resetMaterials)) + _manager.ChangeResetAdvancedDyes(_selector.Selected!, resetMaterials); + ImGuiUtil.HoverTooltip("Set this design to reset any previously applied advanced dyes when it is applied through any means."); ImGuiUtil.DrawFrameColumn("Color"); var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color; diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 633fc10..d74ddf7 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -380,7 +380,7 @@ public class StateEditor( Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key); } - if (settings.ResetMaterials) + if (settings.ResetMaterials || mergedDesign.ResetAdvancedDyes) state.Materials.Clear(); foreach (var (key, value) in mergedDesign.Design.Materials) From da944b50cc41c65fb0e162d47acb842442ff1393 Mon Sep 17 00:00:00 2001 From: anya-hichu Date: Sat, 12 Oct 2024 14:48:57 +0200 Subject: [PATCH 494/786] Fix inverted log --- Glamourer/Designs/DesignManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index a9ea66a..15e38ee 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -330,7 +330,7 @@ public sealed class DesignManager : DesignEditor design.ForcedRedraw = forcedRedraw; SaveService.QueueSave(design); - Glamourer.Log.Debug($"Set {design.Identifier} to {(forcedRedraw ? "not" : string.Empty)} force redraws."); + Glamourer.Log.Debug($"Set {design.Identifier} to {(forcedRedraw ? string.Empty : "not")} force redraws."); DesignChanged.Invoke(DesignChanged.Type.ForceRedraw, design, null); } @@ -341,7 +341,7 @@ public sealed class DesignManager : DesignEditor design.ResetAdvancedDyes = resetAdvancedDyes; SaveService.QueueSave(design); - Glamourer.Log.Debug($"Set {design.Identifier} to {(resetAdvancedDyes ? "not" : string.Empty)} reset advanced dyes."); + Glamourer.Log.Debug($"Set {design.Identifier} to {(resetAdvancedDyes ? string.Empty : "not")} reset advanced dyes."); DesignChanged.Invoke(DesignChanged.Type.ResetAdvancedDyes, design, null); } From 11111817d765b73130021a7928d09897231a6aac Mon Sep 17 00:00:00 2001 From: anya-hichu Date: Sat, 12 Oct 2024 15:02:55 +0200 Subject: [PATCH 495/786] Fix DesignInfoTable width --- Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index fea444c..0742197 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -58,7 +58,7 @@ public class DesignDetailTab if (!table) return; - ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Last Update Datem").X); + ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Reset Advanced Dyes").X); ImGui.TableSetupColumn("Data", ImGuiTableColumnFlags.WidthStretch); ImGuiUtil.DrawFrameColumn("Design Name"); From 6db4a9623b548e7475bd9d5bef90b4a32a7aae99 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 12 Oct 2024 15:14:03 +0200 Subject: [PATCH 496/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 2f6acca..63cbf82 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 2f6acca678b71203763ac4404c3f054747c14f75 +Subproject commit 63cbf824178b5b1f91fd9edc22a6c2bbc2e1cd23 From 530166b81a8d9424876698f2549c278d80b8ab70 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 12 Oct 2024 13:16:00 +0000 Subject: [PATCH 497/786] [CI] Updating repo.json for testing_1.3.2.1 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index f3ea329..13c0105 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.2.0", - "TestingAssemblyVersion": "1.3.2.0", + "TestingAssemblyVersion": "1.3.2.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.2.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.2.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.2.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.2.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 87d8876972291724fdbc210c77208ca01df418e6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 13 Oct 2024 14:40:37 +0200 Subject: [PATCH 498/786] Fix some bonus item related things. --- Glamourer/Designs/DesignData.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs | 15 +++++++++++++-- Glamourer/Services/ItemManager.cs | 4 ++-- Penumbra.GameData | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 3e32eac..4205996 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -112,7 +112,7 @@ public unsafe struct DesignData => slot switch { // @formatter:off - BonusItemFlag.Glasses => EquipItem.FromIds(_bonusIds[0], _iconIds[12], _bonusModelIds[0], 0, _bonusVariants[0], FullEquipType.Glasses, 0, _nameGlasses), + BonusItemFlag.Glasses => EquipItem.FromBonusIds(_bonusIds[0], _iconIds[12], _bonusModelIds[0], _bonusVariants[0], BonusItemFlag.Glasses, _nameGlasses), _ => EquipItem.BonusItemNothing(slot), // @formatter:on }; diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index c875cf1..f5fe088 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -9,7 +9,6 @@ using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; -using FFXIVClientStructs.FFXIV.Shader; namespace Glamourer.Gui.Tabs.DebugTab; @@ -67,7 +66,13 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM static string ItemString(in DesignData data, EquipSlot slot) { var item = data.Item(slot); - return $"{item.Name} ({item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; + return $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; + } + + static string BonusItemString(in DesignData data, BonusItemFlag slot) + { + var item = data.BonusItem(slot); + return $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; } PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state.Sources[MetaIndex.ModelId]); @@ -93,6 +98,12 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM ImGuiUtil.DrawTableColumn(state.Sources[slot, true].ToString()); } + foreach (var slot in BonusExtensions.AllFlags) + { + PrintRow(slot.ToName(), BonusItemString(state.BaseData, slot), BonusItemString(state.ModelData, slot), state.Sources[slot]); + ImGui.TableNextRow(); + } + foreach (var type in Enum.GetValues()) { PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 6e55888..07a4829 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -142,7 +142,7 @@ public class ItemManager public EquipItem Resolve(BonusItemFlag slot, CustomItemId id) { // Only from early designs as migration. - if (!id.IsBonusItem) + if (!id.IsBonusItem || id.Id == 0) { IsBonusItemValid(slot, (BonusItemId)id.Id, out var item); return item; @@ -207,7 +207,7 @@ public class ItemManager if (itemId.Id != 0) return DictBonusItems.TryGetValue(itemId, out item) && slot == item.Type.ToBonus(); - item = new EquipItem(Nothing, (BonusItemId)0, 0, 0, 0, 0, slot.ToEquipType(), 0, 0, 0); + item = EquipItem.BonusItemNothing(slot); return true; } diff --git a/Penumbra.GameData b/Penumbra.GameData index 63cbf82..554e28a 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 63cbf824178b5b1f91fd9edc22a6c2bbc2e1cd23 +Subproject commit 554e28a3d1fca9394a20fd9856f6387e2a5e4a57 From 667ff2490df2a63de63cfb3662fd2889c6400253 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 13 Oct 2024 12:42:47 +0000 Subject: [PATCH 499/786] [CI] Updating repo.json for testing_1.3.2.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 13c0105..d4497b4 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.2.0", - "TestingAssemblyVersion": "1.3.2.1", + "TestingAssemblyVersion": "1.3.2.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.2.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.2.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.2.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.2.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c49102959f9790f2a7140c9dd9302f37adc34b92 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 18 Oct 2024 15:34:39 +0200 Subject: [PATCH 500/786] Add tails and Height 255 to available NPC options. --- Glamourer/GameData/CustomizeManager.cs | 1 - Glamourer/GameData/CustomizeSetFactory.cs | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Glamourer/GameData/CustomizeManager.cs b/Glamourer/GameData/CustomizeManager.cs index 5a06cf4..9e065b4 100644 --- a/Glamourer/GameData/CustomizeManager.cs +++ b/Glamourer/GameData/CustomizeManager.cs @@ -1,5 +1,4 @@ using Dalamud.Interface.Textures; -using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; using OtterGui.Classes; using OtterGui.Services; diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 850c7c9..f626750 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -84,9 +84,13 @@ internal class CustomizeSetFactory( CustomizeIndex.TattooColor, CustomizeIndex.EyeColorLeft, CustomizeIndex.EyeColorRight, + CustomizeIndex.TailShape, }; - var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>(); + var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>() + { + (CustomizeIndex.Height, CustomizeValue.Max), + }; _npcCustomizeSet.Awaiter.Wait(); foreach (var customize in _npcCustomizeSet.Select(s => s.Customize) .Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1)) From 4738830b8a32c9ca549a055910450af85a02a79b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 18 Oct 2024 16:02:30 +0200 Subject: [PATCH 501/786] 1.3.3.0 --- Glamourer/Gui/GlamourerChangelog.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 77a5788..4826839 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -36,6 +36,7 @@ public class GlamourerChangelog Add1_2_3_0(Changelog); Add1_3_1_0(Changelog); Add1_3_2_0(Changelog); + Add1_3_3_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -56,6 +57,14 @@ public class GlamourerChangelog } } + private static void Add1_3_3_0(Changelog log) + => log.NextVersion("Version 1.3.3.0") + .RegisterHighlight("Added the option to create automations for owned human NPCs (like trust avatars).") + .RegisterEntry("Added some special filters to the Actors tab selector, hover over it to see the options.") + .RegisterEntry("Added an option for designs to always reset all previously applied advanced dyes.") + .RegisterEntry("Added some new NPC-only customizations to the valid customizations.") + .RegisterEntry("Reworked quite a bit of things around face wear / bonus items. Please let me know if anything broke."); + private static void Add1_3_2_0(Changelog log) => log.NextVersion("Version 1.3.2.0") .RegisterEntry("Fixed an issue with weapon hiding when leaving GPose or changing zones.") From dd217a24759e24d7525317753838b7b696574ff8 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 18 Oct 2024 14:26:43 +0000 Subject: [PATCH 502/786] [CI] Updating repo.json for 1.3.3.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index d4497b4..b309240 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.2.0", - "TestingAssemblyVersion": "1.3.2.2", + "AssemblyVersion": "1.3.3.0", + "TestingAssemblyVersion": "1.3.3.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.2.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.2.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.2.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a5998b84ba60c134707d1d2a801cc172ff23efad Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Nov 2024 10:20:30 +0100 Subject: [PATCH 503/786] Fix issues with ResetAdvancedDyes and weapon types. --- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 2 +- Glamourer/Gui/Equipment/WeaponCombo.cs | 2 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 10 +++++++--- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs | 6 +++--- Glamourer/State/StateEditor.cs | 2 +- Penumbra.GameData | 2 +- 7 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 38c8263..601d3ae 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -558,7 +558,7 @@ public class EquipmentDrawer label = combo.Label; var locked = offhand.Locked - || !_gPose.InGPose && (offhand.CurrentItem.Type is FullEquipType.Unknown || mainhand.CurrentItem.Type is FullEquipType.Unknown); + || !_gPose.InGPose && (offhand.CurrentItem.Type.IsUnknown() || mainhand.CurrentItem.Type.IsUnknown()); using var disabled = ImRaii.Disabled(locked); if (!locked && open) UiHelpers.OpenCombo($"##{combo.Label}"); diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index 67f09f5..37d9d3c 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -81,7 +81,7 @@ public sealed class WeaponCombo : FilterComboCache => obj.Name; private static string GetLabel(FullEquipType type) - => type is FullEquipType.Unknown ? "Mainhand" : type.ToName(); + => type.IsUnknown() ? "Mainhand" : type.ToName(); private static IReadOnlyList GetWeapons(FavoriteManager favorites, ItemManager items, FullEquipType type) { diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index c6f2fc4..68ba18e 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -28,8 +28,10 @@ public sealed class PenumbraChangedItemTooltip : IDisposable => EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand).Zip(_lastItems) .Select(p => new KeyValuePair(p.First, p.Second)); - public DateTime LastTooltip { get; private set; } = DateTime.MinValue; - public DateTime LastClick { get; private set; } = DateTime.MinValue; + public ChangedItemType LastType { get; private set; } = ChangedItemType.None; + public uint LastId { get; private set; } = 0; + public DateTime LastTooltip { get; private set; } = DateTime.MinValue; + public DateTime LastClick { get; private set; } = DateTime.MinValue; public PenumbraChangedItemTooltip(PenumbraService penumbra, StateManager stateManager, ItemManager items, ObjectManager objects, CustomizeService customize, GPoseService gpose) @@ -160,6 +162,8 @@ public sealed class PenumbraChangedItemTooltip : IDisposable private void OnPenumbraTooltip(ChangedItemType type, uint id) { + LastType = type; + LastId = id; LastTooltip = DateTime.UtcNow; if (!Player()) return; @@ -204,7 +208,7 @@ public sealed class PenumbraChangedItemTooltip : IDisposable return true; var main = _objects.Player.GetMainhand(); - var mainItem = _items.Identify(slot, main.Skeleton, main.Weapon, main.Variant); + var mainItem = _items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant); if (slot == EquipSlot.MainHand) return item.Type == mainItem.Type; diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index f1097e8..3714c82 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -76,7 +76,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem } ImGuiUtil.DrawTableColumn("Last Tooltip Date"); - ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastTooltip > DateTime.MinValue ? _penumbraTooltip.LastTooltip.ToLongTimeString() : "Never"); + ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastTooltip > DateTime.MinValue ? $"{_penumbraTooltip.LastTooltip.ToLongTimeString()} ({_penumbraTooltip.LastType} {_penumbraTooltip.LastId})" : "Never"); ImGui.TableNextColumn(); ImGuiUtil.DrawTableColumn("Last Click Date"); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 0742197..bd74772 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -143,11 +143,11 @@ public class DesignDetailTab _manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw); ImGuiUtil.HoverTooltip("Set this design to always force a redraw when it is applied through any means."); - var resetMaterials = _selector.Selected!.ResetAdvancedDyes; + var resetAdvancedDyes = _selector.Selected!.ResetAdvancedDyes; ImGuiUtil.DrawFrameColumn("Reset Advanced Dyes"); ImGui.TableNextColumn(); - if (ImGui.Checkbox("##ResetAdvancedDyes", ref resetMaterials)) - _manager.ChangeResetAdvancedDyes(_selector.Selected!, resetMaterials); + if (ImGui.Checkbox("##ResetAdvancedDyes", ref resetAdvancedDyes)) + _manager.ChangeResetAdvancedDyes(_selector.Selected!, resetAdvancedDyes); ImGuiUtil.HoverTooltip("Set this design to reset any previously applied advanced dyes when it is applied through any means."); ImGuiUtil.DrawFrameColumn("Color"); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index d74ddf7..959d810 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -380,7 +380,7 @@ public class StateEditor( Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key); } - if (settings.ResetMaterials || mergedDesign.ResetAdvancedDyes) + if (settings.ResetMaterials || (!settings.RespectManual && mergedDesign.ResetAdvancedDyes)) state.Materials.Clear(); foreach (var (key, value) in mergedDesign.Design.Materials) diff --git a/Penumbra.GameData b/Penumbra.GameData index 554e28a..77d52b0 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 554e28a3d1fca9394a20fd9856f6387e2a5e4a57 +Subproject commit 77d52b02d21e770b30c08f89bdf06e0cb75562f7 From 2ce8076e9a5e1f1ee6cb5ea812a4f241323bdfc1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 16 Nov 2024 21:57:17 +0100 Subject: [PATCH 504/786] Current State. --- Glamourer/GameData/CharaMakeParams.cs | 126 -------------- Glamourer/GameData/CustomizeSet.cs | 41 ++--- Glamourer/GameData/CustomizeSetFactory.cs | 159 ++++++++---------- Glamourer/GameData/MenuType.cs | 14 ++ Glamourer/GameData/NpcCustomizeSet.cs | 81 +++++---- Glamourer/Glamourer.json | 2 +- .../Customization/CustomizationDrawer.Icon.cs | 2 +- .../Gui/Customization/CustomizationDrawer.cs | 12 +- Glamourer/Gui/Equipment/BonusItemCombo.cs | 6 +- Glamourer/Gui/Equipment/ItemCombo.cs | 24 +-- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 2 +- Glamourer/Interop/CrestService.cs | 12 +- Glamourer/Interop/ScalingService.cs | 32 ++-- Glamourer/Services/CommandService.cs | 2 +- Glamourer/Services/ItemManager.cs | 17 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 16 +- Glamourer/Unlocks/ItemUnlockManager.cs | 31 ++-- OtterGui | 2 +- Penumbra.GameData | 2 +- repo.json | 2 +- 20 files changed, 226 insertions(+), 359 deletions(-) delete mode 100644 Glamourer/GameData/CharaMakeParams.cs create mode 100644 Glamourer/GameData/MenuType.cs diff --git a/Glamourer/GameData/CharaMakeParams.cs b/Glamourer/GameData/CharaMakeParams.cs deleted file mode 100644 index 4db5825..0000000 --- a/Glamourer/GameData/CharaMakeParams.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Lumina.Data; -using Lumina.Excel; -using Lumina.Excel.GeneratedSheets; - -namespace Glamourer.GameData; - -/// A custom version of CharaMakeParams that is easier to parse. -[Sheet("CharaMakeParams")] -public class CharaMakeParams : ExcelRow -{ - public const int NumMenus = 28; - public const int NumVoices = 12; - public const int NumGraphics = 10; - public const int MaxNumValues = 100; - public const int NumFaces = 8; - public const int NumFeatures = 7; - public const int NumEquip = 3; - - public enum MenuType - { - ListSelector = 0, - IconSelector = 1, - ColorPicker = 2, - DoubleColorPicker = 3, - IconCheckmark = 4, - Percentage = 5, - Checkmark = 6, // custom - Nothing = 7, // custom - List1Selector = 8, // custom, 1-indexed lists - } - - public struct Menu - { - public uint Id; - public byte InitVal; - public MenuType Type; - public byte Size; - public byte LookAt; - public uint Mask; - public uint Customize; - public uint[] Values; - public byte[] Graphic; - } - - public struct FacialFeatures - { - public uint[] Icons; - } - - public LazyRow Race { get; set; } = null!; - public LazyRow Tribe { get; set; } = null!; - public sbyte Gender { get; set; } - - public Menu[] Menus { get; set; } = new Menu[NumMenus]; - public byte[] Voices { get; set; } = new byte[NumVoices]; - public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces]; - - public CharaMakeType.CharaMakeTypeUnkData3347Obj[] Equip { get; set; } = new CharaMakeType.CharaMakeTypeUnkData3347Obj[NumEquip]; - - public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language) - { - RowId = parser.RowId; - SubRowId = parser.SubRowId; - Race = new LazyRow(gameData, parser.ReadColumn(0), language); - Tribe = new LazyRow(gameData, parser.ReadColumn(1), language); - Gender = parser.ReadColumn(2); - int currentOffset; - for (var i = 0; i < NumMenus; ++i) - { - currentOffset = 3 + i; - Menus[i].Id = parser.ReadColumn(0 * NumMenus + currentOffset); - Menus[i].InitVal = parser.ReadColumn(1 * NumMenus + currentOffset); - Menus[i].Type = (MenuType)parser.ReadColumn(2 * NumMenus + currentOffset); - Menus[i].Size = parser.ReadColumn(3 * NumMenus + currentOffset); - Menus[i].LookAt = parser.ReadColumn(4 * NumMenus + currentOffset); - Menus[i].Mask = parser.ReadColumn(5 * NumMenus + currentOffset); - Menus[i].Customize = parser.ReadColumn(6 * NumMenus + currentOffset); - Menus[i].Values = new uint[Menus[i].Size]; - - switch (Menus[i].Type) - { - case MenuType.ColorPicker: - case MenuType.DoubleColorPicker: - case MenuType.Percentage: - break; - default: - currentOffset += 7 * NumMenus; - for (var j = 0; j < Menus[i].Size; ++j) - Menus[i].Values[j] = parser.ReadColumn(j * NumMenus + currentOffset); - break; - } - - Menus[i].Graphic = new byte[NumGraphics]; - currentOffset = 3 + (MaxNumValues + 7) * NumMenus + i; - for (var j = 0; j < NumGraphics; ++j) - Menus[i].Graphic[j] = parser.ReadColumn(j * NumMenus + currentOffset); - } - - currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus; - for (var i = 0; i < NumVoices; ++i) - Voices[i] = parser.ReadColumn(currentOffset++); - - for (var i = 0; i < NumFaces; ++i) - { - currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + i; - FacialFeatureByFace[i].Icons = new uint[NumFeatures]; - for (var j = 0; j < NumFeatures; ++j) - FacialFeatureByFace[i].Icons[j] = (uint)parser.ReadColumn(j * NumFaces + currentOffset); - } - - for (var i = 0; i < NumEquip; ++i) - { - currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7; - Equip[i] = new CharaMakeType.CharaMakeTypeUnkData3347Obj() - { - Helmet = parser.ReadColumn(currentOffset + 0), - Top = parser.ReadColumn(currentOffset + 1), - Gloves = parser.ReadColumn(currentOffset + 2), - Legs = parser.ReadColumn(currentOffset + 3), - Shoes = parser.ReadColumn(currentOffset + 4), - Weapon = parser.ReadColumn(currentOffset + 5), - SubWeapon = parser.ReadColumn(currentOffset + 6), - }; - } - } -} diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index 0c80e13..7fcf1d2 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -1,6 +1,7 @@ using OtterGui; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Race = Penumbra.GameData.Enums.Race; namespace Glamourer.GameData; @@ -38,9 +39,9 @@ public class CustomizeSet public string Option(CustomizeIndex index) => OptionName[(int)index]; - public IReadOnlyList Voices { get; internal init; } = null!; - public IReadOnlyList Types { get; internal set; } = null!; - public IReadOnlyDictionary Order { get; internal set; } = null!; + public IReadOnlyList Voices { get; internal init; } = null!; + public IReadOnlyList Types { get; internal set; } = null!; + public IReadOnlyDictionary Order { get; internal set; } = null!; // Always list selector. @@ -97,9 +98,9 @@ public class CustomizeSet return type switch { - CharaMakeParams.MenuType.ListSelector => GetInteger0(out custom), - CharaMakeParams.MenuType.List1Selector => GetInteger1(out custom), - CharaMakeParams.MenuType.IconSelector => index switch + MenuType.ListSelector => GetInteger0(out custom), + MenuType.List1Selector => GetInteger1(out custom), + MenuType.IconSelector => index switch { CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom), CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles, @@ -109,7 +110,7 @@ public class CustomizeSet CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom), _ => Invalid(out custom), }, - CharaMakeParams.MenuType.ColorPicker => index switch + MenuType.ColorPicker => index switch { CustomizeIndex.SkinColor => Get(SkinColors, value, out custom), CustomizeIndex.EyeColorLeft => Get(EyeColors, value, out custom), @@ -121,16 +122,16 @@ public class CustomizeSet CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom), _ => Invalid(out custom), }, - CharaMakeParams.MenuType.DoubleColorPicker => index switch + MenuType.DoubleColorPicker => index switch { CustomizeIndex.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, out custom), CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom), _ => Invalid(out custom), }, - CharaMakeParams.MenuType.IconCheckmark => GetBool(index, value, out custom), - CharaMakeParams.MenuType.Percentage => GetInteger0(out custom), - CharaMakeParams.MenuType.Checkmark => GetBool(index, value, out custom), - _ => Invalid(out custom), + MenuType.IconCheckmark => GetBool(index, value, out custom), + MenuType.Percentage => GetInteger0(out custom), + MenuType.Checkmark => GetBool(index, value, out custom), + _ => Invalid(out custom), }; int Get(IEnumerable list, CustomizeValue v, out CustomizeData? output) @@ -208,10 +209,10 @@ public class CustomizeSet switch (Types[(int)index]) { - case CharaMakeParams.MenuType.Percentage: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx); - case CharaMakeParams.MenuType.ListSelector: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx); - case CharaMakeParams.MenuType.List1Selector: return new CustomizeData(index, (CustomizeValue)(idx + 1), 0, (ushort)idx); - case CharaMakeParams.MenuType.Checkmark: return new CustomizeData(index, CustomizeValue.Bool(idx != 0), 0, (ushort)idx); + case MenuType.Percentage: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx); + case MenuType.ListSelector: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx); + case MenuType.List1Selector: return new CustomizeData(index, (CustomizeValue)(idx + 1), 0, (ushort)idx); + case MenuType.Checkmark: return new CustomizeData(index, CustomizeValue.Bool(idx != 0), 0, (ushort)idx); } return index switch @@ -241,7 +242,7 @@ public class CustomizeSet } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public CharaMakeParams.MenuType Type(CustomizeIndex index) + public MenuType Type(CustomizeIndex index) => Types[(int)index]; [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -256,9 +257,9 @@ public class CustomizeSet return Type(index) switch { - CharaMakeParams.MenuType.Percentage => 101, - CharaMakeParams.MenuType.IconCheckmark => 2, - CharaMakeParams.MenuType.Checkmark => 2, + MenuType.Percentage => 101, + MenuType.IconCheckmark => 2, + MenuType.Checkmark => 2, _ => index switch { CustomizeIndex.Face => Faces.Count, diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index f626750..36cdb1b 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -1,10 +1,10 @@ -using Dalamud; -using Dalamud.Game; +using Dalamud.Game; using Dalamud.Plugin.Services; using Dalamud.Utility; using Lumina.Excel; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using OtterGui.Classes; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; @@ -32,7 +32,7 @@ internal class CustomizeSetFactory( var set = new CustomizeSet(race, gender) { Name = GetName(race, gender), - Voices = row.Voices, + Voices = row.VoiceStruct, HairStyles = GetHairStyles(race, gender), HairColors = hair, SkinColors = skin, @@ -59,7 +59,7 @@ internal class CustomizeSetFactory( } /// Some data can not be set independently of the rest, so we need a post-processing step to finalize. - private void SetPostProcessing(CustomizeSet set, CharaMakeParams row) + private void SetPostProcessing(CustomizeSet set, in CharaMakeType row) { SetAvailability(set, row); SetFacialFeatures(set, row); @@ -112,10 +112,10 @@ internal class CustomizeSetFactory( } private readonly ColorParameters _colorParameters = new(_gameData, _log); - private readonly ExcelSheet _customizeSheet = _gameData.GetExcelSheet(ClientLanguage.English)!; - private readonly ExcelSheet _lobbySheet = _gameData.GetExcelSheet(ClientLanguage.English)!; - private readonly ExcelSheet _hairSheet = _gameData.GetExcelSheet(ClientLanguage.English)!; - private readonly ExcelSheet _tribeSheet = _gameData.GetExcelSheet(ClientLanguage.English)!; + private readonly ExcelSheet _customizeSheet = _gameData.GetExcelSheet(ClientLanguage.English); + private readonly ExcelSheet _lobbySheet = _gameData.GetExcelSheet(ClientLanguage.English); + private readonly ExcelSheet _hairSheet = _gameData.GetExcelSheet(ClientLanguage.English, "HairMakeType"); + private readonly ExcelSheet _tribeSheet = _gameData.GetExcelSheet(ClientLanguage.English); // Those color pickers are shared between all races. private readonly CustomizeData[] _highlightPicker = CreateColors(_colors, CustomizeIndex.HighlightsColor, 256, 192); @@ -126,12 +126,7 @@ internal class CustomizeSetFactory( private readonly CustomizeData[] _facePaintColorPickerLight = CreateColors(_colors, CustomizeIndex.FacePaintColor, 1152, 96, true); private readonly CustomizeData[] _tattooColorPicker = CreateColors(_colors, CustomizeIndex.TattooColor, 0, 192); - private readonly ExcelSheet _charaMakeSheet = _gameData.Excel - .GetType() - .GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)? - .MakeGenericMethod(typeof(CharaMakeParams)) - .Invoke(_gameData.Excel, ["charamaketype", _gameData.Language.ToLumina(), null])! as ExcelSheet - ?? null!; + private readonly ExcelSheet _charaMakeSheet = _gameData.Excel.GetSheet(); /// Obtain available skin and hair colors for the given clan and gender. private (CustomizeData[] Skin, CustomizeData[] Hair) GetSkinHairColors(SubRace race, Gender gender) @@ -150,29 +145,28 @@ internal class CustomizeSetFactory( private string GetName(SubRace race, Gender gender) => gender switch { - Gender.Male => _tribeSheet.GetRow((uint)race)?.Masculine.ToDalamudString().TextValue ?? race.ToName(), - Gender.Female => _tribeSheet.GetRow((uint)race)?.Feminine.ToDalamudString().TextValue ?? race.ToName(), + Gender.Male => _tribeSheet.TryGetRow((uint)race, out var row) ? row.Masculine.ExtractText() : race.ToName(), + Gender.Female => _tribeSheet.TryGetRow((uint)race, out var row) ? row.Feminine.ExtractText() : race.ToName(), _ => "Unknown", }; /// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender. private CustomizeData[] GetHairStyles(SubRace race, Gender gender) { - var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; + var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender); // Unknown30 is the number of available hairstyles. - var hairList = new List(row.Unknown30); + var numHairs = row.ReadUInt8Column(30); + var hairList = new List(numHairs); // Hairstyles can be found starting at Unknown66. - for (var i = 0; i < row.Unknown30; ++i) + for (var i = 0; i < numHairs; ++i) { - var name = $"Unknown{66 + i * 9}"; - var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) - ?? uint.MaxValue; + // Hairs start at Unknown66. + var customizeIdx = row.ReadUInt32Column(66 + i * 9); if (customizeIdx == uint.MaxValue) continue; // Hair Row from CustomizeSheet might not be set in case of unlockable hair. - var hairRow = _customizeSheet.GetRow(customizeIdx); - if (hairRow == null) + if (_customizeSheet.TryGetRow(customizeIdx, out var hairRow)) hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx)); else if (_icons.IconExists(hairRow.Icon)) hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon, @@ -183,45 +177,40 @@ internal class CustomizeSetFactory( } /// Specific icons for tails or ears. - private CustomizeData[] GetTailEarShapes(CharaMakeParams row) - => row.Menus.Cast() - .FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx)?.Values - .Select((v, i) => FromValueAndIndex(CustomizeIndex.TailShape, v, i)).ToArray() - ?? []; + private CustomizeData[] GetTailEarShapes(CharaMakeType row) + => ExtractValues(row, CustomizeIndex.TailShape); /// Specific icons for faces. - private CustomizeData[] GetFaces(CharaMakeParams row) - => row.Menus.Cast().FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.Face.ToByteAndMask().ByteIdx) - ?.Values - .Select((v, i) => FromValueAndIndex(CustomizeIndex.Face, v, i)).ToArray() - ?? []; + private CustomizeData[] GetFaces(CharaMakeType row) + => ExtractValues(row, CustomizeIndex.Face); /// Specific icons for Hrothgar patterns. - private CustomizeData[] HrothgarFurPattern(CharaMakeParams row) - => row.Menus.Cast() - .FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.LipColor.ToByteAndMask().ByteIdx)?.Values - .Select((v, i) => FromValueAndIndex(CustomizeIndex.LipColor, v, i)).ToArray() - ?? []; + private CustomizeData[] HrothgarFurPattern(CharaMakeType row) + => ExtractValues(row, CustomizeIndex.LipColor); + + private CustomizeData[] ExtractValues(CharaMakeType row, CustomizeIndex type) + { + var data = row.CharaMakeStruct.FirstOrNull(m => m.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx); + return data?.SubMenuParam.Take(data.Value.SubMenuNum).Select((v, i) => FromValueAndIndex(type, v, i)).ToArray() ?? []; + } /// Get face paints from the hair sheet via reflection since there are also unlockable face paints. private CustomizeData[] GetFacePaints(SubRace race, Gender gender) { - var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; - var paintList = new List(row.Unknown37); + var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender); // Number of available face paints is at Unknown37. - for (var i = 0; i < row.Unknown37; ++i) + var numPaints = row.ReadUInt8Column(37); + var paintList = new List(numPaints); + + for (var i = 0; i < numPaints; ++i) { // Face paints start at Unknown73. - var name = $"Unknown{73 + i * 9}"; - var customizeIdx = - (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) - ?? uint.MaxValue; + var customizeIdx = row.ReadUInt32Column(73 + i * 9); if (customizeIdx == uint.MaxValue) continue; - var paintRow = _customizeSheet.GetRow(customizeIdx); // Face paint Row from CustomizeSheet might not be set in case of unlockable face paints. - if (paintRow != null) + if (_customizeSheet.TryGetRow(customizeIdx, out var paintRow)) paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId)); else @@ -232,21 +221,18 @@ internal class CustomizeSetFactory( } /// Get List sizes. - private static int GetListSize(CharaMakeParams row, CustomizeIndex index) + private static int GetListSize(CharaMakeType row, CustomizeIndex index) { var gameId = index.ToByteAndMask().ByteIdx; - var menu = row.Menus.Cast().FirstOrDefault(m => m!.Value.Customize == gameId); - return menu?.Size ?? 0; + var menu = row.CharaMakeStruct.FirstOrNull(m => m.Customize == gameId); + return menu?.SubMenuNum ?? 0; } /// Get generic Features. private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index) - { - var row = _customizeSheet.GetRow(value); - return row == null - ? new CustomizeData(id, (CustomizeValue)(index + 1), value) - : new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId); - } + => _customizeSheet.TryGetRow(value, out var row) + ? new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId) + : new CustomizeData(id, (CustomizeValue)(index + 1), value); /// Create generic color sets from the parameters. private static CustomizeData[] CreateColors(ColorParameters colorParameters, CustomizeIndex index, int offset, int num, @@ -264,28 +250,27 @@ internal class CustomizeSetFactory( } /// Set the specific option names for the given set of parameters. - private string[] GetOptionNames(CharaMakeParams row) + private string[] GetOptionNames(CharaMakeType row) { var nameArray = Enum.GetValues().Select(c => { // Find the first menu that corresponds to the Id. var byteId = c.ToByteAndMask().ByteIdx; - var menu = row.Menus - .Cast() - .FirstOrDefault(m => m!.Value.Customize == byteId); + var menu = row.CharaMakeStruct.FirstOrNull(m => m.Customize == byteId); if (menu == null) { // If none exists and the id corresponds to highlights, set the Highlights name. if (c == CustomizeIndex.Highlights) - return string.Intern(_lobbySheet.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights"); + return string.Intern(_lobbySheet.TryGetRow(237, out var text) ? text.Text.ExtractText() : "Highlights"); // Otherwise there is an error and we use the default name. return c.ToDefaultName(); } // Otherwise all is normal, get the menu name or if it does not work the default name. - var textRow = _lobbySheet.GetRow(menu.Value.Id); - return string.Intern(textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName()); + return string.Intern(_lobbySheet.TryGetRow(menu.Value.Menu.RowId, out var textRow) + ? textRow.Text.ExtractText() + : c.ToDefaultName()); }).ToArray(); // Add names for both eye colors. @@ -306,7 +291,7 @@ internal class CustomizeSetFactory( } /// Get the manu types for all available options. - private static CharaMakeParams.MenuType[] GetMenuTypes(CharaMakeParams row) + private static MenuType[] GetMenuTypes(CharaMakeType row) { // Set up the menu types for all customizations. return Enum.GetValues().Select(c => @@ -318,13 +303,13 @@ internal class CustomizeSetFactory( case CustomizeIndex.EyeColorLeft: case CustomizeIndex.EyeColorRight: case CustomizeIndex.FacePaintColor: - return CharaMakeParams.MenuType.ColorPicker; - case CustomizeIndex.BodyType: return CharaMakeParams.MenuType.Nothing; + return MenuType.ColorPicker; + case CustomizeIndex.BodyType: return MenuType.Nothing; case CustomizeIndex.FacePaintReversed: case CustomizeIndex.Highlights: case CustomizeIndex.SmallIris: case CustomizeIndex.Lipstick: - return CharaMakeParams.MenuType.Checkmark; + return MenuType.Checkmark; case CustomizeIndex.FacialFeature1: case CustomizeIndex.FacialFeature2: case CustomizeIndex.FacialFeature3: @@ -333,24 +318,22 @@ internal class CustomizeSetFactory( case CustomizeIndex.FacialFeature6: case CustomizeIndex.FacialFeature7: case CustomizeIndex.LegacyTattoo: - return CharaMakeParams.MenuType.IconCheckmark; + return MenuType.IconCheckmark; } var gameId = c.ToByteAndMask().ByteIdx; // Otherwise find the first menu corresponding to the id. // If there is none, assume a list. - var menu = row.Menus - .Cast() - .FirstOrDefault(m => m!.Value.Customize == gameId); - var ret = menu?.Type ?? CharaMakeParams.MenuType.ListSelector; - if (c is CustomizeIndex.TailShape && ret is CharaMakeParams.MenuType.ListSelector) - ret = CharaMakeParams.MenuType.List1Selector; + var menu = row.CharaMakeStruct.FirstOrNull(m => m.Customize == gameId); + var ret = (MenuType)(menu?.SubMenuType ?? (byte)MenuType.ListSelector); + if (c is CustomizeIndex.TailShape && ret is MenuType.ListSelector) + ret = MenuType.List1Selector; return ret; }).ToArray(); } /// Set the availability of options according to actual availability. - private static void SetAvailability(CustomizeSet set, CharaMakeParams row) + private static void SetAvailability(CustomizeSet set, CharaMakeType row) { Set(true, CustomizeIndex.Height); Set(set.Faces.Count > 0, CustomizeIndex.Face); @@ -401,7 +384,7 @@ internal class CustomizeSetFactory( ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.TattooColor; var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray()); - foreach (var type in Enum.GetValues()) + foreach (var type in Enum.GetValues()) dict.TryAdd(type, []); set.Order = dict; } @@ -425,7 +408,7 @@ internal class CustomizeSetFactory( bool Valid(CustomizeData c) { - var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0; + var data = _customizeSheet.TryGetRow(c.CustomizeId, out var customize) ? customize.Unknown0 : 0; return data == 0 || data == i + set.Faces.Count; } } @@ -437,7 +420,7 @@ internal class CustomizeSetFactory( /// Create a list of lists of facial features and the legacy tattoo. /// Facial Features are bools in a bitfield, so we supply an "off" and an "on" value for simplicity of use. /// - private static void SetFacialFeatures(CustomizeSet set, CharaMakeParams row) + private static void SetFacialFeatures(CustomizeSet set, in CharaMakeType row) { var count = set.Faces.Count; set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count); @@ -446,14 +429,14 @@ internal class CustomizeSetFactory( var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray(); for (var i = 0; i < count; ++i) { - var data = row.FacialFeatureByFace[i].Icons; - tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, data[0]); - tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, data[1]); - tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, data[2]); - tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, data[3]); - tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, data[4]); - tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, data[5]); - tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, data[6]); + var data = row.FacialFeatureOption[i]; + tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, (uint)data.Option1); + tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, (uint)data.Option2); + tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, (uint)data.Option3); + tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, (uint)data.Option4); + tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, (uint)data.Option5); + tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, (uint)data.Option6); + tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, (uint)data.Option7); } set.FacialFeature1 = tmp[0]; diff --git a/Glamourer/GameData/MenuType.cs b/Glamourer/GameData/MenuType.cs new file mode 100644 index 0000000..a1d727b --- /dev/null +++ b/Glamourer/GameData/MenuType.cs @@ -0,0 +1,14 @@ +namespace Glamourer.GameData; + +public enum MenuType +{ + ListSelector = 0, + IconSelector = 1, + ColorPicker = 2, + DoubleColorPicker = 3, + IconCheckmark = 4, + Percentage = 5, + Checkmark = 6, // custom + Nothing = 7, // custom + List1Selector = 8, // custom, 1-indexed lists +} diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index 3c683a5..72ed4b4 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Object; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using OtterGui.Services; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Structs; @@ -52,15 +52,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList /// Create data from event NPCs. private static List CreateEnpcData(IDataManager data, DictENpc eNpcs) { - var enpcSheet = data.GetExcelSheet()!; + var enpcSheet = data.GetExcelSheet(); var list = new List(eNpcs.Count); // Go through all event NPCs already collected into a dictionary. foreach (var (id, name) in eNpcs) { - var row = enpcSheet.GetRow(id.Id); // We only accept NPCs with valid names. - if (row == null || name.IsNullOrWhitespace()) + if (!enpcSheet.TryGetRow(id.Id, out var row) || name.IsNullOrWhitespace()) continue; // Check if the customization is a valid human. @@ -72,14 +71,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList { Name = name, Customize = customize, - ModelId = row.ModelChara.Row, + ModelId = row.ModelChara.RowId, Id = id, Kind = ObjectKind.EventNpc, }; // Event NPCs have a reference to NpcEquip but also contain the appearance in their own row. // Prefer the NpcEquip reference if it is set and the own does not appear to be set, otherwise use the own. - if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip && row is { ModelBody: 0, ModelLegs: 0 }) + if (row.NpcEquip.RowId != 0 && row.NpcEquip.Value is { } equip && row is { ModelBody: 0, ModelLegs: 0 }) ApplyNpcEquip(ref ret, equip); else ApplyNpcEquip(ref ret, row); @@ -93,14 +92,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList /// Create data from battle NPCs. private static List CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames) { - var bnpcSheet = data.GetExcelSheet()!; - var list = new List((int)bnpcSheet.RowCount); + var bnpcSheet = data.GetExcelSheet(); + var list = new List(bnpcSheet.Count); // We go through all battle NPCs in the sheet because the dictionary refers to names. foreach (var baseRow in bnpcSheet) { // Only accept humans. - if (baseRow.ModelChara.Value!.Type != 1) + if (baseRow.ModelChara.Value.Type != 1) continue; var bnpcNameIds = bNpcNames[baseRow.RowId]; @@ -109,15 +108,15 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList continue; // Check if the customization is a valid human. - var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!); + var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value); if (!valid) continue; - var equip = baseRow.NpcEquip.Value!; + var equip = baseRow.NpcEquip.Value; var ret = new NpcData { Customize = customize, - ModelId = baseRow.ModelChara.Row, + ModelId = baseRow.ModelChara.RowId, Id = baseRow.RowId, Kind = ObjectKind.BattleNpc, }; @@ -186,36 +185,36 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList /// Apply equipment from a NpcEquip row. private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) { - data.Set(0, row.ModelHead | (row.DyeHead.Row << 24) | ((ulong)row.Dye2Head.Row << 32)); - data.Set(1, row.ModelBody | (row.DyeBody.Row << 24) | ((ulong)row.Dye2Body.Row << 32)); - data.Set(2, row.ModelHands | (row.DyeHands.Row << 24) | ((ulong)row.Dye2Hands.Row << 32)); - data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24) | ((ulong)row.Dye2Legs.Row << 32)); - data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24) | ((ulong)row.Dye2Feet.Row << 32)); - data.Set(5, row.ModelEars | (row.DyeEars.Row << 24) | ((ulong)row.Dye2Ears.Row << 32)); - data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24) | ((ulong)row.Dye2Neck.Row << 32)); - data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24) | ((ulong)row.Dye2Wrists.Row << 32)); - data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24) | ((ulong)row.Dye2RightRing.Row << 32)); - data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24) | ((ulong)row.Dye2LeftRing.Row << 32)); - data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48) | ((ulong)row.Dye2MainHand.Row << 56)); - data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48) | ((ulong)row.Dye2OffHand.Row << 56)); + data.Set(0, row.ModelHead | (row.DyeHead.RowId << 24) | ((ulong)row.Dye2Head.RowId << 32)); + data.Set(1, row.ModelBody | (row.DyeBody.RowId << 24) | ((ulong)row.Dye2Body.RowId << 32)); + data.Set(2, row.ModelHands | (row.DyeHands.RowId << 24) | ((ulong)row.Dye2Hands.RowId << 32)); + data.Set(3, row.ModelLegs | (row.DyeLegs.RowId << 24) | ((ulong)row.Dye2Legs.RowId << 32)); + data.Set(4, row.ModelFeet | (row.DyeFeet.RowId << 24) | ((ulong)row.Dye2Feet.RowId << 32)); + data.Set(5, row.ModelEars | (row.DyeEars.RowId << 24) | ((ulong)row.Dye2Ears.RowId << 32)); + data.Set(6, row.ModelNeck | (row.DyeNeck.RowId << 24) | ((ulong)row.Dye2Neck.RowId << 32)); + data.Set(7, row.ModelWrists | (row.DyeWrists.RowId << 24) | ((ulong)row.Dye2Wrists.RowId << 32)); + data.Set(8, row.ModelRightRing | (row.DyeRightRing.RowId << 24) | ((ulong)row.Dye2RightRing.RowId << 32)); + data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.RowId << 24) | ((ulong)row.Dye2LeftRing.RowId << 32)); + data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.RowId << 48) | ((ulong)row.Dye2MainHand.RowId << 56)); + data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56)); data.VisorToggled = row.Visor; } /// Apply equipment from a ENpcBase Row row. private static void ApplyNpcEquip(ref NpcData data, ENpcBase row) { - data.Set(0, row.ModelHead | (row.DyeHead.Row << 24) | ((ulong)row.Dye2Head.Row << 32)); - data.Set(1, row.ModelBody | (row.DyeBody.Row << 24) | ((ulong)row.Dye2Body.Row << 32)); - data.Set(2, row.ModelHands | (row.DyeHands.Row << 24) | ((ulong)row.Dye2Hands.Row << 32)); - data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24) | ((ulong)row.Dye2Legs.Row << 32)); - data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24) | ((ulong)row.Dye2Feet.Row << 32)); - data.Set(5, row.ModelEars | (row.DyeEars.Row << 24) | ((ulong)row.Dye2Ears.Row << 32)); - data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24) | ((ulong)row.Dye2Neck.Row << 32)); - data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24) | ((ulong)row.Dye2Wrists.Row << 32)); - data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24) | ((ulong)row.Dye2RightRing.Row << 32)); - data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24) | ((ulong)row.Dye2LeftRing.Row << 32)); - data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48) | ((ulong)row.Dye2MainHand.Row << 56)); - data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48) | ((ulong)row.Dye2OffHand.Row << 56)); + data.Set(0, row.ModelHead | (row.DyeHead.RowId << 24) | ((ulong)row.Dye2Head.RowId << 32)); + data.Set(1, row.ModelBody | (row.DyeBody.RowId << 24) | ((ulong)row.Dye2Body.RowId << 32)); + data.Set(2, row.ModelHands | (row.DyeHands.RowId << 24) | ((ulong)row.Dye2Hands.RowId << 32)); + data.Set(3, row.ModelLegs | (row.DyeLegs.RowId << 24) | ((ulong)row.Dye2Legs.RowId << 32)); + data.Set(4, row.ModelFeet | (row.DyeFeet.RowId << 24) | ((ulong)row.Dye2Feet.RowId << 32)); + data.Set(5, row.ModelEars | (row.DyeEars.RowId << 24) | ((ulong)row.Dye2Ears.RowId << 32)); + data.Set(6, row.ModelNeck | (row.DyeNeck.RowId << 24) | ((ulong)row.Dye2Neck.RowId << 32)); + data.Set(7, row.ModelWrists | (row.DyeWrists.RowId << 24) | ((ulong)row.Dye2Wrists.RowId << 32)); + data.Set(8, row.ModelRightRing | (row.DyeRightRing.RowId << 24) | ((ulong)row.Dye2RightRing.RowId << 32)); + data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.RowId << 24) | ((ulong)row.Dye2LeftRing.RowId << 32)); + data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.RowId << 48) | ((ulong)row.Dye2MainHand.RowId << 56)); + data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56)); data.VisorToggled = row.Visor; } @@ -223,11 +222,11 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize) { var customize = new CustomizeArray(); - customize.SetByIndex(0, (CustomizeValue)(byte)bnpcCustomize.Race.Row); + customize.SetByIndex(0, (CustomizeValue)(byte)bnpcCustomize.Race.RowId); customize.SetByIndex(1, (CustomizeValue)bnpcCustomize.Gender); customize.SetByIndex(2, (CustomizeValue)bnpcCustomize.BodyType); customize.SetByIndex(3, (CustomizeValue)bnpcCustomize.Height); - customize.SetByIndex(4, (CustomizeValue)(byte)bnpcCustomize.Tribe.Row); + customize.SetByIndex(4, (CustomizeValue)(byte)bnpcCustomize.Tribe.RowId); customize.SetByIndex(5, (CustomizeValue)bnpcCustomize.Face); customize.SetByIndex(6, (CustomizeValue)bnpcCustomize.HairStyle); customize.SetByIndex(7, (CustomizeValue)bnpcCustomize.HairHighlight); @@ -261,15 +260,15 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList /// Obtain customizations from a ENpcBase row and check if the human is valid. private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase) { - if (enpcBase.ModelChara.Value?.Type != 1) + if (enpcBase.ModelChara.ValueNullable?.Type != 1) return (false, CustomizeArray.Default); var customize = new CustomizeArray(); - customize.SetByIndex(0, (CustomizeValue)(byte)enpcBase.Race.Row); + customize.SetByIndex(0, (CustomizeValue)(byte)enpcBase.Race.RowId); customize.SetByIndex(1, (CustomizeValue)enpcBase.Gender); customize.SetByIndex(2, (CustomizeValue)enpcBase.BodyType); customize.SetByIndex(3, (CustomizeValue)enpcBase.Height); - customize.SetByIndex(4, (CustomizeValue)(byte)enpcBase.Tribe.Row); + customize.SetByIndex(4, (CustomizeValue)(byte)enpcBase.Tribe.RowId); customize.SetByIndex(5, (CustomizeValue)enpcBase.Face); customize.SetByIndex(6, (CustomizeValue)enpcBase.HairStyle); customize.SetByIndex(7, (CustomizeValue)enpcBase.HairHighlight); diff --git a/Glamourer/Glamourer.json b/Glamourer/Glamourer.json index 08c18f5..1e9edf7 100644 --- a/Glamourer/Glamourer.json +++ b/Glamourer/Glamourer.json @@ -8,7 +8,7 @@ "AssemblyVersion": "9.0.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 10, + "DalamudApiLevel": 11, "ImageUrls": null, "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png" } \ No newline at end of file diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index baedc05..486fdb4 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -193,7 +193,7 @@ public partial class CustomizationDrawer private void DrawMultiIcons() { - var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark]; + var options = _set.Order[MenuType.IconCheckmark]; using var group = ImRaii.Group(); var face = _set.DataByValue(CustomizeIndex.Face, _customize.Face, out _, _customize.Face) < 0 ? _set.Faces[0].Value : _customize.Face; foreach (var (featureIdx, idx) in options.WithIndex()) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 6b6cc0a..4ec6146 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -121,22 +121,22 @@ public partial class CustomizationDrawer( _set = _service.Manager.GetSet(_customize.Clan, _customize.Gender); - foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage]) + foreach (var id in _set.Order[MenuType.Percentage]) PercentageSelector(id); - Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.IconSelector], DrawIconSelector, ImGui.SameLine); + Functions.IteratePairwise(_set.Order[MenuType.IconSelector], DrawIconSelector, ImGui.SameLine); DrawMultiIconSelector(); - foreach (var id in _set.Order[CharaMakeParams.MenuType.ListSelector]) + foreach (var id in _set.Order[MenuType.ListSelector]) DrawListSelector(id, false); - foreach (var id in _set.Order[CharaMakeParams.MenuType.List1Selector]) + foreach (var id in _set.Order[MenuType.List1Selector]) DrawListSelector(id, true); - Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine); + Functions.IteratePairwise(_set.Order[MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine); - Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.Checkmark], DrawCheckbox, + Functions.IteratePairwise(_set.Order[MenuType.Checkmark], DrawCheckbox, () => ImGui.SameLine(_comboSelectorSize - _framedIconSize.X + ImGui.GetStyle().WindowPadding.X)); return Changed != 0 || ChangeApply != _initialApply; } diff --git a/Glamourer/Gui/Equipment/BonusItemCombo.cs b/Glamourer/Gui/Equipment/BonusItemCombo.cs index 735a23f..c333a87 100644 --- a/Glamourer/Gui/Equipment/BonusItemCombo.cs +++ b/Glamourer/Gui/Equipment/BonusItemCombo.cs @@ -2,7 +2,7 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using OtterGui; using OtterGui.Classes; using OtterGui.Log; @@ -91,8 +91,8 @@ public sealed class BonusItemCombo : FilterComboCache return slot switch { - BonusItemFlag.Glasses => sheet.GetRow(16050)?.Text.ToString() ?? "Facewear", - BonusItemFlag.UnkSlot => sheet.GetRow(16051)?.Text.ToString() ?? "Facewear", + BonusItemFlag.Glasses => sheet.TryGetRow(16050, out var text) ? text.Text.ToString() : "Facewear", + BonusItemFlag.UnkSlot => sheet.TryGetRow(16051, out var text) ? text.Text.ToString() : "Facewear", _ => string.Empty, }; diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 1dac468..f7c75e1 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -2,7 +2,7 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using OtterGui; using OtterGui.Classes; using OtterGui.Log; @@ -88,20 +88,20 @@ public sealed class ItemCombo : FilterComboCache private static string GetLabel(IDataManager gameData, EquipSlot slot) { - var sheet = gameData.GetExcelSheet()!; + var sheet = gameData.GetExcelSheet(); return slot switch { - EquipSlot.Head => sheet.GetRow(740)?.Text.ToString() ?? "Head", - EquipSlot.Body => sheet.GetRow(741)?.Text.ToString() ?? "Body", - EquipSlot.Hands => sheet.GetRow(742)?.Text.ToString() ?? "Hands", - EquipSlot.Legs => sheet.GetRow(744)?.Text.ToString() ?? "Legs", - EquipSlot.Feet => sheet.GetRow(745)?.Text.ToString() ?? "Feet", - EquipSlot.Ears => sheet.GetRow(746)?.Text.ToString() ?? "Ears", - EquipSlot.Neck => sheet.GetRow(747)?.Text.ToString() ?? "Neck", - EquipSlot.Wrists => sheet.GetRow(748)?.Text.ToString() ?? "Wrists", - EquipSlot.RFinger => sheet.GetRow(749)?.Text.ToString() ?? "Right Ring", - EquipSlot.LFinger => sheet.GetRow(750)?.Text.ToString() ?? "Left Ring", + EquipSlot.Head => sheet.TryGetRow(740, out var text) ? text.Text.ToString() : "Head", + EquipSlot.Body => sheet.TryGetRow(741, out var text) ? text.Text.ToString() : "Body", + EquipSlot.Hands => sheet.TryGetRow(742, out var text) ? text.Text.ToString() : "Hands", + EquipSlot.Legs => sheet.TryGetRow(744, out var text) ? text.Text.ToString() : "Legs", + EquipSlot.Feet => sheet.TryGetRow(745, out var text) ? text.Text.ToString() : "Feet", + EquipSlot.Ears => sheet.TryGetRow(746, out var text) ? text.Text.ToString() : "Ears", + EquipSlot.Neck => sheet.TryGetRow(747, out var text) ? text.Text.ToString() : "Neck", + EquipSlot.Wrists => sheet.TryGetRow(748, out var text) ? text.Text.ToString() : "Wrists", + EquipSlot.RFinger => sheet.TryGetRow(749, out var text) ? text.Text.ToString() : "Right Ring", + EquipSlot.LFinger => sheet.TryGetRow(750, out var text) ? text.Text.ToString() : "Left Ring", _ => string.Empty, }; } diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index efe2448..afc4f7b 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -40,7 +40,7 @@ public class UnlockOverview( foreach (var type in Enum.GetValues()) { - if (type.IsOffhandType() || !items.ItemData.ByType.TryGetValue(type, out var value) || value.Count == 0) + if (type.IsOffhandType() || type.IsBonus() || !items.ItemData.ByType.TryGetValue(type, out var value) || value.Count == 0) continue; if (ImGui.Selectable(type.ToName(), _selected1 == type)) diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 2b6f1ac..8e217b6 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -47,7 +47,7 @@ public sealed unsafe class CrestService : EventWrapperRef3DrawData, (byte) flags); gameObject.CrestBitfield = currentCrests; } @@ -62,14 +62,14 @@ public sealed unsafe class CrestService : EventWrapperRef3 _crestChangeHook = null!; - private void CrestChangeDetour(Character* character, byte crestFlags) + private void CrestChangeDetour(DrawDataContainer* container, byte crestFlags) { - var actor = (Actor)character; + var actor = (Actor)container->OwnerObject; foreach (var slot in CrestExtensions.AllRelevantSet) { var newValue = ((CrestFlag)crestFlags).HasFlag(slot); @@ -78,9 +78,9 @@ public sealed unsafe class CrestService : EventWrapperRef3((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); - _setupOrnamentHook = interop.HookFromAddress((nint)Ornament.MemberFunctionPointers.SetupOrnament, SetupOrnamentDetour); _calculateHeightHook = - interop.HookFromAddress((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); + interop.HookFromAddress((nint)HeightContainer.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); _setupMountHook.Enable(); - _setupOrnamentHook.Enable(); + _updateOrnamentHook.Enable(); _placeMinionHook.Enable(); _calculateHeightHook.Enable(); } @@ -29,19 +30,21 @@ public unsafe class ScalingService : IDisposable public void Dispose() { _setupMountHook.Dispose(); - _setupOrnamentHook.Dispose(); + _updateOrnamentHook.Dispose(); _placeMinionHook.Dispose(); _calculateHeightHook.Dispose(); } private delegate void SetupMount(MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4); - private delegate void SetupOrnament(Ornament* ornament, uint* unk1, float* unk2); + private delegate void UpdateOrnament(OrnamentContainer* ornament); private delegate void PlaceMinion(Companion* character); private delegate float CalculateHeight(Character* character); private readonly Hook _setupMountHook; - private readonly Hook _setupOrnamentHook; + // TODO: Use client structs sig. + [Signature(Sigs.UpdateOrnament, DetourName = nameof(UpdateOrnamentDetour))] + private readonly Hook _updateOrnamentHook = null!; private readonly Hook _calculateHeightHook; @@ -57,19 +60,12 @@ public unsafe class ScalingService : IDisposable SetScaleCustomize(container->OwnerObject, race, clan, gender); } - private void SetupOrnamentDetour(Ornament* ornament, uint* unk1, float* unk2) + private void UpdateOrnamentDetour(OrnamentContainer* container) { - var character = ornament->GetParentCharacter(); - if (character == null) - { - _setupOrnamentHook.Original(ornament, unk1, unk2); - return; - } - - var (race, clan, gender) = GetScaleRelevantCustomize(character); - SetScaleCustomize(character, character->GameObject.DrawObject); - _setupOrnamentHook.Original(ornament, unk1, unk2); - SetScaleCustomize(character, race, clan, gender); + var (race, clan, gender) = GetScaleRelevantCustomize(container->OwnerObject); + SetScaleCustomize(container->OwnerObject, container->OwnerObject->DrawObject); + _updateOrnamentHook.Original(container); + SetScaleCustomize(container->OwnerObject, race, clan, gender); } private void PlaceMinionDetour(Companion* companion) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index b18c817..6fb32e2 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -548,7 +548,7 @@ public class CommandService : IDisposable, IApiService if (baseValue != null) { var v = baseValue.Value; - if (set.Type(customizeIndex) is CharaMakeParams.MenuType.ListSelector) + if (set.Type(customizeIndex) is MenuType.ListSelector) --v; set.DataByValue(customizeIndex, new CustomizeValue(v), out var data, customize.Face); if (data != null) diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 07a4829..5d6f074 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin.Services; using Lumina.Excel; +using Lumina.Excel.Sheets; using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; @@ -16,12 +17,12 @@ public class ItemManager private readonly Configuration _config; - public readonly ObjectIdentification ObjectIdentification; - public readonly ExcelSheet ItemSheet; - public readonly DictStain Stains; - public readonly ItemData ItemData; - public readonly DictBonusItems DictBonusItems; - public readonly RestrictedGear RestrictedGear; + public readonly ObjectIdentification ObjectIdentification; + public readonly ExcelSheet ItemSheet; + public readonly DictStain Stains; + public readonly ItemData ItemData; + public readonly DictBonusItems DictBonusItems; + public readonly RestrictedGear RestrictedGear; public readonly EquipItem DefaultSword; @@ -29,13 +30,13 @@ public class ItemManager ItemData itemData, DictStain stains, RestrictedGear restrictedGear, DictBonusItems dictBonusItems) { _config = config; - ItemSheet = gameData.GetExcelSheet()!; + ItemSheet = gameData.GetExcelSheet(); ObjectIdentification = objectIdentification; ItemData = itemData; Stains = stains; RestrictedGear = restrictedGear; DictBonusItems = dictBonusItems; - DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)!); // Weathered Shortsword + DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)); // Weathered Shortsword } public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender) diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index 801f211..18f3cac 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -8,7 +8,7 @@ using Glamourer.GameData; using Glamourer.Events; using Glamourer.Interop; using Glamourer.Services; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using Penumbra.GameData; using Penumbra.GameData.Enums; @@ -183,25 +183,25 @@ public class CustomizeUnlockManager : IDisposable, ISavable var list = customizations.Manager.GetSet(clan, gender); foreach (var hair in list.HairStyles) { - var x = sheet.FirstOrDefault(f => f.FeatureID == hair.Value.Value); + var x = sheet.FirstOrNull(f => f.FeatureID == hair.Value.Value); if (x?.IsPurchasable == true) { - var name = x.FeatureID == 61 + var name = x.Value.FeatureID == 61 ? "Eternal Bond" - : x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Aesthetics - ", string.Empty) + : x.Value.HintItem.ValueNullable?.Name.ExtractText().Replace("Modern Aesthetics - ", string.Empty) ?? string.Empty; - ret.TryAdd(hair, (x.Data, name)); + ret.TryAdd(hair, (x.Value.Data, name)); } } foreach (var paint in list.FacePaints) { - var x = sheet.FirstOrDefault(f => f.FeatureID == paint.Value.Value); + var x = sheet.FirstOrNull(f => f.FeatureID == paint.Value.Value); if (x?.IsPurchasable == true) { - var name = x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Cosmetics - ", string.Empty) + var name = x.Value.HintItem.ValueNullable?.Name.ExtractText().Replace("Modern Cosmetics - ", string.Empty) ?? string.Empty; - ret.TryAdd(paint, (x.Data, name)); + ret.TryAdd(paint, (x.Value.Data, name)); } } } diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index 0b95b94..0fc1675 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -3,11 +3,11 @@ using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.UI; using Glamourer.Events; using Glamourer.Services; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.Sheets; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Cabinet = Lumina.Excel.GeneratedSheets.Cabinet; +using Cabinet = Lumina.Excel.Sheets.Cabinet; namespace Glamourer.Unlocks; @@ -192,7 +192,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary= _items.ItemSheet.RowCount) + if (itemId.Id >= (uint) _items.ItemSheet.Count) { time = DateTimeOffset.MinValue; return true; @@ -273,32 +273,31 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary CreateUnlockData(IDataManager gameData, ItemManager items) { var ret = new Dictionary(); - var cabinet = gameData.GetExcelSheet()!; + var cabinet = gameData.GetExcelSheet(); foreach (var row in cabinet) { - if (items.ItemData.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item)) + if (items.ItemData.TryGetValue(row.Item.RowId, EquipSlot.MainHand, out var item)) ret.TryAdd(item.ItemId, new UnlockRequirements(row.RowId, 0, 0, 0, UnlockType.Cabinet)); } - var gilShopItem = gameData.GetExcelSheet()!; - var gilShop = gameData.GetExcelSheet()!; - foreach (var row in gilShopItem) + var gilShopItem = gameData.GetSubrowExcelSheet(); + var gilShop = gameData.GetExcelSheet(); + foreach (var row in gilShopItem.SelectMany(g => g)) { - if (!items.ItemData.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item)) + if (!items.ItemData.TryGetValue(row.Item.RowId, EquipSlot.MainHand, out var item)) continue; - var quest1 = row.QuestRequired[0].Row; - var quest2 = row.QuestRequired[1].Row; - var achievement = row.AchievementRequired.Row; + var quest1 = row.QuestRequired[0].RowId; + var quest2 = row.QuestRequired[1].RowId; + var achievement = row.AchievementRequired.RowId; var state = row.StateRequired; - var shop = gilShop.GetRow(row.RowId); - if (shop != null && shop.Quest.Row != 0) + if (gilShop.TryGetRow(row.RowId, out var shop) && shop.Quest.RowId != 0) { if (quest1 == 0) - quest1 = shop.Quest.Row; + quest1 = shop.Quest.RowId; else if (quest2 == 0) - quest2 = shop.Quest.Row; + quest2 = shop.Quest.RowId; } var type = (quest1 != 0 ? UnlockType.Quest1 : 0) diff --git a/OtterGui b/OtterGui index d9486ae..8ba88ef 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit d9486ae54b5a4b61cf74f79ed27daa659eb1ce5b +Subproject commit 8ba88eff15326bb28ed5e6157f5252c114d40b5f diff --git a/Penumbra.GameData b/Penumbra.GameData index 77d52b0..fb81a0b 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 77d52b02d21e770b30c08f89bdf06e0cb75562f7 +Subproject commit fb81a0b55d3c68f2b26357fac3049c79fb0c22fb diff --git a/repo.json b/repo.json index b309240..067cd7e 100644 --- a/repo.json +++ b/repo.json @@ -22,7 +22,7 @@ "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, - "TestingDalamudApiLevel": 10, + "TestingDalamudApiLevel": 11, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1, From fe028e5ec730a2786f41809d7e5cf72c54feaa9e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 17 Nov 2024 14:27:14 +0100 Subject: [PATCH 505/786] Fixes. --- Glamourer/Automation/AutoDesignApplier.cs | 2 +- Glamourer/GameData/CustomizeSetFactory.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 6 +++--- Glamourer/Interop/MetaService.cs | 2 +- Glamourer/Interop/ScalingService.cs | 2 +- Glamourer/State/FunModule.cs | 2 +- Glamourer/State/StateListener.cs | 2 +- Glamourer/State/StateManager.cs | 6 +++--- OtterGui | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index fba1a58..0ce34d4 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -277,7 +277,7 @@ public sealed class AutoDesignApplier : IDisposable } forcedRedraw = false; - if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) + if (!_humans.IsHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId)) return; if (actor.IsTransformed) diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 36cdb1b..7d80454 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -166,7 +166,7 @@ internal class CustomizeSetFactory( continue; // Hair Row from CustomizeSheet might not be set in case of unlockable hair. - if (_customizeSheet.TryGetRow(customizeIdx, out var hairRow)) + if (!_customizeSheet.TryGetRow(customizeIdx, out var hairRow)) hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx)); else if (_icons.IconExists(hairRow.Icon)) hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon, diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index eeb6f3f..c1b5847 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -52,11 +52,11 @@ public unsafe class ModelEvaluationPanel( ImGui.TableNextColumn(); if (actor.IsCharacter) { - ImGui.TextUnformatted(actor.AsCharacter->CharacterData.ModelCharaId.ToString()); + ImGui.TextUnformatted(actor.AsCharacter->ModelContainer.ModelCharaId.ToString()); if (actor.AsCharacter->CharacterData.TransformationId != 0) ImGui.TextUnformatted($"Transformation Id: {actor.AsCharacter->CharacterData.TransformationId}"); - if (actor.AsCharacter->CharacterData.ModelCharaId_2 != -1) - ImGui.TextUnformatted($"ModelChara2 {actor.AsCharacter->CharacterData.ModelCharaId_2}"); + if (actor.AsCharacter->ModelContainer.ModelCharaId_2 != -1) + ImGui.TextUnformatted($"ModelChara2 {actor.AsCharacter->ModelContainer.ModelCharaId_2}"); ImGuiUtil.DrawTableColumn("Character Mode"); ImGuiUtil.DrawTableColumn($"{actor.AsCharacter->Mode}"); diff --git a/Glamourer/Interop/MetaService.cs b/Glamourer/Interop/MetaService.cs index 1c79cec..6225986 100644 --- a/Glamourer/Interop/MetaService.cs +++ b/Glamourer/Interop/MetaService.cs @@ -50,7 +50,7 @@ public unsafe class MetaService : IDisposable // The function seems to not do anything if the head is 0, but also breaks for carbuncles turned human, sometimes? var old = actor.AsCharacter->DrawData.Equipment(DrawDataContainer.EquipmentSlot.Head).Id; - if (old == 0 && actor.AsCharacter->CharacterData.ModelCharaId == 0) + if (old == 0 && actor.AsCharacter->ModelContainer.ModelCharaId == 0) actor.AsCharacter->DrawData.Equipment(DrawDataContainer.EquipmentSlot.Head).Id = 1; _hideHatGearHook.Original(&actor.AsCharacter->DrawData, 0, (byte)(value ? 0 : 1)); actor.AsCharacter->DrawData.Equipment(DrawDataContainer.EquipmentSlot.Head).Id = old; diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index f7f08f6..aa64b77 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -19,7 +19,7 @@ public unsafe class ScalingService : IDisposable _setupMountHook = interop.HookFromAddress((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); _calculateHeightHook = - interop.HookFromAddress((nint)HeightContainer.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); + interop.HookFromAddress((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); _setupMountHook.Enable(); _updateOrnamentHook.Enable(); diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index de39a53..1ca5c48 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -333,7 +333,7 @@ public unsafe class FunModule : IDisposable => actor.IsCharacter && actor.AsObject->ObjectKind is ObjectKind.Pc && !actor.IsTransformed - && actor.AsCharacter->CharacterData.ModelCharaId == 0; + && actor.AsCharacter->ModelContainer.ModelCharaId == 0; private static void KeepOldArmor(Actor actor, EquipSlot slot, ref CharacterArmor armor) => armor = actor.Model.Valid ? actor.Model.GetArmor(slot) : armor; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 9c0dd67..d054a25 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -593,7 +593,7 @@ public class StateListener : IDisposable private unsafe UpdateState UpdateBaseData(Actor actor, ActorState state, uint modelId, nint customizeData, nint equipData) { // Model ID does not agree between game object and new draw object => Transformation. - if (modelId != (uint)actor.AsCharacter->CharacterData.ModelCharaId) + if (modelId != (uint)actor.AsCharacter->ModelContainer.ModelCharaId) return UpdateState.Transformed; // Model ID did not change to stored state. diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 4ff232a..f16dd22 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -114,14 +114,14 @@ public sealed class StateManager( // Model ID is only unambiguously contained in the game object. // The draw object only has the object type. // TODO reverse search model data to get model id from model. - if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) + if (!_humans.IsHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId)) { - ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData, + ret.LoadNonHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData, (nint)Unsafe.AsPointer(ref actor.AsCharacter->DrawData.EquipmentModelIds[0])); return ret; } - ret.ModelId = (uint)actor.AsCharacter->CharacterData.ModelCharaId; + ret.ModelId = (uint)actor.AsCharacter->ModelContainer.ModelCharaId; ret.IsHuman = true; CharacterWeapon main; diff --git a/OtterGui b/OtterGui index 8ba88ef..95b8d17 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 8ba88eff15326bb28ed5e6157f5252c114d40b5f +Subproject commit 95b8d177883b03f804d77434f45e9de97fdb9adf From c7b9791a6af078ca760b16e6d7eeb63f879dfe64 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 17 Nov 2024 14:29:11 +0100 Subject: [PATCH 506/786] Not yet increment API level. --- repo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo.json b/repo.json index 067cd7e..b309240 100644 --- a/repo.json +++ b/repo.json @@ -22,7 +22,7 @@ "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, - "TestingDalamudApiLevel": 11, + "TestingDalamudApiLevel": 10, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1, From 60c7debb5eb1f34a94ab7089512e925eeca023bc Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 17 Nov 2024 18:13:20 +0100 Subject: [PATCH 507/786] Fix offset --- Glamourer/Interop/ScalingService.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index aa64b77..2f7ba66 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -5,8 +5,7 @@ using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Penumbra.GameData; using Penumbra.GameData.Interop; -using Penumbra.GameData.Structs; -using System.ComponentModel; +using FFXIVClientStructs.FFXIV.Client.Game.Object; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; namespace Glamourer.Interop; @@ -70,8 +69,7 @@ public unsafe class ScalingService : IDisposable private void PlaceMinionDetour(Companion* companion) { - // TODO Update CS - var owner = (Actor)((nint*)companion)[0x45C]; + var owner = (Actor)(GameObject*)companion->Owner; if (!owner.IsCharacter) { _placeMinionHook.Original(companion); From db34255127297b8c87ee1d3acc7360235f9001f9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 17 Nov 2024 22:00:50 +0100 Subject: [PATCH 508/786] Fix customization bug. --- Glamourer/GameData/CustomizeSetFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 7d80454..e2291d4 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -190,7 +190,7 @@ internal class CustomizeSetFactory( private CustomizeData[] ExtractValues(CharaMakeType row, CustomizeIndex type) { - var data = row.CharaMakeStruct.FirstOrNull(m => m.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx); + var data = row.CharaMakeStruct.FirstOrNull(m => m.Customize == type.ToByteAndMask().ByteIdx); return data?.SubMenuParam.Take(data.Value.SubMenuNum).Select((v, i) => FromValueAndIndex(type, v, i)).ToArray() ?? []; } From de9d605fb09edd837168df47bbca2bde2a437887 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 17 Nov 2024 22:01:36 +0100 Subject: [PATCH 509/786] 1.3.3.1 --- .github/workflows/release.yml | 2 +- repo.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e22a656..bac600a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | diff --git a/repo.json b/repo.json index b309240..067cd7e 100644 --- a/repo.json +++ b/repo.json @@ -22,7 +22,7 @@ "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, - "TestingDalamudApiLevel": 10, + "TestingDalamudApiLevel": 11, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1, From 0ba9f538baf97591c2106dda2373627be826f160 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 17 Nov 2024 21:04:05 +0000 Subject: [PATCH 510/786] [CI] Updating repo.json for testing_1.3.3.1 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 067cd7e..edef16c 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.3.0", - "TestingAssemblyVersion": "1.3.3.0", + "TestingAssemblyVersion": "1.3.3.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.3.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 7605c7cb29d582685cfc03d739e43a55e19d84f3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 18 Nov 2024 00:29:24 +0100 Subject: [PATCH 511/786] Fix screen actor indices. --- Glamourer.sln | 2 ++ Penumbra.GameData | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Glamourer.sln b/Glamourer.sln index 254f8e4..4ac3356 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -6,7 +6,9 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{383AEE76-D423-431C-893A-7AB3DEA13630}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .github\workflows\release.yml = .github\workflows\release.yml repo.json = repo.json + .github\workflows\test_release.yml = .github\workflows\test_release.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer", "Glamourer\Glamourer.csproj", "{01EB903D-871F-4285-A8CF-6486561D5B5B}" diff --git a/Penumbra.GameData b/Penumbra.GameData index fb81a0b..79d8d78 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit fb81a0b55d3c68f2b26357fac3049c79fb0c22fb +Subproject commit 79d8d782b3b454a41f7f87f398806ec4d08d485f From f9c7e567b6e11318d645f226bb017c5d2401baee Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 17 Nov 2024 23:31:26 +0000 Subject: [PATCH 512/786] [CI] Updating repo.json for testing_1.3.3.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index edef16c..93738f1 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.3.0", - "TestingAssemblyVersion": "1.3.3.1", + "TestingAssemblyVersion": "1.3.3.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.3.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.3.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 86d370226a2931caa81770bbb01c36f5e0fd3d8b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 20 Nov 2024 11:50:30 +0100 Subject: [PATCH 513/786] Update weapon changed data offset. --- Glamourer/Interop/Material/MaterialManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 7f13c2d..f3c1875 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -200,7 +200,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// private static CharacterWeapon GetTempSlot(Weapon* weapon) { - var changedData = *(void**)((byte*)weapon + 0xA00); + // TODO: Use ClientStructs + var changedData = *(void**)((byte*)weapon + 0xA40); if (changedData == null) return new CharacterWeapon(weapon->ModelSetId, weapon->SecondaryId, (Variant)weapon->Variant, StainIds.FromWeapon(*weapon)); From b44a147fdd4c1900b57515e35cd806e4add84f73 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 20 Nov 2024 13:01:59 +0000 Subject: [PATCH 514/786] [CI] Updating repo.json for testing_1.3.3.3 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 93738f1..d9d03e8 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.3.0", - "TestingAssemblyVersion": "1.3.3.2", + "TestingAssemblyVersion": "1.3.3.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.3.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.3.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 525f65e70aa0f60d447dde2496cb74ad29b5c945 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Nov 2024 14:27:14 +0100 Subject: [PATCH 515/786] Fix issues with shared weapon types. --- Glamourer/Automation/AutoDesignApplier.cs | 4 ++-- Glamourer/Designs/Links/DesignMerger.cs | 4 ++-- Glamourer/Designs/Links/MergedDesign.cs | 8 ++++---- Glamourer/Glamourer.cs | 1 + Glamourer/State/JobChangeState.cs | 5 +++-- Glamourer/State/StateEditor.cs | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 0ce34d4..ccaae21 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -77,7 +77,7 @@ public sealed class AutoDesignApplier : IDisposable { case EquipSlot.MainHand: { - if (_jobChangeState.TryGetValue(current.Type, actor.Job, out var data)) + if (_jobChangeState.TryGetValue(current.Type, actor.Job, false, out var data)) { Glamourer.Log.Verbose( $"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}."); @@ -89,7 +89,7 @@ public sealed class AutoDesignApplier : IDisposable } case EquipSlot.OffHand when current.Type == state.BaseData.MainhandType.Offhand(): { - if (_jobChangeState.TryGetValue(current.Type, actor.Job, out var data)) + if (_jobChangeState.TryGetValue(current.Type, actor.Job, false, out var data)) { Glamourer.Log.Verbose( $"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}."); diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 5767c7a..e0e5d95 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -1,4 +1,5 @@ using Glamourer.Automation; +using Glamourer.Designs.Special; using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Services; @@ -23,8 +24,7 @@ public class DesignMerger( modAssociations); public MergedDesign Merge(IEnumerable<(IDesignStandIn, ApplicationType, JobFlag)> designs, in CustomizeArray currentCustomize, - in DesignData baseRef, - bool respectOwnership, bool modAssociations) + in DesignData baseRef, bool respectOwnership, bool modAssociations) { var ret = new MergedDesign(designManager); ret.Design.SetCustomize(_customize, currentCustomize); diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index 0748633..9c9e079 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -23,8 +23,8 @@ public readonly struct WeaponList _list.Add(type, list); } - var remainingFlags = list.Select(t => t.Item3) - .Aggregate(flags, (current, existingFlags) => current & ~existingFlags); + var existingFlags = list.Count == 0 ? 0 : list.Select(t => t.Item3).Aggregate((t, existing) => t | existing); + var remainingFlags = flags & ~existingFlags; if (remainingFlags == 0) return false; @@ -33,7 +33,7 @@ public readonly struct WeaponList return true; } - public bool TryGet(FullEquipType type, JobId id, out (EquipItem, StateSource) ret) + public bool TryGet(FullEquipType type, JobId id, bool gameStateAllowed, out (EquipItem, StateSource) ret) { if (!_list.TryGetValue(type, out var list)) { @@ -45,7 +45,7 @@ public readonly struct WeaponList foreach (var (item, source, flags) in list) { - if (flags.HasFlag(flag)) + if (flags.HasFlag(flag) && (gameStateAllowed || source is not StateSource.Game)) { ret = (item, source); return true; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index ee202d6..b4489e8 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -139,6 +139,7 @@ public class Glamourer : IDalamudPlugin ReadOnlySpan relevantPlugins = [ "Penumbra", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge", + "LoporritSync", ]; var plugins = _services.GetService().InstalledPlugins .GroupBy(p => p.InternalName) diff --git a/Glamourer/State/JobChangeState.cs b/Glamourer/State/JobChangeState.cs index 0fe1820..d568375 100644 --- a/Glamourer/State/JobChangeState.cs +++ b/Glamourer/State/JobChangeState.cs @@ -24,11 +24,12 @@ public sealed class JobChangeState : IService public ActorIdentifier Identifier => State?.Identifier ?? ActorIdentifier.Invalid; - public bool TryGetValue(FullEquipType slot, JobId jobId, out (EquipItem, StateSource) data) - => _weaponList.TryGet(slot, jobId, out data); + public bool TryGetValue(FullEquipType slot, JobId jobId, bool gameStateAllowed, out (EquipItem, StateSource) data) + => _weaponList.TryGet(slot, jobId, gameStateAllowed, out data); public void Set(ActorState state, IEnumerable<(EquipItem, StateSource, JobFlag)> items) { + Reset(); foreach (var (item, source, flags) in items.Where(p => p.Item1.Valid)) _weaponList.TryAdd(item.Type, item, source, flags); State = state; diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 959d810..59f27a2 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -359,7 +359,7 @@ public class StateEditor( } var currentType = state.BaseData.Item(weaponSlot).Type; - if (mergedDesign.Weapons.TryGet(currentType, state.LastJob, out var weapon)) + if (mergedDesign.Weapons.TryGet(currentType, state.LastJob, true, out var weapon)) { var source = settings.UseSingleSource ? settings.Source : weapon.Item2 is StateSource.Game ? StateSource.Game : settings.Source; From 5464fa3a0f631dac13a57a32021e6289de32bac4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Nov 2024 14:27:32 +0100 Subject: [PATCH 516/786] 1.3.4.0 --- Glamourer/Gui/GlamourerChangelog.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 4826839..57f7343 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -37,6 +37,7 @@ public class GlamourerChangelog Add1_3_1_0(Changelog); Add1_3_2_0(Changelog); Add1_3_3_0(Changelog); + Add1_3_4_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -57,6 +58,12 @@ public class GlamourerChangelog } } + private static void Add1_3_4_0(Changelog log) + => log.NextVersion("Version 1.3.4.0") + .RegisterEntry("Glamourer has been updated for Dalamud API 11 and patch 7.1.") + .RegisterEntry("Maybe fixed issues with shared weapon types and reset designs.") + .RegisterEntry("Fixed issues with resetting advanced dyes and certain weapon types"); + private static void Add1_3_3_0(Changelog log) => log.NextVersion("Version 1.3.3.0") .RegisterHighlight("Added the option to create automations for owned human NPCs (like trust avatars).") From 3722df199bb0c265cf64e2ab561814786d4180b8 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 22 Nov 2024 13:29:49 +0000 Subject: [PATCH 517/786] [CI] Updating repo.json for 1.3.4.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index d9d03e8..38fafa4 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.3.0", - "TestingAssemblyVersion": "1.3.3.3", + "AssemblyVersion": "1.3.4.0", + "TestingAssemblyVersion": "1.3.4.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.3.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.3.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 9ed8c9517bfabc05c2620a885d4abbc8630e54ac Mon Sep 17 00:00:00 2001 From: Ottermandias <70807659+Ottermandias@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:44:29 +0100 Subject: [PATCH 518/786] Update repo.json --- repo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo.json b/repo.json index 38fafa4..2fe08e4 100644 --- a/repo.json +++ b/repo.json @@ -21,7 +21,7 @@ "TestingAssemblyVersion": "1.3.4.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 10, + "DalamudApiLevel": 11, "TestingDalamudApiLevel": 11, "IsHide": "False", "IsTestingExclusive": "False", From 40a684ff465f9b778598bb0ed7050f05466995e4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 23 Nov 2024 13:58:16 +0100 Subject: [PATCH 519/786] Fix CalculateHeight. --- Glamourer/Interop/ScalingService.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 2f7ba66..141d5f2 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -18,7 +18,7 @@ public unsafe class ScalingService : IDisposable _setupMountHook = interop.HookFromAddress((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); _calculateHeightHook = - interop.HookFromAddress((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); + interop.HookFromAddress((nint)ModelContainer.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); _setupMountHook.Enable(); _updateOrnamentHook.Enable(); @@ -37,7 +37,7 @@ public unsafe class ScalingService : IDisposable private delegate void SetupMount(MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4); private delegate void UpdateOrnament(OrnamentContainer* ornament); private delegate void PlaceMinion(Companion* character); - private delegate float CalculateHeight(Character* character); + private delegate float CalculateHeight(ModelContainer* character); private readonly Hook _setupMountHook; @@ -85,12 +85,12 @@ public unsafe class ScalingService : IDisposable } } - private float CalculateHeightDetour(Character* character) + private float CalculateHeightDetour(ModelContainer* container) { - var (gender, bodyType, clan, height) = GetHeightRelevantCustomize(character); - SetHeightCustomize(character, character->GameObject.DrawObject); - var ret = _calculateHeightHook.Original(character); - SetHeightCustomize(character, gender, bodyType, clan, height); + var (gender, bodyType, clan, height) = GetHeightRelevantCustomize(container->OwnerObject); + SetHeightCustomize(container->OwnerObject, container->OwnerObject->DrawObject); + var ret = _calculateHeightHook.Original(container); + SetHeightCustomize(container->OwnerObject, gender, bodyType, clan, height); return ret; } From 22c7e32760bdc22f608ae78cae4ae274ddcdfeae Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Nov 2024 12:18:04 +0100 Subject: [PATCH 520/786] Fix some context menu things. --- Glamourer/Interop/ContextMenuService.cs | 47 +++++++++++++++++++------ 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index eeee70a..71a9280 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -11,27 +11,24 @@ namespace Glamourer.Interop; public class ContextMenuService : IDisposable { - public const int ItemSearchContextItemId = 0x1738; - public const int ChatLogContextItemId = 0x950; + public const int ChatLogContextItemId = 0x958; private readonly ItemManager _items; private readonly IContextMenu _contextMenu; private readonly StateManager _state; private readonly ObjectManager _objects; - private readonly IGameGui _gameGui; private EquipItem _lastItem; private readonly StainId[] _lastStains = new StainId[StainId.NumStains]; private readonly MenuItem _inventoryItem; - public ContextMenuService(ItemManager items, StateManager state, ObjectManager objects, IGameGui gameGui, Configuration config, + public ContextMenuService(ItemManager items, StateManager state, ObjectManager objects, Configuration config, IContextMenu context) { _contextMenu = context; _items = items; _state = state; _objects = objects; - _gameGui = gameGui; if (config.EnableGameContextMenu) Enable(); @@ -55,7 +52,7 @@ public class ContextMenuService : IDisposable if (arg.TargetItem.HasValue && HandleItem(arg.TargetItem.Value.ItemId)) { for (var i = 0; i < arg.TargetItem.Value.Stains.Length; ++i) - _lastStains[i] = (StainId)arg.TargetItem.Value.Stains[i]; + _lastStains[i] = arg.TargetItem.Value.Stains[i]; args.AddMenuItem(_inventoryItem); } } @@ -72,8 +69,8 @@ public class ContextMenuService : IDisposable } case "ChatLog": { - var agent = _gameGui.FindAgentInterface("ChatLog"); - if (agent == nint.Zero || !ValidateChatLogContext(agent)) + var agent = AgentChatLog.Instance(); + if (agent == null || !ValidateChatLogContext(agent)) return; if (HandleItem(*(ItemId*)(agent + ChatLogContextItemId))) @@ -83,6 +80,36 @@ public class ContextMenuService : IDisposable args.AddMenuItem(_inventoryItem); } + break; + } + case "RecipeNote": + { + var agent = AgentRecipeNote.Instance(); + if (agent == null) + return; + + if (HandleItem(agent->ContextMenuResultItemId)) + { + for (var i = 0; i < _lastStains.Length; ++i) + _lastStains[i] = 0; + args.AddMenuItem(_inventoryItem); + } + + break; + } + case "InclusionShop": + { + var agent = AgentRecipeItemContext.Instance(); + if (agent == null) + return; + + if (HandleItem(agent->ResultItemId)) + { + for (var i = 0; i < _lastStains.Length; ++i) + _lastStains[i] = 0; + args.AddMenuItem(_inventoryItem); + } + break; } } @@ -125,6 +152,6 @@ public class ContextMenuService : IDisposable return _items.ItemData.TryGetValue(itemId, EquipSlot.MainHand, out _lastItem); } - private static unsafe bool ValidateChatLogContext(nint agent) - => *(uint*)(agent + ChatLogContextItemId + 8) == 3; + private static unsafe bool ValidateChatLogContext(AgentChatLog* agent) + => *(&agent->ContextItemId + 8) == 3; } From 7f95726cc30748e27b1987915af508f387e15a9e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 25 Nov 2024 16:53:33 +0100 Subject: [PATCH 521/786] Do not use DX calls to read color tables except in UI. --- Glamourer/State/StateApplier.cs | 27 ++++++++++++++++----------- Glamourer/State/StateManager.cs | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 6ba6c06..2e086d6 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -309,27 +309,31 @@ public class StateApplier( return data; } - public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, ColorRow? value, bool force) + public unsafe void ChangeMaterialValue(ActorState state, ActorData data, MaterialValueIndex changedIndex, ColorRow? changedValue, + bool force) { if (!force && !_config.UseAdvancedDyes) return; foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) { - if (!index.TryGetTexture(actor, out var texture, out var mode)) + if (!changedIndex.TryGetTexture(actor, out var texture)) continue; - if (!_directX.TryGetColorTable(*texture, out var table)) + if (!PrepareColorSet.TryGetColorTable(actor, changedIndex, out var baseTable, out var mode)) continue; - if (value.HasValue) - value.Value.Apply(ref table[index.RowIndex], mode); - else if (PrepareColorSet.TryGetColorTable(actor, index, out var baseTable, out _)) - table[index.RowIndex] = baseTable[index.RowIndex]; - else - continue; + foreach (var (index, value) in state.Materials.GetValues( + MaterialValueIndex.Min(changedIndex.DrawObject, changedIndex.SlotIndex, changedIndex.MaterialIndex), + MaterialValueIndex.Max(changedIndex.DrawObject, changedIndex.SlotIndex, changedIndex.MaterialIndex))) + { + if (index == changedIndex.Key) + changedValue?.Apply(ref baseTable[changedIndex.RowIndex], mode); + else + value.Model.Apply(ref baseTable[MaterialValueIndex.FromKey(index).RowIndex], mode); + } - _directX.ReplaceColorTable(texture, table); + _directX.ReplaceColorTable(texture, baseTable); } } @@ -337,7 +341,8 @@ public class StateApplier( { var data = GetData(state); if (apply) - ChangeMaterialValue(data, index, state.Materials.TryGetValue(index, out var v) ? v.Model : null, state.IsLocked); + ChangeMaterialValue(state, data, index, state.Materials.TryGetValue(index, out var v) ? v.Model : null, state.IsLocked); + return data; } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index f16dd22..eabaf2f 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -293,7 +293,7 @@ public sealed class StateManager( { actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); foreach (var (idx, mat) in state.Materials.Values) - Applier.ChangeMaterialValue(actors, MaterialValueIndex.FromKey(idx), mat.Game, true); + Applier.ChangeMaterialValue(state, actors, MaterialValueIndex.FromKey(idx), mat.Game, true); } state.Materials.Clear(); From 9e09d64c66b75f04d7643456b26b9325e9f65134 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 25 Nov 2024 16:00:32 +0000 Subject: [PATCH 522/786] [CI] Updating repo.json for 1.3.4.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 2fe08e4..6398610 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.4.0", - "TestingAssemblyVersion": "1.3.4.0", + "AssemblyVersion": "1.3.4.1", + "TestingAssemblyVersion": "1.3.4.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 66ed721105d0c8c021a858142a7e14e56bb5c8f3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 27 Nov 2024 23:07:42 +0100 Subject: [PATCH 523/786] Treat no Automation Set as empty automation set. --- Glamourer/Automation/AutoDesignApplier.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index ccaae21..e0a4c33 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -222,12 +222,11 @@ public sealed class AutoDesignApplier : IDisposable if (!_config.EnableAutoDesigns) return; - if (!GetPlayerSet(identifier, out var set)) - return; - if (reset) _state.ResetState(state, StateSource.Game); - Reduce(actor, state, set, false, false, out forcedRedraw); + + if (GetPlayerSet(identifier, out var set)) + Reduce(actor, state, set, false, false, out forcedRedraw); } public bool Reduce(Actor actor, ActorIdentifier identifier, [NotNullWhen(true)] out ActorState? state) @@ -284,7 +283,8 @@ public sealed class AutoDesignApplier : IDisposable return; var mergedDesign = _designMerger.Merge( - set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design.AllLinks.Select(l => (l.Design, l.Flags & d.Type, d.Jobs.Flags))), + 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)); From 533c53fd8d51801f6c40f407802bb111bfcf25eb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 27 Nov 2024 23:28:56 +0100 Subject: [PATCH 524/786] Fix some issues with Crests --- Glamourer/Glamourer.cs | 2 +- Glamourer/Interop/CrestService.cs | 25 +++++++++++++++++++ .../Interop/Penumbra/ModSettingApplier.cs | 2 +- Penumbra.GameData | 2 +- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index b4489e8..93173ba 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -139,7 +139,7 @@ public class Glamourer : IDalamudPlugin ReadOnlySpan relevantPlugins = [ "Penumbra", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge", - "LoporritSync", + "LoporritSync", "GagSpeak", "RoleplayingVoiceDalamud", ]; var plugins = _services.GetService().InstalledPlugins .GroupBy(p => p.InternalName) diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 8e217b6..95b3587 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Event; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; using Penumbra.GameData; @@ -37,6 +38,7 @@ public sealed unsafe class CrestService : EventWrapperRef3 _crestChangeCallerHook = null!; + + private delegate void CrestChangeCallerDelegate(DrawDataContainer* container, byte* data); + + private void CrestChangeCallerDetour(DrawDataContainer* container, byte* data) + { + var actor = (Actor)container->OwnerObject; + ref var flags = ref data[16]; + foreach (var slot in CrestExtensions.AllRelevantSet) + { + var newValue = ((CrestFlag)flags).HasFlag(slot); + Invoke(actor, slot, ref newValue); + flags = (byte)(newValue ? flags | (byte)slot : flags & (byte)~slot); + } + Glamourer.Log.Verbose( + $"Called inlined CrestChange via CrestChangeCaller on {(ulong)container:X} with {(flags & 0x1F):X} and prior flags {actor.CrestBitfield}."); + + using var _ = _inUpdate.EnterMethod(); + _crestChangeCallerHook.Original(container, data); + } + public static bool GetModelCrest(Actor gameObject, CrestFlag slot) { if (!gameObject.IsCharacter) diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index fcdc7b7..a6fe3e5 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -18,7 +18,7 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O if (!objects.TryGetValue(state.Identifier, out var data)) { Glamourer.Log.Verbose( - $"[Mod Applier] No mod settings applied because no actor for {state.Identifier} could be found to associate collection."); + $"[Mod Applier] No mod settings applied because no actor for {state.Identifier.Incognito(null)} could be found to associate collection."); return; } diff --git a/Penumbra.GameData b/Penumbra.GameData index 79d8d78..cceebd8 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 79d8d782b3b454a41f7f87f398806ec4d08d485f +Subproject commit cceebd89a19e27ac843b0a7d17d3c2bb41c77367 From 4215e0ad447cd425d6f9f4b27fe0d46db0cfd6ef Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 27 Nov 2024 23:44:30 +0100 Subject: [PATCH 525/786] Add default settings for design configuration. --- Glamourer/Configuration.cs | 9 +++++ Glamourer/Designs/Design.cs | 12 ++++--- Glamourer/Designs/DesignManager.cs | 36 +++++++++++-------- Glamourer/Designs/Links/LinkContainer.cs | 16 +++++++-- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 15 ++++++++ 5 files changed, 67 insertions(+), 21 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 165e20d..77fc84f 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -25,6 +25,13 @@ public enum HeightDisplayType OlympicPool, } +public class DefaultDesignSettings +{ + public bool AlwaysForceRedrawing = false; + public bool ResetAdvancedDyes = false; + public bool ShowQuickDesignBar = true; +} + public class Configuration : IPluginConfiguration, ISavable { [JsonIgnore] @@ -59,6 +66,8 @@ public class Configuration : IPluginConfiguration, ISavable public bool AllowDoubleClickToApply { get; set; } = false; public bool RespectManualOnAutomationUpdate { get; set; } = false; + public DefaultDesignSettings DefaultDesignSettings { get; set; } = new(); + public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre; public RenameField ShowRename { get; set; } = RenameField.BothDataPrio; public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index c58d74f..6cc9eef 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -28,10 +28,14 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn internal Design(Design other) : base(other) { - Tags = [.. other.Tags]; - Description = other.Description; - QuickDesign = other.QuickDesign; - AssociatedMods = new SortedList(other.AssociatedMods); + Tags = [.. other.Tags]; + Description = other.Description; + QuickDesign = other.QuickDesign; + ForcedRedraw = other.ForcedRedraw; + ResetAdvancedDyes = other.ResetAdvancedDyes; + Color = other.Color; + AssociatedMods = new SortedList(other.AssociatedMods); + Links = Links.Clone(); } // Metadata diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 15e38ee..b889649 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -99,11 +99,14 @@ public sealed class DesignManager : DesignEditor var (actualName, path) = ParseName(name, handlePath); var design = new Design(Customizations, Items) { - CreationDate = DateTimeOffset.UtcNow, - LastEdit = DateTimeOffset.UtcNow, - Identifier = CreateNewGuid(), - Name = actualName, - Index = Designs.Count, + CreationDate = DateTimeOffset.UtcNow, + LastEdit = DateTimeOffset.UtcNow, + Identifier = CreateNewGuid(), + Name = actualName, + Index = Designs.Count, + ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing, + ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes, + QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar, }; Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier}."); @@ -118,11 +121,14 @@ public sealed class DesignManager : DesignEditor var (actualName, path) = ParseName(name, handlePath); var design = new Design(clone) { - CreationDate = DateTimeOffset.UtcNow, - LastEdit = DateTimeOffset.UtcNow, - Identifier = CreateNewGuid(), - Name = actualName, - Index = Designs.Count, + CreationDate = DateTimeOffset.UtcNow, + LastEdit = DateTimeOffset.UtcNow, + Identifier = CreateNewGuid(), + Name = actualName, + Index = Designs.Count, + ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing, + ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes, + QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar, }; Designs.Add(design); @@ -138,11 +144,11 @@ public sealed class DesignManager : DesignEditor var (actualName, path) = ParseName(name, handlePath); var design = new Design(clone) { - CreationDate = DateTimeOffset.UtcNow, - LastEdit = DateTimeOffset.UtcNow, - Identifier = CreateNewGuid(), - Name = actualName, - Index = Designs.Count, + CreationDate = DateTimeOffset.UtcNow, + LastEdit = DateTimeOffset.UtcNow, + Identifier = CreateNewGuid(), + Name = actualName, + Index = Designs.Count, }; Designs.Add(design); Glamourer.Log.Debug( diff --git a/Glamourer/Designs/Links/LinkContainer.cs b/Glamourer/Designs/Links/LinkContainer.cs index ef67688..6cfc121 100644 --- a/Glamourer/Designs/Links/LinkContainer.cs +++ b/Glamourer/Designs/Links/LinkContainer.cs @@ -14,6 +14,16 @@ public sealed class LinkContainer : List public new int Count => base.Count + After.Count; + public LinkContainer Clone() + { + var ret = new LinkContainer(); + ret.EnsureCapacity(base.Count); + ret.After.EnsureCapacity(After.Count); + ret.AddRange(this); + ret.After.AddRange(After); + return ret; + } + public bool Reorder(int fromIndex, LinkOrder fromOrder, int toIndex, LinkOrder toOrder) { var fromList = fromOrder switch @@ -89,13 +99,15 @@ public sealed class LinkContainer : List if (GetAllLinks(parent).Any(l => l.Link.Link == child && l.Order != order)) { - error = $"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the parent already links to the child in the opposite direction."; + error = + $"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the parent already links to the child in the opposite direction."; return false; } if (GetAllLinks(child).Any(l => l.Link.Link == parent && l.Order == order)) { - error = $"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the child already links to the parent in the opposite direction."; + error = + $"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the child already links to the parent in the opposite direction."; return false; } diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 1540696..052c0b8 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -10,6 +10,7 @@ using Glamourer.Interop.PalettePlus; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Text; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.SettingsTab; @@ -51,6 +52,7 @@ public class SettingsTab( using (ImRaii.Child("SettingsChild")) { DrawBehaviorSettings(); + DrawDesignDefaultSettings(); DrawInterfaceSettings(); DrawColorSettings(); overrides.Draw(); @@ -101,6 +103,19 @@ public class SettingsTab( ImGui.NewLine(); } + private void DrawDesignDefaultSettings() + { + if (!ImUtf8.CollapsingHeader("Design Defaults")) + return; + + Checkbox("Show in Quick Design Bar", "Newly created designs will be shown in the quick design bar by default.", + config.DefaultDesignSettings.ShowQuickDesignBar, v => config.DefaultDesignSettings.ShowQuickDesignBar = v); + Checkbox("Reset Advanced Dyes", "Newly created designs will be configured to reset advanced dyes on application by default.", + config.DefaultDesignSettings.ResetAdvancedDyes, v => config.DefaultDesignSettings.ResetAdvancedDyes = v); + Checkbox("Always Force Redraw", "Newly created designs will be configured to force character redraws on application by default.", + config.DefaultDesignSettings.AlwaysForceRedrawing, v => config.DefaultDesignSettings.AlwaysForceRedrawing = v); + } + private void DrawInterfaceSettings() { if (!ImGui.CollapsingHeader("Interface")) From 467dc2c22f7d4d6ffa1f1d6fd5737a1214eec467 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 29 Nov 2024 16:36:00 +0000 Subject: [PATCH 526/786] [CI] Updating repo.json for 1.3.4.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 6398610..186f396 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.4.1", - "TestingAssemblyVersion": "1.3.4.1", + "AssemblyVersion": "1.3.4.2", + "TestingAssemblyVersion": "1.3.4.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c9febe2c74d3ed7c94eaed5f4c59504842444108 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 30 Nov 2024 22:11:06 +0100 Subject: [PATCH 527/786] Try to make random designs in automation stick around when redrawing/changing zone. --- Glamourer/Automation/AutoDesignApplier.cs | 16 ++++++++-------- Glamourer/Designs/Design.cs | 2 +- Glamourer/Designs/IDesignStandIn.cs | 2 +- Glamourer/Designs/Special/QuickSelectedDesign.cs | 4 ++-- Glamourer/Designs/Special/RandomDesign.cs | 16 ++++++++-------- Glamourer/Designs/Special/RevertDesign.cs | 4 ++-- Glamourer/State/StateEditor.cs | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index e0a4c33..8e0b75c 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -152,7 +152,7 @@ public sealed class AutoDesignApplier : IDisposable { if (_state.GetOrCreate(id, data.Objects[0], out var state)) { - Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false, out var forcedRedraw); + Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw); foreach (var actor in data.Objects) _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); } @@ -164,7 +164,7 @@ 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, out var forcedRedraw); + Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw); _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); } } @@ -212,7 +212,7 @@ public sealed class AutoDesignApplier : IDisposable var respectManual = state.LastJob == newJob.Id; state.LastJob = actor.Job; - Reduce(actor, state, set, respectManual, true, out var forcedRedraw); + Reduce(actor, state, set, respectManual, true, true, out var forcedRedraw); _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); } @@ -226,7 +226,7 @@ public sealed class AutoDesignApplier : IDisposable _state.ResetState(state, StateSource.Game); if (GetPlayerSet(identifier, out var set)) - Reduce(actor, state, set, false, false, out forcedRedraw); + Reduce(actor, state, set, false, false, false, out forcedRedraw); } public bool Reduce(Actor actor, ActorIdentifier identifier, [NotNullWhen(true)] out ActorState? state) @@ -253,11 +253,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, out _); + Reduce(actor, state, set, respectManual, false, false, out _); return true; } - private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange, out bool forcedRedraw) + private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange, bool newApplication, out bool forcedRedraw) { if (set.BaseState is AutoDesignSet.Base.Game) { @@ -284,7 +284,7 @@ public sealed class AutoDesignApplier : IDisposable var mergedDesign = _designMerger.Merge( set.Designs.Where(d => d.IsActive(actor)) - .SelectMany(d => d.Design.AllLinks.Select(l => (l.Design, l.Flags & d.Type, d.Jobs.Flags))), + .SelectMany(d => d.Design.AllLinks(newApplication).Select(l => (l.Design, l.Flags & d.Type, d.Jobs.Flags))), state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods); _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false)); @@ -337,7 +337,7 @@ public sealed class AutoDesignApplier : IDisposable var respectManual = prior == id; NewGearsetId = id; - Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob, out var forcedRedraw); + Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob, prior == id, out var forcedRedraw); NewGearsetId = -1; foreach (var actor in data.Objects) _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 6cc9eef..bafffc9 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -58,7 +58,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public string Incognito => Identifier.ToString()[..8]; - public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks(bool newApplication) => LinkContainer.GetAllLinks(this).Select(t => ((IDesignStandIn)t.Link.Link, t.Link.Type, JobFlag.All)); #endregion diff --git a/Glamourer/Designs/IDesignStandIn.cs b/Glamourer/Designs/IDesignStandIn.cs index fd76b4b..02baee4 100644 --- a/Glamourer/Designs/IDesignStandIn.cs +++ b/Glamourer/Designs/IDesignStandIn.cs @@ -16,7 +16,7 @@ public interface IDesignStandIn : IEquatable public string SerializeName(); public StateSource AssociatedSource(); - public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks { get; } + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks(bool newApplication); public void AddData(JObject jObj); diff --git a/Glamourer/Designs/Special/QuickSelectedDesign.cs b/Glamourer/Designs/Special/QuickSelectedDesign.cs index 1919929..31fb40f 100644 --- a/Glamourer/Designs/Special/QuickSelectedDesign.cs +++ b/Glamourer/Designs/Special/QuickSelectedDesign.cs @@ -39,8 +39,8 @@ public class QuickSelectedDesign(QuickDesignCombo combo) : IDesignStandIn, IServ public StateSource AssociatedSource() => StateSource.Manual; - public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks - => combo.Design?.AllLinks ?? []; + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks(bool newApplication) + => combo.Design?.AllLinks(newApplication) ?? []; public void AddData(JObject jObj) { } diff --git a/Glamourer/Designs/Special/RandomDesign.cs b/Glamourer/Designs/Special/RandomDesign.cs index bbb9b7d..13d914a 100644 --- a/Glamourer/Designs/Special/RandomDesign.cs +++ b/Glamourer/Designs/Special/RandomDesign.cs @@ -46,17 +46,17 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn public StateSource AssociatedSource() => StateSource.Manual; - public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks(bool newApplication) { - get - { + if (newApplication) _currentDesign = rng.Design(Predicates); - if (_currentDesign == null) - yield break; + else + _currentDesign ??= rng.Design(Predicates); + if (_currentDesign == null) + yield break; - foreach (var (link, type, jobs) in _currentDesign.AllLinks) - yield return (link, type, jobs); - } + foreach (var (link, type, jobs) in _currentDesign.AllLinks(newApplication)) + yield return (link, type, jobs); } public void AddData(JObject jObj) diff --git a/Glamourer/Designs/Special/RevertDesign.cs b/Glamourer/Designs/Special/RevertDesign.cs index 5f8d8c6..8704339 100644 --- a/Glamourer/Designs/Special/RevertDesign.cs +++ b/Glamourer/Designs/Special/RevertDesign.cs @@ -29,9 +29,9 @@ public class RevertDesign : IDesignStandIn public StateSource AssociatedSource() => StateSource.Game; - public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks + public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks(bool _) { - get { yield return (this, ApplicationType.All, JobFlag.All); } + yield return (this, ApplicationType.All, JobFlag.All); } public void AddData(JObject jObj) diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 59f27a2..f9ddb89 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -437,7 +437,7 @@ public class StateEditor( if (!settings.MergeLinks || design is not Design d) merged = new MergedDesign(design); else - merged = merger.Merge(d.AllLinks, state.ModelData.IsHuman ? state.ModelData.Customize : CustomizeArray.Default, state.BaseData, + merged = merger.Merge(d.AllLinks(true), state.ModelData.IsHuman ? state.ModelData.Customize : CustomizeArray.Default, state.BaseData, false, Config.AlwaysApplyAssociatedMods); ApplyDesign(data, merged, settings with From 31cd8125194380e5ba7f099d18455c1c654eec62 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 9 Dec 2024 21:23:33 +0100 Subject: [PATCH 528/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index cceebd8..da74a4b 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit cceebd89a19e27ac843b0a7d17d3c2bb41c77367 +Subproject commit da74a4be9c9728c6c52134c42603cd8a7040c568 From 27e9223c814ee0071639e7340750532a0d78c8ae Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 9 Dec 2024 21:29:35 +0100 Subject: [PATCH 529/786] Again --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index da74a4b..fb692d1 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit da74a4be9c9728c6c52134c42603cd8a7040c568 +Subproject commit fb692d13205fed5e6c5f4c939477c28473198a3b From f424857bd399ef7182b2b568272905ba01aaa25d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 13 Dec 2024 15:42:49 +0100 Subject: [PATCH 530/786] Again. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index fb692d1..315258f 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit fb692d13205fed5e6c5f4c939477c28473198a3b +Subproject commit 315258f4f8a59d744aa4d2d1f8c31d410d041729 From c951854d5a6cc2c76deccbf4ce307b9f23214f5c Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 13 Dec 2024 17:14:14 +0000 Subject: [PATCH 531/786] [CI] Updating repo.json for 1.3.4.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 186f396..f7d8512 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.4.2", - "TestingAssemblyVersion": "1.3.4.2", + "AssemblyVersion": "1.3.4.3", + "TestingAssemblyVersion": "1.3.4.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From b90e68fbafb5d9e4c0b26f4afa3e7508e14aed35 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Dec 2024 14:17:47 +0100 Subject: [PATCH 532/786] Fix inverted application of apply flags in IPC --- Glamourer/Api/StateApi.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 385cf3e..331942b 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -322,8 +322,8 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable 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), + string s => _converter.FromBase64(s, (flags & ApplyFlag.Customization) != 0, (flags & ApplyFlag.Equipment) != 0, out version), + JObject j => _converter.FromJObject(j, (flags & ApplyFlag.Customization) != 0, (flags & ApplyFlag.Equipment) != 0), _ => null, }; } From f3eb5429407fc427983f971df5fc0458b06bc299 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Dec 2024 14:18:34 +0100 Subject: [PATCH 533/786] Add option to always reset random designs. --- Glamourer/Designs/Special/RandomDesign.cs | 28 +++++++++++++------ Glamourer/Glamourer.cs | 2 +- .../AutomationTab/RandomRestrictionDrawer.cs | 6 ++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Glamourer/Designs/Special/RandomDesign.cs b/Glamourer/Designs/Special/RandomDesign.cs index 13d914a..a54ffcf 100644 --- a/Glamourer/Designs/Special/RandomDesign.cs +++ b/Glamourer/Designs/Special/RandomDesign.cs @@ -12,7 +12,8 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn public const string ResolvedName = "Random"; private Design? _currentDesign; - public IReadOnlyList Predicates { get; private set; } = []; + public IReadOnlyList Predicates { get; private set; } = []; + public bool ResetOnRedraw { get; set; } = false; public string ResolveName(bool _) => ResolvedName; @@ -40,6 +41,7 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn public bool Equals(IDesignStandIn? other) => other is RandomDesign r + && r.ResetOnRedraw == ResetOnRedraw && string.Equals(RandomPredicate.GeneratePredicateString(r.Predicates), RandomPredicate.GeneratePredicateString(Predicates), StringComparison.OrdinalIgnoreCase); @@ -48,7 +50,7 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks(bool newApplication) { - if (newApplication) + if (newApplication || ResetOnRedraw) _currentDesign = rng.Design(Predicates); else _currentDesign ??= rng.Design(Predicates); @@ -61,22 +63,32 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn public void AddData(JObject jObj) { - jObj["Restrictions"] = RandomPredicate.GeneratePredicateString(Predicates); + jObj["Restrictions"] = RandomPredicate.GeneratePredicateString(Predicates); + jObj["ResetOnRedraw"] = ResetOnRedraw; } public void ParseData(JObject jObj) { var restrictions = jObj["Restrictions"]?.ToObject() ?? string.Empty; - Predicates = RandomPredicate.GeneratePredicates(restrictions); + Predicates = RandomPredicate.GeneratePredicates(restrictions); + ResetOnRedraw = jObj["ResetOnRedraw"]?.ToObject() ?? false; } public bool ChangeData(object data) { - if (data is not List predicates) - return false; + if (data is List predicates) + { + Predicates = predicates; + return true; + } - Predicates = predicates; - return true; + if (data is bool resetOnRedraw) + { + ResetOnRedraw = resetOnRedraw; + return true; + } + + return false; } public bool ForcedRedraw diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 93173ba..9c4583f 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -139,7 +139,7 @@ public class Glamourer : IDalamudPlugin ReadOnlySpan relevantPlugins = [ "Penumbra", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge", - "LoporritSync", "GagSpeak", "RoleplayingVoiceDalamud", + "LoporritSync", "GagSpeak", "ProjectGagSpeak", "RoleplayingVoiceDalamud", ]; var plugins = _services.GetService().InstalledPlugins .GroupBy(p => p.InternalName) diff --git a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs index f125f36..e7efc09 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs @@ -8,6 +8,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; namespace Glamourer.Gui.Tabs.AutomationTab; @@ -385,6 +386,11 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ImGui.GetStyle().WindowPadding.Y + ImGuiHelpers.GlobalScale); ImGui.Separator(); ImGui.Dummy(Vector2.Zero); + var reset = random.ResetOnRedraw; + if (ImUtf8.Checkbox("Reset Chosen Design On Every Redraw"u8, ref reset)) + _autoDesignManager.ChangeData(_set!, _designIndex, reset); + ImGui.Separator(); + ImGui.Dummy(Vector2.Zero); var list = random.Predicates.ToList(); if (list.Count == 0) From 33bf5c53920194876a726bbe356b6242db4faa7d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Dec 2024 14:18:51 +0100 Subject: [PATCH 534/786] Update GameData versioning. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 315258f..983b018 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 315258f4f8a59d744aa4d2d1f8c31d410d041729 +Subproject commit 983b0184830bfe4336651ba94175526c0f891e10 From a1162439007212d7bf3cbb198ae7f6ad69215d1c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Dec 2024 14:29:32 +0100 Subject: [PATCH 535/786] Maybe fix disabling auto designs. --- Glamourer/Automation/AutoDesignApplier.cs | 21 +++++++++++++++---- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 10 +++++++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 8e0b75c..e506d9f 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -55,6 +55,15 @@ public sealed class AutoDesignApplier : IDisposable _equippedGearset.Subscribe(OnEquippedGearset, EquippedGearset.Priority.AutoDesignApplier); } + public void OnEnableAutoDesignsChanged(bool value) + { + if (value) + return; + + foreach (var state in _state.Values) + state.Sources.RemoveFixedDesignSources(); + } + public void Dispose() { _weapons.Unsubscribe(OnWeaponLoading); @@ -234,9 +243,6 @@ public sealed class AutoDesignApplier : IDisposable AutoDesignSet set; if (!_state.TryGetValue(identifier, out state)) { - if (!_config.EnableAutoDesigns) - return false; - if (!GetPlayerSet(identifier, out set!)) return false; @@ -257,7 +263,8 @@ public sealed class AutoDesignApplier : IDisposable return true; } - private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange, bool newApplication, out bool forcedRedraw) + private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange, bool newApplication, + out bool forcedRedraw) { if (set.BaseState is AutoDesignSet.Base.Game) { @@ -294,6 +301,12 @@ public sealed class AutoDesignApplier : IDisposable /// Get world-specific first and all-world afterward. private bool GetPlayerSet(ActorIdentifier identifier, [NotNullWhen(true)] out AutoDesignSet? set) { + if (!_config.EnableAutoDesigns) + { + set = null; + return false; + } + switch (identifier.Type) { case IdentifierType.Player: diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 052c0b8..c6d69fd 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -3,6 +3,7 @@ using Dalamud.Interface; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; +using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; @@ -27,7 +28,8 @@ public class SettingsTab( PalettePlusChecker paletteChecker, CollectionOverrideDrawer overrides, CodeDrawer codeDrawer, - Glamourer glamourer) + Glamourer glamourer, + AutoDesignApplier autoDesignApplier) : ITab { private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); @@ -43,7 +45,11 @@ public class SettingsTab( Checkbox("Enable Auto Designs", "Enable the application of designs associated to characters in the Automation tab to be applied automatically.", - config.EnableAutoDesigns, v => config.EnableAutoDesigns = v); + config.EnableAutoDesigns, v => + { + config.EnableAutoDesigns = v; + autoDesignApplier.OnEnableAutoDesignsChanged(v); + }); ImGui.NewLine(); ImGui.NewLine(); ImGui.NewLine(); From 664b42eee849cf0c0f4cb2d68403372f03c7043f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 17 Dec 2024 18:05:07 +0100 Subject: [PATCH 536/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 983b018..6848397 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 983b0184830bfe4336651ba94175526c0f891e10 +Subproject commit 6848397dd77cfcdbff1accd860d5b7e95f8c9fe5 From 56d014e14fb8527d06e5221e51404bb730ed3443 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 25 Dec 2024 22:21:58 +0100 Subject: [PATCH 537/786] Fix ImGui Assertion. --- Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs | 2 +- OtterGui | 2 +- Penumbra.GameData | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 2e5524f..2c797b8 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -295,7 +295,7 @@ public partial class CustomizationDrawer private void ApplyCheckbox(CustomizeIndex index) { - SetId(index); + using var id = SetId(index); if (UiHelpers.DrawCheckbox("##apply", $"Apply the {_currentOption} customization in this design.", _currentApply, out _, _locked)) ToggleApply(); } diff --git a/OtterGui b/OtterGui index 95b8d17..d9caded 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 95b8d177883b03f804d77434f45e9de97fdb9adf +Subproject commit d9caded5efb7c9db0a273a43bb5f6d53cf4ace7f diff --git a/Penumbra.GameData b/Penumbra.GameData index 6848397..ffc149c 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 6848397dd77cfcdbff1accd860d5b7e95f8c9fe5 +Subproject commit ffc149cc8c169c2c6e838cbd138676f6fe4daeea From ebd3fb3fbfb6ff9981e399b0d09c2a5c89decb3c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 29 Dec 2024 13:38:21 +0100 Subject: [PATCH 538/786] Update submodules. --- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Penumbra.Api b/Penumbra.Api index 97e9f42..882b778 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 97e9f427406f82a59ddef764b44ecea654a51623 +Subproject commit 882b778e78bb0806dd7d38e8b3670ff138a84a31 diff --git a/Penumbra.GameData b/Penumbra.GameData index ffc149c..19355cf 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit ffc149cc8c169c2c6e838cbd138676f6fe4daeea +Subproject commit 19355cfa0ec80e8d5a91de11ecffc49257b37b53 diff --git a/Penumbra.String b/Penumbra.String index f04abba..0647fbc 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit f04abbabedf5e757c5cbb970f3e513fef23e53cf +Subproject commit 0647fbc5017ef9ced3f3ce1c2496eefd57c5b7a8 From 70cf21cf57040fa54b8a7a55dc3507bba8b33c95 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 29 Dec 2024 15:48:54 +0100 Subject: [PATCH 539/786] Accept more colors in designs as valid (independently of clan/gender and mixing highlights/hair and both eyes). --- Glamourer/GameData/CustomizeSet.cs | 14 ++++-- Glamourer/GameData/CustomizeSetFactory.cs | 9 +--- Glamourer/GameData/NpcCustomizeSet.cs | 32 +++++++++++-- .../DebugTab/CustomizationServicePanel.cs | 48 ++++++++++++++++++- 4 files changed, 85 insertions(+), 18 deletions(-) diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index 7fcf1d2..178ef07 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -11,12 +11,15 @@ namespace Glamourer.GameData; /// public class CustomizeSet { - internal CustomizeSet(SubRace clan, Gender gender) + private NpcCustomizeSet _npcCustomizations; + + internal CustomizeSet(NpcCustomizeSet npcCustomizations, SubRace clan, Gender gender) { - Gender = gender; - Clan = clan; - Race = clan.ToRace(); - SettingAvailable = 0; + _npcCustomizations = npcCustomizations; + Gender = gender; + Clan = clan; + Race = clan.ToRace(); + SettingAvailable = 0; } public Gender Gender { get; } @@ -85,6 +88,7 @@ public class CustomizeSet { if (IsAvailable(index)) return DataByValue(index, value, out custom, face) >= 0 + || _npcCustomizations.CheckColor(index, value) || NpcOptions.Any(t => t.Type == index && t.Value == value); custom = null; diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index e2291d4..13a9865 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -1,6 +1,5 @@ using Dalamud.Game; using Dalamud.Plugin.Services; -using Dalamud.Utility; using Lumina.Excel; using Lumina.Excel.Sheets; using OtterGui.Classes; @@ -29,7 +28,7 @@ internal class CustomizeSetFactory( var row = _charaMakeSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!; var hrothgar = race.ToRace() == Race.Hrothgar; // Create the initial set with all the easily accessible parameters available for anyone. - var set = new CustomizeSet(race, gender) + var set = new CustomizeSet(_npcCustomizeSet, race, gender) { Name = GetName(race, gender), Voices = row.VoiceStruct, @@ -77,13 +76,7 @@ internal class CustomizeSetFactory( CustomizeIndex.Hairstyle, CustomizeIndex.LipColor, CustomizeIndex.SkinColor, - CustomizeIndex.FacePaintColor, - CustomizeIndex.HighlightsColor, - CustomizeIndex.HairColor, CustomizeIndex.FacePaint, - CustomizeIndex.TattooColor, - CustomizeIndex.EyeColorLeft, - CustomizeIndex.EyeColorRight, CustomizeIndex.TailShape, }; diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index 72ed4b4..4dbfd83 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -4,6 +4,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; using Lumina.Excel.Sheets; using OtterGui.Services; using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.GameData; @@ -35,6 +36,23 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList /// The list of data. private readonly List _data = []; + private readonly BitArray _hairColors = new(256); + private readonly BitArray _eyeColors = new(256); + private readonly BitArray _facepaintColors = new(256); + private readonly BitArray _tattooColors = new(256); + + public bool CheckColor(CustomizeIndex type, CustomizeValue value) + => type switch + { + CustomizeIndex.HairColor => _hairColors[value.Value], + CustomizeIndex.HighlightsColor => _hairColors[value.Value], + CustomizeIndex.EyeColorLeft => _eyeColors[value.Value], + CustomizeIndex.EyeColorRight => _eyeColors[value.Value], + CustomizeIndex.FacePaintColor => _facepaintColors[value.Value], + CustomizeIndex.TattooColor => _tattooColors[value.Value], + _ => false, + }; + /// Create the data when ready. public NpcCustomizeSet(IDataManager data, DictENpc eNpcs, DictBNpc bNpcs, DictBNpcNames bNpcNames) { @@ -147,6 +165,12 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList for (var i = 0; i < duplicates.Count; ++i) { var current = duplicates[i]; + _hairColors[current.Customize[CustomizeIndex.HairColor].Value] = true; + _hairColors[current.Customize[CustomizeIndex.HighlightsColor].Value] = true; + _eyeColors[current.Customize[CustomizeIndex.EyeColorLeft].Value] = true; + _eyeColors[current.Customize[CustomizeIndex.EyeColorRight].Value] = true; + _facepaintColors[current.Customize[CustomizeIndex.FacePaintColor].Value] = true; + _tattooColors[current.Customize[CustomizeIndex.TattooColor].Value] = true; for (var j = 0; j < i; ++j) { if (current.DataEquals(duplicates[j])) @@ -195,8 +219,8 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList data.Set(7, row.ModelWrists | (row.DyeWrists.RowId << 24) | ((ulong)row.Dye2Wrists.RowId << 32)); data.Set(8, row.ModelRightRing | (row.DyeRightRing.RowId << 24) | ((ulong)row.Dye2RightRing.RowId << 32)); data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.RowId << 24) | ((ulong)row.Dye2LeftRing.RowId << 32)); - data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.RowId << 48) | ((ulong)row.Dye2MainHand.RowId << 56)); - data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56)); + data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.RowId << 48) | ((ulong)row.Dye2MainHand.RowId << 56)); + data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56)); data.VisorToggled = row.Visor; } @@ -213,8 +237,8 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList data.Set(7, row.ModelWrists | (row.DyeWrists.RowId << 24) | ((ulong)row.Dye2Wrists.RowId << 32)); data.Set(8, row.ModelRightRing | (row.DyeRightRing.RowId << 24) | ((ulong)row.Dye2RightRing.RowId << 32)); data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.RowId << 24) | ((ulong)row.Dye2LeftRing.RowId << 32)); - data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.RowId << 48) | ((ulong)row.Dye2MainHand.RowId << 56)); - data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56)); + data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.RowId << 48) | ((ulong)row.Dye2MainHand.RowId << 56)); + data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56)); data.VisorToggled = row.Visor; } diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index 17e180e..afc7d56 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -1,10 +1,13 @@ -using Glamourer.GameData; +using Dalamud.Interface; +using Glamourer.GameData; using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; @@ -24,6 +27,49 @@ public class CustomizationServicePanel(CustomizeService customize) : IGameDataDr DrawCustomizationInfo(set); DrawNpcCustomizationInfo(set); } + + DrawColorInfo(); + } + + private void DrawColorInfo() + { + using var tree = ImUtf8.TreeNode("NPC Colors"u8); + if (!tree) + return; + + using var table = ImUtf8.Table("data"u8, 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + if (!table) + return; + + ImGui.TableNextColumn(); + ImUtf8.TableHeader("Id"u8); + ImGui.TableNextColumn(); + ImUtf8.TableHeader("Hair"u8); + ImGui.TableNextColumn(); + ImUtf8.TableHeader("Eyes"u8); + ImGui.TableNextColumn(); + ImUtf8.TableHeader("Facepaint"u8); + ImGui.TableNextColumn(); + ImUtf8.TableHeader("Tattoos"u8); + + for (var i = 192; i < 256; ++i) + { + var index = new CustomizeValue((byte)i); + ImUtf8.DrawTableColumn($"{i:D3}"); + using var font = ImRaii.PushFont(UiBuilder.IconFont); + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.HairColor, index) + ? FontAwesomeIcon.Check.ToIconString() + : FontAwesomeIcon.Times.ToIconString()); + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.EyeColorLeft, index) + ? FontAwesomeIcon.Check.ToIconString() + : FontAwesomeIcon.Times.ToIconString()); + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.FacePaintColor, index) + ? FontAwesomeIcon.Check.ToIconString() + : FontAwesomeIcon.Times.ToIconString()); + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.TattooColor, index) + ? FontAwesomeIcon.Check.ToIconString() + : FontAwesomeIcon.Times.ToIconString()); + } } private void DrawCustomizationInfo(CustomizeSet set) From 71e80740f679ae68f5d5deec6969455af13665a6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 31 Dec 2024 13:20:36 +0100 Subject: [PATCH 540/786] Add selection designs to chat commands. --- Glamourer/Services/CommandService.cs | 130 +++++--------------- Glamourer/Services/DesignResolver.cs | 173 +++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 101 deletions(-) create mode 100644 Glamourer/Services/DesignResolver.cs diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 6fb32e2..cee4f57 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -6,6 +6,7 @@ using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.GameData; using Glamourer.Gui; +using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop.Penumbra; using Glamourer.State; using ImGuiNET; @@ -22,31 +23,30 @@ namespace Glamourer.Services; public class CommandService : IDisposable, IApiService { - private const string RandomString = "random"; private const string MainCommandString = "/glamourer"; private const string ApplyCommandString = "/glamour"; - private readonly ICommandManager _commands; - private readonly MainWindow _mainWindow; - private readonly IChatGui _chat; - private readonly ActorManager _actors; - private readonly ObjectManager _objects; - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly AutoDesignManager _autoDesignManager; - private readonly DesignManager _designManager; - private readonly DesignConverter _converter; - private readonly DesignFileSystem _designFileSystem; - private readonly Configuration _config; - private readonly ModSettingApplier _modApplier; - private readonly ItemManager _items; - private readonly RandomDesignGenerator _randomDesign; - private readonly CustomizeService _customizeService; + private readonly ICommandManager _commands; + private readonly MainWindow _mainWindow; + private readonly IChatGui _chat; + private readonly ActorManager _actors; + private readonly ObjectManager _objects; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly AutoDesignManager _autoDesignManager; + private readonly Configuration _config; + private readonly ModSettingApplier _modApplier; + private readonly ItemManager _items; + private readonly CustomizeService _customizeService; + private readonly DesignManager _designManager; + private readonly DesignConverter _converter; + private readonly DesignResolver _resolver; public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier, - ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService) + ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemSelector designSelector, + QuickDesignCombo quickDesignCombo, DesignResolver resolver) { _commands = commands; _mainWindow = mainWindow; @@ -57,13 +57,12 @@ public class CommandService : IDisposable, IApiService _stateManager = stateManager; _designManager = designManager; _converter = converter; - _designFileSystem = designFileSystem; _autoDesignManager = autoDesignManager; _config = config; _modApplier = modApplier; _items = items; - _randomDesign = randomDesign; _customizeService = customizeService; + _resolver = resolver; _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); _commands.AddHandler(ApplyCommandString, @@ -611,7 +610,7 @@ public class CommandService : IDisposable, IApiService if (split.Length is not 2) { _chat.Print(new SeStringBuilder().AddText("Use with /glamour apply ") - .AddYellow("[Design Name, Path or Identifier, Random, or Clipboard]") + .AddYellow("[Design Name, Path or Identifier, Quick, Selection, Random, or Clipboard]") .AddText(" | ") .AddGreen("[Character Identifier]") .AddText("; ") @@ -628,6 +627,10 @@ public class CommandService : IDisposable, IApiService _chat.Print(new SeStringBuilder() .AddText(" 》 The design path is the folder path in the selector, with '/' as separators. It is also case-insensitive.") .BuiltString); + _chat.Print(new SeStringBuilder() + .AddText(" 》 Quick will use the design currently selected in the Quick Design Bar, if any.").BuiltString); + _chat.Print(new SeStringBuilder() + .AddText(" 》 Selection will use the design currently selected in the main interfaces Designs tab, if any.").BuiltString); _chat.Print(new SeStringBuilder() .AddText(" 》 Clipboard as a single word will try to apply a design string currently in your clipboard.").BuiltString); _chat.Print(new SeStringBuilder() @@ -656,7 +659,7 @@ public class CommandService : IDisposable, IApiService "y" => true, _ => false, }; - if (!GetDesign(split[0], out var design, true) || !IdentifierHandling(split2[0], out var identifiers, false, true)) + if (!_resolver.GetDesign(split[0], out var design, true) || !IdentifierHandling(split2[0], out var identifiers, false, true)) return false; _objects.Update(); @@ -688,7 +691,7 @@ public class CommandService : IDisposable, IApiService if (!applyMods || design is not Design d) return; - var (messages, appliedMods, collection, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); + var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); foreach (var message in messages) Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); @@ -717,7 +720,7 @@ public class CommandService : IDisposable, IApiService return false; } - if (!GetDesign(argument, out var designBase, false) || designBase is not Design d) + if (!_resolver.GetDesign(argument, out var designBase, false) || designBase is not Design d) return false; _designManager.Delete(d); @@ -796,81 +799,6 @@ public class CommandService : IDisposable, IApiService return false; } - private bool GetDesign(string argument, [NotNullWhen(true)] out DesignBase? design, bool allowSpecial) - { - design = null; - if (argument.Length == 0) - return false; - - if (allowSpecial) - { - if (string.Equals("clipboard", argument, StringComparison.OrdinalIgnoreCase)) - { - try - { - var clipboardText = ImGui.GetClipboardText(); - if (clipboardText.Length > 0) - design = _converter.FromBase64(clipboardText, true, true, out _); - } - catch - { - // ignored - } - - if (design != null) - return true; - - _chat.Print(new SeStringBuilder().AddText("Your current clipboard did not contain a valid design string.").BuiltString); - return false; - } - - if (argument.StartsWith(RandomString, StringComparison.OrdinalIgnoreCase)) - { - try - { - if (argument.Length == RandomString.Length) - design = _randomDesign.Design(); - else if (argument[RandomString.Length] == ':') - design = _randomDesign.Design(argument[(RandomString.Length + 1)..]); - if (design == null) - { - _chat.Print(new SeStringBuilder().AddText("No design matched your restrictions.").BuiltString); - return false; - } - - _chat.Print($"Chose random design {((Design)design).Name}."); - } - catch (Exception ex) - { - _chat.Print(new SeStringBuilder().AddText($"Error in the restriction string: {ex.Message}").BuiltString); - return false; - } - - return true; - } - } - - if (Guid.TryParse(argument, out var guid)) - { - design = _designManager.Designs.ByIdentifier(guid); - } - else - { - var lower = argument.ToLowerInvariant(); - design = _designManager.Designs.FirstOrDefault(d - => d.Name.Lower == lower || lower.Length > 3 && d.Identifier.ToString().StartsWith(lower)); - if (design == null && _designFileSystem.Find(lower, out var child) && child is DesignFileSystem.Leaf leaf) - design = leaf.Value; - } - - if (design != null) - return true; - - _chat.Print(new SeStringBuilder().AddText("The token ").AddYellow(argument, true).AddText(" did not resolve to an existing design.") - .BuiltString); - return false; - } - private unsafe bool IdentifierHandling(string argument, out ActorIdentifier[] identifiers, bool allowAnyWorld, bool allowIndex) { try @@ -882,7 +810,7 @@ public class CommandService : IDisposable, IApiService { _chat.Print(new SeStringBuilder().AddText("The placeholder ").AddGreen(argument) .AddText(" did not resolve to a game object with a valid identifier.").BuiltString); - identifiers = Array.Empty(); + identifiers = []; return false; } @@ -913,7 +841,7 @@ public class CommandService : IDisposable, IApiService _chat.Print(new SeStringBuilder().AddText("The argument ").AddRed(argument, true) .AddText($" could not be converted to an identifier. {e.Message}") .BuiltString); - identifiers = Array.Empty(); + identifiers = []; return false; } } diff --git a/Glamourer/Services/DesignResolver.cs b/Glamourer/Services/DesignResolver.cs new file mode 100644 index 0000000..68b54bb --- /dev/null +++ b/Glamourer/Services/DesignResolver.cs @@ -0,0 +1,173 @@ +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Plugin.Services; +using Glamourer.Designs; +using Glamourer.Designs.Special; +using Glamourer.Gui; +using Glamourer.Gui.Tabs.DesignTab; +using ImGuiNET; +using OtterGui.Services; +using OtterGui.Classes; + +namespace Glamourer.Services; + +public class DesignResolver( + DesignFileSystemSelector designSelector, + QuickDesignCombo quickDesignCombo, + DesignConverter converter, + DesignManager manager, + DesignFileSystem designFileSystem, + RandomDesignGenerator randomDesign, + IChatGui chat) : IService +{ + private const string RandomString = "random"; + + public bool GetDesign(string argument, [NotNullWhen(true)] out DesignBase? design, bool allowSpecial) + { + if (GetDesign(argument, out design, out var error, out var message, allowSpecial)) + { + if (message != null) + chat.Print(message); + return true; + } + + if (error != null) + chat.Print(error); + return false; + } + + public bool GetDesign(string argument, [NotNullWhen(true)] out DesignBase? design, out SeString? error, out SeString? message, + bool allowSpecial) + { + design = null; + error = null; + message = null; + + if (argument.Length == 0) + return false; + + if (allowSpecial) + { + if (string.Equals("selection", argument, StringComparison.OrdinalIgnoreCase)) + return GetSelectedDesign(ref design, ref error); + + if (string.Equals("quick", argument, StringComparison.OrdinalIgnoreCase)) + return GetQuickDesign(ref design, ref error); + + if (string.Equals("clipboard", argument, StringComparison.OrdinalIgnoreCase)) + return GetClipboardDesign(ref design, ref error); + + if (argument.StartsWith(RandomString, StringComparison.OrdinalIgnoreCase)) + return GetRandomDesign(argument, ref design, ref error, ref message); + } + + return GetStandardDesign(argument, ref design, ref error); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool GetSelectedDesign(ref DesignBase? design, ref SeString? error) + { + design = designSelector.Selected; + if (design != null) + return true; + + error = "You do not have selected any design in the Designs Tab."; + return false; + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool GetQuickDesign(ref DesignBase? design, ref SeString? error) + { + design = quickDesignCombo.Design as Design; + if (design != null) + return true; + + error = "You do not have selected any design in the Quick Design Bar."; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool GetClipboardDesign(ref DesignBase? design, ref SeString? error) + { + try + { + var clipboardText = ImGui.GetClipboardText(); + if (clipboardText.Length > 0) + design = converter.FromBase64(clipboardText, true, true, out _); + } + catch + { + // ignored + } + + if (design != null) + return true; + + error = "Your current clipboard did not contain a valid design string."; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool GetRandomDesign(string argument, ref DesignBase? design, ref SeString? error, ref SeString? message) + { + try + { + if (argument.Length == RandomString.Length) + design = randomDesign.Design(); + else if (argument[RandomString.Length] == ':') + design = randomDesign.Design(argument[(RandomString.Length + 1)..]); + if (design == null) + { + error = "No design matched your restrictions."; + return false; + } + + message = $"Chose random design {((Design)design).Name}."; + } + catch (Exception ex) + { + error = $"Error in the restriction string: {ex.Message}"; + return false; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool GetStandardDesign(string argument, ref DesignBase? design, ref SeString? error) + { + // As Guid + if (Guid.TryParse(argument, out var guid)) + { + design = manager.Designs.ByIdentifier(guid); + } + else + { + var lower = argument.ToLowerInvariant(); + // Search for design by name and partial identifier. + design = manager.Designs.FirstOrDefault(MatchNameAndIdentifier(lower)); + // Search for design by path, if nothing was found. + if (design == null && designFileSystem.Find(lower, out var child) && child is DesignFileSystem.Leaf leaf) + design = leaf.Value; + } + + if (design != null) + return true; + + error = new SeStringBuilder().AddText("The token ").AddYellow(argument, true).AddText(" did not resolve to an existing design.") + .BuiltString; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Func MatchNameAndIdentifier(string lower) + { + // Check for names and identifiers, prefer names + if (lower.Length > 3) + return d => d.Name.Lower == lower || d.Identifier.ToString().StartsWith(lower); + + // Check only for names. + return d => d.Name.Lower == lower; + } +} From e41755ed7e9d5bc81db2afaa4e9edda6113adb6e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 31 Dec 2024 15:16:14 +0100 Subject: [PATCH 541/786] Freeze the application buttons at the top of the panels. --- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 8 ++- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 13 ++++- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 61 +++++++++++---------- 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 14a9e11..2549240 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -154,10 +154,11 @@ public class ActorPanel private unsafe void DrawPanel() { - using var child = ImRaii.Child("##Panel", -Vector2.One, true); - if (!child || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state)) + using var table = ImUtf8.Table("##Panel", 1, ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, ImGui.GetContentRegionAvail()); + if (!table || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state)) return; - + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableNextColumn(); var transformationId = _actor.IsCharacter ? _actor.AsCharacter->CharacterData.TransformationId : 0; if (transformationId != 0) ImGuiUtil.DrawTextButton($"Currently transformed to Transformation {transformationId}.", @@ -168,6 +169,7 @@ public class ActorPanel DrawApplyToTarget(); RevertButtons(); + ImGui.TableNextColumn(); using var disabled = ImRaii.Disabled(transformationId != 0); if (_state.ModelData.IsHuman) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 5fb2d57..26553a7 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -16,6 +16,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Text; +using OtterGuiInternal.Structs; using Penumbra.GameData.Enums; using static Glamourer.Gui.Tabs.HeaderDrawer; @@ -415,11 +416,16 @@ public class DesignPanel private void DrawPanel() { - using var child = ImRaii.Child("##Panel", -Vector2.One, true); - if (!child || _selector.Selected == null) + using var table = ImUtf8.Table("##Panel", 1, ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, ImGui.GetContentRegionAvail()); + if (!table || _selector.Selected == null) + return; + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableNextColumn(); + if (_selector.Selected == null) return; - DrawButtonRow(); + ImGui.TableNextColumn(); + DrawCustomize(); DrawEquipment(); DrawCustomizeParameters(); @@ -432,6 +438,7 @@ public class DesignPanel private void DrawButtonRow() { + ImGui.Dummy(Vector2.Zero); DrawApplyToSelf(); ImGui.SameLine(); DrawApplyToTarget(); diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 974912e..312bceb 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -11,6 +11,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Enums; using static Glamourer.Gui.Tabs.HeaderDrawer; @@ -30,8 +31,8 @@ public class NpcPanel private readonly StateManager _state; private readonly ObjectManager _objects; private readonly DesignColors _colors; - private readonly Button[] _leftButtons; - private readonly Button[] _rightButtons; + private readonly Button[] _leftButtons; + private readonly Button[] _rightButtons; public NpcPanel(NpcSelector selector, LocalNpcAppearanceData favorites, @@ -114,11 +115,16 @@ public class NpcPanel private void DrawPanel() { - using var child = ImRaii.Child("##Panel", -Vector2.One, true); - if (!child || !_selector.HasSelection) + using var table = ImUtf8.Table("##Panel", 1, ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, ImGui.GetContentRegionAvail()); + if (!table || !_selector.HasSelection) return; + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableNextColumn(); + ImGui.Dummy(Vector2.Zero); DrawButtonRow(); + + ImGui.TableNextColumn(); DrawCustomization(); DrawEquipment(); DrawAppearanceInfo(); @@ -133,10 +139,9 @@ public class NpcPanel private void DrawCustomization() { - var header = _selector.Selection.ModelId == 0 - ? "Customization" - : $"Customization (Model Id #{_selector.Selection.ModelId})###Customization"; - using var h = ImRaii.CollapsingHeader(header); + using var h = _selector.Selection.ModelId == 0 + ? ImUtf8.CollapsingHeaderId("Customization"u8) + : ImUtf8.CollapsingHeaderId($"Customization (Model Id #{_selector.Selection.ModelId})###Customization"); if (!h) return; @@ -146,7 +151,7 @@ public class NpcPanel private void DrawEquipment() { - using var h = ImRaii.CollapsingHeader("Equipment"); + using var h = ImUtf8.CollapsingHeaderId("Equipment"u8); if (!h) return; @@ -185,9 +190,7 @@ public class NpcPanel private void DrawApplyToSelf() { var (id, data) = _objects.PlayerData; - if (!ImGuiUtil.DrawDisabledButton("Apply to Yourself", Vector2.Zero, - "Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations.", - !data.Valid)) + if (!ImUtf8.ButtonEx("Apply to Yourself"u8, "Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8, Vector2.Zero, !data.Valid)) return; if (_state.GetOrCreate(id, data.Objects[0], out var state)) @@ -202,10 +205,10 @@ public class NpcPanel var (id, data) = _objects.TargetData; var tt = id.IsValid ? data.Valid - ? "Apply the current NPC appearance to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations." - : "The current target can not be manipulated." - : "No valid target selected."; - if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, !data.Valid)) + ? "Apply the current NPC appearance to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8 + : "The current target can not be manipulated."u8 + : "No valid target selected."u8; + if (!ImUtf8.ButtonEx("Apply to Target"u8, tt, Vector2.Zero, !data.Valid)) return; if (_state.GetOrCreate(id, data.Objects[0], out var state)) @@ -218,28 +221,28 @@ public class NpcPanel private void DrawAppearanceInfo() { - using var h = ImRaii.CollapsingHeader("Appearance Details"); + using var h = ImUtf8.CollapsingHeaderId("Appearance Details"u8); if (!h) return; - using var table = ImRaii.Table("Details", 2); + using var table = ImUtf8.Table("Details"u8, 2); if (!table) return; using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); - ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Last Update Datem").X); - ImGui.TableSetupColumn("Data", ImGuiTableColumnFlags.WidthStretch); + ImUtf8.TableSetupColumn("Type"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Last Update Datem").X); + ImUtf8.TableSetupColumn("Data"u8, ImGuiTableColumnFlags.WidthStretch); var selection = _selector.Selection; - CopyButton("NPC Name", selection.Name); - CopyButton("NPC ID", selection.Id.Id.ToString()); + CopyButton("NPC Name"u8, selection.Name); + CopyButton("NPC ID"u8, selection.Id.Id.ToString()); ImGuiUtil.DrawFrameColumn("NPC Type"); ImGui.TableNextColumn(); var width = ImGui.GetContentRegionAvail().X; ImGuiUtil.DrawTextButton(selection.Kind is ObjectKind.BattleNpc ? "Battle NPC" : "Event NPC", new Vector2(width, 0), ImGui.GetColorU32(ImGuiCol.FrameBg)); - ImGuiUtil.DrawFrameColumn("Color"); + ImUtf8.DrawFrameColumn("Color"u8); var color = _favorites.GetColor(selection); var colorName = color.Length == 0 ? DesignColors.AutomaticName : color; ImGui.TableNextColumn(); @@ -272,18 +275,18 @@ public class NpcPanel var size = new Vector2(ImGui.GetFrameHeight()); using var font = ImRaii.PushFont(UiBuilder.IconFont); ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor); - ImGuiUtil.HoverTooltip("The color associated with this design does not exist."); + ImUtf8.HoverTooltip("The color associated with this design does not exist."u8); } return; - static void CopyButton(string label, string text) + static void CopyButton(ReadOnlySpan label, string text) { - ImGuiUtil.DrawFrameColumn(label); + ImUtf8.DrawFrameColumn(label); ImGui.TableNextColumn(); - if (ImGui.Button(text, new Vector2(ImGui.GetContentRegionAvail().X, 0))) - ImGui.SetClipboardText(text); - ImGuiUtil.HoverTooltip("Click to copy to clipboard."); + if (ImUtf8.Button(text, new Vector2(ImGui.GetContentRegionAvail().X, 0))) + ImUtf8.SetClipboardText(text); + ImUtf8.HoverTooltip("Click to copy to clipboard."u8); } } From 24452f3c79e17baea103e1951e7d4a34a789c7f8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 31 Dec 2024 16:35:08 +0100 Subject: [PATCH 542/786] Add initial support for setting temporary mod settings. --- Glamourer/Automation/AutoDesignApplier.cs | 2 + Glamourer/Automation/AutoDesignManager.cs | 9 +- Glamourer/Automation/AutoDesignSet.cs | 14 +- Glamourer/Configuration.cs | 8 +- Glamourer/Designs/Design.cs | 99 +++---- Glamourer/Designs/DesignManager.cs | 55 ++-- Glamourer/Designs/IDesignStandIn.cs | 3 +- Glamourer/Designs/Links/DesignMerger.cs | 2 + Glamourer/Designs/Links/MergedDesign.cs | 1 + .../Designs/Special/QuickSelectedDesign.cs | 3 + Glamourer/Designs/Special/RandomDesign.cs | 7 +- Glamourer/Designs/Special/RevertDesign.cs | 3 + Glamourer/Events/DesignChanged.cs | 3 + Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 1 + .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 73 +++--- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 25 +- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 5 + .../Interop/Penumbra/ModSettingApplier.cs | 32 ++- Glamourer/Interop/Penumbra/PenumbraService.cs | 245 ++++++++++++------ Glamourer/Services/CommandService.cs | 2 +- Penumbra.Api | 2 +- 22 files changed, 394 insertions(+), 202 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index e506d9f..660acf4 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -293,6 +293,8 @@ public sealed class AutoDesignApplier : IDisposable set.Designs.Where(d => d.IsActive(actor)) .SelectMany(d => d.Design.AllLinks(newApplication).Select(l => (l.Design, l.Flags & d.Type, d.Jobs.Flags))), state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods); + if (set.ResetTemporarySettings) + mergedDesign.ResetTemporarySettings = true; _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false)); forcedRedraw = mergedDesign.ForcedRedraw; diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index a219376..5d30de0 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -444,8 +444,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos var set = new AutoDesignSet(name, group) { - Enabled = obj["Enabled"]?.ToObject() ?? false, - BaseState = obj["BaseState"]?.ToObject() ?? AutoDesignSet.Base.Current, + Enabled = obj["Enabled"]?.ToObject() ?? false, + ResetTemporarySettings = obj["ResetTemporarySettings"]?.ToObject() ?? false, + BaseState = obj["BaseState"]?.ToObject() ?? AutoDesignSet.Base.Current, }; if (set.Enabled) @@ -602,9 +603,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos ? ActorIdentifier.RetainerType.Mannequin : ActorIdentifier.RetainerType.Bell).CreatePermanent(), ], - IdentifierType.Npc => CreateNpcs(_actors, identifier), + IdentifierType.Npc => CreateNpcs(_actors, identifier), IdentifierType.Owned => CreateNpcs(_actors, identifier), - _ => [], + _ => [], }; static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier) diff --git a/Glamourer/Automation/AutoDesignSet.cs b/Glamourer/Automation/AutoDesignSet.cs index adaa355..f8987af 100644 --- a/Glamourer/Automation/AutoDesignSet.cs +++ b/Glamourer/Automation/AutoDesignSet.cs @@ -10,7 +10,8 @@ public class AutoDesignSet(string name, ActorIdentifier[] identifiers, List(other.AssociatedMods); - Links = Links.Clone(); + Tags = [.. other.Tags]; + Description = other.Description; + QuickDesign = other.QuickDesign; + ForcedRedraw = other.ForcedRedraw; + ResetAdvancedDyes = other.ResetAdvancedDyes; + ResetTemporarySettings = other.ResetTemporarySettings; + Color = other.Color; + AssociatedMods = new SortedList(other.AssociatedMods); + Links = Links.Clone(); } // Metadata public new const int FileVersion = 2; - public Guid Identifier { get; internal init; } - public DateTimeOffset CreationDate { get; internal init; } - public DateTimeOffset LastEdit { get; internal set; } - public LowerString Name { get; internal set; } = LowerString.Empty; - 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 ResetAdvancedDyes { get; internal set; } - public bool QuickDesign { get; internal set; } = true; - public string Color { get; internal set; } = string.Empty; - public SortedList AssociatedMods { get; private set; } = []; - public LinkContainer Links { get; private set; } = []; + public Guid Identifier { get; internal init; } + public DateTimeOffset CreationDate { get; internal init; } + public DateTimeOffset LastEdit { get; internal set; } + public LowerString Name { get; internal set; } = LowerString.Empty; + 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 ResetAdvancedDyes { get; internal set; } + public bool ResetTemporarySettings { get; internal set; } + public bool QuickDesign { get; internal set; } = true; + public string Color { get; internal set; } = string.Empty; + public SortedList AssociatedMods { get; private set; } = []; + public LinkContainer Links { get; private set; } = []; public string Incognito => Identifier.ToString()[..8]; @@ -100,25 +102,26 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn { var ret = new JObject() { - ["FileVersion"] = FileVersion, - ["Identifier"] = Identifier, - ["CreationDate"] = CreationDate, - ["LastEdit"] = LastEdit, - ["Name"] = Name.Text, - ["Description"] = Description, - ["ForcedRedraw"] = ForcedRedraw, - ["ResetAdvancedDyes"] = ResetAdvancedDyes, - ["Color"] = Color, - ["QuickDesign"] = QuickDesign, - ["Tags"] = JArray.FromObject(Tags), - ["WriteProtected"] = WriteProtected(), - ["Equipment"] = SerializeEquipment(), - ["Bonus"] = SerializeBonusItems(), - ["Customize"] = SerializeCustomize(), - ["Parameters"] = SerializeParameters(), - ["Materials"] = SerializeMaterials(), - ["Mods"] = SerializeMods(), - ["Links"] = Links.Serialize(), + ["FileVersion"] = FileVersion, + ["Identifier"] = Identifier, + ["CreationDate"] = CreationDate, + ["LastEdit"] = LastEdit, + ["Name"] = Name.Text, + ["Description"] = Description, + ["ForcedRedraw"] = ForcedRedraw, + ["ResetAdvancedDyes"] = ResetAdvancedDyes, + ["ResetTemporarySettings"] = ResetTemporarySettings, + ["Color"] = Color, + ["QuickDesign"] = QuickDesign, + ["Tags"] = JArray.FromObject(Tags), + ["WriteProtected"] = WriteProtected(), + ["Equipment"] = SerializeEquipment(), + ["Bonus"] = SerializeBonusItems(), + ["Customize"] = SerializeCustomize(), + ["Parameters"] = SerializeParameters(), + ["Materials"] = SerializeMaterials(), + ["Mods"] = SerializeMods(), + ["Links"] = Links.Serialize(), }; return ret; } @@ -250,9 +253,10 @@ 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.ForcedRedraw = json["ForcedRedraw"]?.ToObject() ?? false; - design.ResetAdvancedDyes = json["ResetAdvancedDyes"]?.ToObject() ?? false; + design.Color = json["Color"]?.ToObject() ?? string.Empty; + design.ForcedRedraw = json["ForcedRedraw"]?.ToObject() ?? false; + design.ResetAdvancedDyes = json["ResetAdvancedDyes"]?.ToObject() ?? false; + design.ResetTemporarySettings = json["ResetTemporarySettings"]?.ToObject() ?? false; return design; static string[] ParseTags(JObject json) @@ -278,12 +282,15 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn continue; } - var settingsDict = tok["Settings"]?.ToObject>>() ?? []; - var settings = new Dictionary>(settingsDict.Count); + var forceInherit = tok["Inherit"]?.ToObject() ?? false; + var removeSetting = tok["Remove"]?.ToObject() ?? false; + 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; - if (!design.AssociatedMods.TryAdd(new Mod(name, directory), new ModSettings(settings, priority, enabled.Value))) + if (!design.AssociatedMods.TryAdd(new Mod(name, directory), + new ModSettings(settings, priority, enabled.Value, forceInherit, removeSetting))) Glamourer.Messager.NotificationMessage("The loaded design contains a mod more than once, skipped.", NotificationType.Warning); } } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index b889649..f931489 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -99,14 +99,15 @@ public sealed class DesignManager : DesignEditor var (actualName, path) = ParseName(name, handlePath); var design = new Design(Customizations, Items) { - CreationDate = DateTimeOffset.UtcNow, - LastEdit = DateTimeOffset.UtcNow, - Identifier = CreateNewGuid(), - Name = actualName, - Index = Designs.Count, - ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing, - ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes, - QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar, + CreationDate = DateTimeOffset.UtcNow, + LastEdit = DateTimeOffset.UtcNow, + Identifier = CreateNewGuid(), + Name = actualName, + Index = Designs.Count, + ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing, + ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes, + QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar, + ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings, }; Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier}."); @@ -121,14 +122,15 @@ public sealed class DesignManager : DesignEditor var (actualName, path) = ParseName(name, handlePath); var design = new Design(clone) { - CreationDate = DateTimeOffset.UtcNow, - LastEdit = DateTimeOffset.UtcNow, - Identifier = CreateNewGuid(), - Name = actualName, - Index = Designs.Count, - ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing, - ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes, - QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar, + CreationDate = DateTimeOffset.UtcNow, + LastEdit = DateTimeOffset.UtcNow, + Identifier = CreateNewGuid(), + Name = actualName, + Index = Designs.Count, + ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing, + ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes, + QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar, + ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings, }; Designs.Add(design); @@ -144,11 +146,11 @@ public sealed class DesignManager : DesignEditor var (actualName, path) = ParseName(name, handlePath); var design = new Design(clone) { - CreationDate = DateTimeOffset.UtcNow, - LastEdit = DateTimeOffset.UtcNow, - Identifier = CreateNewGuid(), - Name = actualName, - Index = Designs.Count, + CreationDate = DateTimeOffset.UtcNow, + LastEdit = DateTimeOffset.UtcNow, + Identifier = CreateNewGuid(), + Name = actualName, + Index = Designs.Count, }; Designs.Add(design); Glamourer.Log.Debug( @@ -351,6 +353,17 @@ public sealed class DesignManager : DesignEditor DesignChanged.Invoke(DesignChanged.Type.ResetAdvancedDyes, design, null); } + public void ChangeResetTemporarySettings(Design design, bool resetTemporarySettings) + { + if (design.ResetTemporarySettings == resetTemporarySettings) + return; + + design.ResetTemporarySettings = resetTemporarySettings; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set {design.Identifier} to {(resetTemporarySettings ? string.Empty : "not")} reset temporary settings."); + DesignChanged.Invoke(DesignChanged.Type.ResetTemporarySettings, 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 02baee4..d07acb9 100644 --- a/Glamourer/Designs/IDesignStandIn.cs +++ b/Glamourer/Designs/IDesignStandIn.cs @@ -26,5 +26,6 @@ public interface IDesignStandIn : IEquatable public bool ForcedRedraw { get; } - public bool ResetAdvancedDyes { get; } + public bool ResetAdvancedDyes { get; } + public bool ResetTemporarySettings { get; } } diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index e0e5d95..0284322 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -58,6 +58,8 @@ public class DesignMerger( ret.ForcedRedraw = true; if (design.ResetAdvancedDyes) ret.ResetAdvancedDyes = true; + if (design.ResetTemporarySettings) + ret.ResetTemporarySettings = true; } ApplyFixFlags(ret, fixFlags); diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index 9c9e079..3d81cda 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -101,4 +101,5 @@ public sealed class MergedDesign public StateSources Sources = new(); public bool ForcedRedraw; public bool ResetAdvancedDyes; + public bool ResetTemporarySettings; } diff --git a/Glamourer/Designs/Special/QuickSelectedDesign.cs b/Glamourer/Designs/Special/QuickSelectedDesign.cs index 31fb40f..740bb7f 100644 --- a/Glamourer/Designs/Special/QuickSelectedDesign.cs +++ b/Glamourer/Designs/Special/QuickSelectedDesign.cs @@ -56,4 +56,7 @@ public class QuickSelectedDesign(QuickDesignCombo combo) : IDesignStandIn, IServ public bool ResetAdvancedDyes => combo.Design?.ResetAdvancedDyes ?? false; + + public bool ResetTemporarySettings + => combo.Design?.ResetTemporarySettings ?? false; } diff --git a/Glamourer/Designs/Special/RandomDesign.cs b/Glamourer/Designs/Special/RandomDesign.cs index a54ffcf..844f203 100644 --- a/Glamourer/Designs/Special/RandomDesign.cs +++ b/Glamourer/Designs/Special/RandomDesign.cs @@ -92,8 +92,11 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn } public bool ForcedRedraw - => false; + => _currentDesign?.ForcedRedraw ?? false; public bool ResetAdvancedDyes - => false; + => _currentDesign?.ResetAdvancedDyes ?? false; + + public bool ResetTemporarySettings + => _currentDesign?.ResetTemporarySettings ?? false; } diff --git a/Glamourer/Designs/Special/RevertDesign.cs b/Glamourer/Designs/Special/RevertDesign.cs index 8704339..4caf7b6 100644 --- a/Glamourer/Designs/Special/RevertDesign.cs +++ b/Glamourer/Designs/Special/RevertDesign.cs @@ -48,4 +48,7 @@ public class RevertDesign : IDesignStandIn public bool ResetAdvancedDyes => true; + + public bool ResetTemporarySettings + => true; } diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 8cd8f5c..04bb46a 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -93,6 +93,9 @@ public sealed class DesignChanged() /// An existing design had changed whether it always resets advanced dyes or not. ResetAdvancedDyes, + /// An existing design had changed whether it always resets all prior temporary settings or not. + ResetTemporarySettings, + /// An existing design changed whether a specific customization is applied. ApplyCustomize, diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 2549240..265e1d9 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -159,6 +159,7 @@ public class ActorPanel return; ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableNextColumn(); + ImGui.Dummy(Vector2.Zero); var transformationId = _actor.IsCharacter ? _actor.AsCharacter->CharacterData.TransformationId : 0; if (transformationId != 0) ImGuiUtil.DrawTextButton($"Currently transformed to Transformation {transformationId}.", diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index bd74772..1469deb 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -6,6 +6,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Text; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.DesignTab; @@ -41,7 +42,7 @@ public class DesignDetailTab public void Draw() { - using var h = ImRaii.CollapsingHeader("Design Details"); + using var h = ImUtf8.CollapsingHeaderId("Design Details"u8); if (!h) return; @@ -54,19 +55,19 @@ public class DesignDetailTab private void DrawDesignInfoTable() { using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); - using var table = ImRaii.Table("Details", 2); + using var table = ImUtf8.Table("Details"u8, 2); if (!table) return; - ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Reset Advanced Dyes").X); - ImGui.TableSetupColumn("Data", ImGuiTableColumnFlags.WidthStretch); + ImUtf8.TableSetupColumn("Type"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Reset Temporary Settings"u8).X); + ImUtf8.TableSetupColumn("Data"u8, ImGuiTableColumnFlags.WidthStretch); - ImGuiUtil.DrawFrameColumn("Design Name"); + ImUtf8.DrawFrameColumn("Design Name"u8); ImGui.TableNextColumn(); var width = new Vector2(ImGui.GetContentRegionAvail().X, 0); var name = _newName ?? _selector.Selected!.Name; ImGui.SetNextItemWidth(width.X); - if (ImGui.InputText("##Name", ref name, 128)) + if (ImUtf8.InputText("##Name"u8, ref name)) { _newName = name; _changeDesign = _selector.Selected; @@ -80,10 +81,10 @@ public class DesignDetailTab } var identifier = _selector.Selected!.Identifier.ToString(); - ImGuiUtil.DrawFrameColumn("Unique Identifier"); + ImUtf8.DrawFrameColumn("Unique Identifier"u8); ImGui.TableNextColumn(); var fileName = _saveService.FileNames.DesignFile(_selector.Selected!); - using (var mono = ImRaii.PushFont(UiBuilder.MonoFont)) + using (ImRaii.PushFont(UiBuilder.MonoFont)) { if (ImGui.Button(identifier, width)) try @@ -100,14 +101,14 @@ public class DesignDetailTab ImGui.SetClipboardText(identifier); } - ImGuiUtil.HoverTooltip( + ImUtf8.HoverTooltip( $"Open the file\n\t{fileName}\ncontaining this design in the .json-editor of your choice.\n\nRight-Click to copy identifier to clipboard."); - ImGuiUtil.DrawFrameColumn("Full Selector Path"); + ImUtf8.DrawFrameColumn("Full Selector Path"u8); ImGui.TableNextColumn(); var path = _newPath ?? _selector.SelectedLeaf!.FullName(); ImGui.SetNextItemWidth(width.X); - if (ImGui.InputText("##Path", ref path, 1024)) + if (ImUtf8.InputText("##Path"u8, ref path)) { _newPath = path; _changeLeaf = _selector.SelectedLeaf!; @@ -125,32 +126,42 @@ public class DesignDetailTab Glamourer.Messager.NotificationMessage(ex, ex.Message, "Could not rename or move design", NotificationType.Error); } - ImGuiUtil.DrawFrameColumn("Quick Design Bar"); + ImUtf8.DrawFrameColumn("Quick Design Bar"u8); ImGui.TableNextColumn(); - if (ImGui.RadioButton("Display##qdb", _selector.Selected.QuickDesign)) + if (ImUtf8.RadioButton("Display##qdb"u8, _selector.Selected.QuickDesign)) _manager.SetQuickDesign(_selector.Selected!, true); var hovered = ImGui.IsItemHovered(); ImGui.SameLine(); - if (ImGui.RadioButton("Hide##qdb", !_selector.Selected.QuickDesign)) + if (ImUtf8.RadioButton("Hide##qdb"u8, !_selector.Selected.QuickDesign)) _manager.SetQuickDesign(_selector.Selected!, false); if (hovered || ImGui.IsItemHovered()) - ImGui.SetTooltip("Display or hide this design in your quick design bar."); + { + using var tt = ImUtf8.Tooltip(); + ImUtf8.Text("Display or hide this design in your quick design bar."u8); + } var forceRedraw = _selector.Selected!.ForcedRedraw; - ImGuiUtil.DrawFrameColumn("Force Redrawing"); + ImUtf8.DrawFrameColumn("Force Redrawing"u8); ImGui.TableNextColumn(); - if (ImGui.Checkbox("##ForceRedraw", ref forceRedraw)) + if (ImUtf8.Checkbox("##ForceRedraw"u8, ref forceRedraw)) _manager.ChangeForcedRedraw(_selector.Selected!, forceRedraw); - ImGuiUtil.HoverTooltip("Set this design to always force a redraw when it is applied through any means."); + ImUtf8.HoverTooltip("Set this design to always force a redraw when it is applied through any means."u8); var resetAdvancedDyes = _selector.Selected!.ResetAdvancedDyes; - ImGuiUtil.DrawFrameColumn("Reset Advanced Dyes"); + ImUtf8.DrawFrameColumn("Reset Advanced Dyes"u8); ImGui.TableNextColumn(); - if (ImGui.Checkbox("##ResetAdvancedDyes", ref resetAdvancedDyes)) + if (ImUtf8.Checkbox("##ResetAdvancedDyes"u8, ref resetAdvancedDyes)) _manager.ChangeResetAdvancedDyes(_selector.Selected!, resetAdvancedDyes); - ImGuiUtil.HoverTooltip("Set this design to reset any previously applied advanced dyes when it is applied through any means."); + ImUtf8.HoverTooltip("Set this design to reset any previously applied advanced dyes when it is applied through any means."u8); - ImGuiUtil.DrawFrameColumn("Color"); + var resetTemporarySettings = _selector.Selected!.ResetTemporarySettings; + ImUtf8.DrawFrameColumn("Reset Temporary Settings"u8); + ImGui.TableNextColumn(); + if (ImUtf8.Checkbox("##ResetTemporarySettings"u8, ref resetTemporarySettings)) + _manager.ChangeResetTemporarySettings(_selector.Selected!, resetTemporarySettings); + ImUtf8.HoverTooltip("Set this design to reset any temporary settings previously applied to the associated collection when it is applied through any means."u8); + + ImUtf8.DrawFrameColumn("Color"u8); var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color; ImGui.TableNextColumn(); if (_colorCombo.Draw("##colorCombo", colorName, "Associate a color with this design.\n" @@ -178,18 +189,18 @@ public class DesignDetailTab var size = new Vector2(ImGui.GetFrameHeight()); using var font = ImRaii.PushFont(UiBuilder.IconFont); ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor); - ImGuiUtil.HoverTooltip("The color associated with this design does not exist."); + ImUtf8.HoverTooltip("The color associated with this design does not exist."u8); } - ImGuiUtil.DrawFrameColumn("Creation Date"); + ImUtf8.DrawFrameColumn("Creation Date"u8); ImGui.TableNextColumn(); ImGuiUtil.DrawTextButton(_selector.Selected!.CreationDate.LocalDateTime.ToString("F"), width, 0); - ImGuiUtil.DrawFrameColumn("Last Update Date"); + ImUtf8.DrawFrameColumn("Last Update Date"u8); ImGui.TableNextColumn(); ImGuiUtil.DrawTextButton(_selector.Selected!.LastEdit.LocalDateTime.ToString("F"), width, 0); - ImGuiUtil.DrawFrameColumn("Tags"); + ImUtf8.DrawFrameColumn("Tags"u8); ImGui.TableNextColumn(); DrawTags(); } @@ -219,18 +230,18 @@ public class DesignDetailTab var size = new Vector2(ImGui.GetContentRegionAvail().X, 12 * ImGui.GetTextLineHeightWithSpacing()); if (!_editDescriptionMode) { - using (var textBox = ImRaii.ListBox("##desc", size)) + using (var textBox = ImUtf8.ListBox("##desc"u8, size)) { - ImGuiUtil.TextWrapped(desc); + ImUtf8.TextWrapped(desc); } - if (ImGui.Button("Edit Description")) + if (ImUtf8.Button("Edit Description"u8)) _editDescriptionMode = true; } else { var edit = _newDescription ?? desc; - if (ImGui.InputTextMultiline("##desc", ref edit, (uint)Math.Max(2000, 4 * edit.Length), size)) + if (ImUtf8.InputMultiLine("##desc"u8, ref edit, size)) _newDescription = edit; if (ImGui.IsItemDeactivatedAfterEdit()) @@ -239,7 +250,7 @@ public class DesignDetailTab _newDescription = null; } - if (ImGui.Button("Stop Editing")) + if (ImUtf8.Button("Stop Editing"u8)) _editDescriptionMode = false; } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 26553a7..070ca1e 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -423,6 +423,7 @@ public class DesignPanel ImGui.TableNextColumn(); if (_selector.Selected == null) return; + ImGui.Dummy(Vector2.Zero); DrawButtonRow(); ImGui.TableNextColumn(); @@ -438,7 +439,6 @@ public class DesignPanel private void DrawButtonRow() { - ImGui.Dummy(Vector2.Zero); DrawApplyToSelf(); ImGui.SameLine(); DrawApplyToTarget(); diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 1f1e1ad..20ddbe8 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -88,16 +88,18 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect private void DrawTable() { - using var table = ImRaii.Table("Mods", 5, ImGuiTableFlags.RowBg); + using var table = ImUtf8.Table("Mods"u8, 7, ImGuiTableFlags.RowBg); if (!table) return; - ImGui.TableSetupColumn("##Buttons", ImGuiTableColumnFlags.WidthFixed, + ImUtf8.TableSetupColumn("##Buttons"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight() * 3 + ImGui.GetStyle().ItemInnerSpacing.X * 2); - ImGui.TableSetupColumn("Mod 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("Applym").X); + ImUtf8.TableSetupColumn("Mod Name"u8, ImGuiTableColumnFlags.WidthStretch); + ImUtf8.TableSetupColumn("Remove"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Remove"u8).X); + ImUtf8.TableSetupColumn("Inherit"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Inherit"u8).X); + ImUtf8.TableSetupColumn("State"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("State"u8).X); + ImUtf8.TableSetupColumn("Priority"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Priority"u8).X); + ImUtf8.TableSetupColumn("##Options"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Applym"u8).X); ImGui.TableHeadersRow(); Mod? removedMod = null; @@ -183,6 +185,17 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect if (ImGui.IsItemHovered()) ImGui.SetTooltip($"Mod Directory: {mod.DirectoryName}\n\nClick to open mod page in Penumbra."); ImGui.TableNextColumn(); + var remove = settings.Remove; + if (TwoStateCheckbox.Instance.Draw("##Remove"u8, ref remove)) + updatedMod = (mod, settings with { Remove = remove }); + ImUtf8.HoverTooltip( + "Remove any temporary settings applied by Glamourer instead of applying the configured settings. Only works when using temporary settings, ignored otherwise."u8); + ImGui.TableNextColumn(); + var inherit = settings.ForceInherit; + if (TwoStateCheckbox.Instance.Draw("##Enabled"u8, ref inherit)) + updatedMod = (mod, settings with { ForceInherit = inherit }); + ImUtf8.HoverTooltip("Force the mod to inherit its settings from inherited collections."u8); + ImGui.TableNextColumn(); var enabled = settings.Enabled; if (TwoStateCheckbox.Instance.Draw("##Enabled"u8, ref enabled)) updatedMod = (mod, settings with { Enabled = enabled }); diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index c6d69fd..ab40a48 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -106,6 +106,9 @@ public class SettingsTab( + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n" + "If you enable this setting, you are aware that any resulting misconfiguration is your own fault.", config.AlwaysApplyAssociatedMods, v => config.AlwaysApplyAssociatedMods = v); + Checkbox("Use Temporary Mod Settings", + "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down.", config.UseTemporarySettings, + v => config.UseTemporarySettings = v); ImGui.NewLine(); } @@ -120,6 +123,8 @@ public class SettingsTab( config.DefaultDesignSettings.ResetAdvancedDyes, v => config.DefaultDesignSettings.ResetAdvancedDyes = v); Checkbox("Always Force Redraw", "Newly created designs will be configured to force character redraws on application by default.", config.DefaultDesignSettings.AlwaysForceRedrawing, v => config.DefaultDesignSettings.AlwaysForceRedrawing = v); + Checkbox("Reset Temporary Settings", "Newly created designs will be configured to clear all advanced settings applied by Glamourer to the collection by default.", + config.DefaultDesignSettings.ResetTemporarySettings, v => config.DefaultDesignSettings.ResetTemporarySettings = v); } private void DrawInterfaceSettings() diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index a6fe3e5..b804720 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -3,12 +3,15 @@ using Glamourer.Services; using Glamourer.State; using OtterGui.Services; using Penumbra.GameData.Interop; +using Penumbra.GameData.Structs; namespace Glamourer.Interop.Penumbra; public class ModSettingApplier(PenumbraService penumbra, Configuration config, ObjectManager objects, CollectionOverrideService overrides) : IService { + private readonly HashSet _collectionTracker = []; + public void HandleStateApplication(ActorState state, MergedDesign design) { if (!config.AlwaysApplyAssociatedMods || design.AssociatedMods.Count == 0) @@ -22,20 +25,20 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O return; } - var collections = new HashSet(); - + _collectionTracker.Clear(); foreach (var actor in data.Objects) { var (collection, _, overridden) = overrides.GetCollection(actor, state.Identifier); if (collection == Guid.Empty) continue; - if (!collections.Add(collection)) + if (!_collectionTracker.Add(collection)) continue; + var index = ResetOldSettings(collection, actor, design.ResetTemporarySettings); foreach (var (mod, setting) in design.AssociatedMods) { - var message = penumbra.SetMod(mod, setting, collection); + var message = penumbra.SetMod(mod, setting, collection, index); if (message.Length > 0) Glamourer.Log.Verbose($"[Mod Applier] Error applying mod settings: {message}"); else @@ -45,7 +48,8 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O } } - public (List Messages, int Applied, Guid Collection, string Name, bool Overridden) ApplyModSettings(IReadOnlyDictionary settings, Actor actor) + public (List Messages, int Applied, Guid Collection, string Name, bool Overridden) ApplyModSettings( + IReadOnlyDictionary settings, Actor actor, bool resetOther) { var (collection, name, overridden) = overrides.GetCollection(actor); if (collection == Guid.Empty) @@ -53,9 +57,11 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O var messages = new List(); var appliedMods = 0; + + var index = ResetOldSettings(collection, actor, resetOther); foreach (var (mod, setting) in settings) { - var message = penumbra.SetMod(mod, setting, collection); + var message = penumbra.SetMod(mod, setting, collection, index); if (message.Length > 0) messages.Add($"Error applying mod settings: {message}"); else @@ -64,4 +70,18 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O return (messages, appliedMods, collection, name, overridden); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ObjectIndex? ResetOldSettings(Guid collection, Actor actor, bool resetOther) + { + ObjectIndex? index = actor.Valid ? actor.Index : null; + if (!resetOther) + return index; + + if (index == null) + penumbra.RemoveAllTemporarySettings(collection); + else + penumbra.RemoveAllTemporarySettings(index.Value); + return index; + } } diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 4f6c44a..868ce11 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -21,10 +21,10 @@ public readonly record struct Mod(string Name, string DirectoryName) : IComparab } } -public readonly record struct ModSettings(Dictionary> Settings, int Priority, bool Enabled) +public readonly record struct ModSettings(Dictionary> Settings, int Priority, bool Enabled, bool ForceInherit, bool Remove) { public ModSettings() - : this(new Dictionary>(), 0, false) + : this(new Dictionary>(), 0, false, false, false) { } public static ModSettings Empty @@ -33,30 +33,41 @@ public readonly record struct ModSettings(Dictionary> Setti public class PenumbraService : IDisposable { - public const int RequiredPenumbraBreakingVersion = 5; - public const int RequiredPenumbraFeatureVersion = 0; + public const int RequiredPenumbraBreakingVersion = 5; + public const int RequiredPenumbraFeatureVersion = 3; + public const int RequiredPenumbraFeatureVersionTemp = 4; - private readonly IDalamudPluginInterface _pluginInterface; + private const int Key = -1610; + + private readonly IDalamudPluginInterface _pluginInterface; + private readonly Configuration _config; private readonly EventSubscriber _tooltipSubscriber; private readonly EventSubscriber _clickSubscriber; private readonly EventSubscriber _creatingCharacterBase; private readonly EventSubscriber _createdCharacterBase; private readonly EventSubscriber _modSettingChanged; - private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; - private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; - private global::Penumbra.Api.IpcSubscribers.RedrawObject? _redraw; - private global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo? _drawObjectInfo; - private global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex? _cutsceneParent; - private global::Penumbra.Api.IpcSubscribers.GetCollectionForObject? _objectCollection; - private global::Penumbra.Api.IpcSubscribers.GetModList? _getMods; - private global::Penumbra.Api.IpcSubscribers.GetCollection? _currentCollection; - private global::Penumbra.Api.IpcSubscribers.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 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.TryInheritMod? _inheritMod; + private global::Penumbra.Api.IpcSubscribers.TrySetMod? _setMod; + private global::Penumbra.Api.IpcSubscribers.TrySetModPriority? _setModPriority; + private global::Penumbra.Api.IpcSubscribers.TrySetModSetting? _setModSetting; + private global::Penumbra.Api.IpcSubscribers.TrySetModSettings? _setModSettings; + private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings? _setTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer? _setTemporaryModSettingsPlayer; + private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings? _removeTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer? _removeTemporaryModSettingsPlayer; + private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings? _removeAllTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer? _removeAllTemporaryModSettingsPlayer; + private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; private readonly IDisposable _initializedEvent; private readonly IDisposable _disposedEvent; @@ -68,10 +79,11 @@ public class PenumbraService : IDisposable public int CurrentMinor { get; private set; } public DateTime AttachTime { get; private set; } - public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded) + public PenumbraService(IDalamudPluginInterface pi, PenumbraReloaded penumbraReloaded, Configuration config) { _pluginInterface = pi; _penumbraReloaded = penumbraReloaded; + _config = config; _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); @@ -128,7 +140,7 @@ public class PenumbraService : IDisposable 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; + return tuple.HasValue ? new ModSettings(tuple.Value.Item3, tuple.Value.Item2, tuple.Value.Item1, false, false) : ModSettings.Empty; } catch (Exception ex) { @@ -164,7 +176,7 @@ public class PenumbraService : IDisposable .Select(t => (new Mod(t.Item2, t.Item1), !t.Item3.Item2.HasValue ? ModSettings.Empty - : new ModSettings(t.Item3.Item2!.Value.Item3, t.Item3.Item2!.Value.Item2, t.Item3.Item2!.Value.Item1))) + : new ModSettings(t.Item3.Item2!.Value.Item3, t.Item3.Item2!.Value.Item2, t.Item3.Item2!.Value.Item1, false, false))) .OrderByDescending(p => p.Item2.Enabled) .ThenBy(p => p.Item1.Name) .ThenBy(p => p.Item1.DirectoryName) @@ -195,7 +207,7 @@ public class PenumbraService : IDisposable /// Try to set all mod settings as desired. Only sets when the mod should be enabled. /// If it is disabled, ignore all other settings. /// - public string SetMod(Mod mod, ModSettings settings, Guid? collectionInput = null) + public string SetMod(Mod mod, ModSettings settings, Guid? collectionInput = null, ObjectIndex? index = null) { if (!Available) return "Penumbra is not available."; @@ -204,40 +216,10 @@ public class PenumbraService : IDisposable try { 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."; - case PenumbraApiEc.CollectionMissing: return $"The collection {collection} could not be found."; - } - - if (!settings.Enabled) - return string.Empty; - - 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, setting, list[0]) - : _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list); - switch (ec) - { - case PenumbraApiEc.OptionGroupMissing: - sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}."); - break; - case PenumbraApiEc.OptionMissing: - sb.AppendLine($"Could not find all desired options in the option group {setting} in mod {mod.Name}."); - break; - case PenumbraApiEc.Success: - case PenumbraApiEc.NothingChanged: - break; - default: - sb.AppendLine($"Could not apply options in the option group {setting} in mod {mod.Name} for unknown reason {ec}."); - break; - } - } + if (_config.UseTemporarySettings && _setTemporaryModSettings != null) + SetModTemporary(sb, mod, settings, collection, index); + else + SetModPermanent(sb, mod, settings, collection); return sb.ToString(); } @@ -247,6 +229,103 @@ public class PenumbraService : IDisposable } } + public void RemoveAllTemporarySettings(Guid collection) + => _removeAllTemporaryModSettings?.Invoke(collection, Key); + + public void RemoveAllTemporarySettings(ObjectIndex index) + => _removeAllTemporaryModSettingsPlayer?.Invoke(index.Index, Key); + + public void ClearAllTemporarySettings() + { + if (!Available || _removeAllTemporaryModSettings == null) + return; + + var collections = _collections!.Invoke(); + foreach (var collection in collections) + RemoveAllTemporarySettings(collection.Key); + } + + private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, ObjectIndex? index) + { + var ex = settings.Remove + ? index.HasValue + ? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, Key) + : _removeTemporaryModSettings!.Invoke(collection, mod.DirectoryName, Key) + : index.HasValue + ? _setTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, settings.ForceInherit, settings.Enabled, + settings.Priority, + settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), "Glamourer", Key) + : _setTemporaryModSettings!.Invoke(collection, mod.DirectoryName, settings.ForceInherit, settings.Enabled, settings.Priority, + settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), "Glamourer", Key); + switch (ex) + { + case PenumbraApiEc.InvalidArgument: + sb.Append($"No actor with index {index!.Value.Index} could be identified."); + return; + case PenumbraApiEc.ModMissing: + sb.Append($"The mod {mod.Name} [{mod.DirectoryName}] could not be found."); + return; + case PenumbraApiEc.CollectionMissing: + sb.Append($"The collection {collection} could not be found."); + return; + case PenumbraApiEc.TemporarySettingImpossible: + sb.Append($"The collection {collection} can not have settings."); + return; + case PenumbraApiEc.TemporarySettingDisallowed: + sb.Append($"The mod {mod.Name} [{mod.DirectoryName}] already has temporary settings with a different key in {collection}."); + return; + case PenumbraApiEc.OptionGroupMissing: + case PenumbraApiEc.OptionMissing: + sb.Append($"The provided settings for {mod.Name} [{mod.DirectoryName}] did not correspond to its actual options."); + return; + } + } + + private void SetModPermanent(StringBuilder sb, Mod mod, ModSettings settings, Guid collection) + { + var ec = settings.ForceInherit + ? _inheritMod!.Invoke(collection, mod.DirectoryName, true) + : _setMod!.Invoke(collection, mod.DirectoryName, settings.Enabled); + switch (ec) + { + case PenumbraApiEc.ModMissing: + sb.Append($"The mod {mod.Name} [{mod.DirectoryName}] could not be found."); + return; + case PenumbraApiEc.CollectionMissing: + sb.Append($"The collection {collection} could not be found."); + return; + } + + if (settings.ForceInherit || !settings.Enabled) + return; + + 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, setting, list[0]) + : _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list); + switch (ec) + { + case PenumbraApiEc.OptionGroupMissing: + sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}."); + break; + case PenumbraApiEc.OptionMissing: + sb.AppendLine($"Could not find all desired options in the option group {setting} in mod {mod.Name}."); + break; + case PenumbraApiEc.Success: + case PenumbraApiEc.NothingChanged: + break; + default: + sb.AppendLine($"Could not apply options in the option group {setting} in mod {mod.Name} for unknown reason {ec}."); + break; + } + } + } + + /// Obtain the name of the collection currently assigned to the player. public Guid GetCurrentPlayerCollection() { @@ -347,12 +426,24 @@ public class PenumbraService : IDisposable _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface); _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface); + _inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface); _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface); _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface); _setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface); _setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface); _openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface); - Available = true; + if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp) + { + _setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface); + _setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface); + _removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface); + _removeTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer(_pluginInterface); + _removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface); + _removeAllTemporaryModSettingsPlayer = + new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface); + } + + Available = true; _penumbraReloaded.Invoke(); Glamourer.Log.Debug("Glamourer attached to Penumbra."); } @@ -373,27 +464,35 @@ public class PenumbraService : IDisposable _modSettingChanged.Disable(); if (Available) { - _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; + _collectionByIdentifier = null; + _collections = null; + _redraw = null; + _drawObjectInfo = null; + _cutsceneParent = null; + _objectCollection = null; + _getMods = null; + _currentCollection = null; + _getCurrentSettings = null; + _inheritMod = null; + _setMod = null; + _setModPriority = null; + _setModSetting = null; + _setModSettings = null; + _openModPage = null; + _setTemporaryModSettings = null; + _setTemporaryModSettingsPlayer = null; + _removeTemporaryModSettings = null; + _removeTemporaryModSettingsPlayer = null; + _removeAllTemporaryModSettings = null; + _removeAllTemporaryModSettingsPlayer = null; + Available = false; Glamourer.Log.Debug("Glamourer detached from Penumbra."); } } public void Dispose() { + ClearAllTemporarySettings(); Unattach(); _tooltipSubscriber.Dispose(); _clickSubscriber.Dispose(); diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index cee4f57..10f68ee 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -691,7 +691,7 @@ public class CommandService : IDisposable, IApiService if (!applyMods || design is not Design d) return; - var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor); + var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor, d.ResetTemporarySettings); foreach (var message in messages) Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); diff --git a/Penumbra.Api b/Penumbra.Api index 882b778..de0f281 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 882b778e78bb0806dd7d38e8b3670ff138a84a31 +Subproject commit de0f281fbf9d8d9d3aa8463a28025d54877cde8d From 9b9e356ad199d8469aac8319af87dca4c96fe18d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 31 Dec 2024 16:36:05 +0100 Subject: [PATCH 543/786] Update Submodules. --- OtterGui | 2 +- Penumbra.GameData | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OtterGui b/OtterGui index d9caded..fcc96da 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit d9caded5efb7c9db0a273a43bb5f6d53cf4ace7f +Subproject commit fcc96daa02633f673325c14aeea6b6b568924f1e diff --git a/Penumbra.GameData b/Penumbra.GameData index 19355cf..33de79b 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 19355cfa0ec80e8d5a91de11ecffc49257b37b53 +Subproject commit 33de79bc62eb014298856ed5c6b6edbe819db26c From d57743763bccabda90dc68fd548cf142f8d4bed7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 31 Dec 2024 18:03:56 +0100 Subject: [PATCH 544/786] Update OtterGui. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index fcc96da..fd38721 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit fcc96daa02633f673325c14aeea6b6b568924f1e +Subproject commit fd387218d2d2d237075cb35be6ca89eeb53e14e5 From 6475c3c65b4727fe055946e3e800eb9c422933d8 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 31 Dec 2024 17:06:22 +0000 Subject: [PATCH 545/786] [CI] Updating repo.json for testing_1.3.4.4 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index f7d8512..f3066dd 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.4.3", - "TestingAssemblyVersion": "1.3.4.3", + "TestingAssemblyVersion": "1.3.4.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.4.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From d675cdc80492532f5f32878cd6053059403e71a5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Jan 2025 19:59:52 +0100 Subject: [PATCH 546/786] Improve scaling of advanced dye window. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 0c2b11e..b842d7f 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -198,7 +198,7 @@ public sealed unsafe class AdvancedDyePopup( + 3 * ImGui.GetStyle().ItemSpacing.X // around text + 7 * ImGui.GetStyle().ItemInnerSpacing.X + 200 * ImGuiHelpers.GlobalScale // Drags - + 7 * UiBuilder.MonoFont.GetCharAdvance(' ') // Row + + 7 * UiBuilder.MonoFont.GetCharAdvance(' ') * ImGuiHelpers.GlobalScale // Row + 2 * ImGui.GetStyle().WindowPadding.X; var height = 19 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + 3 * ImGui.GetStyle().ItemSpacing.Y; ImGui.SetNextWindowSize(new Vector2(width, height)); From 3d6d04dde149d9abd5bde84d0ee6f2b66689d28f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Jan 2025 20:00:16 +0100 Subject: [PATCH 547/786] Fix bug in automation tab setting gearsets. --- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index ab2b3d6..924f822 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -230,6 +230,7 @@ public class SetPanel( } private int _tmpGearset = int.MaxValue; + private int _whichIndex = -1; private void DrawConditions(AutoDesign design, int idx) { @@ -245,14 +246,19 @@ public class SetPanel( ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); if (usingGearset) { - var set = 1 + (_tmpGearset == int.MaxValue ? design.GearsetIndex : _tmpGearset); + var set = 1 + (_tmpGearset == int.MaxValue || _whichIndex != idx ? design.GearsetIndex : _tmpGearset); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); if (ImGui.InputInt("##whichGearset", ref set, 0, 0)) + { + _whichIndex = idx; _tmpGearset = Math.Clamp(set, 1, 100); + } + if (ImGui.IsItemDeactivatedAfterEdit()) { _manager.ChangeGearsetCondition(Selection, idx, (short)(_tmpGearset - 1)); _tmpGearset = int.MaxValue; + _whichIndex = -1; } } else From 8a87ff16b442efb9bf4e64ffae10cac258aa86eb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Jan 2025 20:00:28 +0100 Subject: [PATCH 548/786] Improve mod associations display. --- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 20ddbe8..b02ece6 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -88,15 +88,16 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect private void DrawTable() { - using var table = ImUtf8.Table("Mods"u8, 7, ImGuiTableFlags.RowBg); + using var table = ImUtf8.Table("Mods"u8, config.UseTemporarySettings ? 7 : 6, ImGuiTableFlags.RowBg); if (!table) return; ImUtf8.TableSetupColumn("##Buttons"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight() * 3 + ImGui.GetStyle().ItemInnerSpacing.X * 2); - ImUtf8.TableSetupColumn("Mod Name"u8, ImGuiTableColumnFlags.WidthStretch); - ImUtf8.TableSetupColumn("Remove"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Remove"u8).X); - ImUtf8.TableSetupColumn("Inherit"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Inherit"u8).X); + ImUtf8.TableSetupColumn("Mod Name"u8, ImGuiTableColumnFlags.WidthStretch); + if (config.UseTemporarySettings) + ImUtf8.TableSetupColumn("Remove"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Remove"u8).X); + ImUtf8.TableSetupColumn("Inherit"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Inherit"u8).X); ImUtf8.TableSetupColumn("State"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("State"u8).X); ImUtf8.TableSetupColumn("Priority"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Priority"u8).X); ImUtf8.TableSetupColumn("##Options"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Applym"u8).X); @@ -184,12 +185,16 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect penumbra.OpenModPage(mod); if (ImGui.IsItemHovered()) ImGui.SetTooltip($"Mod Directory: {mod.DirectoryName}\n\nClick to open mod page in Penumbra."); - ImGui.TableNextColumn(); - var remove = settings.Remove; - if (TwoStateCheckbox.Instance.Draw("##Remove"u8, ref remove)) - updatedMod = (mod, settings with { Remove = remove }); - ImUtf8.HoverTooltip( - "Remove any temporary settings applied by Glamourer instead of applying the configured settings. Only works when using temporary settings, ignored otherwise."u8); + if (config.UseTemporarySettings) + { + ImGui.TableNextColumn(); + var remove = settings.Remove; + if (TwoStateCheckbox.Instance.Draw("##Remove"u8, ref remove)) + updatedMod = (mod, settings with { Remove = remove }); + ImUtf8.HoverTooltip( + "Remove any temporary settings applied by Glamourer instead of applying the configured settings. Only works when using temporary settings, ignored otherwise."u8); + } + ImGui.TableNextColumn(); var inherit = settings.ForceInherit; if (TwoStateCheckbox.Instance.Draw("##Enabled"u8, ref inherit)) From 157a5b150be60d8fdb8f8f0d42d258d46258be30 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Jan 2025 20:01:39 +0100 Subject: [PATCH 549/786] Update Submodules. --- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Penumbra.Api b/Penumbra.Api index de0f281..f60de67 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit de0f281fbf9d8d9d3aa8463a28025d54877cde8d +Subproject commit f60de67d24afe6e175f17d03cd234f493ea91265 diff --git a/Penumbra.GameData b/Penumbra.GameData index 33de79b..63ffca0 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 33de79bc62eb014298856ed5c6b6edbe819db26c +Subproject commit 63ffca0ff0ad626605120e58809c888d92053d64 From 2e038350ef6930d72c4edbed92216857148d56eb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Jan 2025 20:13:51 +0100 Subject: [PATCH 550/786] 1.3.5.0 --- Glamourer/Gui/GlamourerChangelog.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 57f7343..0c9d99b 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -38,6 +38,7 @@ public class GlamourerChangelog Add1_3_2_0(Changelog); Add1_3_3_0(Changelog); Add1_3_4_0(Changelog); + Add1_3_5_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -58,11 +59,27 @@ public class GlamourerChangelog } } + private static void Add1_3_5_0(Changelog log) + => log.NextVersion("Version 1.3.5.0") + .RegisterHighlight("Added the usage of the new Temporary Mod Setting functionality from Penumbra to apply mod associations. This is on by default but can be turned back to permanent changes in the settings.") + .RegisterEntry("Designs now have a setting to always reset all prior temporary settings made by Glamourer on application.", 1) + .RegisterEntry("Automation Sets also have a setting to do this, independently of the designs contained in them.", 1) + .RegisterHighlight("More NPC customization options should now be accepted as valid for designs, regardless of clan/gender.") + .RegisterHighlight("The 'Apply' chat command had the currently selected design and the current quick bar design added as choices.") + .RegisterEntry("The application buttons for designs, NPCs or actors should now stick at the top of their respective panels even when scrolling down.") + .RegisterHighlight("Randomly chosen designs should now stay across loading screens or redrawing. (1.3.4.3)") + .RegisterEntry("In automation, Random designs now have an option to always choose another design, including during loading screens or redrawing.", 1) + .RegisterEntry("Fixed an issue where disabling auto designs did not work as expected.") + .RegisterEntry("Fixed the inversion of application flags in IPC calls.") + .RegisterEntry("Fixed an issue with the scaling of the Advanced Dye popup with increased font sizes.") + .RegisterEntry("Fixed a bug when editing gear set conditions in the automation tab.") + .RegisterEntry("Fixed some ImGui issues."); + private static void Add1_3_4_0(Changelog log) => log.NextVersion("Version 1.3.4.0") .RegisterEntry("Glamourer has been updated for Dalamud API 11 and patch 7.1.") .RegisterEntry("Maybe fixed issues with shared weapon types and reset designs.") - .RegisterEntry("Fixed issues with resetting advanced dyes and certain weapon types"); + .RegisterEntry("Fixed issues with resetting advanced dyes and certain weapon types."); private static void Add1_3_3_0(Changelog log) => log.NextVersion("Version 1.3.3.0") From c541fd62c44a4aec1e7377188e58b703043d6f29 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 10 Jan 2025 19:16:32 +0000 Subject: [PATCH 551/786] [CI] Updating repo.json for 1.3.5.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index f3066dd..5420efa 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.4.3", - "TestingAssemblyVersion": "1.3.4.4", + "AssemblyVersion": "1.3.5.0", + "TestingAssemblyVersion": "1.3.5.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.4.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.4.4/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 7d490f9cfb75f6c64bafae9650410f5522e5475b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 11 Jan 2025 13:47:39 +0100 Subject: [PATCH 552/786] Fix designs not resetting temporary settings if they do not have mod associations. --- Glamourer/Interop/Penumbra/ModSettingApplier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index b804720..5b27e6e 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -14,7 +14,7 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O public void HandleStateApplication(ActorState state, MergedDesign design) { - if (!config.AlwaysApplyAssociatedMods || design.AssociatedMods.Count == 0) + if (!config.AlwaysApplyAssociatedMods || (design.AssociatedMods.Count == 0 && !design.ResetTemporarySettings)) return; objects.Update(); From 02bfd177943f55c887ec12010b8eb3b36cdced1a Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 11 Jan 2025 12:49:40 +0000 Subject: [PATCH 553/786] [CI] Updating repo.json for 1.3.5.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 5420efa..a5754ab 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.5.0", - "TestingAssemblyVersion": "1.3.5.0", + "AssemblyVersion": "1.3.5.1", + "TestingAssemblyVersion": "1.3.5.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c83ddf054ae3e7dc3815aa577279d87e4809dcd7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 12 Jan 2025 00:03:54 +0100 Subject: [PATCH 554/786] Add counts to multi design selection. --- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 115 ++++++++++-------- 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 6e5b1b1..a1f17d4 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -3,79 +3,95 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using ImGuiNET; using OtterGui; -using OtterGui.Filesystem; using OtterGui.Raii; +using OtterGui.Text; namespace Glamourer.Gui.Tabs.DesignTab; -public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager _editor, DesignColors _colors) +public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager editor, DesignColors colors) { - private readonly DesignColorCombo _colorCombo = new(_colors, true); + private readonly DesignColorCombo _colorCombo = new(colors, true); public void Draw() { - if (_selector.SelectedPaths.Count == 0) + if (selector.SelectedPaths.Count == 0) return; var width = ImGuiHelpers.ScaledVector2(145, 0); ImGui.NewLine(); - DrawDesignList(); + var treeNodePos = ImGui.GetCursorPos(); + _numDesigns = DrawDesignList(); + DrawCounts(treeNodePos); var offset = DrawMultiTagger(width); DrawMultiColor(width, offset); DrawMultiQuickDesignBar(offset); } - private void DrawDesignList() + private void DrawCounts(Vector2 treeNodePos) { - using var tree = ImRaii.TreeNode("Currently Selected Objects", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.NoTreePushOnOpen); + var startPos = ImGui.GetCursorPos(); + var numFolders = selector.SelectedPaths.Count - _numDesigns; + var text = (_numDesigns, numFolders) switch + { + (0, 0) => string.Empty, // should not happen + (> 0, 0) => $"{_numDesigns} Designs", + (0, > 0) => $"{numFolders} Folders", + _ => $"{_numDesigns} Designs, {numFolders} Folders", + }; + ImGui.SetCursorPos(treeNodePos); + ImUtf8.TextRightAligned(text); + ImGui.SetCursorPos(startPos); + } + + private int DrawDesignList() + { + using var tree = ImUtf8.TreeNode("Currently Selected Objects"u8, ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.NoTreePushOnOpen); ImGui.Separator(); if (!tree) - return; + return selector.SelectedPaths.Count(l => l is DesignFileSystem.Leaf); - var sizeType = ImGui.GetFrameHeight(); - var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType - 4 * ImGui.GetStyle().CellPadding.X) / 100; + var sizeType = new Vector2(ImGui.GetFrameHeight()); + var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType.X - 4 * ImGui.GetStyle().CellPadding.X) / 100; var sizeMods = availableSizePercent * 35; var sizeFolders = availableSizePercent * 65; _numQuickDesignEnabled = 0; - _numDesigns = 0; - using (var table = ImRaii.Table("mods", 3, ImGuiTableFlags.RowBg)) + var numDesigns = 0; + using (var table = ImUtf8.Table("mods"u8, 3, ImGuiTableFlags.RowBg)) { if (!table) - return; + return selector.SelectedPaths.Count(l => l is DesignFileSystem.Leaf); - ImGui.TableSetupColumn("type", ImGuiTableColumnFlags.WidthFixed, sizeType); - ImGui.TableSetupColumn("mod", ImGuiTableColumnFlags.WidthFixed, sizeMods); - ImGui.TableSetupColumn("path", ImGuiTableColumnFlags.WidthFixed, sizeFolders); + ImUtf8.TableSetupColumn("type"u8, ImGuiTableColumnFlags.WidthFixed, sizeType.X); + ImUtf8.TableSetupColumn("mod"u8, ImGuiTableColumnFlags.WidthFixed, sizeMods); + ImUtf8.TableSetupColumn("path"u8, ImGuiTableColumnFlags.WidthFixed, sizeFolders); var i = 0; - foreach (var (fullName, path) in _selector.SelectedPaths.Select(p => (p.FullName(), p)) + foreach (var (fullName, path) in selector.SelectedPaths.Select(p => (p.FullName(), p)) .OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase)) { using var id = ImRaii.PushId(i++); + var (icon, text) = path is DesignFileSystem.Leaf l + ? (FontAwesomeIcon.FileCircleMinus, l.Value.Name.Text) + : (FontAwesomeIcon.FolderMinus, string.Empty); ImGui.TableNextColumn(); - var icon = (path is DesignFileSystem.Leaf ? FontAwesomeIcon.FileCircleMinus : FontAwesomeIcon.FolderMinus).ToIconString(); - if (ImGuiUtil.DrawDisabledButton(icon, new Vector2(sizeType), "Remove from selection.", false, true)) - _selector.RemovePathFromMultiSelection(path); + if (ImUtf8.IconButton(icon, "Remove from selection."u8, sizeType)) + selector.RemovePathFromMultiSelection(path); - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(path is DesignFileSystem.Leaf l ? l.Value.Name : string.Empty); - - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(fullName); + ImUtf8.DrawFrameColumn(text); + ImUtf8.DrawFrameColumn(fullName); if (path is not DesignFileSystem.Leaf l2) continue; - ++_numDesigns; + ++numDesigns; if (l2.Value.QuickDesign) ++_numQuickDesignEnabled; } } ImGui.Separator(); + return numDesigns; } private string _tag = string.Empty; @@ -86,12 +102,11 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager private float DrawMultiTagger(Vector2 width) { - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Multi Tagger:"); + ImUtf8.TextFrameAligned("Multi Tagger:"u8); ImGui.SameLine(); var offset = ImGui.GetItemRectSize().X; ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X)); - ImGui.InputTextWithHint("##tag", "Tag Name...", ref _tag, 128); + ImUtf8.InputText("##tag"u8, ref _tag, "Tag Name..."u8); UpdateTagCache(); var label = _addDesigns.Count > 0 @@ -103,9 +118,9 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager : $"All designs selected already contain the tag \"{_tag}\"." : $"Add the tag \"{_tag}\" to {_addDesigns.Count} designs as a local tag:\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _addDesigns.Count == 0)) + if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) foreach (var design in _addDesigns) - _editor.AddTag(design, _tag); + editor.AddTag(design, _tag); label = _removeDesigns.Count > 0 ? $"Remove from {_removeDesigns.Count} Designs" @@ -116,41 +131,39 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager : $"No selected design contains the tag \"{_tag}\" locally." : $"Remove the local tag \"{_tag}\" from {_removeDesigns.Count} designs:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _removeDesigns.Count == 0)) + if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0)) foreach (var (design, index) in _removeDesigns) - _editor.RemoveTag(design, index); + editor.RemoveTag(design, index); ImGui.Separator(); return offset; } private void DrawMultiQuickDesignBar(float offset) { - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Multi QDB:"); + ImUtf8.TextFrameAligned("Multi QDB:"u8); ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); var diff = _numDesigns - _numQuickDesignEnabled; var tt = diff == 0 ? $"All {_numDesigns} selected designs are already displayed in the quick design bar." : $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs."; - if (ImGuiUtil.DrawDisabledButton("Display Selected Designs in QDB", buttonWidth, tt, diff == 0)) - foreach(var design in _selector.SelectedPaths.OfType()) - _editor.SetQuickDesign(design.Value, true); + if (ImUtf8.ButtonEx("Display Selected Designs in QDB"u8, tt, buttonWidth, diff == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.SetQuickDesign(design.Value, true); ImGui.SameLine(); tt = _numQuickDesignEnabled == 0 ? $"All {_numDesigns} selected designs are already hidden in the quick design bar." : $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs."; - if (ImGuiUtil.DrawDisabledButton("Hide Selected Designs in QDB", buttonWidth, tt, _numQuickDesignEnabled == 0)) - foreach (var design in _selector.SelectedPaths.OfType()) - _editor.SetQuickDesign(design.Value, false); + if (ImUtf8.ButtonEx("Hide Selected Designs in QDB"u8, tt, buttonWidth, _numQuickDesignEnabled == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.SetQuickDesign(design.Value, false); ImGui.Separator(); } private void DrawMultiColor(Vector2 width, float offset) { - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("Multi Colors:"); + ImUtf8.TextFrameAligned("Multi Colors:"); ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); _colorCombo.Draw("##color", _colorCombo.CurrentSelection ?? string.Empty, "Select a design color.", ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X), ImGui.GetTextLineHeight()); @@ -168,9 +181,9 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager } : $"Set the color of {_addDesigns.Count} designs to \"{_colorCombo.CurrentSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _addDesigns.Count == 0)) + if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) foreach (var design in _addDesigns) - _editor.ChangeColor(design, _colorCombo.CurrentSelection!); + editor.ChangeColor(design, _colorCombo.CurrentSelection!); label = _removeDesigns.Count > 0 ? $"Unset {_removeDesigns.Count} Designs" @@ -179,9 +192,9 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager ? "No selected design is set to a non-automatic color." : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _removeDesigns.Count == 0)) + if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0)) foreach (var (design, _) in _removeDesigns) - _editor.ChangeColor(design, string.Empty); + editor.ChangeColor(design, string.Empty); ImGui.Separator(); } @@ -193,7 +206,7 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager if (_tag.Length == 0) return; - foreach (var leaf in _selector.SelectedPaths.OfType()) + foreach (var leaf in selector.SelectedPaths.OfType()) { var index = leaf.Value.Tags.IndexOf(_tag); if (index >= 0) @@ -208,7 +221,7 @@ public class MultiDesignPanel(DesignFileSystemSelector _selector, DesignManager _addDesigns.Clear(); _removeDesigns.Clear(); var selection = _colorCombo.CurrentSelection ?? DesignColors.AutomaticName; - foreach (var leaf in _selector.SelectedPaths.OfType()) + foreach (var leaf in selector.SelectedPaths.OfType()) { if (leaf.Value.Color.Length > 0) _removeDesigns.Add((leaf.Value, 0)); From 60a1ee728ad67750f219fccb5c58fc73c40aa8d5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 14 Jan 2025 14:51:36 +0100 Subject: [PATCH 555/786] Use current temporary settings for mod associations if they exist. --- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 2 +- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 6 ++++- Glamourer/Interop/Penumbra/PenumbraService.cs | 26 +++++++++++++++---- Penumbra.Api | 2 +- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 601d3ae..c8a4b11 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -118,7 +118,7 @@ public class EquipmentDrawer public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) { - if (mainhand.CurrentItem.PrimaryId.Id == 0) + if (mainhand.CurrentItem.PrimaryId.Id == 0 && !allWeapons) return; if (_config.HideApplyCheckmarks) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index b02ece6..5eda7cc 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -149,12 +149,14 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ImUtf8.IconButton(FontAwesomeIcon.RedoAlt, "Update the settings of this mod association."u8); if (ImGui.IsItemHovered()) { - var newSettings = penumbra.GetModSettings(mod); + var newSettings = penumbra.GetModSettings(mod, out var source); if (ImGui.IsItemClicked()) updatedMod = (mod, newSettings); using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale); using var tt = ImUtf8.Tooltip(); + if (source.Length > 0) + ImUtf8.Text($"Using temporary settings made by {source}."); ImGui.Separator(); var namesDifferent = mod.Name != mod.DirectoryName; ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0)); @@ -162,6 +164,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect { if (namesDifferent) ImUtf8.Text("Directory Name"u8); + ImUtf8.Text("Force Inherit"u8); ImUtf8.Text("Enabled"u8); ImUtf8.Text("Priority"u8); ModCombo.DrawSettingsLeft(newSettings); @@ -173,6 +176,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect if (namesDifferent) ImUtf8.Text(mod.DirectoryName); + ImUtf8.Text(newSettings.ForceInherit.ToString()); ImUtf8.Text(newSettings.Enabled.ToString()); ImUtf8.Text(newSettings.Priority.ToString()); ModCombo.DrawSettingsRight(newSettings); diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 868ce11..13be628 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -33,9 +33,10 @@ public readonly record struct ModSettings(Dictionary> Setti public class PenumbraService : IDisposable { - public const int RequiredPenumbraBreakingVersion = 5; - public const int RequiredPenumbraFeatureVersion = 3; - public const int RequiredPenumbraFeatureVersionTemp = 4; + public const int RequiredPenumbraBreakingVersion = 5; + public const int RequiredPenumbraFeatureVersion = 3; + public const int RequiredPenumbraFeatureVersionTemp = 4; + public const int RequiredPenumbraFeatureVersionTemp2 = 5; private const int Key = -1610; @@ -67,6 +68,7 @@ public class PenumbraService : IDisposable private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer? _removeTemporaryModSettingsPlayer; private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings? _removeAllTemporaryModSettings; private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer? _removeAllTemporaryModSettingsPlayer; + private global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings? _queryTemporaryModSettings; private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; private readonly IDisposable _initializedEvent; @@ -128,19 +130,30 @@ public class PenumbraService : IDisposable public Dictionary GetCollections() => Available ? _collections!.Invoke() : []; - public ModSettings GetModSettings(in Mod mod) + public ModSettings GetModSettings(in Mod mod, out string source) { + source = string.Empty; if (!Available) return ModSettings.Empty; try { var collection = _currentCollection!.Invoke(ApiCollectionType.Current); + if (_queryTemporaryModSettings != null) + { + var tempEc = _queryTemporaryModSettings.Invoke(collection!.Value.Id, mod.DirectoryName, out var tempTuple, out source); + if (tempEc is PenumbraApiEc.Success && tempTuple != null) + return new ModSettings(tempTuple.Value.Settings, tempTuple.Value.Priority, tempTuple.Value.Enabled, + tempTuple.Value.ForceInherit, false); + } + var (ec, tuple) = _getCurrentSettings!.Invoke(collection!.Value.Id, mod.DirectoryName); if (ec is not PenumbraApiEc.Success) return ModSettings.Empty; - return tuple.HasValue ? new ModSettings(tuple.Value.Item3, tuple.Value.Item2, tuple.Value.Item1, false, false) : ModSettings.Empty; + return tuple.HasValue + ? new ModSettings(tuple.Value.Item3, tuple.Value.Item2, tuple.Value.Item1, false, false) + : ModSettings.Empty; } catch (Exception ex) { @@ -441,6 +454,8 @@ public class PenumbraService : IDisposable _removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface); _removeAllTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface); + if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) + _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); } Available = true; @@ -485,6 +500,7 @@ public class PenumbraService : IDisposable _removeTemporaryModSettingsPlayer = null; _removeAllTemporaryModSettings = null; _removeAllTemporaryModSettingsPlayer = null; + _queryTemporaryModSettings = null; Available = false; Glamourer.Log.Debug("Glamourer detached from Penumbra."); } diff --git a/Penumbra.Api b/Penumbra.Api index f60de67..b4e716f 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit f60de67d24afe6e175f17d03cd234f493ea91265 +Subproject commit b4e716f86d94cd4d98d8f58e580ed5f619ea87ae From 8160f420db3ac048021bb1606775b5b1c35cf96d Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 15 Jan 2025 17:05:57 +0000 Subject: [PATCH 556/786] [CI] Updating repo.json for testing_1.3.5.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index a5754ab..dab02ea 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.5.1", - "TestingAssemblyVersion": "1.3.5.1", + "TestingAssemblyVersion": "1.3.5.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c605d1951031e95f0bc5923ebaa986f8f1057472 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Thu, 16 Jan 2025 19:34:46 -0800 Subject: [PATCH 557/786] Progress made. May be successful! --- Glamourer/Api/StateApi.cs | 2 + Glamourer/Automation/AutoDesignApplier.cs | 12 ++ Glamourer/Interop/ChangeCustomizeService.cs | 7 +- Glamourer/Interop/InventoryService.cs | 82 ++++++++----- Glamourer/Interop/JobService.cs | 2 +- Glamourer/Interop/UpdateSlotService.cs | 124 ++++++++++++++++++-- Glamourer/State/StateEditor.cs | 26 ++-- Glamourer/State/StateListener.cs | 5 +- Glamourer/State/StateManager.cs | 4 +- 9 files changed, 201 insertions(+), 63 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 331942b..ce7d226 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -333,6 +333,8 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { + Glamourer.Log.Error($"[OnStateChanged API CALL] Sending out OnStateChanged with type {type}."); + if (StateChanged != null) foreach (var actor in actors.Objects) StateChanged.Invoke(actor.Address); diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 660acf4..d49864f 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -1,4 +1,5 @@ using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Designs; using Glamourer.Designs.Links; @@ -201,11 +202,22 @@ public sealed class AutoDesignApplier : IDisposable } } + /// + /// JOB CHANGE IS CALLED UPON HERE. + /// private void OnJobChange(Actor actor, Job oldJob, Job newJob) { + unsafe + { + var drawObject = actor.AsCharacter->DrawObject; + Glamourer.Log.Information($"[AutoDesignApplier][OnJobChange] 0x{(nint)drawObject:X} changed job from {oldJob} ({oldJob.Id}) to {newJob} ({newJob.Id})."); + } + if (!_config.EnableAutoDesigns || !actor.Identifier(_actors, out var id)) return; + Glamourer.Log.Information($"[AutoDesignApplier][OnJobChange] We had EnableAutoDesigns active, and are a valid actor!"); + if (!GetPlayerSet(id, out var set)) { if (_state.TryGetValue(id, out var s)) diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 495d69c..032412e 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -64,12 +64,13 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 _changeCustomizeHook; + // manual invoke by calling the detours _original call to `execute to` instead of `listen to`. public bool UpdateCustomize(Model model, CustomizeArray customize) { if (!model.IsHuman) return false; - Glamourer.Log.Verbose($"[ChangeCustomize] Invoked on 0x{model.Address:X} with {customize}."); + Glamourer.Log.Information($"[ChangeCustomize] Glamour-Invoked on 0x{model.Address:X} with {customize}."); using var _ = InUpdate.EnterMethod(); var ret = _original(model.AsHuman, customize.Data, true); return ret; @@ -78,16 +79,20 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 UpdateCustomize(actor.Model, customize); + // detoured method. private bool ChangeCustomizeDetour(Human* human, byte* data, byte skipEquipment) { if (!InUpdate.InMethod) Invoke(human, ref *(CustomizeArray*)data); var ret = _changeCustomizeHook.Original(human, data, skipEquipment); + + Glamourer.Log.Information($"[ChangeCustomize] Called on with {*(CustomizeArray*)data} ({ret})."); _postEvent.Invoke(human); return ret; } + public void Subscribe(Action action, Post.Priority priority) => _postEvent.Subscribe(action, priority); diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 6d8e58b..605ca15 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -1,9 +1,11 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Events; using OtterGui.Services; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; @@ -16,39 +18,48 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService private readonly EquippedGearset _gearsetEvent; private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); + // This can be moved into client structs or penumbra.gamedata when needed. + public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; + private delegate nint ChangeGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); + + [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] + private readonly Hook _equipGearsetInternalHook = null!; + public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { _movedItemsEvent = movedItemsEvent; _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - _equipGearsetHook = - interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); + _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); + interop.InitializeFromAttributes(this); _moveItemHook.Enable(); _equipGearsetHook.Enable(); + _equipGearsetInternalHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); _equipGearsetHook.Dispose(); + _equipGearsetInternalHook.Dispose(); } private delegate int EquipGearsetDelegate(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId); private readonly Hook _equipGearsetHook; - private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) + private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { var prior = module->CurrentGearsetIndex; - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); - var set = module->GetGearset(gearsetId); - _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), gearsetId, prior, glamourPlateId, set->ClassJob); - Glamourer.Log.Excessive($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); + var set = module->GetGearset((int)gearsetId); + _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); + Glamourer.Log.Warning($"[InventoryService] [EquipInternal] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { - var entry = module->GetGearset(gearsetId); + var entry = module->GetGearset((int)gearsetId); if (entry == null) return ret; @@ -73,18 +84,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService } var plate = MirageManager.Instance()->GlamourPlates[glamourPlateId - 1]; - Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]); - Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]); - Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]); - Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]); - Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]); - Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]); - Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]); - Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]); - Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]); - Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]); - Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]); - Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]); + Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]); + Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]); + Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]); + Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]); + Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]); + Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]); + Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]); + Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]); + Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]); + Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]); + Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]); + Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]); } else { @@ -99,17 +110,17 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService } Add(EquipSlot.MainHand, ref entry->Items[0]); - Add(EquipSlot.OffHand, ref entry->Items[1]); - Add(EquipSlot.Head, ref entry->Items[2]); - Add(EquipSlot.Body, ref entry->Items[3]); - Add(EquipSlot.Hands, ref entry->Items[5]); - Add(EquipSlot.Legs, ref entry->Items[6]); - Add(EquipSlot.Feet, ref entry->Items[7]); - Add(EquipSlot.Ears, ref entry->Items[8]); - Add(EquipSlot.Neck, ref entry->Items[9]); - Add(EquipSlot.Wrists, ref entry->Items[10]); - Add(EquipSlot.RFinger, ref entry->Items[11]); - Add(EquipSlot.LFinger, ref entry->Items[12]); + Add(EquipSlot.OffHand, ref entry->Items[1]); + Add(EquipSlot.Head, ref entry->Items[2]); + Add(EquipSlot.Body, ref entry->Items[3]); + Add(EquipSlot.Hands, ref entry->Items[5]); + Add(EquipSlot.Legs, ref entry->Items[6]); + Add(EquipSlot.Feet, ref entry->Items[7]); + Add(EquipSlot.Ears, ref entry->Items[8]); + Add(EquipSlot.Neck, ref entry->Items[9]); + Add(EquipSlot.Wrists, ref entry->Items[10]); + Add(EquipSlot.RFinger, ref entry->Items[11]); + Add(EquipSlot.LFinger, ref entry->Items[12]); } _movedItemsEvent.Invoke(_itemList.ToArray()); @@ -118,6 +129,13 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService return ret; } + private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) + { + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); + Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + return ret; + } + private static uint FixId(uint itemId) => itemId % 50000; @@ -130,7 +148,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService InventoryType targetContainer, ushort targetSlot, byte unk) { var ret = _moveItemHook.Original(manager, sourceContainer, sourceSlot, targetContainer, targetSlot, unk); - Glamourer.Log.Excessive($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})"); + Glamourer.Log.Verbose($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})"); if (ret == 0) { if (InvokeSource(sourceContainer, sourceSlot, out var source)) diff --git a/Glamourer/Interop/JobService.cs b/Glamourer/Interop/JobService.cs index 1797809..f687715 100644 --- a/Glamourer/Interop/JobService.cs +++ b/Glamourer/Interop/JobService.cs @@ -50,7 +50,7 @@ public class JobService : IDisposable var newJob = Jobs.TryGetValue(newJobIndex, out var j) ? j : Jobs[0]; var oldJob = Jobs.TryGetValue(oldJobIndex, out var o) ? o : Jobs[0]; - Glamourer.Log.Excessive($"{actor} changed job from {oldJob} to {newJob}."); + Glamourer.Log.Error($"{actor} changed job from {oldJob} to {newJob}."); JobChanged?.Invoke(actor, oldJob, newJob); } } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index d1004e6..e43dc74 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -1,36 +1,124 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; using Penumbra.GameData; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; - namespace Glamourer.Interop; +/// +/// This struct is the struct that loadallequipment passes in as its gearsetData container. +/// +[StructLayout(LayoutKind.Explicit)] // Size of 70 bytes maybe? +public readonly struct GearsetItemDataStruct +{ + // Stores the weapon data. Includes both dyes in the data. + [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; + [FieldOffset(8)] public readonly WeaponModelId OffhandWeaponData; + + [FieldOffset(16)] public readonly byte CrestBitField; // A Bitfield:: ShieldCrest == 1, HeadCrest == 2, Chest Crest == 4 + [FieldOffset(17)] public readonly byte JobId; // Job ID associated with the gearset change. + + // Flicks from 0 to 128 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. + [FieldOffset(18)] public readonly byte UNK_18; + [FieldOffset(19)] public readonly byte UNK_19; // I have never seen this be anything other than 0. + + // Legacy helmet equip slot armor for a character. + [FieldOffset(20)] public readonly LegacyCharacterArmor HeadSlotArmor; + [FieldOffset(24)] public readonly LegacyCharacterArmor TopSlotArmor; + [FieldOffset(28)] public readonly LegacyCharacterArmor ArmsSlotArmor; + [FieldOffset(32)] public readonly LegacyCharacterArmor LegsSlotArmor; + [FieldOffset(26)] public readonly LegacyCharacterArmor FeetSlotArmor; + [FieldOffset(40)] public readonly LegacyCharacterArmor EarSlotArmor; + [FieldOffset(44)] public readonly LegacyCharacterArmor NeckSlotArmor; + [FieldOffset(48)] public readonly LegacyCharacterArmor WristSlotArmor; + [FieldOffset(52)] public readonly LegacyCharacterArmor RFingerSlotArmor; + [FieldOffset(56)] public readonly LegacyCharacterArmor LFingerSlotArmor; + + // Byte array of all slot's secondary dyes. + [FieldOffset(60)] public readonly byte HeadSlotSecondaryDye; + [FieldOffset(61)] public readonly byte TopSlotSecondaryDye; + [FieldOffset(62)] public readonly byte ArmsSlotSecondaryDye; + [FieldOffset(63)] public readonly byte LegsSlotSecondaryDye; + [FieldOffset(64)] public readonly byte FeetSlotSecondaryDye; + [FieldOffset(65)] public readonly byte EarSlotSecondaryDye; + [FieldOffset(66)] public readonly byte NeckSlotSecondaryDye; + [FieldOffset(67)] public readonly byte WristSlotSecondaryDye; + [FieldOffset(68)] public readonly byte RFingerSlotSecondaryDye; + [FieldOffset(69)] public readonly byte LFingerSlotSecondaryDye; +} + public unsafe class UpdateSlotService : IDisposable { - public readonly EquipSlotUpdating EquipSlotUpdatingEvent; - public readonly BonusSlotUpdating BonusSlotUpdatingEvent; - private readonly DictBonusItems _bonusItems; + public readonly EquipSlotUpdating EquipSlotUpdatingEvent; + public readonly BonusSlotUpdating BonusSlotUpdatingEvent; + private readonly DictBonusItems _bonusItems; + + #region LoadAllEquipData + /////////////////////////////////////////////////// + // This is a currently undocumented signature that loads all equipment after changing a gearset. + // :: Signature Maintainers Note: + // To obtain this signature, get the stacktrace from FlagSlotForUpdate for human, and find func `sub_140842F50`. + // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. + // + // By detouring this function, and executing the original, then logic after, we have a consistant point in time where we know all + // slots have been flagged, meaning a consistant point in time that glamourer has processed all of its updates. + public const string LoadAllEquipmentSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; + private delegate Int64 LoadAllEquipmentDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); + private Int64 LoadAllEquipmentDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) + { + // return original first so we can log the changes after + var ret = _loadAllEquipmentHook.Original(drawDataContainer, gearsetData); + + // perform logic stuff. + var owner = drawDataContainer->OwnerObject; + Glamourer.Log.Warning($"[LoadAllEquipmentDetour] Owner: 0x{(nint)owner->DrawObject:X} Finished Applying its GameState!"); + Glamourer.Log.Warning($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + + // return original. + return ret; + } + + private string FormatWeaponModelId(WeaponModelId weaponModelId) => $"Id: {weaponModelId.Id}, Type: {weaponModelId.Type}, Variant: {weaponModelId.Variant}, Stain0: {weaponModelId.Stain0}, Stain1: {weaponModelId.Stain1}"; + + private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetItemData) + { + string ret = $"\nMainhandWeaponData: {FormatWeaponModelId(gearsetItemData.MainhandWeaponData)}," + + $"\nOffhandWeaponData: {FormatWeaponModelId(gearsetItemData.OffhandWeaponData)}," + + $"\nCrestBitField: {gearsetItemData.CrestBitField} | JobId: {gearsetItemData.JobId} | UNK_18: {gearsetItemData.UNK_18} | UNK_19: {gearsetItemData.UNK_19}"; + // Iterate through offsets from 20 to 60 and format the CharacterArmor data + for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) + { + LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetItemData + offset); + int dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset + byte* dyePtr = (byte*)&gearsetItemData + dyeOffset; + ret += $"\nEquipSlot {((EquipSlot)(dyeOffset-60)).ToString()}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; + } + return ret; + } +#endregion LoadAllEquipData public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, IGameInteropProvider interop, DictBonusItems bonusItems) { EquipSlotUpdatingEvent = equipSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating; - _bonusItems = bonusItems; + _bonusItems = bonusItems; interop.InitializeFromAttributes(this); _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); + _loadAllEquipmentHook.Enable(); } public void Dispose() { _flagSlotForUpdateHook.Dispose(); _flagBonusSlotForUpdateHook.Dispose(); + _loadAllEquipmentHook.Dispose(); } public void UpdateEquipSlot(Model drawObject, EquipSlot slot, CharacterArmor data) @@ -79,24 +167,36 @@ public unsafe class UpdateSlotService : IDisposable [Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))] private readonly Hook _flagBonusSlotForUpdateHook = null!; + [Signature(LoadAllEquipmentSig, DetourName = nameof(LoadAllEquipmentDetour))] + private readonly Hook _loadAllEquipmentHook = null!; + private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToEquipSlot(); + var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; + EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); - Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + Glamourer.Log.Information($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); + returnValue = returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + + return returnValue; } private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToBonusSlot(); + var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; + BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); - Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + Glamourer.Log.Information($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); + returnValue = returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; + + return returnValue; } private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) - => _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); + { + Glamourer.Log.Warning($"Glamour-Invoked Equip Slot update for 0x{drawObject.Address:X} with {slot} and {armor}."); + return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); + } } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index f9ddb89..b592b65 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -51,7 +51,7 @@ public class StateEditor( return; var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); - Glamourer.Log.Verbose( + Glamourer.Log.Information( $"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Customize, settings.Source, state, actors, new CustomizeTransaction(idx, old, value)); } @@ -64,7 +64,7 @@ public class StateEditor( return; var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); - Glamourer.Log.Verbose( + Glamourer.Log.Information( $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.EntireCustomize, settings.Source, state, actors, new EntireCustomizeTransaction(applied, old, customizeInput)); @@ -75,7 +75,10 @@ public class StateEditor( { var state = (ActorState)data; if (!Editor.ChangeItem(state, slot, item, settings.Source, out var old, settings.Key)) + { + Glamourer.Log.Information("Not Setting State or invoking, Editor requested us not to change it!"); return; + } var type = slot.ToIndex() < 10 ? StateChangeType.Equip : StateChangeType.Weapon; var actors = type is StateChangeType.Equip @@ -86,8 +89,8 @@ public class StateEditor( if (slot is EquipSlot.MainHand) ApplyMainhandPeriphery(state, item, null, settings); - Glamourer.Log.Verbose( - $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Debug( + $"[ChangeItem] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { @@ -116,8 +119,8 @@ public class StateEditor( return; var actors = Applier.ChangeBonusItem(state, slot, settings.Source.RequiresChange()); - Glamourer.Log.Verbose( - $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Debug( + $"[ChangeBonus] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.BonusItem, settings.Source, state, actors, new BonusItemTransaction(slot, old, item)); } @@ -149,8 +152,8 @@ public class StateEditor( if (slot is EquipSlot.MainHand) ApplyMainhandPeriphery(state, item, stains, settings); - Glamourer.Log.Verbose( - $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Debug( + $"[ChangeEquip] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { StateChanged.Invoke(type, settings.Source, state, actors, new EquipTransaction(slot, old, item!.Value)); @@ -181,7 +184,7 @@ public class StateEditor( return; var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); - Glamourer.Log.Verbose( + Glamourer.Log.Debug( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, old, stains)); } @@ -250,7 +253,7 @@ public class StateEditor( return; var actors = Applier.ChangeMetaState(state, index, settings.Source.RequiresChange()); - Glamourer.Log.Verbose( + Glamourer.Log.Debug( $"Set {index.ToName()} in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Other, settings.Source, state, actors, new MetaTransaction(index, old, value)); } @@ -414,8 +417,7 @@ public class StateEditor( ? Applier.ApplyAll(state, requiresRedraw, false) : ActorData.Invalid; - Glamourer.Log.Verbose( - $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Debug($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later return; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d054a25..382e488 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -216,8 +216,7 @@ public class StateListener : IDisposable // then we do not want to use our restricted gear protection // since we assume the player has that gear modded to availability. var locked = false; - if (actor.Identifier(_actors, out var identifier) - && _manager.TryGetValue(identifier, out var state)) + if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor); locked = state.Sources[slot, false] is StateSource.IpcFixed; @@ -383,7 +382,7 @@ public class StateListener : IDisposable lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, weapon.Stains); _fistOffhands[actor] = lastFistOffhand; - Glamourer.Log.Excessive($"Storing fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); + Glamourer.Log.Verbose($"Storing fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); } _funModule.ApplyFunToWeapon(actor, ref weapon, slot); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index eabaf2f..736dd6e 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -273,7 +273,7 @@ public sealed class StateManager( if (source is not StateSource.Game) actors = Applier.ApplyAll(state, redraw, true); - Glamourer.Log.Verbose( + Glamourer.Log.Debug( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); } @@ -298,7 +298,7 @@ public sealed class StateManager( state.Materials.Clear(); - Glamourer.Log.Verbose( + Glamourer.Log.Debug( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); } From e1a41b5f3c7d144cae20c291ff2ee2fe77978e87 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 17 Jan 2025 17:39:26 -0800 Subject: [PATCH 558/786] Implements true endpoints for all glamourer operations, also correctly marks reverts and gearsets. Replaced back excessive logging to maintain with logging formats expected by glamourer. --- Glamourer/Api/DesignsApi.cs | 2 +- Glamourer/Api/IpcProviders.cs | 1 + Glamourer/Api/StateApi.cs | 27 ++++- Glamourer/Automation/AutoDesignApplier.cs | 2 - Glamourer/Designs/IDesignEditor.cs | 6 +- Glamourer/Events/GearsetDataLoaded.cs | 23 ++++ Glamourer/Events/StateUpdated.cs | 26 +++++ Glamourer/Gui/DesignQuickBar.cs | 8 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 14 +-- .../Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 4 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 4 +- Glamourer/Interop/ChangeCustomizeService.cs | 4 +- Glamourer/Interop/InventoryService.cs | 10 +- Glamourer/Interop/JobService.cs | 2 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 4 +- Glamourer/Interop/UpdateSlotService.cs | 100 ++++++++---------- Glamourer/Services/CommandService.cs | 8 +- Glamourer/State/StateEditor.cs | 5 + Glamourer/State/StateListener.cs | 29 ++++- Glamourer/State/StateManager.cs | 43 +++++++- 21 files changed, 225 insertions(+), 99 deletions(-) create mode 100644 Glamourer/Events/GearsetDataLoaded.cs create mode 100644 Glamourer/Events/StateUpdated.cs diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index ee49bd5..6c3037e 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -33,7 +33,7 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager { var once = (flags & ApplyFlag.Once) != 0; var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true, - ResetMaterials: !once && key != 0); + ResetMaterials: !once && key != 0, SendStateUpdate: true); using var restrict = ApiHelpers.Restrict(design, flags); stateManager.ApplyDesign(state, design, settings); diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 8639a22..166245f 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -52,6 +52,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), IpcSubscribers.StateChanged.Provider(pi, api.State), IpcSubscribers.StateChangedWithType.Provider(pi, api.State), + IpcSubscribers.StateUpdated.Provider(pi, api.State), IpcSubscribers.GPoseChanged.Provider(pi, api.State), ]; _initializedProvider.Invoke(); diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index ce7d226..248e294 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -11,9 +11,10 @@ using OtterGui.Services; using Penumbra.GameData.Interop; using ObjectManager = Glamourer.Interop.ObjectManager; using StateChanged = Glamourer.Events.StateChanged; +using StateUpdated = Glamourer.Events.StateUpdated; namespace Glamourer.Api; - + public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { private readonly ApiHelpers _helpers; @@ -23,6 +24,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private readonly AutoDesignApplier _autoDesigns; private readonly ObjectManager _objects; private readonly StateChanged _stateChanged; + private readonly StateUpdated _stateUpdated; private readonly GPoseService _gPose; public StateApi(ApiHelpers helpers, @@ -32,6 +34,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable AutoDesignApplier autoDesigns, ObjectManager objects, StateChanged stateChanged, + StateUpdated stateUpdated, GPoseService gPose) { _helpers = helpers; @@ -41,8 +44,10 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable _autoDesigns = autoDesigns; _objects = objects; _stateChanged = stateChanged; + _stateUpdated = stateUpdated; _gPose = gPose; _stateChanged.Subscribe(OnStateChanged, Events.StateChanged.Priority.GlamourerIpc); + _stateUpdated.Subscribe(OnStateUpdated, Events.StateUpdated.Priority.GlamourerIpc); _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.GlamourerIpc); } @@ -250,13 +255,14 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public event Action? StateChanged; public event Action? StateChangedWithType; + public event Action? StateUpdated; public event Action? GPoseChanged; private void ApplyDesign(ActorState state, DesignBase design, uint key, ApplyFlag flags) { var once = (flags & ApplyFlag.Once) != 0; var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true, - ResetMaterials: !once && key != 0); + ResetMaterials: !once && key != 0, SendStateUpdate: true); _stateManager.ApplyDesign(state, design, settings); ApiHelpers.Lock(state, key, flags); } @@ -296,7 +302,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; _autoDesigns.ReapplyAutomation(actor, state.Identifier, state, true, out var forcedRedraw); - _stateManager.ReapplyState(actor, state, forcedRedraw, source); + _stateManager.ReapplyAutomationState(actor, state, forcedRedraw, true, source); ApiHelpers.Lock(state, key, flags); } @@ -333,7 +339,8 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { - Glamourer.Log.Error($"[OnStateChanged API CALL] Sending out OnStateChanged with type {type}."); + // Remove this comment before creating PR. + Glamourer.Log.Verbose($"[OnStateChanged] Sending out OnStateChanged with type {type}."); if (StateChanged != null) foreach (var actor in actors.Objects) @@ -343,4 +350,16 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable foreach (var actor in actors.Objects) StateChangedWithType.Invoke(actor.Address, type); } + + private void OnStateUpdated(StateUpdateType type, ActorData actors) + { + if (StateUpdated != null) + foreach (var actor in actors.Objects) + { + // Remove these before creating PR + Glamourer.Log.Information($"[ENDPOINT DEBUGGING] 0x{actor.Address:X} had update of type {type}."); + Glamourer.Log.Information("--------------------------------------------------------------"); + StateUpdated.Invoke(actor.Address, type); + } + } } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index d49864f..bcc907c 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -216,8 +216,6 @@ public sealed class AutoDesignApplier : IDisposable if (!_config.EnableAutoDesigns || !actor.Identifier(_actors, out var id)) return; - Glamourer.Log.Information($"[AutoDesignApplier][OnJobChange] We had EnableAutoDesigns active, and are a valid actor!"); - if (!GetPlayerSet(id, out var set)) { if (_state.TryGetValue(id, out var s)) diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index 935263b..0178620 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -13,7 +13,8 @@ public readonly record struct ApplySettings( bool FromJobChange = false, bool UseSingleSource = false, bool MergeLinks = false, - bool ResetMaterials = false) + bool ResetMaterials = false, + bool SendStateUpdate = false) { public static readonly ApplySettings Manual = new() { @@ -24,6 +25,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = false, ResetMaterials = false, + SendStateUpdate = false, }; public static readonly ApplySettings ManualWithLinks = new() @@ -35,6 +37,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = true, ResetMaterials = false, + SendStateUpdate = false, }; public static readonly ApplySettings Game = new() @@ -46,6 +49,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = false, ResetMaterials = true, + SendStateUpdate = false, }; } diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs new file mode 100644 index 0000000..47d0108 --- /dev/null +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -0,0 +1,23 @@ +using OtterGui.Classes; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; +using Penumbra.GameData.Structs; + +namespace Glamourer.Events; + +/// +/// Triggers when the equipped gearset finished running all of its LoadEquipment, LoadWeapon, and crest calls. +/// This defines a universal endpoint of base game state application to monitor. +/// +/// The model drawobject associated with the finished load (should always be ClientPlayer) +/// +/// +public sealed class GearsetDataLoaded() + : EventWrapper(nameof(GearsetDataLoaded)) +{ + public enum Priority + { + /// + StateListener = 0, + } +} \ No newline at end of file diff --git a/Glamourer/Events/StateUpdated.cs b/Glamourer/Events/StateUpdated.cs new file mode 100644 index 0000000..82d737f --- /dev/null +++ b/Glamourer/Events/StateUpdated.cs @@ -0,0 +1,26 @@ +using Glamourer.Api.Enums; +using Glamourer.Designs.History; +using Glamourer.Interop.Structs; +using Glamourer.State; +using OtterGui.Classes; + +namespace Glamourer.Events; + +/// +/// Triggered when a Design is edited in any way. +/// +/// Parameter is the type of the change +/// Parameter is the changed saved state. +/// Parameter is the existing actors using this saved state. +/// Parameter is any additional data depending on the type of change. +/// +/// +public sealed class StateUpdated() + : EventWrapper(nameof(StateUpdated)) +{ + public enum Priority + { + /// + GlamourerIpc = int.MinValue, + } +} diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 31ca45e..bdc345f 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -183,7 +183,7 @@ public sealed class DesignQuickBar : Window, IDisposable } using var _ = design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } private void DrawRevertButton(Vector2 buttonSize) @@ -213,7 +213,7 @@ public sealed class DesignQuickBar : Window, IDisposable var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); ImGui.SameLine(); if (clicked) - _stateManager.ResetState(state!, StateSource.Manual); + _stateManager.ResetState(state!, StateSource.Manual, stateUpdate: true); } private void DrawRevertAutomationButton(Vector2 buttonSize) @@ -252,7 +252,7 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { _autoDesignApplier.ReapplyAutomation(actor, id, state!, true, out var forcedRedraw); - _stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual); + _stateManager.ReapplyAutomationState(actor, forcedRedraw, true, StateSource.Manual); } } @@ -292,7 +292,7 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { _autoDesignApplier.ReapplyAutomation(actor, id, state!, false, out var forcedRedraw); - _stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual); + _stateManager.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Manual); } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 265e1d9..ced78fb 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -385,7 +385,7 @@ public class ActorPanel { if (ImGuiUtil.DrawDisabledButton("Revert to Game", Vector2.Zero, "Revert the character to its actual state in the game.", _state!.IsLocked)) - _stateManager.ResetState(_state!, StateSource.Manual); + _stateManager.ResetState(_state!, StateSource.Manual, stateUpdate: true); ImGui.SameLine(); @@ -394,7 +394,7 @@ public class ActorPanel !_config.EnableAutoDesigns || _state!.IsLocked)) { _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false, out var forcedRedraw); - _stateManager.ReapplyState(_actor, forcedRedraw, StateSource.Manual); + _stateManager.ReapplyAutomationState(_actor, forcedRedraw, false, StateSource.Manual); } ImGui.SameLine(); @@ -403,14 +403,14 @@ public class ActorPanel !_config.EnableAutoDesigns || _state!.IsLocked)) { _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true, out var forcedRedraw); - _stateManager.ReapplyState(_actor, forcedRedraw, StateSource.Manual); + _stateManager.ReapplyAutomationState(_actor, forcedRedraw, true, StateSource.Manual); } ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton("Reapply", Vector2.Zero, "Try to reapply the configured state if something went wrong. Should generally not be necessary.", _state!.IsLocked)) - _stateManager.ReapplyState(_actor, false, StateSource.Manual); + _stateManager.ReapplyState(_actor, false, StateSource.Manual, true); } private void DrawApplyToSelf() @@ -423,7 +423,7 @@ public class ActorPanel if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), - ApplySettings.Manual); + ApplySettings.Manual with { SendStateUpdate = true }); } private void DrawApplyToTarget() @@ -440,7 +440,7 @@ public class ActorPanel if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), - ApplySettings.Manual); + ApplySettings.Manual with { SendStateUpdate = true }); } @@ -467,7 +467,7 @@ public class ActorPanel var text = ImGui.GetClipboardText(); var design = panel._converter.FromBase64(text, applyCustomize, applyGear, out _) ?? throw new Exception("The clipboard did not contain valid data."); - panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks); + panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } catch (Exception ex) { diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index 394bd7f..c44a722 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -79,7 +79,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled)) { var design = CreateDesign(plate); - _state.ApplyDesign(state!, design, ApplySettings.Manual); + _state.ApplyDesign(state!, design, ApplySettings.Manual with { SendStateUpdate = true }); } using (ImRaii.Group()) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 070ca1e..fe71609 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -460,7 +460,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } } @@ -478,7 +478,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 312bceb..345df11 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -196,7 +196,7 @@ public class NpcPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); - _state.ApplyDesign(state, design, ApplySettings.Manual); + _state.ApplyDesign(state, design, ApplySettings.Manual with { SendStateUpdate = true }); } } @@ -214,7 +214,7 @@ public class NpcPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); - _state.ApplyDesign(state, design, ApplySettings.Manual); + _state.ApplyDesign(state, design, ApplySettings.Manual with { SendStateUpdate = true }); } } diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 032412e..10e3a12 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -70,7 +70,7 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 _itemList = new(12); - // This can be moved into client structs or penumbra.gamedata when needed. + // Called by EquipGearset, but returns a pointer instead of an int. + // This is the internal function processed by all sources of Equipping a gearset, + // such as hotbar gearset application and command gearset application public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; private delegate nint ChangeGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); @@ -56,7 +58,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); var set = module->GetGearset((int)gearsetId); _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); - Glamourer.Log.Warning($"[InventoryService] [EquipInternal] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + Glamourer.Log.Verbose($"[InventoryService] [EquipInternal] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { var entry = module->GetGearset((int)gearsetId); @@ -132,7 +134,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) { var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); - Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + Glamourer.Log.Excessive($"[InventoryService] (old) Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); return ret; } @@ -148,7 +150,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService InventoryType targetContainer, ushort targetSlot, byte unk) { var ret = _moveItemHook.Original(manager, sourceContainer, sourceSlot, targetContainer, targetSlot, unk); - Glamourer.Log.Verbose($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})"); + Glamourer.Log.Excessive($"[InventoryService] Moved {sourceContainer} {sourceSlot} {targetContainer} {targetSlot} (Returned {ret})"); if (ret == 0) { if (InvokeSource(sourceContainer, sourceSlot, out var source)) diff --git a/Glamourer/Interop/JobService.cs b/Glamourer/Interop/JobService.cs index f687715..1797809 100644 --- a/Glamourer/Interop/JobService.cs +++ b/Glamourer/Interop/JobService.cs @@ -50,7 +50,7 @@ public class JobService : IDisposable var newJob = Jobs.TryGetValue(newJobIndex, out var j) ? j : Jobs[0]; var oldJob = Jobs.TryGetValue(oldJobIndex, out var o) ? o : Jobs[0]; - Glamourer.Log.Error($"{actor} changed job from {oldJob} to {newJob}."); + Glamourer.Log.Excessive($"{actor} changed job from {oldJob} to {newJob}."); JobChanged?.Invoke(actor, oldJob, newJob); } } diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index fbe0d9d..3e48fe9 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -88,7 +88,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService _actions.Enqueue((state, () => { foreach (var actor in actors.Objects) - _state.ReapplyState(actor, state, false, StateSource.IpcManual); + _state.ReapplyState(actor, state, false, StateSource.IpcManual, true); Glamourer.Log.Debug($"Automatically applied mod settings of type {type} to {id.Incognito(null)}."); }, WaitFrames)); } @@ -108,7 +108,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService _frame = currentFrame; _framework.RunOnFrameworkThread(() => { - _state.ReapplyState(_objects.Player, false, StateSource.IpcManual); + _state.ReapplyState(_objects.Player, false, StateSource.IpcManual, true); Glamourer.Log.Debug( $"Automatically applied mod settings of type {type} to {_objects.PlayerData.Identifier.Incognito(null)} (Local Player)."); }); diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index e43dc74..7e5cf59 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -23,7 +23,7 @@ public readonly struct GearsetItemDataStruct [FieldOffset(16)] public readonly byte CrestBitField; // A Bitfield:: ShieldCrest == 1, HeadCrest == 2, Chest Crest == 4 [FieldOffset(17)] public readonly byte JobId; // Job ID associated with the gearset change. - // Flicks from 0 to 128 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. + // Flicks from 0 to 127 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. [FieldOffset(18)] public readonly byte UNK_18; [FieldOffset(19)] public readonly byte UNK_19; // I have never seen this be anything other than 0. @@ -56,69 +56,47 @@ public unsafe class UpdateSlotService : IDisposable { public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent; + public readonly GearsetDataLoaded GearsetDataLoadedEvent; private readonly DictBonusItems _bonusItems; - #region LoadAllEquipData - /////////////////////////////////////////////////// - // This is a currently undocumented signature that loads all equipment after changing a gearset. - // :: Signature Maintainers Note: - // To obtain this signature, get the stacktrace from FlagSlotForUpdate for human, and find func `sub_140842F50`. - // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. - // - // By detouring this function, and executing the original, then logic after, we have a consistant point in time where we know all - // slots have been flagged, meaning a consistant point in time that glamourer has processed all of its updates. - public const string LoadAllEquipmentSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; - private delegate Int64 LoadAllEquipmentDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); - private Int64 LoadAllEquipmentDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) + // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. (MetaData not included) + public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; + private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); + private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) { - // return original first so we can log the changes after - var ret = _loadAllEquipmentHook.Original(drawDataContainer, gearsetData); + // Let the gearset data process all of its loads and slot flag update calls first. + var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); + // Ensure that the owner of the drawdata container is a character base. + Model ownerDrawObject = drawDataContainer->OwnerObject->DrawObject; + if (!ownerDrawObject.IsCharacterBase) + return ret; - // perform logic stuff. - var owner = drawDataContainer->OwnerObject; - Glamourer.Log.Warning($"[LoadAllEquipmentDetour] Owner: 0x{(nint)owner->DrawObject:X} Finished Applying its GameState!"); - Glamourer.Log.Warning($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); - - // return original. + // invoke the changed event for the state listener and return. + Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{ownerDrawObject.Address:X} Finished Applying its GameState!"); + // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject->DrawObject); return ret; } - private string FormatWeaponModelId(WeaponModelId weaponModelId) => $"Id: {weaponModelId.Id}, Type: {weaponModelId.Type}, Variant: {weaponModelId.Variant}, Stain0: {weaponModelId.Stain0}, Stain1: {weaponModelId.Stain1}"; - - private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetItemData) - { - string ret = $"\nMainhandWeaponData: {FormatWeaponModelId(gearsetItemData.MainhandWeaponData)}," + - $"\nOffhandWeaponData: {FormatWeaponModelId(gearsetItemData.OffhandWeaponData)}," + - $"\nCrestBitField: {gearsetItemData.CrestBitField} | JobId: {gearsetItemData.JobId} | UNK_18: {gearsetItemData.UNK_18} | UNK_19: {gearsetItemData.UNK_19}"; - // Iterate through offsets from 20 to 60 and format the CharacterArmor data - for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) - { - LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetItemData + offset); - int dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset - byte* dyePtr = (byte*)&gearsetItemData + dyeOffset; - ret += $"\nEquipSlot {((EquipSlot)(dyeOffset-60)).ToString()}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; - } - return ret; - } -#endregion LoadAllEquipData - - public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, IGameInteropProvider interop, - DictBonusItems bonusItems) + public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, GearsetDataLoaded gearsetDataLoaded, + IGameInteropProvider interop, DictBonusItems bonusItems) { EquipSlotUpdatingEvent = equipSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating; + GearsetDataLoadedEvent = gearsetDataLoaded; + _bonusItems = bonusItems; interop.InitializeFromAttributes(this); _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); - _loadAllEquipmentHook.Enable(); + _loadGearsetDataHook.Enable(); } public void Dispose() { _flagSlotForUpdateHook.Dispose(); _flagBonusSlotForUpdateHook.Dispose(); - _loadAllEquipmentHook.Dispose(); + _loadGearsetDataHook.Dispose(); } public void UpdateEquipSlot(Model drawObject, EquipSlot slot, CharacterArmor data) @@ -167,18 +145,16 @@ public unsafe class UpdateSlotService : IDisposable [Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))] private readonly Hook _flagBonusSlotForUpdateHook = null!; - [Signature(LoadAllEquipmentSig, DetourName = nameof(LoadAllEquipmentDetour))] - private readonly Hook _loadAllEquipmentHook = null!; + [Signature(LoadGearsetDataSig, DetourName = nameof(LoadGearsetDataDetour))] + private readonly Hook _loadGearsetDataHook = null!; private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; - EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); - Glamourer.Log.Information($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); + Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); returnValue = returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; - return returnValue; } @@ -186,17 +162,35 @@ public unsafe class UpdateSlotService : IDisposable { var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; - BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); - Glamourer.Log.Information($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); + Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); returnValue = returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; - return returnValue; } private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) { - Glamourer.Log.Warning($"Glamour-Invoked Equip Slot update for 0x{drawObject.Address:X} with {slot} and {armor}."); + Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Invoked by Glamourer on 0x{drawObject.Address:X} on {slot} with itemdata {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } + + // If you ever care to debug this, here is a formatted string output of this new gearsetDataPacket struct. + private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetData) + { + string ret = + $"\nMainhandWeaponData: Id: {gearsetData.MainhandWeaponData.Id}, Type: {gearsetData.MainhandWeaponData.Type}, " + + $"Variant: {gearsetData.MainhandWeaponData.Variant}, Stain0: {gearsetData.MainhandWeaponData.Stain0}, Stain1: {gearsetData.MainhandWeaponData.Stain1}" + + $"\nOffhandWeaponData: Id: {gearsetData.OffhandWeaponData.Id}, Type: {gearsetData.OffhandWeaponData.Type}, " + + $"Variant: {gearsetData.OffhandWeaponData.Variant}, Stain0: {gearsetData.OffhandWeaponData.Stain0}, Stain1: {gearsetData.OffhandWeaponData.Stain1}" + + $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId} | UNK_18: {gearsetData.UNK_18} | UNK_19: {gearsetData.UNK_19}"; + // Iterate through offsets from 20 to 60 and format the CharacterArmor data + for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) + { + LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); + int dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset + byte* dyePtr = (byte*)&gearsetData + dyeOffset; + ret += $"\nEquipSlot {((EquipSlot)(dyeOffset - 60)).ToString()}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; + } + return ret; + } } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 10f68ee..a869a09 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -329,7 +329,7 @@ public class CommandService : IDisposable, IApiService if (_stateManager.GetOrCreate(identifier, actor, out var state)) { _autoDesignApplier.ReapplyAutomation(actor, identifier, state, revert, out var forcedRedraw); - _stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual); + _stateManager.ReapplyAutomationState(actor, forcedRedraw, revert, StateSource.Manual); } } } @@ -378,7 +378,7 @@ public class CommandService : IDisposable, IApiService return true; foreach (var actor in data.Objects) - _stateManager.ReapplyState(actor, false, StateSource.Manual); + _stateManager.ReapplyState(actor, false, StateSource.Manual, true); } @@ -668,7 +668,7 @@ public class CommandService : IDisposable, IApiService if (!_objects.TryGetValue(identifier, out var actors)) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } else { @@ -677,7 +677,7 @@ public class CommandService : IDisposable, IApiService if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) { ApplyModSettings(design, actor, applyMods); - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); } } } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index b592b65..e1bd6a4 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -17,6 +17,7 @@ public class StateEditor( InternalStateEditor editor, StateApplier applier, StateChanged stateChanged, + StateUpdated stateUpdated, JobChangeState jobChange, Configuration config, ItemManager items, @@ -27,6 +28,7 @@ public class StateEditor( protected readonly InternalStateEditor Editor = editor; protected readonly StateApplier Applier = applier; protected readonly StateChanged StateChanged = stateChanged; + protected readonly StateUpdated StateUpdated = stateUpdated; protected readonly Configuration Config = config; protected readonly ItemManager Items = items; @@ -41,6 +43,7 @@ public class StateEditor( Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Model, source, state, actors, null); + StateUpdated.Invoke(StateUpdateType.ModelChange, actors); } /// @@ -419,6 +422,8 @@ public class StateEditor( Glamourer.Log.Debug($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later + if(settings.SendStateUpdate) + StateUpdated.Invoke(StateUpdateType.DesignApplied, actors); return; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 382e488..4d10c49 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -14,6 +14,7 @@ using Penumbra.GameData.DataContainers; using Glamourer.Designs; using Penumbra.GameData.Interop; using ObjectManager = Glamourer.Interop.ObjectManager; +using Glamourer.Api.Enums; namespace Glamourer.State; @@ -34,10 +35,12 @@ public class StateListener : IDisposable private readonly PenumbraService _penumbra; private readonly EquipSlotUpdating _equipSlotUpdating; private readonly BonusSlotUpdating _bonusSlotUpdating; + private readonly GearsetDataLoaded _gearsetDataLoaded; private readonly WeaponLoading _weaponLoading; private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly VisorStateChanged _visorState; private readonly WeaponVisibilityChanged _weaponVisibility; + private readonly StateUpdated _stateUpdated; private readonly AutoDesignApplier _autoDesignApplier; private readonly FunModule _funModule; private readonly HumanModelList _humans; @@ -54,11 +57,11 @@ public class StateListener : IDisposable private ActorState? _customizeState; public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, - EquipSlotUpdating equipSlotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, + EquipSlotUpdating equipSlotUpdating, GearsetDataLoaded gearsetDataLoaded, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, - CrestService crestService, BonusSlotUpdating bonusSlotUpdating) + CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateUpdated stateUpdated) { _manager = manager; _items = items; @@ -66,6 +69,7 @@ public class StateListener : IDisposable _actors = actors; _config = config; _equipSlotUpdating = equipSlotUpdating; + _gearsetDataLoaded = gearsetDataLoaded; _weaponLoading = weaponLoading; _visorState = visorState; _weaponVisibility = weaponVisibility; @@ -82,6 +86,7 @@ public class StateListener : IDisposable _condition = condition; _crestService = crestService; _bonusSlotUpdating = bonusSlotUpdating; + _stateUpdated = stateUpdated; Subscribe(); } @@ -259,6 +264,22 @@ public class StateListener : IDisposable } } + private void OnGearsetDataLoaded(Model model) + { + var actor = _penumbra.GameObjectFromDrawObject(model); + if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + return; + + // ensure actor and state are valid. + if (!actor.Identifier(_actors, out var identifier)) + return; + + _objects.Update(); + if (_objects.TryGetValue(identifier, out var actors) && actors.Valid) + _stateUpdated.Invoke(StateUpdateType.Gearset, actors); + } + + private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items) { _objects.Update(); @@ -382,7 +403,7 @@ public class StateListener : IDisposable lastFistOffhand = new CharacterWeapon((PrimaryId)(weapon.Skeleton.Id + 50), weapon.Weapon, weapon.Variant, weapon.Stains); _fistOffhands[actor] = lastFistOffhand; - Glamourer.Log.Verbose($"Storing fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); + Glamourer.Log.Excessive($"Storing fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); } _funModule.ApplyFunToWeapon(actor, ref weapon, slot); @@ -765,6 +786,7 @@ public class StateListener : IDisposable _penumbra.CreatedCharacterBase += OnCreatedCharacterBase; _equipSlotUpdating.Subscribe(OnEquipSlotUpdating, EquipSlotUpdating.Priority.StateListener); _bonusSlotUpdating.Subscribe(OnBonusSlotUpdating, BonusSlotUpdating.Priority.StateListener); + _gearsetDataLoaded.Subscribe(OnGearsetDataLoaded, GearsetDataLoaded.Priority.StateListener); _movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener); _weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener); _visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener); @@ -782,6 +804,7 @@ public class StateListener : IDisposable _penumbra.CreatedCharacterBase -= OnCreatedCharacterBase; _equipSlotUpdating.Unsubscribe(OnEquipSlotUpdating); _bonusSlotUpdating.Unsubscribe(OnBonusSlotUpdating); + _gearsetDataLoaded.Unsubscribe(OnGearsetDataLoaded); _movedEquipment.Unsubscribe(OnMovedEquipment); _weaponLoading.Unsubscribe(OnWeaponLoading); _visorState.Unsubscribe(OnVisorChange); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 736dd6e..129f8bb 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -21,6 +21,7 @@ public sealed class StateManager( ActorManager _actors, ItemManager items, StateChanged @event, + StateUpdated @event2, StateApplier applier, InternalStateEditor editor, HumanModelList _humans, @@ -30,7 +31,7 @@ public sealed class StateManager( DesignMerger merger, ModSettingApplier modApplier, GPoseService gPose) - : StateEditor(editor, applier, @event, jobChange, config, items, merger, modApplier, gPose), + : StateEditor(editor, applier, @event, @event2, jobChange, config, items, merger, modApplier, gPose), IReadOnlyDictionary { private readonly Dictionary _states = []; @@ -235,7 +236,7 @@ public sealed class StateManager( public void TurnHuman(ActorState state, StateSource source, uint key = 0) => ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key); - public void ResetState(ActorState state, StateSource source, uint key = 0) + public void ResetState(ActorState state, StateSource source, uint key = 0, bool stateUpdate = false) { if (!state.Unlock(key)) return; @@ -276,6 +277,9 @@ public sealed class StateManager( Glamourer.Log.Debug( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); + // only invoke if we define this reset call as the final call in our state update. + if(stateUpdate) + StateUpdated.Invoke(StateUpdateType.Revert, actors); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -301,6 +305,8 @@ public sealed class StateManager( Glamourer.Log.Debug( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); + // Update that we have completed a full operation. (We can do this directly as nothing else is linked) + StateUpdated.Invoke(StateUpdateType.RevertAdvanced, actors); } public void ResetCustomize(ActorState state, StateSource source, uint key = 0) @@ -318,6 +324,8 @@ public sealed class StateManager( actors = Applier.ChangeCustomize(state, true); Glamourer.Log.Verbose( $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + // Update that we have completed a full operation. (We can do this directly as nothing else is linked) + StateUpdated.Invoke(StateUpdateType.RevertCustomize, actors); } public void ResetEquip(ActorState state, StateSource source, uint key = 0) @@ -367,6 +375,8 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); + // Update that we have completed a full operation. (We can do this directly as nothing else is linked) + StateUpdated.Invoke(StateUpdateType.RevertEquipment, actors); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) @@ -443,21 +453,44 @@ public sealed class StateManager( } } - public void ReapplyState(Actor actor, bool forceRedraw, StateSource source) + public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool isUpdate = false) { if (!GetOrCreate(actor, out var state)) return; - ReapplyState(actor, state, forceRedraw, source); + ReapplyState(actor, state, forceRedraw, source, isUpdate); } - public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source) + public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool isUpdate) { var data = Applier.ApplyAll(state, forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); + if(isUpdate) + StateUpdated.Invoke(StateUpdateType.Reapply, data); + } + + /// Automation variant for reapply, to fire the correct StateUpdateType once reapplied. + public void ReapplyAutomationState(Actor actor, bool forceRedraw, bool wasReset, StateSource source) + { + if (!GetOrCreate(actor, out var state)) + return; + + ReapplyAutomationState(actor, state, forceRedraw, wasReset, source); + } + + /// Automation variant for reapply, to fire the correct StateUpdateType once reapplied. + public void ReapplyAutomationState(Actor actor, ActorState state, bool forceRedraw, bool wasReset, StateSource source) + { + var data = Applier.ApplyAll(state, + forceRedraw + || !actor.Model.IsHuman + || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); + StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); + // invoke the automation update based on what reset is. + StateUpdated.Invoke(wasReset ? StateUpdateType.RevertAutomation : StateUpdateType.ReapplyAutomation, data); } public void DeleteState(ActorIdentifier identifier) From 8b609e5f0506e4d64f843631e47ecc6adcd10e90 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 17 Jan 2025 18:09:10 -0800 Subject: [PATCH 559/786] corrected comments and formatting to reflect Glamourer's main branch. --- Glamourer/Automation/AutoDesignApplier.cs | 9 ---- Glamourer/Events/GearsetDataLoaded.cs | 4 +- Glamourer/Events/StateUpdated.cs | 4 +- Glamourer/Interop/ChangeCustomizeService.cs | 3 -- Glamourer/Interop/InventoryService.cs | 38 +++++++-------- Glamourer/Interop/UpdateSlotService.cs | 51 ++++++++++----------- Glamourer/State/StateEditor.cs | 25 +++++----- Glamourer/State/StateListener.cs | 2 +- Glamourer/State/StateManager.cs | 12 ++--- 9 files changed, 64 insertions(+), 84 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index bcc907c..52956cc 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -202,17 +202,8 @@ public sealed class AutoDesignApplier : IDisposable } } - /// - /// JOB CHANGE IS CALLED UPON HERE. - /// private void OnJobChange(Actor actor, Job oldJob, Job newJob) { - unsafe - { - var drawObject = actor.AsCharacter->DrawObject; - Glamourer.Log.Information($"[AutoDesignApplier][OnJobChange] 0x{(nint)drawObject:X} changed job from {oldJob} ({oldJob.Id}) to {newJob} ({newJob.Id})."); - } - if (!_config.EnableAutoDesigns || !actor.Identifier(_actors, out var id)) return; diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index 47d0108..680ae3f 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -1,7 +1,5 @@ using OtterGui.Classes; -using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; -using Penumbra.GameData.Structs; namespace Glamourer.Events; @@ -9,7 +7,7 @@ namespace Glamourer.Events; /// Triggers when the equipped gearset finished running all of its LoadEquipment, LoadWeapon, and crest calls. /// This defines a universal endpoint of base game state application to monitor. /// -/// The model drawobject associated with the finished load (should always be ClientPlayer) +/// The model drawobject associated with the finished load (Also fired by other players on render) /// /// public sealed class GearsetDataLoaded() diff --git a/Glamourer/Events/StateUpdated.cs b/Glamourer/Events/StateUpdated.cs index 82d737f..f18a69a 100644 --- a/Glamourer/Events/StateUpdated.cs +++ b/Glamourer/Events/StateUpdated.cs @@ -9,10 +9,8 @@ namespace Glamourer.Events; /// /// Triggered when a Design is edited in any way. /// -/// Parameter is the type of the change -/// Parameter is the changed saved state. +/// Parameter is the operation that finished updating the saved state. /// Parameter is the existing actors using this saved state. -/// Parameter is any additional data depending on the type of change. /// /// public sealed class StateUpdated() diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 10e3a12..495d69c 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -64,7 +64,6 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 _changeCustomizeHook; - // manual invoke by calling the detours _original call to `execute to` instead of `listen to`. public bool UpdateCustomize(Model model, CustomizeArray customize) { if (!model.IsHuman) @@ -79,7 +78,6 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 UpdateCustomize(actor.Model, customize); - // detoured method. private bool ChangeCustomizeDetour(Human* human, byte* data, byte skipEquipment) { if (!InUpdate.InMethod) @@ -90,7 +88,6 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 action, Post.Priority priority) => _postEvent.Subscribe(action, priority); diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 743bea1..33ba5cf 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -5,7 +5,6 @@ using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Events; using OtterGui.Services; -using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; @@ -14,10 +13,6 @@ namespace Glamourer.Interop; public sealed unsafe class InventoryService : IDisposable, IRequiredService { - private readonly MovedEquipment _movedItemsEvent; - private readonly EquippedGearset _gearsetEvent; - private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); - // Called by EquipGearset, but returns a pointer instead of an int. // This is the internal function processed by all sources of Equipping a gearset, // such as hotbar gearset application and command gearset application @@ -27,10 +22,16 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] private readonly Hook _equipGearsetInternalHook = null!; + // The following above is currently pending for an accepted PR in FFXIVCLientStructs. + // Once accepted, remove everything above this comment and replace EquipGearset with EquipGearsetInternal. + + private readonly MovedEquipment _movedItemsEvent; + private readonly EquippedGearset _gearsetEvent; + private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { _movedItemsEvent = movedItemsEvent; - _gearsetEvent = gearsetEvent; + _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); @@ -58,7 +59,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); var set = module->GetGearset((int)gearsetId); _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); - Glamourer.Log.Verbose($"[InventoryService] [EquipInternal] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { var entry = module->GetGearset((int)gearsetId); @@ -131,9 +132,10 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService return ret; } + // Remove once internal is added. This no longer serves any purpose. private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) { - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); Glamourer.Log.Excessive($"[InventoryService] (old) Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); return ret; } @@ -216,18 +218,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService private static EquipSlot GetSlot(uint slot) => slot switch { - 0 => EquipSlot.MainHand, - 1 => EquipSlot.OffHand, - 2 => EquipSlot.Head, - 3 => EquipSlot.Body, - 4 => EquipSlot.Hands, - 6 => EquipSlot.Legs, - 7 => EquipSlot.Feet, - 8 => EquipSlot.Ears, - 9 => EquipSlot.Neck, + 0 => EquipSlot.MainHand, + 1 => EquipSlot.OffHand, + 2 => EquipSlot.Head, + 3 => EquipSlot.Body, + 4 => EquipSlot.Hands, + 6 => EquipSlot.Legs, + 7 => EquipSlot.Feet, + 8 => EquipSlot.Ears, + 9 => EquipSlot.Neck, 10 => EquipSlot.Wrists, 11 => EquipSlot.RFinger, 12 => EquipSlot.LFinger, - _ => EquipSlot.Unknown, + _ => EquipSlot.Unknown, }; } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 7e5cf59..9ee8d8f 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -8,12 +8,11 @@ using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; + namespace Glamourer.Interop; -/// -/// This struct is the struct that loadallequipment passes in as its gearsetData container. -/// -[StructLayout(LayoutKind.Explicit)] // Size of 70 bytes maybe? +// This struct is implemented into a PR for FFXIVClientStructs. Once merged, remove this struct and reference the data in ClientStructs instead. +[StructLayout(LayoutKind.Explicit)] public readonly struct GearsetItemDataStruct { // Stores the weapon data. Includes both dyes in the data. @@ -54,30 +53,15 @@ public readonly struct GearsetItemDataStruct public unsafe class UpdateSlotService : IDisposable { + // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. (MetaData not included) + public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; + private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); + // The above can be removed after the FFXIVClientStruct Merge is made! + public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent; public readonly GearsetDataLoaded GearsetDataLoadedEvent; private readonly DictBonusItems _bonusItems; - - // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. (MetaData not included) - public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; - private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) - { - // Let the gearset data process all of its loads and slot flag update calls first. - var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); - // Ensure that the owner of the drawdata container is a character base. - Model ownerDrawObject = drawDataContainer->OwnerObject->DrawObject; - if (!ownerDrawObject.IsCharacterBase) - return ret; - - // invoke the changed event for the state listener and return. - Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{ownerDrawObject.Address:X} Finished Applying its GameState!"); - // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); - GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject->DrawObject); - return ret; - } - public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, GearsetDataLoaded gearsetDataLoaded, IGameInteropProvider interop, DictBonusItems bonusItems) { @@ -150,7 +134,7 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToEquipSlot(); + var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -160,7 +144,7 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToBonusSlot(); + var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -173,6 +157,20 @@ public unsafe class UpdateSlotService : IDisposable Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Invoked by Glamourer on 0x{drawObject.Address:X} on {slot} with itemdata {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } + private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) + { + // Let the gearset data process all of its loads and slot flag update calls first. + var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); + Model ownerDrawObject = drawDataContainer->OwnerObject->DrawObject; + if (!ownerDrawObject.IsCharacterBase) + return ret; + + // invoke the changed event for the state listener and return. + Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{ownerDrawObject.Address:X} Finished Applying its GameState!"); + // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject->DrawObject); + return ret; + } // If you ever care to debug this, here is a formatted string output of this new gearsetDataPacket struct. private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetData) @@ -183,7 +181,6 @@ public unsafe class UpdateSlotService : IDisposable $"\nOffhandWeaponData: Id: {gearsetData.OffhandWeaponData.Id}, Type: {gearsetData.OffhandWeaponData.Type}, " + $"Variant: {gearsetData.OffhandWeaponData.Variant}, Stain0: {gearsetData.OffhandWeaponData.Stain0}, Stain1: {gearsetData.OffhandWeaponData.Stain1}" + $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId} | UNK_18: {gearsetData.UNK_18} | UNK_19: {gearsetData.UNK_19}"; - // Iterate through offsets from 20 to 60 and format the CharacterArmor data for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) { LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index e1bd6a4..891c61d 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -54,7 +54,7 @@ public class StateEditor( return; var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); - Glamourer.Log.Information( + Glamourer.Log.Verbose( $"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Customize, settings.Source, state, actors, new CustomizeTransaction(idx, old, value)); } @@ -67,7 +67,7 @@ public class StateEditor( return; var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); - Glamourer.Log.Information( + Glamourer.Log.Verbose( $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.EntireCustomize, settings.Source, state, actors, new EntireCustomizeTransaction(applied, old, customizeInput)); @@ -78,10 +78,7 @@ public class StateEditor( { var state = (ActorState)data; if (!Editor.ChangeItem(state, slot, item, settings.Source, out var old, settings.Key)) - { - Glamourer.Log.Information("Not Setting State or invoking, Editor requested us not to change it!"); return; - } var type = slot.ToIndex() < 10 ? StateChangeType.Equip : StateChangeType.Weapon; var actors = type is StateChangeType.Equip @@ -92,8 +89,8 @@ public class StateEditor( if (slot is EquipSlot.MainHand) ApplyMainhandPeriphery(state, item, null, settings); - Glamourer.Log.Debug( - $"[ChangeItem] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { @@ -122,8 +119,8 @@ public class StateEditor( return; var actors = Applier.ChangeBonusItem(state, slot, settings.Source.RequiresChange()); - Glamourer.Log.Debug( - $"[ChangeBonus] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.BonusItem, settings.Source, state, actors, new BonusItemTransaction(slot, old, item)); } @@ -155,8 +152,8 @@ public class StateEditor( if (slot is EquipSlot.MainHand) ApplyMainhandPeriphery(state, item, stains, settings); - Glamourer.Log.Debug( - $"[ChangeEquip] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { StateChanged.Invoke(type, settings.Source, state, actors, new EquipTransaction(slot, old, item!.Value)); @@ -187,7 +184,7 @@ public class StateEditor( return; var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); - Glamourer.Log.Debug( + Glamourer.Log.Verbose( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, old, stains)); } @@ -419,8 +416,8 @@ public class StateEditor( var actors = settings.Source.RequiresChange() ? Applier.ApplyAll(state, requiresRedraw, false) : ActorData.Invalid; - - Glamourer.Log.Debug($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); + + Glamourer.Log.Verbose($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later if(settings.SendStateUpdate) StateUpdated.Invoke(StateUpdateType.DesignApplied, actors); diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 4d10c49..d312815 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -13,8 +13,8 @@ using Glamourer.GameData; using Penumbra.GameData.DataContainers; using Glamourer.Designs; using Penumbra.GameData.Interop; -using ObjectManager = Glamourer.Interop.ObjectManager; using Glamourer.Api.Enums; +using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.State; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 129f8bb..0348148 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -274,7 +274,7 @@ public sealed class StateManager( if (source is not StateSource.Game) actors = Applier.ApplyAll(state, redraw, true); - Glamourer.Log.Debug( + Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // only invoke if we define this reset call as the final call in our state update. @@ -302,7 +302,7 @@ public sealed class StateManager( state.Materials.Clear(); - Glamourer.Log.Debug( + Glamourer.Log.Verbose( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) @@ -453,22 +453,22 @@ public sealed class StateManager( } } - public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool isUpdate = false) + public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool stateUpdate = false) { if (!GetOrCreate(actor, out var state)) return; - ReapplyState(actor, state, forceRedraw, source, isUpdate); + ReapplyState(actor, state, forceRedraw, source, stateUpdate); } - public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool isUpdate) + public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool stateUpdate) { var data = Applier.ApplyAll(state, forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); - if(isUpdate) + if(stateUpdate) StateUpdated.Invoke(StateUpdateType.Reapply, data); } From 9c57935a872dc98fa0ddfdcc81d891dada3b0054 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 17 Jan 2025 18:52:16 -0800 Subject: [PATCH 560/786] removed unessisary usings and corrected gearsetDataLoaded stateListener ref. --- Glamourer/Automation/AutoDesignApplier.cs | 1 - Glamourer/Events/GearsetDataLoaded.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 52956cc..660acf4 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -1,5 +1,4 @@ using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Designs; using Glamourer.Designs.Links; diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index 680ae3f..dd12bc1 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -15,7 +15,7 @@ public sealed class GearsetDataLoaded() { public enum Priority { - /// + /// StateListener = 0, } } \ No newline at end of file From 1d185e9bfe8f9badb6b58c62f6cfda49bcefba8a Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Sun, 19 Jan 2025 09:07:43 -0800 Subject: [PATCH 561/786] Added Proper Unsubsribe from OnStateUpdated. Ensures that ReapplyAutomation is called from OnAutomationChange when change occurs. Reformatted temporary Signature locations and comments to align with the structure of the respective classes. --- Glamourer/Api/StateApi.cs | 1 + Glamourer/Automation/AutoDesignApplier.cs | 4 +-- Glamourer/Interop/InventoryService.cs | 35 ++++++------------ Glamourer/Interop/UpdateSlotService.cs | 44 +++++++++++++---------- 4 files changed, 39 insertions(+), 45 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 248e294..34f4ad9 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -54,6 +54,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public void Dispose() { _stateChanged.Unsubscribe(OnStateChanged); + _stateUpdated.Unsubscribe(OnStateUpdated); _gPose.Unsubscribe(OnGPoseChange); } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 660acf4..1655c15 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -163,7 +163,7 @@ public sealed class AutoDesignApplier : IDisposable { Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw); foreach (var actor in data.Objects) - _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); + _state.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Fixed); } } else if (_objects.TryGetValueAllWorld(id, out data) || _objects.TryGetValueNonOwned(id, out data)) @@ -174,7 +174,7 @@ public sealed class AutoDesignApplier : IDisposable if (_state.GetOrCreate(specificId, actor, out var state)) { Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw); - _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); + _state.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Fixed); } } } diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 33ba5cf..6c4a223 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -13,18 +13,6 @@ namespace Glamourer.Interop; public sealed unsafe class InventoryService : IDisposable, IRequiredService { - // Called by EquipGearset, but returns a pointer instead of an int. - // This is the internal function processed by all sources of Equipping a gearset, - // such as hotbar gearset application and command gearset application - public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; - private delegate nint ChangeGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - - [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] - private readonly Hook _equipGearsetInternalHook = null!; - - // The following above is currently pending for an accepted PR in FFXIVCLientStructs. - // Once accepted, remove everything above this comment and replace EquipGearset with EquipGearsetInternal. - private readonly MovedEquipment _movedItemsEvent; private readonly EquippedGearset _gearsetEvent; private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); @@ -34,24 +22,29 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); + // This can be uncommented after ClientStructs Updates with EquipGearsetInternal after merged PR. (See comment below) + //_equipGearsetInternalHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetInternalDetour); + + // Can be removed after ClientStructs Update since this is only needed for current EquipGearsetInternal [Signature] interop.InitializeFromAttributes(this); _moveItemHook.Enable(); - _equipGearsetHook.Enable(); _equipGearsetInternalHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); - _equipGearsetHook.Dispose(); _equipGearsetInternalHook.Dispose(); } - private delegate int EquipGearsetDelegate(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId); + // This is the internal function processed by all sources of Equipping a gearset, such as hotbar gearset application and command gearset application. + // Currently is pending to ClientStructs for integration. See: https://github.com/aers/FFXIVClientStructs/pull/1277 + public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; + private delegate nint EquipGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - private readonly Hook _equipGearsetHook; + [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] + private readonly Hook _equipGearsetInternalHook = null!; private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { @@ -132,14 +125,6 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService return ret; } - // Remove once internal is added. This no longer serves any purpose. - private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) - { - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); - Glamourer.Log.Excessive($"[InventoryService] (old) Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); - return ret; - } - private static uint FixId(uint itemId) => itemId % 50000; diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 9ee8d8f..5f6e9cb 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -11,9 +11,9 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop; -// This struct is implemented into a PR for FFXIVClientStructs. Once merged, remove this struct and reference the data in ClientStructs instead. +// Can be removed once merged with client structs and referenced directly. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files [StructLayout(LayoutKind.Explicit)] -public readonly struct GearsetItemDataStruct +public readonly struct GearsetDataStruct { // Stores the weapon data. Includes both dyes in the data. [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; @@ -53,11 +53,6 @@ public readonly struct GearsetItemDataStruct public unsafe class UpdateSlotService : IDisposable { - // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. (MetaData not included) - public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; - private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); - // The above can be removed after the FFXIVClientStruct Merge is made! - public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent; public readonly GearsetDataLoaded GearsetDataLoadedEvent; @@ -68,9 +63,12 @@ public unsafe class UpdateSlotService : IDisposable EquipSlotUpdatingEvent = equipSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating; GearsetDataLoadedEvent = gearsetDataLoaded; - _bonusItems = bonusItems; + + // Usable after the merge with client structs. + //_loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); interop.InitializeFromAttributes(this); + _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); _loadGearsetDataHook.Enable(); @@ -129,6 +127,19 @@ public unsafe class UpdateSlotService : IDisposable [Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))] private readonly Hook _flagBonusSlotForUpdateHook = null!; + // This signature is what calls the weapon/equipment/crest load functions in the drawData container inherited from a human/characterBase. + // + // Contrary to assumption, this is not frequently fired when any slot changes, and is instead only called when another player + // initially loads, or when the client player changes gearsets. (Does not fire when another player or self is redrawn) + // + // This functions purpose is to iterate all Equipment/Weapon/Crest data on gearset change / initial player load, and determine which slots need to fire FlagSlotForUpdate. + // + // Because Glamourer processes GameState changes by detouring this method, this means by returning original after detour, any logic performed after will occur + // AFTER Glamourer finishes applying all changes to the game State, providing a gearset endpoint. (MetaData not included) + // Currently pending a merge to clientStructs, after which it can be removed, along with the explicit struct. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files + public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; + private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetDataStruct* gearsetData); + [Signature(LoadGearsetDataSig, DetourName = nameof(LoadGearsetDataDetour))] private readonly Hook _loadGearsetDataHook = null!; @@ -157,23 +168,20 @@ public unsafe class UpdateSlotService : IDisposable Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Invoked by Glamourer on 0x{drawObject.Address:X} on {slot} with itemdata {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) + private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetDataStruct* gearsetData) { // Let the gearset data process all of its loads and slot flag update calls first. var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); - Model ownerDrawObject = drawDataContainer->OwnerObject->DrawObject; - if (!ownerDrawObject.IsCharacterBase) - return ret; - - // invoke the changed event for the state listener and return. - Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{ownerDrawObject.Address:X} Finished Applying its GameState!"); + Model drawObject = drawDataContainer->OwnerObject->DrawObject; + Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{drawObject.Address:X} Finished Applying its GameState!"); + GearsetDataLoadedEvent.Invoke(drawObject); + // Can use for debugging, if desired. // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); - GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject->DrawObject); return ret; } - // If you ever care to debug this, here is a formatted string output of this new gearsetDataPacket struct. - private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetData) + // If you ever care to debug this, here is a formatted string output of this new gearsetData struct. + private string FormatGearsetItemDataStruct(GearsetDataStruct gearsetData) { string ret = $"\nMainhandWeaponData: Id: {gearsetData.MainhandWeaponData.Id}, Type: {gearsetData.MainhandWeaponData.Type}, " + From 1cd8e5fb7ec29097aaa2e413fce01a8aadb7a0cf Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Sun, 19 Jan 2025 09:35:10 -0800 Subject: [PATCH 562/786] Corrected comments on StateApi for PR --- Glamourer/Api/StateApi.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 34f4ad9..cb7fe51 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -340,9 +340,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { - // Remove this comment before creating PR. - Glamourer.Log.Verbose($"[OnStateChanged] Sending out OnStateChanged with type {type}."); - + // Glamourer.Log.Verbose($"[OnStateChanged] Sending out OnStateChanged with type {type}."); if (StateChanged != null) foreach (var actor in actors.Objects) StateChanged.Invoke(actor.Address); @@ -354,13 +352,9 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateUpdated(StateUpdateType type, ActorData actors) { + // Glamourer.Log.Verbose($"[OnStateUpdated] Sending out OnStateUpdated with type {type}."); if (StateUpdated != null) foreach (var actor in actors.Objects) - { - // Remove these before creating PR - Glamourer.Log.Information($"[ENDPOINT DEBUGGING] 0x{actor.Address:X} had update of type {type}."); - Glamourer.Log.Information("--------------------------------------------------------------"); StateUpdated.Invoke(actor.Address, type); - } } } From ebdfd3f8bc56dcd7a12f4cb3e9f84164e8dd946e Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Sun, 19 Jan 2025 09:50:34 -0800 Subject: [PATCH 563/786] Correct spacing and formatting further to align with main Glamourer branch preferences. --- Glamourer/Interop/InventoryService.cs | 71 ++++++++++---------- Glamourer/Interop/UpdateSlotService.cs | 91 +++++++++++++------------- Glamourer/State/StateEditor.cs | 5 +- 3 files changed, 83 insertions(+), 84 deletions(-) diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 6c4a223..f0ed6b5 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -13,9 +13,10 @@ namespace Glamourer.Interop; public sealed unsafe class InventoryService : IDisposable, IRequiredService { - private readonly MovedEquipment _movedItemsEvent; - private readonly EquippedGearset _gearsetEvent; + private readonly MovedEquipment _movedItemsEvent; + private readonly EquippedGearset _gearsetEvent; private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); + public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { _movedItemsEvent = movedItemsEvent; @@ -80,18 +81,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService } var plate = MirageManager.Instance()->GlamourPlates[glamourPlateId - 1]; - Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]); - Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]); - Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]); - Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]); - Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]); - Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]); - Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]); - Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]); - Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]); - Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]); - Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]); - Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]); + Add(EquipSlot.MainHand, plate.ItemIds[0], StainIds.FromGlamourPlate(plate, 0), ref entry->Items[0]); + Add(EquipSlot.OffHand, plate.ItemIds[1], StainIds.FromGlamourPlate(plate, 1), ref entry->Items[1]); + Add(EquipSlot.Head, plate.ItemIds[2], StainIds.FromGlamourPlate(plate, 2), ref entry->Items[2]); + Add(EquipSlot.Body, plate.ItemIds[3], StainIds.FromGlamourPlate(plate, 3), ref entry->Items[3]); + Add(EquipSlot.Hands, plate.ItemIds[4], StainIds.FromGlamourPlate(plate, 4), ref entry->Items[5]); + Add(EquipSlot.Legs, plate.ItemIds[5], StainIds.FromGlamourPlate(plate, 5), ref entry->Items[6]); + Add(EquipSlot.Feet, plate.ItemIds[6], StainIds.FromGlamourPlate(plate, 6), ref entry->Items[7]); + Add(EquipSlot.Ears, plate.ItemIds[7], StainIds.FromGlamourPlate(plate, 7), ref entry->Items[8]); + Add(EquipSlot.Neck, plate.ItemIds[8], StainIds.FromGlamourPlate(plate, 8), ref entry->Items[9]); + Add(EquipSlot.Wrists, plate.ItemIds[9], StainIds.FromGlamourPlate(plate, 9), ref entry->Items[10]); + Add(EquipSlot.RFinger, plate.ItemIds[10], StainIds.FromGlamourPlate(plate, 10), ref entry->Items[11]); + Add(EquipSlot.LFinger, plate.ItemIds[11], StainIds.FromGlamourPlate(plate, 11), ref entry->Items[12]); } else { @@ -106,17 +107,17 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService } Add(EquipSlot.MainHand, ref entry->Items[0]); - Add(EquipSlot.OffHand, ref entry->Items[1]); - Add(EquipSlot.Head, ref entry->Items[2]); - Add(EquipSlot.Body, ref entry->Items[3]); - Add(EquipSlot.Hands, ref entry->Items[5]); - Add(EquipSlot.Legs, ref entry->Items[6]); - Add(EquipSlot.Feet, ref entry->Items[7]); - Add(EquipSlot.Ears, ref entry->Items[8]); - Add(EquipSlot.Neck, ref entry->Items[9]); - Add(EquipSlot.Wrists, ref entry->Items[10]); - Add(EquipSlot.RFinger, ref entry->Items[11]); - Add(EquipSlot.LFinger, ref entry->Items[12]); + Add(EquipSlot.OffHand, ref entry->Items[1]); + Add(EquipSlot.Head, ref entry->Items[2]); + Add(EquipSlot.Body, ref entry->Items[3]); + Add(EquipSlot.Hands, ref entry->Items[5]); + Add(EquipSlot.Legs, ref entry->Items[6]); + Add(EquipSlot.Feet, ref entry->Items[7]); + Add(EquipSlot.Ears, ref entry->Items[8]); + Add(EquipSlot.Neck, ref entry->Items[9]); + Add(EquipSlot.Wrists, ref entry->Items[10]); + Add(EquipSlot.RFinger, ref entry->Items[11]); + Add(EquipSlot.LFinger, ref entry->Items[12]); } _movedItemsEvent.Invoke(_itemList.ToArray()); @@ -203,18 +204,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService private static EquipSlot GetSlot(uint slot) => slot switch { - 0 => EquipSlot.MainHand, - 1 => EquipSlot.OffHand, - 2 => EquipSlot.Head, - 3 => EquipSlot.Body, - 4 => EquipSlot.Hands, - 6 => EquipSlot.Legs, - 7 => EquipSlot.Feet, - 8 => EquipSlot.Ears, - 9 => EquipSlot.Neck, + 0 => EquipSlot.MainHand, + 1 => EquipSlot.OffHand, + 2 => EquipSlot.Head, + 3 => EquipSlot.Body, + 4 => EquipSlot.Hands, + 6 => EquipSlot.Legs, + 7 => EquipSlot.Feet, + 8 => EquipSlot.Ears, + 9 => EquipSlot.Neck, 10 => EquipSlot.Wrists, 11 => EquipSlot.RFinger, 12 => EquipSlot.LFinger, - _ => EquipSlot.Unknown, + _ => EquipSlot.Unknown, }; } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 5f6e9cb..e453c6e 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -11,46 +11,6 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop; -// Can be removed once merged with client structs and referenced directly. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files -[StructLayout(LayoutKind.Explicit)] -public readonly struct GearsetDataStruct -{ - // Stores the weapon data. Includes both dyes in the data. - [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; - [FieldOffset(8)] public readonly WeaponModelId OffhandWeaponData; - - [FieldOffset(16)] public readonly byte CrestBitField; // A Bitfield:: ShieldCrest == 1, HeadCrest == 2, Chest Crest == 4 - [FieldOffset(17)] public readonly byte JobId; // Job ID associated with the gearset change. - - // Flicks from 0 to 127 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. - [FieldOffset(18)] public readonly byte UNK_18; - [FieldOffset(19)] public readonly byte UNK_19; // I have never seen this be anything other than 0. - - // Legacy helmet equip slot armor for a character. - [FieldOffset(20)] public readonly LegacyCharacterArmor HeadSlotArmor; - [FieldOffset(24)] public readonly LegacyCharacterArmor TopSlotArmor; - [FieldOffset(28)] public readonly LegacyCharacterArmor ArmsSlotArmor; - [FieldOffset(32)] public readonly LegacyCharacterArmor LegsSlotArmor; - [FieldOffset(26)] public readonly LegacyCharacterArmor FeetSlotArmor; - [FieldOffset(40)] public readonly LegacyCharacterArmor EarSlotArmor; - [FieldOffset(44)] public readonly LegacyCharacterArmor NeckSlotArmor; - [FieldOffset(48)] public readonly LegacyCharacterArmor WristSlotArmor; - [FieldOffset(52)] public readonly LegacyCharacterArmor RFingerSlotArmor; - [FieldOffset(56)] public readonly LegacyCharacterArmor LFingerSlotArmor; - - // Byte array of all slot's secondary dyes. - [FieldOffset(60)] public readonly byte HeadSlotSecondaryDye; - [FieldOffset(61)] public readonly byte TopSlotSecondaryDye; - [FieldOffset(62)] public readonly byte ArmsSlotSecondaryDye; - [FieldOffset(63)] public readonly byte LegsSlotSecondaryDye; - [FieldOffset(64)] public readonly byte FeetSlotSecondaryDye; - [FieldOffset(65)] public readonly byte EarSlotSecondaryDye; - [FieldOffset(66)] public readonly byte NeckSlotSecondaryDye; - [FieldOffset(67)] public readonly byte WristSlotSecondaryDye; - [FieldOffset(68)] public readonly byte RFingerSlotSecondaryDye; - [FieldOffset(69)] public readonly byte LFingerSlotSecondaryDye; -} - public unsafe class UpdateSlotService : IDisposable { public readonly EquipSlotUpdating EquipSlotUpdatingEvent; @@ -145,22 +105,20 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToEquipSlot(); + var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - returnValue = returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; - return returnValue; + return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToBonusSlot(); + var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); - returnValue = returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; - return returnValue; + return returnValue == ulong.MaxValue ? _flagBonusSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; } private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) @@ -175,7 +133,6 @@ public unsafe class UpdateSlotService : IDisposable Model drawObject = drawDataContainer->OwnerObject->DrawObject; Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{drawObject.Address:X} Finished Applying its GameState!"); GearsetDataLoadedEvent.Invoke(drawObject); - // Can use for debugging, if desired. // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } @@ -199,3 +156,43 @@ public unsafe class UpdateSlotService : IDisposable return ret; } } + +// Can be removed once merged with client structs and referenced directly. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files +[StructLayout(LayoutKind.Explicit)] +public readonly struct GearsetDataStruct +{ + // Stores the weapon data. Includes both dyes in the data. + [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; + [FieldOffset(8)] public readonly WeaponModelId OffhandWeaponData; + + [FieldOffset(16)] public readonly byte CrestBitField; // A Bitfield:: ShieldCrest == 1, HeadCrest == 2, Chest Crest == 4 + [FieldOffset(17)] public readonly byte JobId; // Job ID associated with the gearset change. + + // Flicks from 0 to 127 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. + [FieldOffset(18)] public readonly byte UNK_18; + [FieldOffset(19)] public readonly byte UNK_19; // I have never seen this be anything other than 0. + + // Legacy helmet equip slot armor for a character. + [FieldOffset(20)] public readonly LegacyCharacterArmor HeadSlotArmor; + [FieldOffset(24)] public readonly LegacyCharacterArmor TopSlotArmor; + [FieldOffset(28)] public readonly LegacyCharacterArmor ArmsSlotArmor; + [FieldOffset(32)] public readonly LegacyCharacterArmor LegsSlotArmor; + [FieldOffset(26)] public readonly LegacyCharacterArmor FeetSlotArmor; + [FieldOffset(40)] public readonly LegacyCharacterArmor EarSlotArmor; + [FieldOffset(44)] public readonly LegacyCharacterArmor NeckSlotArmor; + [FieldOffset(48)] public readonly LegacyCharacterArmor WristSlotArmor; + [FieldOffset(52)] public readonly LegacyCharacterArmor RFingerSlotArmor; + [FieldOffset(56)] public readonly LegacyCharacterArmor LFingerSlotArmor; + + // Byte array of all slot's secondary dyes. + [FieldOffset(60)] public readonly byte HeadSlotSecondaryDye; + [FieldOffset(61)] public readonly byte TopSlotSecondaryDye; + [FieldOffset(62)] public readonly byte ArmsSlotSecondaryDye; + [FieldOffset(63)] public readonly byte LegsSlotSecondaryDye; + [FieldOffset(64)] public readonly byte FeetSlotSecondaryDye; + [FieldOffset(65)] public readonly byte EarSlotSecondaryDye; + [FieldOffset(66)] public readonly byte NeckSlotSecondaryDye; + [FieldOffset(67)] public readonly byte WristSlotSecondaryDye; + [FieldOffset(68)] public readonly byte RFingerSlotSecondaryDye; + [FieldOffset(69)] public readonly byte LFingerSlotSecondaryDye; +} diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 891c61d..b122352 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -253,7 +253,7 @@ public class StateEditor( return; var actors = Applier.ChangeMetaState(state, index, settings.Source.RequiresChange()); - Glamourer.Log.Debug( + Glamourer.Log.Verbose( $"Set {index.ToName()} in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Other, settings.Source, state, actors, new MetaTransaction(index, old, value)); } @@ -417,7 +417,8 @@ public class StateEditor( ? Applier.ApplyAll(state, requiresRedraw, false) : ActorData.Invalid; - Glamourer.Log.Verbose($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later if(settings.SendStateUpdate) StateUpdated.Invoke(StateUpdateType.DesignApplied, actors); From 8add6e5519c6998194ad4adc5f818704e5fa96d6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 Jan 2025 15:37:21 +0100 Subject: [PATCH 564/786] Fix some .net update issues. --- Glamourer/Gui/Customization/CustomizeParameterDrawData.cs | 4 ++-- Glamourer/Gui/Equipment/BonusDrawData.cs | 4 ++-- Glamourer/Gui/Equipment/EquipDrawData.cs | 4 ++-- Penumbra.GameData | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs index aa43b79..421caed 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs @@ -6,8 +6,8 @@ namespace Glamourer.Gui.Customization; public struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data) { - private IDesignEditor _editor; - private object _object; + private IDesignEditor _editor = null!; + private object _object = null!; public readonly CustomizeParameterFlag Flag = flag; public bool Locked; public bool DisplayApplication; diff --git a/Glamourer/Gui/Equipment/BonusDrawData.cs b/Glamourer/Gui/Equipment/BonusDrawData.cs index 9c023b8..e19287a 100644 --- a/Glamourer/Gui/Equipment/BonusDrawData.cs +++ b/Glamourer/Gui/Equipment/BonusDrawData.cs @@ -7,8 +7,8 @@ namespace Glamourer.Gui.Equipment; public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) { - private IDesignEditor _editor; - private object _object; + private IDesignEditor _editor = null!; + private object _object = null!; public readonly BonusItemFlag Slot = slot; public bool Locked; public bool DisplayApplication; diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 9a3142b..b72e234 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -7,8 +7,8 @@ namespace Glamourer.Gui.Equipment; public struct EquipDrawData(EquipSlot slot, in DesignData designData) { - private IDesignEditor _editor; - private object _object; + private IDesignEditor _editor = null!; + private object _object = null!; public readonly EquipSlot Slot = slot; public bool Locked; public bool DisplayApplication; diff --git a/Penumbra.GameData b/Penumbra.GameData index 63ffca0..c525072 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 63ffca0ff0ad626605120e58809c888d92053d64 +Subproject commit c525072299d5febd2bb638ab229060b0073ba6a6 From cdc67a035c5efdc04801b8630e799ad59ea742f7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 Jan 2025 15:37:59 +0100 Subject: [PATCH 565/786] Check for ENPC player copies and treat them as transformations. --- Glamourer/State/StateListener.cs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d054a25..651e63e 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -9,6 +9,7 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.GameData; using Penumbra.GameData.DataContainers; using Glamourer.Designs; @@ -50,6 +51,7 @@ public class StateListener : IDisposable private readonly Dictionary _fistOffhands = []; private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid; + private bool _isPlayerNpc; private ActorState? _creatingState; private ActorState? _customizeState; @@ -117,11 +119,13 @@ public class StateListener : IDisposable return; _creatingIdentifier = actor.GetIdentifier(_actors); - ref var modelId = ref *(uint*)modelPtr; ref var customize = ref *(CustomizeArray*)customizePtr; if (_autoDesignApplier.Reduce(actor, _creatingIdentifier, out _creatingState)) { + _isPlayerNpc = _creatingIdentifier.Type is IdentifierType.Player + && actor.IsCharacter + && actor.AsCharacter->GetObjectKind() is ObjectKind.EventNpc; switch (UpdateBaseData(actor, _creatingState, modelId, customizePtr, equipDataPtr)) { // TODO handle right @@ -327,7 +331,7 @@ public class StateListener : IDisposable && weapon.Weapon.Id != 0 && _fistOffhands.TryGetValue(actor, out var lastFistOffhand)) { - Glamourer.Log.Information($"Applying stored fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); + Glamourer.Log.Verbose($"Applying stored fist weapon offhand {lastFistOffhand} for 0x{actor.Address:X}."); weapon = lastFistOffhand; } @@ -420,15 +424,22 @@ public class StateListener : IDisposable } var baseData = state.BaseData.Armor(slot); - var change = UpdateState.NoChange; + + var change = UpdateState.NoChange; if (baseData.Stains != armor.Stains) { + if (_isPlayerNpc) + return UpdateState.Transformed; + state.BaseData.SetStain(slot, armor.Stains); change = UpdateState.Change; } if (baseData.Set.Id != armor.Set.Id || baseData.Variant != armor.Variant && !fistWeapon) { + if (_isPlayerNpc) + return UpdateState.Transformed; + var item = _items.Identify(slot, armor.Set, armor.Variant); state.BaseData.SetItem(slot, item); change = UpdateState.Change; @@ -460,6 +471,9 @@ public class StateListener : IDisposable var change = UpdateState.NoChange; if (baseData.Id != actorItem.Id || baseData.PrimaryId != item.Set || baseData.Variant != item.Variant) { + if (_isPlayerNpc) + return UpdateState.Transformed; + var identified = _items.Identify(slot, item.Set, item.Variant); state.BaseData.SetBonusItem(slot, identified); change = UpdateState.Change; @@ -576,6 +590,9 @@ public class StateListener : IDisposable if (baseData.Skeleton.Id != weapon.Skeleton.Id || baseData.Weapon.Id != weapon.Weapon.Id || baseData.Variant != weapon.Variant) { + if (_isPlayerNpc) + return UpdateState.Transformed; + var item = _items.Identify(slot, weapon.Skeleton, weapon.Weapon, weapon.Variant, slot is EquipSlot.OffHand ? state.BaseData.MainhandType : FullEquipType.Unknown); state.BaseData.SetItem(slot, item); @@ -623,6 +640,10 @@ public class StateListener : IDisposable if (checkTransform && !actor.Customize->Equals(customize)) return UpdateState.Transformed; + // Check for player NPCs with a different game state. + if (_isPlayerNpc && !actor.Customize->Equals(state.BaseData.Customize)) + return UpdateState.Transformed; + // Customize array did not change to stored state. if (state.BaseData.Customize.Equals(customize)) return UpdateState.NoChange; // TODO: handle wrong base data. From 4db08224430ad4405a67b03c83e168b46841e173 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 Jan 2025 15:40:08 +0100 Subject: [PATCH 566/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index c525072..5bac66e 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit c525072299d5febd2bb638ab229060b0073ba6a6 +Subproject commit 5bac66e5ad73e57919aff7f8b046606b75e191a2 From 96dca5dbe7f4a61086b0f192521a60c3949372d2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 20 Jan 2025 17:47:36 +0100 Subject: [PATCH 567/786] Cherry-Pick from ebdfd3f8 to use EquipGearSetInternal. --- Glamourer/Interop/InventoryService.cs | 34 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 6d8e58b..f0ed6b5 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -1,5 +1,6 @@ using Dalamud.Hooking; using Dalamud.Plugin.Services; +using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Events; @@ -19,36 +20,43 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { _movedItemsEvent = movedItemsEvent; - _gearsetEvent = gearsetEvent; + _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - _equipGearsetHook = - interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); + // This can be uncommented after ClientStructs Updates with EquipGearsetInternal after merged PR. (See comment below) + //_equipGearsetInternalHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetInternalDetour); + + // Can be removed after ClientStructs Update since this is only needed for current EquipGearsetInternal [Signature] + interop.InitializeFromAttributes(this); _moveItemHook.Enable(); - _equipGearsetHook.Enable(); + _equipGearsetInternalHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); - _equipGearsetHook.Dispose(); + _equipGearsetInternalHook.Dispose(); } - private delegate int EquipGearsetDelegate(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId); + // This is the internal function processed by all sources of Equipping a gearset, such as hotbar gearset application and command gearset application. + // Currently is pending to ClientStructs for integration. See: https://github.com/aers/FFXIVClientStructs/pull/1277 + public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; + private delegate nint EquipGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - private readonly Hook _equipGearsetHook; + [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] + private readonly Hook _equipGearsetInternalHook = null!; - private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) + private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { var prior = module->CurrentGearsetIndex; - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); - var set = module->GetGearset(gearsetId); - _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), gearsetId, prior, glamourPlateId, set->ClassJob); - Glamourer.Log.Excessive($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); + var set = module->GetGearset((int)gearsetId); + _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); + Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { - var entry = module->GetGearset(gearsetId); + var entry = module->GetGearset((int)gearsetId); if (entry == null) return ret; From f1b335e794fe8e4bd72f6b08ba9b492c2dac6efc Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 20 Jan 2025 16:49:52 +0000 Subject: [PATCH 568/786] [CI] Updating repo.json for testing_1.3.5.3 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index dab02ea..5f4ae31 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.5.1", - "TestingAssemblyVersion": "1.3.5.2", + "TestingAssemblyVersion": "1.3.5.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c43ce9d978f57f10f62f8c981533a5344cd4b638 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Tue, 21 Jan 2025 10:47:12 -0800 Subject: [PATCH 569/786] Updated new functionality and internal gearset to use FFXIVClientStructs member functions and implemented structs, corrected remaining spacing issues. --- Glamourer/Api/StateApi.cs | 5 +- Glamourer/Events/GearsetDataLoaded.cs | 8 +-- Glamourer/Interop/InventoryService.cs | 20 ++---- Glamourer/Interop/UpdateSlotService.cs | 89 ++++++-------------------- Glamourer/State/StateEditor.cs | 2 +- Glamourer/State/StateListener.cs | 3 +- 6 files changed, 33 insertions(+), 94 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index cb7fe51..41f7650 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -14,7 +14,6 @@ using StateChanged = Glamourer.Events.StateChanged; using StateUpdated = Glamourer.Events.StateUpdated; namespace Glamourer.Api; - public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { private readonly ApiHelpers _helpers; @@ -340,7 +339,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { - // Glamourer.Log.Verbose($"[OnStateChanged] Sending out OnStateChanged with type {type}."); + Glamourer.Log.Excessive($"[OnStateChanged] State Changed with Type {type} [Affecting {actors.ToLazyString("nothing")}.]"); if (StateChanged != null) foreach (var actor in actors.Objects) StateChanged.Invoke(actor.Address); @@ -352,7 +351,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void OnStateUpdated(StateUpdateType type, ActorData actors) { - // Glamourer.Log.Verbose($"[OnStateUpdated] Sending out OnStateUpdated with type {type}."); + Glamourer.Log.Verbose($"[OnStateUpdated] State Updated with Type {type}. [Affecting {actors.ToLazyString("nothing")}.]"); if (StateUpdated != null) foreach (var actor in actors.Objects) StateUpdated.Invoke(actor.Address, type); diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index dd12bc1..4750939 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -4,10 +4,10 @@ using Penumbra.GameData.Interop; namespace Glamourer.Events; /// -/// Triggers when the equipped gearset finished running all of its LoadEquipment, LoadWeapon, and crest calls. -/// This defines a universal endpoint of base game state application to monitor. +/// Triggers when the equipped gearset finished all LoadEquipment, LoadWeapon, and LoadCrest calls. (All Non-MetaData) +/// This defines an endpoint for when the gameState is updated. /// -/// The model drawobject associated with the finished load (Also fired by other players on render) +/// The model draw object associated with the finished load (Also fired by other players on render) /// /// public sealed class GearsetDataLoaded() @@ -18,4 +18,4 @@ public sealed class GearsetDataLoaded() /// StateListener = 0, } -} \ No newline at end of file +} diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index f0ed6b5..4b98d46 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -23,34 +23,26 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - // This can be uncommented after ClientStructs Updates with EquipGearsetInternal after merged PR. (See comment below) - //_equipGearsetInternalHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetInternalDetour); - - // Can be removed after ClientStructs Update since this is only needed for current EquipGearsetInternal [Signature] - interop.InitializeFromAttributes(this); + _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetDetour); _moveItemHook.Enable(); - _equipGearsetInternalHook.Enable(); + _equipGearsetHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); - _equipGearsetInternalHook.Dispose(); + _equipGearsetHook.Dispose(); } - // This is the internal function processed by all sources of Equipping a gearset, such as hotbar gearset application and command gearset application. - // Currently is pending to ClientStructs for integration. See: https://github.com/aers/FFXIVClientStructs/pull/1277 - public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; private delegate nint EquipGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] - private readonly Hook _equipGearsetInternalHook = null!; + private readonly Hook _equipGearsetHook = null!; - private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) + private nint EquipGearSetDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { var prior = module->CurrentGearsetIndex; - var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); var set = module->GetGearset((int)gearsetId); _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index e453c6e..466f1ae 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Network; using Glamourer.Events; using Penumbra.GameData; using Penumbra.GameData.DataContainers; @@ -13,22 +14,20 @@ namespace Glamourer.Interop; public unsafe class UpdateSlotService : IDisposable { - public readonly EquipSlotUpdating EquipSlotUpdatingEvent; - public readonly BonusSlotUpdating BonusSlotUpdatingEvent; - public readonly GearsetDataLoaded GearsetDataLoadedEvent; - private readonly DictBonusItems _bonusItems; + public readonly EquipSlotUpdating EquipSlotUpdatingEvent; + public readonly BonusSlotUpdating BonusSlotUpdatingEvent; + public readonly GearsetDataLoaded GearsetDataLoadedEvent; + private readonly DictBonusItems _bonusItems; public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, GearsetDataLoaded gearsetDataLoaded, IGameInteropProvider interop, DictBonusItems bonusItems) { EquipSlotUpdatingEvent = equipSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating; GearsetDataLoadedEvent = gearsetDataLoaded; - _bonusItems = bonusItems; + _bonusItems = bonusItems; - // Usable after the merge with client structs. - //_loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); + _loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); interop.InitializeFromAttributes(this); - _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); _loadGearsetDataHook.Enable(); @@ -87,25 +86,15 @@ public unsafe class UpdateSlotService : IDisposable [Signature(Sigs.FlagBonusSlotForUpdate, DetourName = nameof(FlagBonusSlotForUpdateDetour))] private readonly Hook _flagBonusSlotForUpdateHook = null!; - // This signature is what calls the weapon/equipment/crest load functions in the drawData container inherited from a human/characterBase. - // - // Contrary to assumption, this is not frequently fired when any slot changes, and is instead only called when another player - // initially loads, or when the client player changes gearsets. (Does not fire when another player or self is redrawn) - // - // This functions purpose is to iterate all Equipment/Weapon/Crest data on gearset change / initial player load, and determine which slots need to fire FlagSlotForUpdate. - // - // Because Glamourer processes GameState changes by detouring this method, this means by returning original after detour, any logic performed after will occur - // AFTER Glamourer finishes applying all changes to the game State, providing a gearset endpoint. (MetaData not included) - // Currently pending a merge to clientStructs, after which it can be removed, along with the explicit struct. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files - public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; - private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetDataStruct* gearsetData); - - [Signature(LoadGearsetDataSig, DetourName = nameof(LoadGearsetDataDetour))] + /// Detours the func that makes all FlagSlotForUpdate calls on a gearset change or initial render of a given actor (Only Cases this is Called). + /// Logic done after returning the original hook executes After all equipment/weapon/crest data is loaded into the Actors BaseData. + /// + private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData); private readonly Hook _loadGearsetDataHook = null!; private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToEquipSlot(); + var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -114,7 +103,7 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToBonusSlot(); + var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -123,29 +112,27 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) { - Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Invoked by Glamourer on 0x{drawObject.Address:X} on {slot} with itemdata {armor}."); + Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Glamourer-Invoked on 0x{drawObject.Address:X} on {slot} with item data {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetDataStruct* gearsetData) + private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData) { - // Let the gearset data process all of its loads and slot flag update calls first. var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); Model drawObject = drawDataContainer->OwnerObject->DrawObject; - Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{drawObject.Address:X} Finished Applying its GameState!"); GearsetDataLoadedEvent.Invoke(drawObject); - // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + // Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } // If you ever care to debug this, here is a formatted string output of this new gearsetData struct. - private string FormatGearsetItemDataStruct(GearsetDataStruct gearsetData) + private string FormatGearsetItemDataStruct(PacketPlayerGearsetData gearsetData) { string ret = $"\nMainhandWeaponData: Id: {gearsetData.MainhandWeaponData.Id}, Type: {gearsetData.MainhandWeaponData.Type}, " + $"Variant: {gearsetData.MainhandWeaponData.Variant}, Stain0: {gearsetData.MainhandWeaponData.Stain0}, Stain1: {gearsetData.MainhandWeaponData.Stain1}" + $"\nOffhandWeaponData: Id: {gearsetData.OffhandWeaponData.Id}, Type: {gearsetData.OffhandWeaponData.Type}, " + $"Variant: {gearsetData.OffhandWeaponData.Variant}, Stain0: {gearsetData.OffhandWeaponData.Stain0}, Stain1: {gearsetData.OffhandWeaponData.Stain1}" + - $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId} | UNK_18: {gearsetData.UNK_18} | UNK_19: {gearsetData.UNK_19}"; + $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId}"; for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) { LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); @@ -156,43 +143,3 @@ public unsafe class UpdateSlotService : IDisposable return ret; } } - -// Can be removed once merged with client structs and referenced directly. See: https://github.com/aers/FFXIVClientStructs/pull/1277/files -[StructLayout(LayoutKind.Explicit)] -public readonly struct GearsetDataStruct -{ - // Stores the weapon data. Includes both dyes in the data. - [FieldOffset(0)] public readonly WeaponModelId MainhandWeaponData; - [FieldOffset(8)] public readonly WeaponModelId OffhandWeaponData; - - [FieldOffset(16)] public readonly byte CrestBitField; // A Bitfield:: ShieldCrest == 1, HeadCrest == 2, Chest Crest == 4 - [FieldOffset(17)] public readonly byte JobId; // Job ID associated with the gearset change. - - // Flicks from 0 to 127 (anywhere inbetween), have yet to associate what it is linked to. Remains the same when flicking between gearsets of the same job. - [FieldOffset(18)] public readonly byte UNK_18; - [FieldOffset(19)] public readonly byte UNK_19; // I have never seen this be anything other than 0. - - // Legacy helmet equip slot armor for a character. - [FieldOffset(20)] public readonly LegacyCharacterArmor HeadSlotArmor; - [FieldOffset(24)] public readonly LegacyCharacterArmor TopSlotArmor; - [FieldOffset(28)] public readonly LegacyCharacterArmor ArmsSlotArmor; - [FieldOffset(32)] public readonly LegacyCharacterArmor LegsSlotArmor; - [FieldOffset(26)] public readonly LegacyCharacterArmor FeetSlotArmor; - [FieldOffset(40)] public readonly LegacyCharacterArmor EarSlotArmor; - [FieldOffset(44)] public readonly LegacyCharacterArmor NeckSlotArmor; - [FieldOffset(48)] public readonly LegacyCharacterArmor WristSlotArmor; - [FieldOffset(52)] public readonly LegacyCharacterArmor RFingerSlotArmor; - [FieldOffset(56)] public readonly LegacyCharacterArmor LFingerSlotArmor; - - // Byte array of all slot's secondary dyes. - [FieldOffset(60)] public readonly byte HeadSlotSecondaryDye; - [FieldOffset(61)] public readonly byte TopSlotSecondaryDye; - [FieldOffset(62)] public readonly byte ArmsSlotSecondaryDye; - [FieldOffset(63)] public readonly byte LegsSlotSecondaryDye; - [FieldOffset(64)] public readonly byte FeetSlotSecondaryDye; - [FieldOffset(65)] public readonly byte EarSlotSecondaryDye; - [FieldOffset(66)] public readonly byte NeckSlotSecondaryDye; - [FieldOffset(67)] public readonly byte WristSlotSecondaryDye; - [FieldOffset(68)] public readonly byte RFingerSlotSecondaryDye; - [FieldOffset(69)] public readonly byte LFingerSlotSecondaryDye; -} diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index b122352..13b0706 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -416,7 +416,7 @@ public class StateEditor( var actors = settings.Source.RequiresChange() ? Applier.ApplyAll(state, requiresRedraw, false) : ActorData.Invalid; - + Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d570805..c4c16b5 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -225,7 +225,8 @@ public class StateListener : IDisposable // then we do not want to use our restricted gear protection // since we assume the player has that gear modded to availability. var locked = false; - if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) + if (actor.Identifier(_actors, out var identifier) + && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor); locked = state.Sources[slot, false] is StateSource.IpcFixed; From 70918e539379871eb256a21ebad833d13315031d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 15:04:22 +0100 Subject: [PATCH 570/786] Add AutoDesignSet toggle for resetting settings. --- Glamourer/Automation/AutoDesignManager.cs | 16 +++++ Glamourer/Events/AutomationChanged.cs | 3 + Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 71 ++++++++++++-------- 3 files changed, 62 insertions(+), 28 deletions(-) diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 5d30de0..6635a89 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -234,6 +234,22 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _event.Invoke(AutomationChanged.Type.ChangedBase, set, (old, newBase)); } + public void ChangeResetSettings(int whichSet, bool newValue) + { + if (whichSet >= _data.Count || whichSet < 0) + return; + + var set = _data[whichSet]; + if (newValue == set.ResetTemporarySettings) + return; + + var old = set.ResetTemporarySettings; + set.ResetTemporarySettings = newValue; + Save(); + Glamourer.Log.Debug($"Changed resetting of temporary settings of set {whichSet + 1} from {old} to {newValue}."); + _event.Invoke(AutomationChanged.Type.ChangedTemporarySettingsReset, set, newValue); + } + public void AddDesign(AutoDesignSet set, IDesignStandIn design) { var newDesign = new AutoDesign() diff --git a/Glamourer/Events/AutomationChanged.cs b/Glamourer/Events/AutomationChanged.cs index 26f799a..c368899 100644 --- a/Glamourer/Events/AutomationChanged.cs +++ b/Glamourer/Events/AutomationChanged.cs @@ -37,6 +37,9 @@ public sealed class AutomationChanged() /// Change the used base state of a given set. Additional data is prior and new base. [(AutoDesignSet.Base, AutoDesignSet.Base)]. ChangedBase, + /// Change the resetting of temporary settings for a given set. Additional data is the new value. + ChangedTemporarySettingsReset, + /// Add a new associated design to a given set. Additional data is the index it got added at [int]. AddedDesign, diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 924f822..caa7d60 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -58,38 +58,53 @@ public class SetPanel( var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; - using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + using (ImUtf8.Group()) { - var enabled = Selection.Enabled; - if (ImGui.Checkbox("##Enabled", ref enabled)) - _manager.SetState(_selector.SelectionIndex, enabled); - ImGuiUtil.LabeledHelpMarker("Enabled", - "Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time."); - } - - ImGui.SameLine(); - using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) - { - var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game; - if (ImGui.Checkbox("##gameState", ref useGame)) - _manager.ChangeBaseState(_selector.SelectionIndex, useGame ? AutoDesignSet.Base.Game : AutoDesignSet.Base.Current); - ImGuiUtil.LabeledHelpMarker("Use Game State as Base", - "When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. " - + "Otherwise, they will be applied on top of the characters actual current look using Glamourer."); - } - - ImGui.SameLine(); - using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) - { - var editing = _config.ShowAutomationSetEditing; - if (ImGui.Checkbox("##Show Editing", ref editing)) + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { - _config.ShowAutomationSetEditing = editing; - _config.Save(); + var enabled = Selection.Enabled; + if (ImGui.Checkbox("##Enabled", ref enabled)) + _manager.SetState(_selector.SelectionIndex, enabled); + ImGuiUtil.LabeledHelpMarker("Enabled", + "Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time."); } - ImGuiUtil.LabeledHelpMarker("Show Editing", - "Show options to change the name or the associated character or NPC of this design set."); + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + { + var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game; + if (ImGui.Checkbox("##gameState", ref useGame)) + _manager.ChangeBaseState(_selector.SelectionIndex, useGame ? AutoDesignSet.Base.Game : AutoDesignSet.Base.Current); + ImGuiUtil.LabeledHelpMarker("Use Game State as Base", + "When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. " + + "Otherwise, they will be applied on top of the characters actual current look using Glamourer."); + } + } + + ImGui.SameLine(); + using (ImUtf8.Group()) + { + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + { + var editing = _config.ShowAutomationSetEditing; + if (ImGui.Checkbox("##Show Editing", ref editing)) + { + _config.ShowAutomationSetEditing = editing; + _config.Save(); + } + + ImGuiUtil.LabeledHelpMarker("Show Editing", + "Show options to change the name or the associated character or NPC of this design set."); + } + + using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) + { + var resetSettings = _selector.Selection!.ResetTemporarySettings; + if (ImGui.Checkbox("##resetSettings", ref resetSettings)) + _manager.ChangeResetSettings(_selector.SelectionIndex, resetSettings); + + ImGuiUtil.LabeledHelpMarker("Reset Temporary Settings", + "Always reset all temporary settings applied by Glamourer when this automation set is applied, regardless of active designs."); + } } if (_config.ShowAutomationSetEditing) From 30468e0b09e892085c5d788e1e89d41dcadc927b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 15:04:30 +0100 Subject: [PATCH 571/786] Fix checkbox not working. --- Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 5eda7cc..021a396 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -201,7 +201,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ImGui.TableNextColumn(); var inherit = settings.ForceInherit; - if (TwoStateCheckbox.Instance.Draw("##Enabled"u8, ref inherit)) + if (TwoStateCheckbox.Instance.Draw("##ForceInherit"u8, ref inherit)) updatedMod = (mod, settings with { ForceInherit = inherit }); ImUtf8.HoverTooltip("Force the mod to inherit its settings from inherited collections."u8); ImGui.TableNextColumn(); From 29166693193cee2201a069c0589475627ef0617a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 15:06:29 +0100 Subject: [PATCH 572/786] Change EquipGearsetInternal to CS. --- Glamourer/Interop/InventoryService.cs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index f0ed6b5..4b98d46 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -23,34 +23,26 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); - // This can be uncommented after ClientStructs Updates with EquipGearsetInternal after merged PR. (See comment below) - //_equipGearsetInternalHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetInternalDetour); - - // Can be removed after ClientStructs Update since this is only needed for current EquipGearsetInternal [Signature] - interop.InitializeFromAttributes(this); + _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearsetInternal, EquipGearSetDetour); _moveItemHook.Enable(); - _equipGearsetInternalHook.Enable(); + _equipGearsetHook.Enable(); } public void Dispose() { _moveItemHook.Dispose(); - _equipGearsetInternalHook.Dispose(); + _equipGearsetHook.Dispose(); } - // This is the internal function processed by all sources of Equipping a gearset, such as hotbar gearset application and command gearset application. - // Currently is pending to ClientStructs for integration. See: https://github.com/aers/FFXIVClientStructs/pull/1277 - public const string EquipGearsetInternal = "40 55 53 56 57 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 4C 63 FA"; private delegate nint EquipGearsetInternalDelegate(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId); - [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] - private readonly Hook _equipGearsetInternalHook = null!; + private readonly Hook _equipGearsetHook = null!; - private nint EquipGearSetInternalDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) + private nint EquipGearSetDetour(RaptureGearsetModule* module, uint gearsetId, byte glamourPlateId) { var prior = module->CurrentGearsetIndex; - var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); var set = module->GetGearset((int)gearsetId); _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); From 7be283ca30629e648725e4e1861616d86eda7238 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 16:46:10 +0100 Subject: [PATCH 573/786] Apply API renames. --- Glamourer.Api | 2 +- Glamourer/Api/IpcProviders.cs | 2 +- Glamourer/Api/StateApi.cs | 8 ++++---- Glamourer/Events/StateUpdated.cs | 2 +- Glamourer/State/StateEditor.cs | 4 ++-- Glamourer/State/StateListener.cs | 2 +- Glamourer/State/StateManager.cs | 12 ++++++------ 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index b1b90e6..4ac38fb 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit b1b90e6ecfeee76a12cb27793753fa87af21083f +Subproject commit 4ac38fbed6fb0f31c0b75de26950ab82d3bee258 diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 166245f..515cd34 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -52,7 +52,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), IpcSubscribers.StateChanged.Provider(pi, api.State), IpcSubscribers.StateChangedWithType.Provider(pi, api.State), - IpcSubscribers.StateUpdated.Provider(pi, api.State), + IpcSubscribers.StateFinalized.Provider(pi, api.State), IpcSubscribers.GPoseChanged.Provider(pi, api.State), ]; _initializedProvider.Invoke(); diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 41f7650..347d2b6 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -255,7 +255,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public event Action? StateChanged; public event Action? StateChangedWithType; - public event Action? StateUpdated; + public event Action? StateFinalized; public event Action? GPoseChanged; private void ApplyDesign(ActorState state, DesignBase design, uint key, ApplyFlag flags) @@ -349,11 +349,11 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable StateChangedWithType.Invoke(actor.Address, type); } - private void OnStateUpdated(StateUpdateType type, ActorData actors) + private void OnStateUpdated(StateFinalizationType type, ActorData actors) { Glamourer.Log.Verbose($"[OnStateUpdated] State Updated with Type {type}. [Affecting {actors.ToLazyString("nothing")}.]"); - if (StateUpdated != null) + if (StateFinalized != null) foreach (var actor in actors.Objects) - StateUpdated.Invoke(actor.Address, type); + StateFinalized.Invoke(actor.Address, type); } } diff --git a/Glamourer/Events/StateUpdated.cs b/Glamourer/Events/StateUpdated.cs index f18a69a..faaf33a 100644 --- a/Glamourer/Events/StateUpdated.cs +++ b/Glamourer/Events/StateUpdated.cs @@ -14,7 +14,7 @@ namespace Glamourer.Events; /// /// public sealed class StateUpdated() - : EventWrapper(nameof(StateUpdated)) + : EventWrapper(nameof(StateUpdated)) { public enum Priority { diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 13b0706..ebf347f 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -43,7 +43,7 @@ public class StateEditor( Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Model, source, state, actors, null); - StateUpdated.Invoke(StateUpdateType.ModelChange, actors); + StateUpdated.Invoke(StateFinalizationType.ModelChange, actors); } /// @@ -421,7 +421,7 @@ public class StateEditor( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later if(settings.SendStateUpdate) - StateUpdated.Invoke(StateUpdateType.DesignApplied, actors); + StateUpdated.Invoke(StateFinalizationType.DesignApplied, actors); return; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index c4c16b5..d8648bb 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -281,7 +281,7 @@ public class StateListener : IDisposable _objects.Update(); if (_objects.TryGetValue(identifier, out var actors) && actors.Valid) - _stateUpdated.Invoke(StateUpdateType.Gearset, actors); + _stateUpdated.Invoke(StateFinalizationType.Gearset, actors); } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 0348148..948e225 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -279,7 +279,7 @@ public sealed class StateManager( StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // only invoke if we define this reset call as the final call in our state update. if(stateUpdate) - StateUpdated.Invoke(StateUpdateType.Revert, actors); + StateUpdated.Invoke(StateFinalizationType.Revert, actors); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -306,7 +306,7 @@ public sealed class StateManager( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateUpdateType.RevertAdvanced, actors); + StateUpdated.Invoke(StateFinalizationType.RevertAdvanced, actors); } public void ResetCustomize(ActorState state, StateSource source, uint key = 0) @@ -325,7 +325,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateUpdateType.RevertCustomize, actors); + StateUpdated.Invoke(StateFinalizationType.RevertCustomize, actors); } public void ResetEquip(ActorState state, StateSource source, uint key = 0) @@ -376,7 +376,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateUpdateType.RevertEquipment, actors); + StateUpdated.Invoke(StateFinalizationType.RevertEquipment, actors); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) @@ -469,7 +469,7 @@ public sealed class StateManager( || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); if(stateUpdate) - StateUpdated.Invoke(StateUpdateType.Reapply, data); + StateUpdated.Invoke(StateFinalizationType.Reapply, data); } /// Automation variant for reapply, to fire the correct StateUpdateType once reapplied. @@ -490,7 +490,7 @@ public sealed class StateManager( || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); // invoke the automation update based on what reset is. - StateUpdated.Invoke(wasReset ? StateUpdateType.RevertAutomation : StateUpdateType.ReapplyAutomation, data); + StateUpdated.Invoke(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data); } public void DeleteState(ActorIdentifier identifier) From d9f9937d41390b34eab9d151e08f5b0e5d983789 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 17:50:58 +0100 Subject: [PATCH 574/786] Continue renames, some cleanup. --- Glamourer/Api/DesignsApi.cs | 2 +- Glamourer/Api/IpcProviders.cs | 1 - Glamourer/Api/StateApi.cs | 40 +++---- Glamourer/Designs/IDesignEditor.cs | 8 +- Glamourer/Events/GPoseService.cs | 4 +- .../{StateUpdated.cs => StateFinalized.cs} | 13 +-- Glamourer/Gui/DesignQuickBar.cs | 4 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 8 +- .../Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 2 +- .../Tabs/DebugTab/IpcTester/IpcTesterPanel.cs | 2 + .../Tabs/DebugTab/IpcTester/StateIpcTester.cs | 107 ++++++++++++------ Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 4 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 4 +- Glamourer/Interop/UpdateSlotService.cs | 29 ++--- Glamourer/Services/CommandService.cs | 4 +- Glamourer/State/StateEditor.cs | 27 ++--- Glamourer/State/StateListener.cs | 8 +- Glamourer/State/StateManager.cs | 42 +++---- 18 files changed, 175 insertions(+), 134 deletions(-) rename Glamourer/Events/{StateUpdated.cs => StateFinalized.cs} (53%) diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index 6c3037e..08f5ddc 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -33,7 +33,7 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager { var once = (flags & ApplyFlag.Once) != 0; var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true, - ResetMaterials: !once && key != 0, SendStateUpdate: true); + ResetMaterials: !once && key != 0, IsFinal: true); using var restrict = ApiHelpers.Restrict(design, flags); stateManager.ApplyDesign(state, design, settings); diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 515cd34..704f008 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -2,7 +2,6 @@ using Dalamud.Plugin; using Glamourer.Api.Api; using Glamourer.Api.Helpers; using OtterGui.Services; -using System.Reflection.Emit; using Glamourer.Api.Enums; namespace Glamourer.Api; diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 347d2b6..eaf9d01 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -11,9 +11,9 @@ using OtterGui.Services; using Penumbra.GameData.Interop; using ObjectManager = Glamourer.Interop.ObjectManager; using StateChanged = Glamourer.Events.StateChanged; -using StateUpdated = Glamourer.Events.StateUpdated; namespace Glamourer.Api; + public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { private readonly ApiHelpers _helpers; @@ -23,7 +23,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private readonly AutoDesignApplier _autoDesigns; private readonly ObjectManager _objects; private readonly StateChanged _stateChanged; - private readonly StateUpdated _stateUpdated; + private readonly StateFinalized _stateFinalized; private readonly GPoseService _gPose; public StateApi(ApiHelpers helpers, @@ -33,27 +33,27 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable AutoDesignApplier autoDesigns, ObjectManager objects, StateChanged stateChanged, - StateUpdated stateUpdated, + StateFinalized stateFinalized, GPoseService gPose) { - _helpers = helpers; - _stateManager = stateManager; - _converter = converter; - _config = config; - _autoDesigns = autoDesigns; - _objects = objects; - _stateChanged = stateChanged; - _stateUpdated = stateUpdated; - _gPose = gPose; + _helpers = helpers; + _stateManager = stateManager; + _converter = converter; + _config = config; + _autoDesigns = autoDesigns; + _objects = objects; + _stateChanged = stateChanged; + _stateFinalized = stateFinalized; + _gPose = gPose; _stateChanged.Subscribe(OnStateChanged, Events.StateChanged.Priority.GlamourerIpc); - _stateUpdated.Subscribe(OnStateUpdated, Events.StateUpdated.Priority.GlamourerIpc); - _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.GlamourerIpc); + _stateFinalized.Subscribe(OnStateFinalized, Events.StateFinalized.Priority.StateApi); + _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.StateApi); } public void Dispose() { _stateChanged.Unsubscribe(OnStateChanged); - _stateUpdated.Unsubscribe(OnStateUpdated); + _stateFinalized.Unsubscribe(OnStateFinalized); _gPose.Unsubscribe(OnGPoseChange); } @@ -253,16 +253,16 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public event Action? StateChanged; - public event Action? StateChangedWithType; + public event Action? StateChanged; + public event Action? StateChangedWithType; public event Action? StateFinalized; - public event Action? GPoseChanged; + public event Action? GPoseChanged; private void ApplyDesign(ActorState state, DesignBase design, uint key, ApplyFlag flags) { var once = (flags & ApplyFlag.Once) != 0; var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true, - ResetMaterials: !once && key != 0, SendStateUpdate: true); + ResetMaterials: !once && key != 0, IsFinal: true); _stateManager.ApplyDesign(state, design, settings); ApiHelpers.Lock(state, key, flags); } @@ -349,7 +349,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable StateChangedWithType.Invoke(actor.Address, type); } - private void OnStateUpdated(StateFinalizationType type, ActorData actors) + private void OnStateFinalized(StateFinalizationType type, ActorData actors) { Glamourer.Log.Verbose($"[OnStateUpdated] State Updated with Type {type}. [Affecting {actors.ToLazyString("nothing")}.]"); if (StateFinalized != null) diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index 0178620..c18c98b 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -14,7 +14,7 @@ public readonly record struct ApplySettings( bool UseSingleSource = false, bool MergeLinks = false, bool ResetMaterials = false, - bool SendStateUpdate = false) + bool IsFinal = false) { public static readonly ApplySettings Manual = new() { @@ -25,7 +25,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = false, ResetMaterials = false, - SendStateUpdate = false, + IsFinal = false, }; public static readonly ApplySettings ManualWithLinks = new() @@ -37,7 +37,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = true, ResetMaterials = false, - SendStateUpdate = false, + IsFinal = false, }; public static readonly ApplySettings Game = new() @@ -49,7 +49,7 @@ public readonly record struct ApplySettings( UseSingleSource = false, MergeLinks = false, ResetMaterials = true, - SendStateUpdate = false, + IsFinal = false, }; } diff --git a/Glamourer/Events/GPoseService.cs b/Glamourer/Events/GPoseService.cs index a84f1d6..44421a0 100644 --- a/Glamourer/Events/GPoseService.cs +++ b/Glamourer/Events/GPoseService.cs @@ -13,8 +13,8 @@ public sealed class GPoseService : EventWrapper public enum Priority { - /// - GlamourerIpc = int.MinValue, + /// + StateApi = int.MinValue, } public bool InGPose { get; private set; } diff --git a/Glamourer/Events/StateUpdated.cs b/Glamourer/Events/StateFinalized.cs similarity index 53% rename from Glamourer/Events/StateUpdated.cs rename to Glamourer/Events/StateFinalized.cs index faaf33a..e8548e9 100644 --- a/Glamourer/Events/StateUpdated.cs +++ b/Glamourer/Events/StateFinalized.cs @@ -1,24 +1,23 @@ +using Glamourer.Api; using Glamourer.Api.Enums; -using Glamourer.Designs.History; using Glamourer.Interop.Structs; -using Glamourer.State; using OtterGui.Classes; namespace Glamourer.Events; /// -/// Triggered when a Design is edited in any way. +/// Triggered when a set of grouped changes finishes being applied to a Glamourer state. /// /// Parameter is the operation that finished updating the saved state. /// Parameter is the existing actors using this saved state. /// /// -public sealed class StateUpdated() - : EventWrapper(nameof(StateUpdated)) +public sealed class StateFinalized() + : EventWrapper(nameof(StateFinalized)) { public enum Priority { - /// - GlamourerIpc = int.MinValue, + /// + StateApi = int.MinValue, } } diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index bdc345f..50f86fd 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -183,7 +183,7 @@ public sealed class DesignQuickBar : Window, IDisposable } using var _ = design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } private void DrawRevertButton(Vector2 buttonSize) @@ -213,7 +213,7 @@ public sealed class DesignQuickBar : Window, IDisposable var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); ImGui.SameLine(); if (clicked) - _stateManager.ResetState(state!, StateSource.Manual, stateUpdate: true); + _stateManager.ResetState(state!, StateSource.Manual, isFinal: true); } private void DrawRevertAutomationButton(Vector2 buttonSize) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index ced78fb..d8f3cd1 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -385,7 +385,7 @@ public class ActorPanel { if (ImGuiUtil.DrawDisabledButton("Revert to Game", Vector2.Zero, "Revert the character to its actual state in the game.", _state!.IsLocked)) - _stateManager.ResetState(_state!, StateSource.Manual, stateUpdate: true); + _stateManager.ResetState(_state!, StateSource.Manual, isFinal: true); ImGui.SameLine(); @@ -423,7 +423,7 @@ public class ActorPanel if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), - ApplySettings.Manual with { SendStateUpdate = true }); + ApplySettings.Manual with { IsFinal = true }); } private void DrawApplyToTarget() @@ -440,7 +440,7 @@ public class ActorPanel if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) _stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)), - ApplySettings.Manual with { SendStateUpdate = true }); + ApplySettings.Manual with { IsFinal = true }); } @@ -467,7 +467,7 @@ public class ActorPanel var text = ImGui.GetClipboardText(); var design = panel._converter.FromBase64(text, applyCustomize, applyGear, out _) ?? throw new Exception("The clipboard did not contain valid data."); - panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } catch (Exception ex) { diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index c44a722..62f93e9 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -79,7 +79,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled)) { var design = CreateDesign(plate); - _state.ApplyDesign(state!, design, ApplySettings.Manual with { SendStateUpdate = true }); + _state.ApplyDesign(state!, design, ApplySettings.Manual with { IsFinal = true }); } using (ImRaii.Group()) diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs index 8f561af..1a78b24 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -51,6 +51,7 @@ public class IpcTesterPanel( Glamourer.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester."); state.GPoseChanged.Enable(); state.StateChanged.Enable(); + state.StateFinalized.Enable(); framework.Update += CheckUnsubscribe; _subscribed = true; } @@ -73,5 +74,6 @@ public class IpcTesterPanel( _subscribed = false; state.GPoseChanged.Disable(); state.StateChanged.Disable(); + state.StateFinalized.Disable(); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index f378625..638bffc 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; using Penumbra.GameData.Interop; using Penumbra.String; @@ -31,9 +32,16 @@ public class StateIpcTester : IUiService, IDisposable private string? _getStateString; public readonly EventSubscriber StateChanged; - private nint _lastStateChangeActor; - private ByteString _lastStateChangeName = ByteString.Empty; - private DateTime _lastStateChangeTime; + private nint _lastStateChangeActor; + private ByteString _lastStateChangeName = ByteString.Empty; + private DateTime _lastStateChangeTime; + private StateChangeType _lastStateChangeType; + + public readonly EventSubscriber StateFinalized; + private nint _lastStateFinalizeActor; + private ByteString _lastStateFinalizeName = ByteString.Empty; + private DateTime _lastStateFinalizeTime; + private StateFinalizationType _lastStateFinalizeType; public readonly EventSubscriber GPoseChanged; private bool _lastGPoseChangeValue; @@ -44,15 +52,18 @@ public class StateIpcTester : IUiService, IDisposable public StateIpcTester(IDalamudPluginInterface pluginInterface) { _pluginInterface = pluginInterface; - StateChanged = Api.IpcSubscribers.StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); + StateChanged = StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); + StateFinalized = Api.IpcSubscribers.StateFinalized.Subscriber(_pluginInterface, OnStateFinalized); GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange); StateChanged.Disable(); + StateFinalized.Disable(); GPoseChanged.Disable(); } public void Dispose() { StateChanged.Dispose(); + StateFinalized.Dispose(); GPoseChanged.Dispose(); } @@ -73,86 +84,88 @@ public class StateIpcTester : IUiService, IDisposable IpcTesterHelpers.DrawIntro("Last Error"); ImGui.TextUnformatted(_lastError.ToString()); IpcTesterHelpers.DrawIntro("Last State Change"); - PrintName(); + PrintChangeName(); + IpcTesterHelpers.DrawIntro("Last State Finalization"); + PrintFinalizeName(); IpcTesterHelpers.DrawIntro("Last GPose Change"); ImGui.TextUnformatted($"{_lastGPoseChangeValue} at {_lastGPoseChangeTime.ToLocalTime().TimeOfDay}"); IpcTesterHelpers.DrawIntro(GetState.Label); DrawStatePopup(); - if (ImGui.Button("Get##Idx")) + if (ImUtf8.Button("Get##Idx"u8)) { (_lastError, _state) = new GetState(_pluginInterface).Invoke(_gameObjectIndex, _key); _stateString = _state?.ToString(Formatting.Indented) ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } IpcTesterHelpers.DrawIntro(GetStateName.Label); - if (ImGui.Button("Get##Name")) + if (ImUtf8.Button("Get##Name"u8)) { (_lastError, _state) = new GetStateName(_pluginInterface).Invoke(_gameObjectName, _key); _stateString = _state?.ToString(Formatting.Indented) ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } IpcTesterHelpers.DrawIntro(GetStateBase64.Label); - if (ImGui.Button("Get##Base64Idx")) + if (ImUtf8.Button("Get##Base64Idx"u8)) { (_lastError, _getStateString) = new GetStateBase64(_pluginInterface).Invoke(_gameObjectIndex, _key); _stateString = _getStateString ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } IpcTesterHelpers.DrawIntro(GetStateBase64Name.Label); - if (ImGui.Button("Get##Base64Idx")) + if (ImUtf8.Button("Get##Base64Idx"u8)) { (_lastError, _getStateString) = new GetStateBase64Name(_pluginInterface).Invoke(_gameObjectName, _key); _stateString = _getStateString ?? "No State Available"; - ImGui.OpenPopup("State"); + ImUtf8.OpenPopup("State"u8); } IpcTesterHelpers.DrawIntro(ApplyState.Label); if (ImGuiUtil.DrawDisabledButton("Apply Last##Idx", Vector2.Zero, string.Empty, _state == null)) _lastError = new ApplyState(_pluginInterface).Invoke(_state!, _gameObjectIndex, _key, _flags); ImGui.SameLine(); - if (ImGui.Button("Apply Base64##Idx")) + if (ImUtf8.Button("Apply Base64##Idx"u8)) _lastError = new ApplyState(_pluginInterface).Invoke(_base64State, _gameObjectIndex, _key, _flags); IpcTesterHelpers.DrawIntro(ApplyStateName.Label); if (ImGuiUtil.DrawDisabledButton("Apply Last##Name", Vector2.Zero, string.Empty, _state == null)) _lastError = new ApplyStateName(_pluginInterface).Invoke(_state!, _gameObjectName, _key, _flags); ImGui.SameLine(); - if (ImGui.Button("Apply Base64##Name")) + if (ImUtf8.Button("Apply Base64##Name"u8)) _lastError = new ApplyStateName(_pluginInterface).Invoke(_base64State, _gameObjectName, _key, _flags); IpcTesterHelpers.DrawIntro(RevertState.Label); - if (ImGui.Button("Revert##Idx")) + if (ImUtf8.Button("Revert##Idx"u8)) _lastError = new RevertState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); IpcTesterHelpers.DrawIntro(RevertStateName.Label); - if (ImGui.Button("Revert##Name")) + if (ImUtf8.Button("Revert##Name"u8)) _lastError = new RevertStateName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); IpcTesterHelpers.DrawIntro(UnlockState.Label); - if (ImGui.Button("Unlock##Idx")) + if (ImUtf8.Button("Unlock##Idx"u8)) _lastError = new UnlockState(_pluginInterface).Invoke(_gameObjectIndex, _key); IpcTesterHelpers.DrawIntro(UnlockStateName.Label); - if (ImGui.Button("Unlock##Name")) + if (ImUtf8.Button("Unlock##Name"u8)) _lastError = new UnlockStateName(_pluginInterface).Invoke(_gameObjectName, _key); IpcTesterHelpers.DrawIntro(UnlockAll.Label); - if (ImGui.Button("Unlock##All")) + if (ImUtf8.Button("Unlock##All"u8)) _numUnlocked = new UnlockAll(_pluginInterface).Invoke(_key); ImGui.SameLine(); ImGui.TextUnformatted($"Unlocked {_numUnlocked}"); IpcTesterHelpers.DrawIntro(RevertToAutomation.Label); - if (ImGui.Button("Revert##AutomationIdx")) + if (ImUtf8.Button("Revert##AutomationIdx"u8)) _lastError = new RevertToAutomation(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); IpcTesterHelpers.DrawIntro(RevertToAutomationName.Label); - if (ImGui.Button("Revert##AutomationName")) + if (ImUtf8.Button("Revert##AutomationName"u8)) _lastError = new RevertToAutomationName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); } @@ -162,44 +175,70 @@ public class StateIpcTester : IUiService, IDisposable if (_stateString == null) return; - using var p = ImRaii.Popup("State"); + using var p = ImUtf8.Popup("State"u8); if (!p) return; - if (ImGui.Button("Copy to Clipboard")) - ImGui.SetClipboardText(_stateString); + if (ImUtf8.Button("Copy to Clipboard"u8)) + ImUtf8.SetClipboardText(_stateString); if (_stateString[0] is '{') { ImGui.SameLine(); - if (ImGui.Button("Copy as Base64") && _state != null) - ImGui.SetClipboardText(DesignConverter.ToBase64(_state)); + if (ImUtf8.Button("Copy as Base64"u8) && _state != null) + ImUtf8.SetClipboardText(DesignConverter.ToBase64(_state)); } using var font = ImRaii.PushFont(UiBuilder.MonoFont); - ImGuiUtil.TextWrapped(_stateString ?? string.Empty); + ImUtf8.TextWrapped(_stateString ?? string.Empty); - if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused()) + if (ImUtf8.Button("Close"u8, -Vector2.UnitX) || !ImGui.IsWindowFocused()) ImGui.CloseCurrentPopup(); } - private unsafe void PrintName() + private unsafe void PrintChangeName() { - ImGuiNative.igTextUnformatted(_lastStateChangeName.Path, _lastStateChangeName.Path + _lastStateChangeName.Length); + ImUtf8.Text(_lastStateChangeName.Span); + ImGui.SameLine(0, 0); + ImUtf8.Text($" ({_lastStateChangeType})"); ImGui.SameLine(); using (ImRaii.PushFont(UiBuilder.MonoFont)) { - ImGuiUtil.CopyOnClickSelectable($"0x{_lastStateChangeActor:X}"); + ImUtf8.CopyOnClickSelectable($"0x{_lastStateChangeActor:X}"); } ImGui.SameLine(); - ImGui.TextUnformatted($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}"); + ImUtf8.Text($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}"); } - private void OnStateChanged(nint actor, StateChangeType _) + private unsafe void PrintFinalizeName() + { + ImUtf8.Text(_lastStateFinalizeName.Span); + ImGui.SameLine(0, 0); + ImUtf8.Text($" ({_lastStateFinalizeType})"); + ImGui.SameLine(); + using (ImRaii.PushFont(UiBuilder.MonoFont)) + { + ImUtf8.CopyOnClickSelectable($"0x{_lastStateFinalizeActor:X}"); + } + + ImGui.SameLine(); + ImUtf8.Text($"at {_lastStateFinalizeTime.ToLocalTime().TimeOfDay}"); + } + + private void OnStateChanged(nint actor, StateChangeType type) { _lastStateChangeActor = actor; _lastStateChangeTime = DateTime.UtcNow; _lastStateChangeName = actor != nint.Zero ? ((Actor)actor).Utf8Name.Clone() : ByteString.Empty; + _lastStateChangeType = type; + } + + private void OnStateFinalized(nint actor, StateFinalizationType type) + { + _lastStateFinalizeActor = actor; + _lastStateFinalizeTime = DateTime.UtcNow; + _lastStateFinalizeName = actor != nint.Zero ? ((Actor)actor).Utf8Name.Clone() : ByteString.Empty; + _lastStateFinalizeType = type; } private void OnGPoseChange(bool value) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index fe71609..42eb8e9 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -460,7 +460,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { IsFinal = true }); } } @@ -478,7 +478,7 @@ public class DesignPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys()); - _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks with { IsFinal = true }); } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 345df11..aeb96f6 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -196,7 +196,7 @@ public class NpcPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); - _state.ApplyDesign(state, design, ApplySettings.Manual with { SendStateUpdate = true }); + _state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); } } @@ -214,7 +214,7 @@ public class NpcPanel if (_state.GetOrCreate(id, data.Objects[0], out var state)) { var design = _converter.Convert(ToDesignData(), new StateMaterialManager(), ApplicationRules.NpcFromModifiers()); - _state.ApplyDesign(state, design, ApplySettings.Manual with { SendStateUpdate = true }); + _state.ApplyDesign(state, design, ApplySettings.Manual with { IsFinal = true }); } } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 466f1ae..ffa6581 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -18,6 +18,7 @@ public unsafe class UpdateSlotService : IDisposable public readonly BonusSlotUpdating BonusSlotUpdatingEvent; public readonly GearsetDataLoaded GearsetDataLoadedEvent; private readonly DictBonusItems _bonusItems; + public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, GearsetDataLoaded gearsetDataLoaded, IGameInteropProvider interop, DictBonusItems bonusItems) { @@ -26,8 +27,8 @@ public unsafe class UpdateSlotService : IDisposable GearsetDataLoadedEvent = gearsetDataLoaded; _bonusItems = bonusItems; - _loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); interop.InitializeFromAttributes(this); + _loadGearsetDataHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadGearsetData, LoadGearsetDataDetour); _flagSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable(); _loadGearsetDataHook.Enable(); @@ -89,8 +90,8 @@ public unsafe class UpdateSlotService : IDisposable /// Detours the func that makes all FlagSlotForUpdate calls on a gearset change or initial render of a given actor (Only Cases this is Called). /// Logic done after returning the original hook executes After all equipment/weapon/crest data is loaded into the Actors BaseData. /// - private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData); - private readonly Hook _loadGearsetDataHook = null!; + private delegate ulong LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData); + private readonly Hook _loadGearsetDataHook; private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { @@ -115,30 +116,30 @@ public unsafe class UpdateSlotService : IDisposable Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Glamourer-Invoked on 0x{drawObject.Address:X} on {slot} with item data {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData) + private ulong LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, PacketPlayerGearsetData* gearsetData) { var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); - Model drawObject = drawDataContainer->OwnerObject->DrawObject; + var drawObject = drawDataContainer->OwnerObject->DrawObject; GearsetDataLoadedEvent.Invoke(drawObject); - // Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } - // If you ever care to debug this, here is a formatted string output of this new gearsetData struct. - private string FormatGearsetItemDataStruct(PacketPlayerGearsetData gearsetData) + + private static string FormatGearsetItemDataStruct(PacketPlayerGearsetData gearsetData) { - string ret = + var ret = $"\nMainhandWeaponData: Id: {gearsetData.MainhandWeaponData.Id}, Type: {gearsetData.MainhandWeaponData.Type}, " + $"Variant: {gearsetData.MainhandWeaponData.Variant}, Stain0: {gearsetData.MainhandWeaponData.Stain0}, Stain1: {gearsetData.MainhandWeaponData.Stain1}" + $"\nOffhandWeaponData: Id: {gearsetData.OffhandWeaponData.Id}, Type: {gearsetData.OffhandWeaponData.Type}, " + $"Variant: {gearsetData.OffhandWeaponData.Variant}, Stain0: {gearsetData.OffhandWeaponData.Stain0}, Stain1: {gearsetData.OffhandWeaponData.Stain1}" + $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId}"; - for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) + for (var offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) { - LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); - int dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset - byte* dyePtr = (byte*)&gearsetData + dyeOffset; - ret += $"\nEquipSlot {((EquipSlot)(dyeOffset - 60)).ToString()}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; + var equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); + var dyeOffset = (offset - 20) / sizeof(LegacyCharacterArmor) + 60; // Calculate the corresponding dye offset + var dyePtr = (byte*)&gearsetData + dyeOffset; + ret += $"\nEquipSlot {(EquipSlot)(dyeOffset - 60)}:: Id: {(*equipSlotPtr).Set}, Variant: {(*equipSlotPtr).Variant}, Stain0: {(*equipSlotPtr).Stain.Id}, Stain1: {*dyePtr}"; } return ret; } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index a869a09..98dfa19 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -668,7 +668,7 @@ public class CommandService : IDisposable, IApiService if (!_objects.TryGetValue(identifier, out var actors)) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } else { @@ -677,7 +677,7 @@ public class CommandService : IDisposable, IApiService if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state)) { ApplyModSettings(design, actor, applyMods); - _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { SendStateUpdate = true }); + _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { IsFinal = true }); } } } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index ebf347f..42058d2 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -17,7 +17,7 @@ public class StateEditor( InternalStateEditor editor, StateApplier applier, StateChanged stateChanged, - StateUpdated stateUpdated, + StateFinalized stateFinalized, JobChangeState jobChange, Configuration config, ItemManager items, @@ -25,12 +25,12 @@ public class StateEditor( ModSettingApplier modApplier, GPoseService gPose) : IDesignEditor { - protected readonly InternalStateEditor Editor = editor; - protected readonly StateApplier Applier = applier; - protected readonly StateChanged StateChanged = stateChanged; - protected readonly StateUpdated StateUpdated = stateUpdated; - protected readonly Configuration Config = config; - protected readonly ItemManager Items = items; + protected readonly InternalStateEditor Editor = editor; + protected readonly StateApplier Applier = applier; + protected readonly StateChanged StateChanged = stateChanged; + protected readonly StateFinalized StateFinalized = stateFinalized; + protected readonly Configuration Config = config; + protected readonly ItemManager Items = items; /// Turn an actor to. public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source, @@ -43,7 +43,7 @@ public class StateEditor( Glamourer.Log.Verbose( $"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Model, source, state, actors, null); - StateUpdated.Invoke(StateFinalizationType.ModelChange, actors); + StateFinalized.Invoke(StateFinalizationType.ModelChange, actors); } /// @@ -383,7 +383,7 @@ public class StateEditor( Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key); } - if (settings.ResetMaterials || (!settings.RespectManual && mergedDesign.ResetAdvancedDyes)) + if (settings.ResetMaterials || !settings.RespectManual && mergedDesign.ResetAdvancedDyes) state.Materials.Clear(); foreach (var (key, value) in mergedDesign.Design.Materials) @@ -420,8 +420,8 @@ public class StateEditor( Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later - if(settings.SendStateUpdate) - StateUpdated.Invoke(StateFinalizationType.DesignApplied, actors); + if (settings.IsFinal) + StateFinalized.Invoke(StateFinalizationType.DesignApplied, actors); return; @@ -442,7 +442,8 @@ public class StateEditor( if (!settings.MergeLinks || design is not Design d) merged = new MergedDesign(design); else - merged = merger.Merge(d.AllLinks(true), state.ModelData.IsHuman ? state.ModelData.Customize : CustomizeArray.Default, state.BaseData, + merged = merger.Merge(d.AllLinks(true), state.ModelData.IsHuman ? state.ModelData.Customize : CustomizeArray.Default, + state.BaseData, false, Config.AlwaysApplyAssociatedMods); ApplyDesign(data, merged, settings with @@ -460,7 +461,7 @@ public class StateEditor( if (!Config.ChangeEntireItem || !settings.Source.IsManual()) return; - var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); + var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand); // Do not change Shields to nothing. if (mh.Type is FullEquipType.Sword) return; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index d8648bb..3a6d6ef 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -41,7 +41,7 @@ public class StateListener : IDisposable private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly VisorStateChanged _visorState; private readonly WeaponVisibilityChanged _weaponVisibility; - private readonly StateUpdated _stateUpdated; + private readonly StateFinalized _stateFinalized; private readonly AutoDesignApplier _autoDesignApplier; private readonly FunModule _funModule; private readonly HumanModelList _humans; @@ -63,7 +63,7 @@ public class StateListener : IDisposable WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, - CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateUpdated stateUpdated) + CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateFinalized stateFinalized) { _manager = manager; _items = items; @@ -88,7 +88,7 @@ public class StateListener : IDisposable _condition = condition; _crestService = crestService; _bonusSlotUpdating = bonusSlotUpdating; - _stateUpdated = stateUpdated; + _stateFinalized = stateFinalized; Subscribe(); } @@ -281,7 +281,7 @@ public class StateListener : IDisposable _objects.Update(); if (_objects.TryGetValue(identifier, out var actors) && actors.Valid) - _stateUpdated.Invoke(StateFinalizationType.Gearset, actors); + _stateFinalized.Invoke(StateFinalizationType.Gearset, actors); } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 948e225..9b71586 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -18,20 +18,20 @@ using Penumbra.GameData.Interop; namespace Glamourer.State; public sealed class StateManager( - ActorManager _actors, + ActorManager actors, ItemManager items, - StateChanged @event, - StateUpdated @event2, + StateChanged changeEvent, + StateFinalized finalizeEvent, StateApplier applier, InternalStateEditor editor, - HumanModelList _humans, - IClientState _clientState, + HumanModelList humans, + IClientState clientState, Configuration config, JobChangeState jobChange, DesignMerger merger, ModSettingApplier modApplier, GPoseService gPose) - : StateEditor(editor, applier, @event, @event2, jobChange, config, items, merger, modApplier, gPose), + : StateEditor(editor, applier, changeEvent, finalizeEvent, jobChange, config, items, merger, modApplier, gPose), IReadOnlyDictionary { private readonly Dictionary _states = []; @@ -62,7 +62,7 @@ public sealed class StateManager( /// public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state) - => GetOrCreate(actor.GetIdentifier(_actors), actor, out state); + => GetOrCreate(actor.GetIdentifier(actors), actor, out state); /// Try to obtain or create a new state for an existing actor. Returns false if no state could be created. public unsafe bool GetOrCreate(ActorIdentifier identifier, Actor actor, [NotNullWhen(true)] out ActorState? state) @@ -82,7 +82,7 @@ public sealed class StateManager( ModelData = FromActor(actor, true, false), BaseData = FromActor(actor, false, false), LastJob = (byte)(actor.IsCharacter ? actor.AsCharacter->CharacterData.ClassJob : 0), - LastTerritory = _clientState.TerritoryType, + LastTerritory = clientState.TerritoryType, }; // state.Identifier is owned. _states.Add(state.Identifier, state); @@ -115,7 +115,7 @@ public sealed class StateManager( // Model ID is only unambiguously contained in the game object. // The draw object only has the object type. // TODO reverse search model data to get model id from model. - if (!_humans.IsHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId)) + if (!humans.IsHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId)) { ret.LoadNonHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData, (nint)Unsafe.AsPointer(ref actor.AsCharacter->DrawData.EquipmentModelIds[0])); @@ -236,7 +236,7 @@ public sealed class StateManager( public void TurnHuman(ActorState state, StateSource source, uint key = 0) => ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key); - public void ResetState(ActorState state, StateSource source, uint key = 0, bool stateUpdate = false) + public void ResetState(ActorState state, StateSource source, uint key = 0, bool isFinal = false) { if (!state.Unlock(key)) return; @@ -278,8 +278,8 @@ public sealed class StateManager( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // only invoke if we define this reset call as the final call in our state update. - if(stateUpdate) - StateUpdated.Invoke(StateFinalizationType.Revert, actors); + if(isFinal) + StateFinalized.Invoke(StateFinalizationType.Revert, actors); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -306,7 +306,7 @@ public sealed class StateManager( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateFinalizationType.RevertAdvanced, actors); + StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, actors); } public void ResetCustomize(ActorState state, StateSource source, uint key = 0) @@ -325,7 +325,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateFinalizationType.RevertCustomize, actors); + StateFinalized.Invoke(StateFinalizationType.RevertCustomize, actors); } public void ResetEquip(ActorState state, StateSource source, uint key = 0) @@ -376,7 +376,7 @@ public sealed class StateManager( Glamourer.Log.Verbose( $"Reset equipment state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) - StateUpdated.Invoke(StateFinalizationType.RevertEquipment, actors); + StateFinalized.Invoke(StateFinalizationType.RevertEquipment, actors); } public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0) @@ -453,23 +453,23 @@ public sealed class StateManager( } } - public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool stateUpdate = false) + public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool isFinal = false) { if (!GetOrCreate(actor, out var state)) return; - ReapplyState(actor, state, forceRedraw, source, stateUpdate); + ReapplyState(actor, state, forceRedraw, source, isFinal); } - public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool stateUpdate) + public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool isFinal) { var data = Applier.ApplyAll(state, forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); - if(stateUpdate) - StateUpdated.Invoke(StateFinalizationType.Reapply, data); + if(isFinal) + StateFinalized.Invoke(StateFinalizationType.Reapply, data); } /// Automation variant for reapply, to fire the correct StateUpdateType once reapplied. @@ -490,7 +490,7 @@ public sealed class StateManager( || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); // invoke the automation update based on what reset is. - StateUpdated.Invoke(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data); + StateFinalized.Invoke(wasReset ? StateFinalizationType.RevertAutomation : StateFinalizationType.ReapplyAutomation, data); } public void DeleteState(ActorIdentifier identifier) From 0123fe1fbd448df9fd2a3873f3d8c761938e7427 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 17:52:06 +0100 Subject: [PATCH 575/786] Increment minor API version. --- Glamourer/Api/GlamourerApi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 24ed840..62107a9 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -6,7 +6,7 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; - public const int CurrentApiVersionMinor = 3; + public const int CurrentApiVersionMinor = 4; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); From cd6a6a462db26d97799b7da11d2d3c12c72f5bb5 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 24 Jan 2025 16:54:22 +0000 Subject: [PATCH 576/786] [CI] Updating repo.json for testing_1.3.5.4 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 5f4ae31..076063b 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.5.1", - "TestingAssemblyVersion": "1.3.5.3", + "TestingAssemblyVersion": "1.3.5.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 356b814102b65e961a8f9c5c9a2e536df4f7f16a Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:14:50 -0800 Subject: [PATCH 577/786] Append conversion for SetMetaFlag -> MetaIndex for help in moving down the API call to the state editor call. --- Glamourer/Designs/MetaIndex.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/MetaIndex.cs b/Glamourer/Designs/MetaIndex.cs index edbf7b6..9cf6472 100644 --- a/Glamourer/Designs/MetaIndex.cs +++ b/Glamourer/Designs/MetaIndex.cs @@ -1,4 +1,5 @@ -using Glamourer.State; +using Glamourer.Api.Enums; +using Glamourer.State; namespace Glamourer.Designs; @@ -37,6 +38,16 @@ public static class MetaExtensions _ => (MetaFlag)byte.MaxValue, }; + public static MetaIndex ToIndex(this SetMetaFlag index) + => index switch + { + SetMetaFlag.Wetness => MetaIndex.Wetness, + SetMetaFlag.HatState => MetaIndex.HatState, + SetMetaFlag.VisorState => MetaIndex.VisorState, + SetMetaFlag.WeaponState => MetaIndex.WeaponState, + _ => (MetaIndex)byte.MaxValue, + }; + public static MetaIndex ToIndex(this MetaFlag index) => index switch { From 091aadd4a62c81f5569746a2f6ed7b50a3d7c512 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:25:40 -0800 Subject: [PATCH 578/786] Add Yield return Indices List for setter helper --- Glamourer/Designs/MetaIndex.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Glamourer/Designs/MetaIndex.cs b/Glamourer/Designs/MetaIndex.cs index 9cf6472..9552e5a 100644 --- a/Glamourer/Designs/MetaIndex.cs +++ b/Glamourer/Designs/MetaIndex.cs @@ -48,6 +48,19 @@ public static class MetaExtensions _ => (MetaIndex)byte.MaxValue, }; + public static IEnumerable ToIndices(this SetMetaFlag index) + { + if (index.HasFlag(SetMetaFlag.Wetness)) + yield return MetaIndex.Wetness; + if (index.HasFlag(SetMetaFlag.HatState)) + yield return MetaIndex.HatState; + if (index.HasFlag(SetMetaFlag.VisorState)) + yield return MetaIndex.VisorState; + if (index.HasFlag(SetMetaFlag.WeaponState)) + yield return MetaIndex.WeaponState; + } + + public static MetaIndex ToIndex(this MetaFlag index) => index switch { From 439849edfac8cde614af660407cd229c0f1fd216 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:35:53 -0800 Subject: [PATCH 579/786] Append SetMetaState with object index. --- Glamourer/Api/ItemsApi.cs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index fd174ca..ef320f8 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -6,6 +6,7 @@ using Glamourer.State; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using static OtterGui.ItemSelector; namespace Glamourer.Api; @@ -96,9 +97,9 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager if (!ResolveBonusItem(slot, bonusItemId, out var item)) return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); - var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key); - var anyHuman = false; - var anyFound = false; + var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key); + var anyHuman = false; + var anyFound = false; var anyUnlocked = false; foreach (var state in helpers.FindStates(playerName)) { @@ -125,6 +126,29 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc SetMetaState(int objectIndex, bool newValue, uint key, SetMetaFlag metaFlags) + { + var args = ApiHelpers.Args("Index", objectIndex, "Value", newValue, "Key", key, "Flags", metaFlags); + if (metaFlags == 0) + return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); + + if (helpers.FindState(objectIndex) is not { } state) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!state.ModelData.IsHuman) + return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args); + + if (!state.CanUnlock(key)) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + // Grab MetaIndices from attached flags, and update the states. + var indices = metaFlags.ToIndices(); + foreach (var index in indices) + stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + + return GlamourerApiEc.Success; } private bool ResolveItem(ApiEquipSlot apiSlot, ulong itemId, out EquipItem item) From 0ed4603ba526371986eec7aaa9bc79dbc43205ac Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:42:52 -0800 Subject: [PATCH 580/786] Corrected Paramater fields to include ApplyFlags and removed unessisary using. --- Glamourer/Api/ItemsApi.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index ef320f8..307a94f 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -6,7 +6,6 @@ using Glamourer.State; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using static OtterGui.ItemSelector; namespace Glamourer.Api; @@ -128,10 +127,10 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public GlamourerApiEc SetMetaState(int objectIndex, bool newValue, uint key, SetMetaFlag metaFlags) + public GlamourerApiEc SetMetaState(int objectIndex, SetMetaFlag metaStates, bool newValue, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Index", objectIndex, "Value", newValue, "Key", key, "Flags", metaFlags); - if (metaFlags == 0) + var args = ApiHelpers.Args("Index", objectIndex, "MetaStates", metaStates, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + if (metaStates == 0) return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); if (helpers.FindState(objectIndex) is not { } state) @@ -144,7 +143,7 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); // Grab MetaIndices from attached flags, and update the states. - var indices = metaFlags.ToIndices(); + var indices = metaStates.ToIndices(); foreach (var index in indices) stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); From 611200d311f1a0d7a9ae816a26f3a4fea886fa13 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:43:26 -0800 Subject: [PATCH 581/786] Added setMetaState by name --- Glamourer/Api/ItemsApi.cs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index 307a94f..0dce795 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -150,6 +150,44 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return GlamourerApiEc.Success; } + public GlamourerApiEc SetMetaStateName(string playerName, SetMetaFlag metaStates, bool newValue, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", playerName, "MetaStates", metaStates, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + if (metaStates == 0) + return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); + + var indices = metaStates.ToIndices(); + var anyHuman = false; + var anyFound = false; + var anyUnlocked = false; + foreach (var state in helpers.FindStates(playerName)) + { + anyFound = true; + if (!state.ModelData.IsHuman) + continue; + + anyHuman = true; + if (!state.CanUnlock(key)) + continue; + + anyUnlocked = true; + // update all MetaStates for this ActorState + foreach (var index in indices) + stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + } + + if (!anyFound) + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); + + if (!anyHuman) + return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args); + + if (!anyUnlocked) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); + + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + private bool ResolveItem(ApiEquipSlot apiSlot, ulong itemId, out EquipItem item) { var id = (CustomItemId)itemId; From 0f127a557d2e5dd4bb85b5e45e87bf1ba22b59a0 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 11:44:09 -0800 Subject: [PATCH 582/786] Introduced locks, if nessisary for change. --- Glamourer/Api/ItemsApi.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index 0dce795..c73c853 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -144,8 +144,11 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager // Grab MetaIndices from attached flags, and update the states. var indices = metaStates.ToIndices(); - foreach (var index in indices) - stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + foreach (var index in indices) + { + stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + ApiHelpers.Lock(state, key, flags); + } return GlamourerApiEc.Success; } @@ -172,8 +175,11 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager anyUnlocked = true; // update all MetaStates for this ActorState - foreach (var index in indices) - stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + foreach (var index in indices) + { + stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); + ApiHelpers.Lock(state, key, flags); + } } if (!anyFound) From 5eb545f62d48ab5a653df95d97161edfa6234adc Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 24 Jan 2025 13:09:49 -0800 Subject: [PATCH 583/786] Namechange: SetMetaState -> SetMeta Help clear up confusion on if it is in the StateApi section or the ItemsApi section (Which it is currently in) --- Glamourer/Api/ItemsApi.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index c73c853..b27264d 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -127,10 +127,10 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public GlamourerApiEc SetMetaState(int objectIndex, SetMetaFlag metaStates, bool newValue, uint key, ApplyFlag flags) + public GlamourerApiEc SetMeta(int objectIndex, SetMetaFlag types, bool newValue, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Index", objectIndex, "MetaStates", metaStates, "NewValue", newValue, "Key", key, "ApplyFlags", flags); - if (metaStates == 0) + var args = ApiHelpers.Args("Index", objectIndex, "Meta Types", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + if (types == 0) return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); if (helpers.FindState(objectIndex) is not { } state) @@ -143,7 +143,7 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); // Grab MetaIndices from attached flags, and update the states. - var indices = metaStates.ToIndices(); + var indices = types.ToIndices(); foreach (var index in indices) { stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); @@ -153,13 +153,13 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return GlamourerApiEc.Success; } - public GlamourerApiEc SetMetaStateName(string playerName, SetMetaFlag metaStates, bool newValue, uint key, ApplyFlag flags) + public GlamourerApiEc SetMetaState(string playerName, SetMetaFlag types, bool newValue, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Name", playerName, "MetaStates", metaStates, "NewValue", newValue, "Key", key, "ApplyFlags", flags); - if (metaStates == 0) + var args = ApiHelpers.Args("Name", playerName, "Meta Types", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + if (types == 0) return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); - var indices = metaStates.ToIndices(); + var indices = types.ToIndices(); var anyHuman = false; var anyFound = false; var anyUnlocked = false; From f6c9ecad6076e32a379f245479b900291de29ad2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 24 Jan 2025 23:28:23 +0100 Subject: [PATCH 584/786] Move MetaFlag completely to API, rename, cleanup. --- Glamourer.Api | 2 +- Glamourer/Api/ItemsApi.cs | 11 +++-- Glamourer/Automation/ApplicationType.cs | 3 +- Glamourer/Designs/ApplicationCollection.cs | 1 + Glamourer/Designs/ApplicationRules.cs | 3 +- Glamourer/Designs/DesignBase64Migration.cs | 3 +- Glamourer/Designs/Links/DesignMerger.cs | 3 +- Glamourer/Designs/MetaIndex.cs | 46 ++++++--------------- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 1 + Glamourer/Gui/ToggleDrawData.cs | 3 +- Glamourer/State/StateIndex.cs | 3 +- 11 files changed, 33 insertions(+), 46 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 4ac38fb..8de6fa7 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 4ac38fbed6fb0f31c0b75de26950ab82d3bee258 +Subproject commit 8de6fa7246a403de50b3be4e17bb5f188717b279 diff --git a/Glamourer/Api/ItemsApi.cs b/Glamourer/Api/ItemsApi.cs index b27264d..ac971c9 100644 --- a/Glamourer/Api/ItemsApi.cs +++ b/Glamourer/Api/ItemsApi.cs @@ -127,9 +127,9 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public GlamourerApiEc SetMeta(int objectIndex, SetMetaFlag types, bool newValue, uint key, ApplyFlag flags) + public GlamourerApiEc SetMetaState(int objectIndex, MetaFlag types, bool newValue, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Index", objectIndex, "Meta Types", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + var args = ApiHelpers.Args("Index", objectIndex, "MetaTypes", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); if (types == 0) return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); @@ -153,13 +153,12 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager return GlamourerApiEc.Success; } - public GlamourerApiEc SetMetaState(string playerName, SetMetaFlag types, bool newValue, uint key, ApplyFlag flags) + public GlamourerApiEc SetMetaStateName(string playerName, MetaFlag types, bool newValue, uint key, ApplyFlag flags) { - var args = ApiHelpers.Args("Name", playerName, "Meta Types", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); + var args = ApiHelpers.Args("Name", playerName, "MetaTypes", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags); if (types == 0) return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args); - var indices = types.ToIndices(); var anyHuman = false; var anyFound = false; var anyUnlocked = false; @@ -175,7 +174,7 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager anyUnlocked = true; // update all MetaStates for this ActorState - foreach (var index in indices) + foreach (var index in types.ToIndices()) { stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual); ApiHelpers.Lock(state, key, flags); diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs index 3d409cb..8871a0e 100644 --- a/Glamourer/Automation/ApplicationType.cs +++ b/Glamourer/Automation/ApplicationType.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Api.Enums; +using Glamourer.Designs; using Glamourer.GameData; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/ApplicationCollection.cs b/Glamourer/Designs/ApplicationCollection.cs index 0fd18f0..13813a3 100644 --- a/Glamourer/Designs/ApplicationCollection.cs +++ b/Glamourer/Designs/ApplicationCollection.cs @@ -1,3 +1,4 @@ +using Glamourer.Api.Enums; using Glamourer.GameData; using ImGuiNET; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/ApplicationRules.cs b/Glamourer/Designs/ApplicationRules.cs index 3c5fed2..703a3ae 100644 --- a/Glamourer/Designs/ApplicationRules.cs +++ b/Glamourer/Designs/ApplicationRules.cs @@ -1,4 +1,5 @@ -using Glamourer.GameData; +using Glamourer.Api.Enums; +using Glamourer.GameData; using Glamourer.State; using ImGuiNET; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index a60c527..cab9e27 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,4 +1,5 @@ -using Glamourer.Services; +using Glamourer.Api.Enums; +using Glamourer.Services; using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index 0284322..847d5f1 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -1,4 +1,5 @@ -using Glamourer.Automation; +using Glamourer.Api.Enums; +using Glamourer.Automation; using Glamourer.Designs.Special; using Glamourer.GameData; using Glamourer.Interop.Material; diff --git a/Glamourer/Designs/MetaIndex.cs b/Glamourer/Designs/MetaIndex.cs index 9552e5a..3fc4655 100644 --- a/Glamourer/Designs/MetaIndex.cs +++ b/Glamourer/Designs/MetaIndex.cs @@ -12,15 +12,6 @@ public enum MetaIndex ModelId = StateIndex.MetaModelId, } -[Flags] -public enum MetaFlag : byte -{ - Wetness = 0x01, - HatState = 0x02, - VisorState = 0x04, - WeaponState = 0x08, -} - public static class MetaExtensions { public static readonly IReadOnlyList AllRelevant = @@ -38,29 +29,6 @@ public static class MetaExtensions _ => (MetaFlag)byte.MaxValue, }; - public static MetaIndex ToIndex(this SetMetaFlag index) - => index switch - { - SetMetaFlag.Wetness => MetaIndex.Wetness, - SetMetaFlag.HatState => MetaIndex.HatState, - SetMetaFlag.VisorState => MetaIndex.VisorState, - SetMetaFlag.WeaponState => MetaIndex.WeaponState, - _ => (MetaIndex)byte.MaxValue, - }; - - public static IEnumerable ToIndices(this SetMetaFlag index) - { - if (index.HasFlag(SetMetaFlag.Wetness)) - yield return MetaIndex.Wetness; - if (index.HasFlag(SetMetaFlag.HatState)) - yield return MetaIndex.HatState; - if (index.HasFlag(SetMetaFlag.VisorState)) - yield return MetaIndex.VisorState; - if (index.HasFlag(SetMetaFlag.WeaponState)) - yield return MetaIndex.WeaponState; - } - - public static MetaIndex ToIndex(this MetaFlag index) => index switch { @@ -68,9 +36,21 @@ public static class MetaExtensions MetaFlag.HatState => MetaIndex.HatState, MetaFlag.VisorState => MetaIndex.VisorState, MetaFlag.WeaponState => MetaIndex.WeaponState, - _ => (MetaIndex)byte.MaxValue, + _ => (MetaIndex)byte.MaxValue, }; + public static IEnumerable ToIndices(this MetaFlag index) + { + if (index.HasFlag(MetaFlag.Wetness)) + yield return MetaIndex.Wetness; + if (index.HasFlag(MetaFlag.HatState)) + yield return MetaIndex.HatState; + if (index.HasFlag(MetaFlag.VisorState)) + yield return MetaIndex.VisorState; + if (index.HasFlag(MetaFlag.WeaponState)) + yield return MetaIndex.WeaponState; + } + public static string ToName(this MetaIndex index) => index switch { diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 42eb8e9..dbe106b 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -2,6 +2,7 @@ using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.System.Framework; +using Glamourer.Api.Enums; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Designs.History; diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index 75edc72..28afc2c 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Api.Enums; +using Glamourer.Designs; using Glamourer.State; using Penumbra.GameData.Enums; diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs index 0ac52ec..e3f1863 100644 --- a/Glamourer/State/StateIndex.cs +++ b/Glamourer/State/StateIndex.cs @@ -1,4 +1,5 @@ -using Glamourer.Designs; +using Glamourer.Api.Enums; +using Glamourer.Designs; using Glamourer.GameData; using Penumbra.GameData.Enums; From 630c4dd894b64c1c0da4af547b51dfda97ef0bbd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 25 Jan 2025 14:00:36 +0100 Subject: [PATCH 585/786] Update Submodules. --- OtterGui | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OtterGui b/OtterGui index fd38721..3c1260c 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit fd387218d2d2d237075cb35be6ca89eeb53e14e5 +Subproject commit 3c1260c9833303c2d33d12d6f77dc2b1afea3f34 diff --git a/Penumbra.GameData b/Penumbra.GameData index 5bac66e..bb59614 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 5bac66e5ad73e57919aff7f8b046606b75e191a2 +Subproject commit bb596141bb582505ae9fae1dd2914773503f4fe7 diff --git a/Penumbra.String b/Penumbra.String index 0647fbc..0bc2b0f 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 0647fbc5017ef9ced3f3ce1c2496eefd57c5b7a8 +Subproject commit 0bc2b0f66eee1a02c9575b2bb30f27ce166f8632 From b3afa2067cc1d7cf4c8a61688886d240e7f1a1e2 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 25 Jan 2025 13:02:30 +0000 Subject: [PATCH 586/786] [CI] Updating repo.json for testing_1.3.5.5 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 076063b..6ee99a9 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.5.1", - "TestingAssemblyVersion": "1.3.5.4", + "TestingAssemblyVersion": "1.3.5.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.4/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 2a067ef60b9b345b1903b4e246d48dfdea900138 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 30 Jan 2025 13:36:06 +0100 Subject: [PATCH 587/786] Accept all existing facepaints. --- Glamourer/GameData/CustomizeSet.cs | 4 +- Glamourer/GameData/CustomizeSetFactory.cs | 1 - Glamourer/GameData/NpcCustomizeSet.cs | 45 ++++++++++++------- .../DebugTab/CustomizationServicePanel.cs | 34 ++++++++++++-- Penumbra.GameData | 2 +- 5 files changed, 63 insertions(+), 23 deletions(-) diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index 178ef07..d0193aa 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -11,7 +11,7 @@ namespace Glamourer.GameData; /// public class CustomizeSet { - private NpcCustomizeSet _npcCustomizations; + private readonly NpcCustomizeSet _npcCustomizations; internal CustomizeSet(NpcCustomizeSet npcCustomizations, SubRace clan, Gender gender) { @@ -88,7 +88,7 @@ public class CustomizeSet { if (IsAvailable(index)) return DataByValue(index, value, out custom, face) >= 0 - || _npcCustomizations.CheckColor(index, value) + || _npcCustomizations.CheckValue(index, value) || NpcOptions.Any(t => t.Type == index && t.Value == value); custom = null; diff --git a/Glamourer/GameData/CustomizeSetFactory.cs b/Glamourer/GameData/CustomizeSetFactory.cs index 13a9865..77a6973 100644 --- a/Glamourer/GameData/CustomizeSetFactory.cs +++ b/Glamourer/GameData/CustomizeSetFactory.cs @@ -76,7 +76,6 @@ internal class CustomizeSetFactory( CustomizeIndex.Hairstyle, CustomizeIndex.LipColor, CustomizeIndex.SkinColor, - CustomizeIndex.FacePaint, CustomizeIndex.TailShape, }; diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index 4dbfd83..3cc19cd 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -3,6 +3,7 @@ using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Lumina.Excel.Sheets; using OtterGui.Services; +using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -40,17 +41,19 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList private readonly BitArray _eyeColors = new(256); private readonly BitArray _facepaintColors = new(256); private readonly BitArray _tattooColors = new(256); + private readonly BitArray _facepaints = new(128); - public bool CheckColor(CustomizeIndex type, CustomizeValue value) + public bool CheckValue(CustomizeIndex type, CustomizeValue value) => type switch { - CustomizeIndex.HairColor => _hairColors[value.Value], - CustomizeIndex.HighlightsColor => _hairColors[value.Value], - CustomizeIndex.EyeColorLeft => _eyeColors[value.Value], - CustomizeIndex.EyeColorRight => _eyeColors[value.Value], - CustomizeIndex.FacePaintColor => _facepaintColors[value.Value], - CustomizeIndex.TattooColor => _tattooColors[value.Value], - _ => false, + CustomizeIndex.HairColor => _hairColors[value.Value], + CustomizeIndex.HighlightsColor => _hairColors[value.Value], + CustomizeIndex.EyeColorLeft => _eyeColors[value.Value], + CustomizeIndex.EyeColorRight => _eyeColors[value.Value], + CustomizeIndex.FacePaintColor => _facepaintColors[value.Value], + CustomizeIndex.TattooColor => _tattooColors[value.Value], + CustomizeIndex.FacePaint when value.Value < 128 => _facepaints[value.Value], + _ => false, }; /// Create the data when ready. @@ -58,13 +61,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList { var waitTask = Task.WhenAll(eNpcs.Awaiter, bNpcs.Awaiter, bNpcNames.Awaiter); Awaiter = waitTask.ContinueWith(_ => - { - var watch = Stopwatch.StartNew(); - var eNpcTask = Task.Run(() => CreateEnpcData(data, eNpcs)); - var bNpcTask = Task.Run(() => CreateBnpcData(data, bNpcs, bNpcNames)); - FilterAndOrderNpcData(eNpcTask.Result, bNpcTask.Result); - Time = watch.ElapsedMilliseconds; - }); + { + var watch = Stopwatch.StartNew(); + var eNpcTask = Task.Run(() => CreateEnpcData(data, eNpcs)); + var bNpcTask = Task.Run(() => CreateBnpcData(data, bNpcs, bNpcNames)); + FilterAndOrderNpcData(eNpcTask.Result, bNpcTask.Result); + Time = watch.ElapsedMilliseconds; + }) + .ContinueWith(_ => CheckFacepaintFiles(data, _facepaints)); } /// Create data from event NPCs. @@ -323,6 +327,17 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList return (true, customize); } + /// Check decal files for existence. + private static void CheckFacepaintFiles(IDataManager data, BitArray facepaints) + { + for (var i = 0; i < 128; ++i) + { + var path = GamePaths.Character.Tex.DecalPath("face", (PrimaryId)i); + if (data.FileExists(path)) + facepaints[i] = true; + } + } + /// public IEnumerator GetEnumerator() => _data.GetEnumerator(); diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index afc7d56..565b6ba 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -28,9 +28,35 @@ public class CustomizationServicePanel(CustomizeService customize) : IGameDataDr DrawNpcCustomizationInfo(set); } + DrawFacepaintInfo(); DrawColorInfo(); } + private void DrawFacepaintInfo() + { + using var tree = ImUtf8.TreeNode("NPC Facepaints"u8); + if (!tree) + return; + + using var table = ImUtf8.Table("data"u8, 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + if (!table) + return; + + ImGui.TableNextColumn(); + ImUtf8.TableHeader("Id"u8); + ImGui.TableNextColumn(); + ImUtf8.TableHeader("Facepaint"u8); + + for (var i = 0; i < 128; ++i) + { + var index = new CustomizeValue((byte)i); + ImUtf8.DrawTableColumn($"{i:D3}"); + using var font = ImRaii.PushFont(UiBuilder.IconFont); + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.FacePaint, index) + ? FontAwesomeIcon.Check.ToIconString() + : FontAwesomeIcon.Times.ToIconString()); + } + } private void DrawColorInfo() { using var tree = ImUtf8.TreeNode("NPC Colors"u8); @@ -57,16 +83,16 @@ public class CustomizationServicePanel(CustomizeService customize) : IGameDataDr var index = new CustomizeValue((byte)i); ImUtf8.DrawTableColumn($"{i:D3}"); using var font = ImRaii.PushFont(UiBuilder.IconFont); - ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.HairColor, index) + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.HairColor, index) ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); - ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.EyeColorLeft, index) + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.EyeColorLeft, index) ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); - ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.FacePaintColor, index) + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.FacePaintColor, index) ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); - ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.TattooColor, index) + ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.TattooColor, index) ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); } diff --git a/Penumbra.GameData b/Penumbra.GameData index bb59614..4880433 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit bb596141bb582505ae9fae1dd2914773503f4fe7 +Subproject commit 488043381efd42238e9c1328ccab3b80abd81ea7 From 1f255a98e6aef912680f2d247133bdecccfea028 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 30 Jan 2025 13:55:28 +0100 Subject: [PATCH 588/786] Fix issue with scaling after zone change. --- Glamourer/Glamourer.cs | 9 ----- Glamourer/Interop/ScalingService.cs | 54 +++++++++++++++++++++++------ Penumbra.GameData | 2 +- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 9c4583f..cf0b278 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -47,15 +47,6 @@ public class Glamourer : IDalamudPlugin _services.GetService(); // initialize commands. _services.GetService(); // initialize IPC. Log.Information($"Glamourer v{Version} loaded successfully."); - - //var text = File.ReadAllBytes(@"C:\FFXIVMods\PBDTest\files\human.pbd"); - //var pbd = new PbdFile(text); - //var roundtrip = pbd.Write(); - //File.WriteAllBytes(@"C:\FFXIVMods\PBDTest\files\Vanilla resaved save.pbd", roundtrip); - //var deformer = pbd.Deformers.FirstOrDefault(d => d.GenderRace is GenderRace.RoegadynFemale)!.RacialDeformer; - //deformer.DeformMatrices["ya_fukubu_phys"] = deformer.DeformMatrices["j_kosi"]; - //var aleks = pbd.Write(); - //File.WriteAllBytes(@"C:\FFXIVMods\PBDTest\files\rue.pbd", aleks); } catch { diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 141d5f2..68dc2e8 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -1,19 +1,27 @@ -using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Hooking; +using Dalamud.Hooking; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Penumbra.GameData; using Penumbra.GameData.Interop; using FFXIVClientStructs.FFXIV.Client.Game.Object; +using Glamourer.State; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; +using CustomizeIndex = Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex; namespace Glamourer.Interop; public unsafe class ScalingService : IDisposable { - public ScalingService(IGameInteropProvider interop) + private readonly ActorManager _actors; + private readonly StateManager _state; + + public ScalingService(IGameInteropProvider interop, StateManager state, ActorManager actors) { + _state = state; + _actors = actors; interop.InitializeFromAttributes(this); _setupMountHook = interop.HookFromAddress((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); @@ -79,7 +87,16 @@ public unsafe class ScalingService : IDisposable var mdl = owner.Model; var oldRace = owner.AsCharacter->DrawData.CustomizeData.Race; if (mdl.IsHuman) + { owner.AsCharacter->DrawData.CustomizeData.Race = mdl.AsHuman->Customize.Race; + } + else + { + var actor = _actors.FromObject(owner, out _, true, false, true); + if (_state.TryGetValue(actor, out var state)) + owner.AsCharacter->DrawData.CustomizeData.Race = (byte)state.ModelData.Customize.Race; + } + _placeMinionHook.Original(companion); owner.AsCharacter->DrawData.CustomizeData.Race = oldRace; } @@ -103,12 +120,20 @@ public unsafe class ScalingService : IDisposable character->DrawData.CustomizeData.Tribe, character->DrawData.CustomizeData[(int)CustomizeIndex.Height]); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static void SetScaleCustomize(Character* character, Model model) + private void SetScaleCustomize(Character* character, Model model) { - if (!model.IsHuman) + if (model.IsHuman) + { + SetScaleCustomize(character, model.AsHuman->Customize.Race, model.AsHuman->Customize.Tribe, model.AsHuman->Customize.Sex); + return; + } + + var actor = _actors.FromObject(character, out _, true, false, true); + if (!_state.TryGetValue(actor, out var state)) return; - SetScaleCustomize(character, model.AsHuman->Customize.Race, model.AsHuman->Customize.Tribe, model.AsHuman->Customize.Sex); + ref var customize = ref state.ModelData.Customize; + SetScaleCustomize(character, (byte)customize.Race, (byte)customize.Clan, customize.Gender.ToGameByte()); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -120,13 +145,22 @@ public unsafe class ScalingService : IDisposable } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static void SetHeightCustomize(Character* character, Model model) + private void SetHeightCustomize(Character* character, Model model) { - if (!model.IsHuman) + if (model.IsHuman) + { + SetHeightCustomize(character, model.AsHuman->Customize.Sex, model.AsHuman->Customize.BodyType, model.AsHuman->Customize.Tribe, + model.AsHuman->Customize[(int)CustomizeIndex.Height]); + return; + } + + var actor = _actors.FromObject(character, out _, true, false, true); + if (!_state.TryGetValue(actor, out var state)) return; - SetHeightCustomize(character, model.AsHuman->Customize.Sex, model.AsHuman->Customize.BodyType, model.AsHuman->Customize.Tribe, - model.AsHuman->Customize[(int)CustomizeIndex.Height]); + ref var customize = ref state.ModelData.Customize; + SetHeightCustomize(character, customize.Gender.ToGameByte(), customize.BodyType.Value, (byte)customize.Clan, + customize[global::Penumbra.GameData.Enums.CustomizeIndex.Height].Value); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] diff --git a/Penumbra.GameData b/Penumbra.GameData index 4880433..33fea10 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 488043381efd42238e9c1328ccab3b80abd81ea7 +Subproject commit 33fea10e18ec9f8a5b309890de557fcb25780086 From 8e0908dbf7fcfe4cd1ee87cb0bba7408cd63887a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 30 Jan 2025 22:39:57 +0100 Subject: [PATCH 589/786] Add more settings to multi design panel. --- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 139 +++++++++++++++++- 1 file changed, 131 insertions(+), 8 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index a1f17d4..3d10e64 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -25,6 +25,10 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e var offset = DrawMultiTagger(width); DrawMultiColor(width, offset); DrawMultiQuickDesignBar(offset); + DrawMultiLock(offset); + DrawMultiResetSettings(offset); + DrawMultiResetDyes(offset); + DrawMultiForceRedraw(offset); } private void DrawCounts(Vector2 treeNodePos) @@ -43,19 +47,46 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ImGui.SetCursorPos(startPos); } + private void ResetCounts() + { + _numQuickDesignEnabled = 0; + _numDesignsLocked = 0; + _numDesignsForcedRedraw = 0; + _numDesignsResetSettings = 0; + _numDesignsResetDyes = 0; + } + + private bool CountLeaves(DesignFileSystem.IPath path) + { + if (path is not DesignFileSystem.Leaf l) + return false; + + if (l.Value.QuickDesign) + ++_numQuickDesignEnabled; + if (l.Value.WriteProtected()) + ++_numDesignsLocked; + if (l.Value.ResetTemporarySettings) + ++_numDesignsResetSettings; + if (l.Value.ForcedRedraw) + ++_numDesignsForcedRedraw; + if (l.Value.ResetAdvancedDyes) + ++_numDesignsResetDyes; + return true; + } + private int DrawDesignList() { + ResetCounts(); using var tree = ImUtf8.TreeNode("Currently Selected Objects"u8, ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.NoTreePushOnOpen); ImGui.Separator(); if (!tree) - return selector.SelectedPaths.Count(l => l is DesignFileSystem.Leaf); + return selector.SelectedPaths.Count(CountLeaves); var sizeType = new Vector2(ImGui.GetFrameHeight()); var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType.X - 4 * ImGui.GetStyle().CellPadding.X) / 100; var sizeMods = availableSizePercent * 35; var sizeFolders = availableSizePercent * 65; - _numQuickDesignEnabled = 0; var numDesigns = 0; using (var table = ImUtf8.Table("mods"u8, 3, ImGuiTableFlags.RowBg)) { @@ -81,12 +112,8 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ImUtf8.DrawFrameColumn(text); ImUtf8.DrawFrameColumn(fullName); - if (path is not DesignFileSystem.Leaf l2) - continue; - - ++numDesigns; - if (l2.Value.QuickDesign) - ++_numQuickDesignEnabled; + if (CountLeaves(path)) + ++numDesigns; } } @@ -96,6 +123,10 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e private string _tag = string.Empty; private int _numQuickDesignEnabled; + private int _numDesignsLocked; + private int _numDesignsForcedRedraw; + private int _numDesignsResetSettings; + private int _numDesignsResetDyes; private int _numDesigns; private readonly List _addDesigns = []; private readonly List<(Design, int)> _removeDesigns = []; @@ -161,6 +192,98 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ImGui.Separator(); } + private void DrawMultiLock(float offset) + { + ImUtf8.TextFrameAligned("Multi Lock:"u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var diff = _numDesigns - _numDesignsLocked; + var tt = diff == 0 + ? $"All {_numDesigns} selected designs are already write protected." + : $"Write-protect all {_numDesigns} designs. Changes {diff} designs."; + if (ImUtf8.ButtonEx("Turn Write-Protected"u8, tt, buttonWidth, diff == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.SetWriteProtection(design.Value, true); + + ImGui.SameLine(); + tt = _numDesignsLocked == 0 + ? $"None of the {_numDesigns} selected designs are write-protected." + : $"Remove the write protection of the {_numDesigns} selected designs. Changes {_numDesignsLocked} designs."; + if (ImUtf8.ButtonEx("Remove Write-Protection"u8, tt, buttonWidth, _numDesignsLocked == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.SetWriteProtection(design.Value, false); + ImGui.Separator(); + } + + private void DrawMultiResetSettings(float offset) + { + ImUtf8.TextFrameAligned("Settings:"u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var diff = _numDesigns - _numDesignsResetSettings; + var tt = diff == 0 + ? $"All {_numDesigns} selected designs already reset temporary settings." + : $"Make all {_numDesigns} selected designs reset temporary settings. Changes {diff} designs."; + if (ImUtf8.ButtonEx("Set Reset Settings"u8, tt, buttonWidth, diff == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeResetTemporarySettings(design.Value, true); + + ImGui.SameLine(); + tt = _numDesignsResetSettings == 0 + ? $"None of the {_numDesigns} selected designs reset temporary settings." + : $"Stop all {_numDesigns} selected designs from resetting temporary settings. Changes {_numDesignsResetSettings} designs."; + if (ImUtf8.ButtonEx("Remove Reset Settings"u8, tt, buttonWidth, _numDesignsResetSettings == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeResetTemporarySettings(design.Value, false); + ImGui.Separator(); + } + + private void DrawMultiResetDyes(float offset) + { + ImUtf8.TextFrameAligned("Adv. Dyes:"u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var diff = _numDesigns - _numDesignsResetDyes; + var tt = diff == 0 + ? $"All {_numDesigns} selected designs already reset advanced dyes." + : $"Make all {_numDesigns} selected designs reset advanced dyes. Changes {diff} designs."; + if (ImUtf8.ButtonEx("Set Reset Dyes"u8, tt, buttonWidth, diff == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeResetAdvancedDyes(design.Value, true); + + ImGui.SameLine(); + tt = _numDesignsLocked == 0 + ? $"None of the {_numDesigns} selected designs reset advanced dyes." + : $"Stop all {_numDesigns} selected designs from resetting advanced dyes. Changes {_numDesignsResetDyes} designs."; + if (ImUtf8.ButtonEx("Remove Reset Dyes"u8, tt, buttonWidth, _numDesignsResetDyes == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeResetAdvancedDyes(design.Value, false); + ImGui.Separator(); + } + + private void DrawMultiForceRedraw(float offset) + { + ImUtf8.TextFrameAligned("Redrawing:"u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var buttonWidth = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var diff = _numDesigns - _numDesignsForcedRedraw; + var tt = diff == 0 + ? $"All {_numDesigns} selected designs already force redraws." + : $"Make all {_numDesigns} designs force redraws. Changes {diff} designs."; + if (ImUtf8.ButtonEx("Force Redraws"u8, tt, buttonWidth, diff == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeForcedRedraw(design.Value, true); + + ImGui.SameLine(); + tt = _numDesignsLocked == 0 + ? $"None of the {_numDesigns} selected designs force redraws." + : $"Stop all {_numDesigns} selected designs from forcing redraws. Changes {_numDesignsForcedRedraw} designs."; + if (ImUtf8.ButtonEx("Remove Forced Redraws"u8, tt, buttonWidth, _numDesignsForcedRedraw == 0)) + foreach (var design in selector.SelectedPaths.OfType()) + editor.ChangeForcedRedraw(design.Value, false); + ImGui.Separator(); + } + private void DrawMultiColor(Vector2 width, float offset) { ImUtf8.TextFrameAligned("Multi Colors:"); From 9a1cf3d9e6023de497d7fa2c29add8490ab539ee Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 30 Jan 2025 22:45:59 +0100 Subject: [PATCH 590/786] Better --- Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 3d10e64..9e894c4 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -224,7 +224,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e var tt = diff == 0 ? $"All {_numDesigns} selected designs already reset temporary settings." : $"Make all {_numDesigns} selected designs reset temporary settings. Changes {diff} designs."; - if (ImUtf8.ButtonEx("Set Reset Settings"u8, tt, buttonWidth, diff == 0)) + if (ImUtf8.ButtonEx("Set Reset Temp. Settings"u8, tt, buttonWidth, diff == 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.ChangeResetTemporarySettings(design.Value, true); @@ -232,7 +232,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e tt = _numDesignsResetSettings == 0 ? $"None of the {_numDesigns} selected designs reset temporary settings." : $"Stop all {_numDesigns} selected designs from resetting temporary settings. Changes {_numDesignsResetSettings} designs."; - if (ImUtf8.ButtonEx("Remove Reset Settings"u8, tt, buttonWidth, _numDesignsResetSettings == 0)) + if (ImUtf8.ButtonEx("Remove Reset Temp. Settings"u8, tt, buttonWidth, _numDesignsResetSettings == 0)) foreach (var design in selector.SelectedPaths.OfType()) editor.ChangeResetTemporarySettings(design.Value, false); ImGui.Separator(); From da46705b52a4537621cc728dcf56bb7525fa950b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 31 Jan 2025 16:06:50 +0100 Subject: [PATCH 591/786] Use new settings API. --- Glamourer/Interop/Penumbra/PenumbraService.cs | 76 ++++++++++++++----- Penumbra.Api | 2 +- 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 13be628..27446ea 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -37,6 +37,7 @@ public class PenumbraService : IDisposable public const int RequiredPenumbraFeatureVersion = 3; public const int RequiredPenumbraFeatureVersionTemp = 4; public const int RequiredPenumbraFeatureVersionTemp2 = 5; + public const int RequiredPenumbraFeatureVersionTemp3 = 6; private const int Key = -1610; @@ -56,7 +57,9 @@ public class PenumbraService : IDisposable private global::Penumbra.Api.IpcSubscribers.GetCollectionForObject? _objectCollection; private global::Penumbra.Api.IpcSubscribers.GetModList? _getMods; private global::Penumbra.Api.IpcSubscribers.GetCollection? _currentCollection; + private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp? _getCurrentSettingsWithTemp; private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings? _getCurrentSettings; + private global::Penumbra.Api.IpcSubscribers.GetAllModSettings? _getAllSettings; private global::Penumbra.Api.IpcSubscribers.TryInheritMod? _inheritMod; private global::Penumbra.Api.IpcSubscribers.TrySetMod? _setMod; private global::Penumbra.Api.IpcSubscribers.TrySetModPriority? _setModPriority; @@ -132,22 +135,28 @@ public class PenumbraService : IDisposable public ModSettings GetModSettings(in Mod mod, out string source) { - source = string.Empty; + source = string.Empty; if (!Available) return ModSettings.Empty; try { var collection = _currentCollection!.Invoke(ApiCollectionType.Current); - if (_queryTemporaryModSettings != null) - { - var tempEc = _queryTemporaryModSettings.Invoke(collection!.Value.Id, mod.DirectoryName, out var tempTuple, out source); - if (tempEc is PenumbraApiEc.Success && tempTuple != null) - return new ModSettings(tempTuple.Value.Settings, tempTuple.Value.Priority, tempTuple.Value.Enabled, - tempTuple.Value.ForceInherit, false); - } + return GetSettings(collection!.Value.Id, mod.DirectoryName, mod.Name, out source); + } + catch (Exception ex) + { + Glamourer.Log.Error($"Error fetching mod settings for {mod.DirectoryName} from Penumbra:\n{ex}"); + return ModSettings.Empty; + } + } - var (ec, tuple) = _getCurrentSettings!.Invoke(collection!.Value.Id, mod.DirectoryName); + private ModSettings GetSettings(Guid collection, string modDirectory, string modName, out string source) + { + if (_getCurrentSettingsWithTemp != null) + { + source = string.Empty; + var (ec, tuple) = _getCurrentSettingsWithTemp!.Invoke(collection, modDirectory, modName, false, false, Key); if (ec is not PenumbraApiEc.Success) return ModSettings.Empty; @@ -155,11 +164,23 @@ public class PenumbraService : IDisposable ? new ModSettings(tuple.Value.Item3, tuple.Value.Item2, tuple.Value.Item1, false, false) : ModSettings.Empty; } - catch (Exception ex) + + if (_queryTemporaryModSettings != null) { - Glamourer.Log.Error($"Error fetching mod settings for {mod.DirectoryName} from Penumbra:\n{ex}"); - return ModSettings.Empty; + var tempEc = _queryTemporaryModSettings.Invoke(collection, modDirectory, out var tempTuple, out source); + if (tempEc is PenumbraApiEc.Success && tempTuple != null) + return new ModSettings(tempTuple.Value.Settings, tempTuple.Value.Priority, tempTuple.Value.Enabled, + tempTuple.Value.ForceInherit, false); } + + source = string.Empty; + var (ec2, tuple2) = _getCurrentSettings!.Invoke(collection, modDirectory); + if (ec2 is not PenumbraApiEc.Success) + return ModSettings.Empty; + + return tuple2.HasValue + ? new ModSettings(tuple2.Value.Item3, tuple2.Value.Item2, tuple2.Value.Item1, false, false) + : ModSettings.Empty; } public (Guid Id, string Name)? CollectionByIdentifier(string identifier) @@ -183,13 +204,23 @@ public class PenumbraService : IDisposable { var allMods = _getMods!.Invoke(); var collection = _currentCollection!.Invoke(ApiCollectionType.Current); + if (_getAllSettings != null) + { + var allSettings = _getAllSettings.Invoke(collection!.Value.Id, false, false, Key); + if (allSettings.Item1 is PenumbraApiEc.Success) + return allMods.Select(m => (new Mod(m.Value, m.Key), + allSettings.Item2!.TryGetValue(m.Key, out var s) + ? new ModSettings(s.Item3, s.Item2, s.Item1, s.Item4 && s.Item5, false) + : ModSettings.Empty)) + .OrderByDescending(p => p.Item2.Enabled) + .ThenBy(p => p.Item1.Name) + .ThenBy(p => p.Item1.DirectoryName) + .ThenByDescending(p => p.Item2.Priority) + .ToList(); + } + return allMods - .Select(m => (m.Key, m.Value, _getCurrentSettings!.Invoke(collection!.Value.Id, m.Key))) - .Where(t => t.Item3.Item1 is PenumbraApiEc.Success) - .Select(t => (new Mod(t.Item2, t.Item1), - !t.Item3.Item2.HasValue - ? ModSettings.Empty - : new ModSettings(t.Item3.Item2!.Value.Item3, t.Item3.Item2!.Value.Item2, t.Item3.Item2!.Value.Item1, false, false))) + .Select(m => (new Mod(m.Value, m.Key), GetSettings(collection!.Value.Id, m.Key, m.Value, out _))) .OrderByDescending(p => p.Item2.Enabled) .ThenBy(p => p.Item1.Name) .ThenBy(p => p.Item1.DirectoryName) @@ -455,7 +486,14 @@ public class PenumbraService : IDisposable _removeAllTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface); if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) + { _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); + if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) + { + _getCurrentSettingsWithTemp = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp(_pluginInterface); + _getAllSettings = new global::Penumbra.Api.IpcSubscribers.GetAllModSettings(_pluginInterface); + } + } } Available = true; @@ -488,6 +526,8 @@ public class PenumbraService : IDisposable _getMods = null; _currentCollection = null; _getCurrentSettings = null; + _getCurrentSettingsWithTemp = null; + _getAllSettings = null; _inheritMod = null; _setMod = null; _setModPriority = null; diff --git a/Penumbra.Api b/Penumbra.Api index b4e716f..35b25be 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit b4e716f86d94cd4d98d8f58e580ed5f619ea87ae +Subproject commit 35b25bef92e9b0be96c44c150a3df89d848d2658 From 87016419c5cc5c33bb2ef03d3b553d0397984b96 Mon Sep 17 00:00:00 2001 From: Diorik Date: Tue, 4 Feb 2025 00:46:12 -0600 Subject: [PATCH 592/786] Add "Reset Design" command A new command to reapply automation while resetting the random design regardless of settings. --- Glamourer/Api/StateApi.cs | 2 +- Glamourer/Automation/AutoDesignApplier.cs | 4 ++-- Glamourer/Gui/DesignQuickBar.cs | 4 ++-- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 4 ++-- Glamourer/Services/CommandService.cs | 11 +++++++---- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index eaf9d01..b2fdc9b 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -301,7 +301,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void RevertToAutomation(Actor actor, ActorState state, uint key, ApplyFlag flags) { var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; - _autoDesigns.ReapplyAutomation(actor, state.Identifier, state, true, out var forcedRedraw); + _autoDesigns.ReapplyAutomation(actor, state.Identifier, state, true, false, out var forcedRedraw); _stateManager.ReapplyAutomationState(actor, state, forcedRedraw, true, source); ApiHelpers.Lock(state, key, flags); } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 1655c15..7f75674 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -225,7 +225,7 @@ public sealed class AutoDesignApplier : IDisposable _state.ReapplyState(actor, forcedRedraw, StateSource.Fixed); } - public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset, out bool forcedRedraw) + public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset, bool forcedNew, out bool forcedRedraw) { forcedRedraw = false; if (!_config.EnableAutoDesigns) @@ -235,7 +235,7 @@ public sealed class AutoDesignApplier : IDisposable _state.ResetState(state, StateSource.Game); if (GetPlayerSet(identifier, out var set)) - Reduce(actor, state, set, false, false, false, out forcedRedraw); + Reduce(actor, state, set, false, false, forcedNew, out forcedRedraw); } public bool Reduce(Actor actor, ActorIdentifier identifier, [NotNullWhen(true)] out ActorState? state) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 50f86fd..b58643c 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -251,7 +251,7 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { - _autoDesignApplier.ReapplyAutomation(actor, id, state!, true, out var forcedRedraw); + _autoDesignApplier.ReapplyAutomation(actor, id, state!, true, false, out var forcedRedraw); _stateManager.ReapplyAutomationState(actor, forcedRedraw, true, StateSource.Manual); } } @@ -291,7 +291,7 @@ public sealed class DesignQuickBar : Window, IDisposable foreach (var actor in data.Objects) { - _autoDesignApplier.ReapplyAutomation(actor, id, state!, false, out var forcedRedraw); + _autoDesignApplier.ReapplyAutomation(actor, id, state!, false, false, out var forcedRedraw); _stateManager.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Manual); } } diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index d8f3cd1..9c8f3cf 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -393,7 +393,7 @@ public class ActorPanel "Reapply the current automation state for the character on top of its current state..", !_config.EnableAutoDesigns || _state!.IsLocked)) { - _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false, out var forcedRedraw); + _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false, false, out var forcedRedraw); _stateManager.ReapplyAutomationState(_actor, forcedRedraw, false, StateSource.Manual); } @@ -402,7 +402,7 @@ public class ActorPanel "Try to revert the character to the state it would have using automated designs.", !_config.EnableAutoDesigns || _state!.IsLocked)) { - _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true, out var forcedRedraw); + _autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true, false, out var forcedRedraw); _stateManager.ReapplyAutomationState(_actor, forcedRedraw, true, StateSource.Manual); } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 98dfa19..bffc072 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -121,8 +121,9 @@ public class CommandService : IDisposable, IApiService "apply" => Apply(argument), "reapply" => ReapplyState(argument), "revert" => Revert(argument), - "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false), - "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true), + "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false, false), + "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true, false), + "resetdesign" => ReapplyAutomation(argument, "resetdesign", false, true), "automation" => SetAutomation(argument), "copy" => CopyState(argument), "save" => SaveState(argument), @@ -151,6 +152,8 @@ public class CommandService : IDisposable, IApiService "Reapplies the current automation state on top of the characters current state.. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("reverttoautomation", "Reverts a given character to its supposed state using automated designs. Use without arguments for help.").BuiltString); + _chat.Print(new SeStringBuilder().AddCommand("resetdesign", + "Reapplies the current automation and resets the random design. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder() .AddCommand("copy", "Copy the current state of a character to clipboard. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder() @@ -306,7 +309,7 @@ public class CommandService : IDisposable, IApiService return true; } - private bool ReapplyAutomation(string argument, string command, bool revert) + private bool ReapplyAutomation(string argument, string command, bool revert, bool forcedNew) { if (argument.Length == 0) { @@ -328,7 +331,7 @@ public class CommandService : IDisposable, IApiService { if (_stateManager.GetOrCreate(identifier, actor, out var state)) { - _autoDesignApplier.ReapplyAutomation(actor, identifier, state, revert, out var forcedRedraw); + _autoDesignApplier.ReapplyAutomation(actor, identifier, state, revert, forcedNew, out var forcedRedraw); _stateManager.ReapplyAutomationState(actor, forcedRedraw, revert, StateSource.Manual); } } From cf308fc118fbcc3fd83c7ad8bb10d71bb712068d Mon Sep 17 00:00:00 2001 From: Diorik Date: Tue, 4 Feb 2025 01:54:34 -0600 Subject: [PATCH 593/786] Prevent repeating random design Cache the last selected random design and prevent it from being chosen again. --- Glamourer/Designs/Special/RandomDesignGenerator.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index 7ed4452..1bee8ad 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -5,15 +5,20 @@ namespace Glamourer.Designs.Special; public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem) : IService { private readonly Random _rng = new(); + private Design? _lastDesign = null; public Design? Design(IReadOnlyList localDesigns) { if (localDesigns.Count == 0) return null; + if (_lastDesign != null && localDesigns.Count > 1) + localDesigns = localDesigns.Where(d => d != _lastDesign).ToList(); + var idx = _rng.Next(0, localDesigns.Count); Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); - return localDesigns[idx]; + _lastDesign = localDesigns[idx]; + return _lastDesign; } public Design? Design() From 5d1c65cce7ef82acdace9743a288ce3d6d44594d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 6 Feb 2025 16:27:10 +0100 Subject: [PATCH 594/786] Fix overwriting with current state keeping old advanced dyes. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index dbe106b..dd198ae 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -636,6 +636,7 @@ public class DesignPanel var design = panel._converter.Convert(state, ApplicationRules.FromModifiers(state)) ?? throw new Exception("The clipboard did not contain valid data."); + panel._selector.Selected!.GetMaterialDataRef().Clear(); panel._manager.ApplyDesign(panel._selector.Selected!, design); } catch (Exception ex) From d849506ecd1d9e6c76dde4c145f3eca03ee3c10d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 6 Feb 2025 16:52:59 +0100 Subject: [PATCH 595/786] 1.3.6.0 --- Glamourer/Gui/GlamourerChangelog.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 0c9d99b..4c8b365 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -39,6 +39,7 @@ public class GlamourerChangelog Add1_3_3_0(Changelog); Add1_3_4_0(Changelog); Add1_3_5_0(Changelog); + Add1_3_6_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -59,6 +60,22 @@ public class GlamourerChangelog } } + private static void Add1_3_6_0(Changelog log) + => log.NextVersion("Version 1.3.6.0") + .RegisterHighlight("Added some new multi design selection functionality to change design settings of many designs at once.") + .RegisterEntry("Also added the number of selected designs and folders to the multi design selection display.", 1) + .RegisterEntry("Glamourer will now use temporary settings when saving mod associations, if they exist in Penumbra.") + .RegisterEntry("Actually added the checkbox to reset all temporary settings to Automation Sets (functionality was there, just not exposed to the UI...).") + .RegisterEntry("Adapted the behavior for identified copies of characters that have a different state than the character itself to deal with the associated Penumbra changes.") + .RegisterEntry("Added '/glamour resetdesign' as a command, that re-applies automation but resets randomly chosen designs (Thanks Diorik).") + .RegisterEntry("All existing facepaints should now be accepted in designs, including NPC facepaints.") + .RegisterEntry("Overwriting a design with your characters current state will now discard any prior advanced dyes and only add those from the current state.") + .RegisterEntry("Fixed an issue with racial mount and accessory scaling when changing zones on a changed race.") + .RegisterEntry("Fixed issues with the detection of gear set changes in certain circumstances (Thanks Cordelia).") + .RegisterEntry("Fixed an issue with the Force to Inherit checkbox in mod associations.") + .RegisterEntry("Added a new IPC event that fires only when Glamourer finalizes its current changes to a character (for/from Cordelia).") + .RegisterEntry("Added new IPC to set a meta flag on actors. (for/from Cordelia)."); + private static void Add1_3_5_0(Changelog log) => log.NextVersion("Version 1.3.5.0") .RegisterHighlight("Added the usage of the new Temporary Mod Setting functionality from Penumbra to apply mod associations. This is on by default but can be turned back to permanent changes in the settings.") From 2c7b7d59be3dab950eeca4d1684828bfea3dcd2a Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 6 Feb 2025 15:55:34 +0000 Subject: [PATCH 596/786] [CI] Updating repo.json for 1.3.6.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 6ee99a9..d506898 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.5.1", - "TestingAssemblyVersion": "1.3.5.5", + "AssemblyVersion": "1.3.6.0", + "TestingAssemblyVersion": "1.3.6.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.5.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.5.5/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 1df2a46884340c1710ff2e08d38d46e8d4047c6f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 6 Feb 2025 17:00:25 +0100 Subject: [PATCH 597/786] Update Submodule Versions. --- Glamourer.Api | 2 +- Penumbra.Api | 2 +- Penumbra.String | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 8de6fa7..9f9bdf0 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 8de6fa7246a403de50b3be4e17bb5f188717b279 +Subproject commit 9f9bdf0873899d2e45fabaca446bb1624303b418 diff --git a/Penumbra.Api b/Penumbra.Api index 35b25be..c678090 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 35b25bef92e9b0be96c44c150a3df89d848d2658 +Subproject commit c67809057fac73a0fd407e3ad567f0aa6bc0bc37 diff --git a/Penumbra.String b/Penumbra.String index 0bc2b0f..4eb7c11 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 0bc2b0f66eee1a02c9575b2bb30f27ce166f8632 +Subproject commit 4eb7c118cdac5873afb97cb04719602f061f03b7 From 67fd65d366133ba0569ce9a3a70760e8f43fec5a Mon Sep 17 00:00:00 2001 From: Diorik Date: Thu, 6 Feb 2025 12:49:23 -0600 Subject: [PATCH 598/786] Make PreventRandomc figurable, clean up logic Will no longer hold design reference or make redundant copy of list --- Glamourer/Configuration.cs | 1 + .../Designs/Special/RandomDesignGenerator.cs | 17 +++++++++-------- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 3 +++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 4b59191..5f838a8 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -67,6 +67,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool UseTemporarySettings { get; set; } = true; public bool AllowDoubleClickToApply { get; set; } = false; public bool RespectManualOnAutomationUpdate { get; set; } = false; + public bool PreventRandomRepeats { get; set; } = false; public DefaultDesignSettings DefaultDesignSettings { get; set; } = new(); diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index 1bee8ad..3ff353b 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -1,24 +1,25 @@ -using OtterGui.Services; +using OtterGui; +using OtterGui.Services; namespace Glamourer.Designs.Special; -public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem) : IService +public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService { private readonly Random _rng = new(); - private Design? _lastDesign = null; + private Guid? _lastDesignID = null; - public Design? Design(IReadOnlyList localDesigns) + public Design? Design(IList localDesigns) { if (localDesigns.Count == 0) return null; - if (_lastDesign != null && localDesigns.Count > 1) - localDesigns = localDesigns.Where(d => d != _lastDesign).ToList(); + if (config.PreventRandomRepeats && _lastDesignID != null && localDesigns.Count > 1 && localDesigns.FindFirst(d => d.Identifier == _lastDesignID, out var found)) + localDesigns.Remove(found); var idx = _rng.Next(0, localDesigns.Count); Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); - _lastDesign = localDesigns[idx]; - return _lastDesign; + _lastDesignID = localDesigns[idx].Identifier; + return localDesigns[idx]; } public Design? Design() diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index ab40a48..2b9548f 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -109,6 +109,9 @@ public class SettingsTab( Checkbox("Use Temporary Mod Settings", "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down.", config.UseTemporarySettings, v => config.UseTemporarySettings = v); + Checkbox("Prevent Random Design Repeats", + "When using random designs, prevent the same design from being chosen twice in a row.", + config.PreventRandomRepeats, v => config.PreventRandomRepeats = v); ImGui.NewLine(); } From 5ca151b675e3f9484dac8ff06236a48f045895db Mon Sep 17 00:00:00 2001 From: Diorik Date: Thu, 6 Feb 2025 13:29:47 -0600 Subject: [PATCH 599/786] PreventRandom use WeakReference, reroll rand instead of changing list --- .../Designs/Special/RandomDesignGenerator.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index 3ff353b..b34a8ba 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -6,19 +6,20 @@ namespace Glamourer.Designs.Special; public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService { private readonly Random _rng = new(); - private Guid? _lastDesignID = null; + private WeakReference _lastDesign = new(null, false); - public Design? Design(IList localDesigns) + public Design? Design(IReadOnlyList localDesigns) { if (localDesigns.Count == 0) return null; - if (config.PreventRandomRepeats && _lastDesignID != null && localDesigns.Count > 1 && localDesigns.FindFirst(d => d.Identifier == _lastDesignID, out var found)) - localDesigns.Remove(found); - - var idx = _rng.Next(0, localDesigns.Count); + int idx; + do + idx = _rng.Next(0, localDesigns.Count); + while (config.PreventRandomRepeats && localDesigns.Count > 1 && _lastDesign.TryGetTarget(out var lastDesign) && lastDesign == localDesigns[idx]); + Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); - _lastDesignID = localDesigns[idx].Identifier; + _lastDesign.SetTarget(localDesigns[idx]); return localDesigns[idx]; } From 4748d1e211731abf01ce3ca5b369065d854a2b67 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 13 Feb 2025 16:30:24 +0100 Subject: [PATCH 600/786] Allow filtered item names. --- Glamourer/Designs/DesignBase.cs | 3 ++ Glamourer/Designs/DesignData.cs | 66 ++++++++++++++++++++++++++------- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 30d4ddd..b21c433 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -195,6 +195,9 @@ public class DesignBase return true; } + public IEnumerable FilteredItemNames + => _designData.FilteredItemNames(Application.Equip, Application.BonusItem); + internal FlagRestrictionResetter TemporarilyRestrictApplication(ApplicationCollection restrictions) => new(this, restrictions); diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 4205996..bba0ccb 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -46,20 +46,9 @@ public unsafe struct DesignData public DesignData() { } + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] public readonly bool ContainsName(LowerString name) - => name.IsContained(_nameHead) - || name.IsContained(_nameBody) - || name.IsContained(_nameHands) - || name.IsContained(_nameLegs) - || name.IsContained(_nameFeet) - || name.IsContained(_nameEars) - || name.IsContained(_nameNeck) - || name.IsContained(_nameWrists) - || name.IsContained(_nameRFinger) - || name.IsContained(_nameLFinger) - || name.IsContained(_nameMainhand) - || name.IsContained(_nameOffhand) - || name.IsContained(_nameGlasses); + => ItemNames.Any(name.IsContained); public readonly StainIds Stain(EquipSlot slot) { @@ -76,6 +65,57 @@ public unsafe struct DesignData public readonly bool Crest(CrestFlag slot) => CrestVisibility.HasFlag(slot); + public readonly IEnumerable ItemNames + { + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + get + { + yield return _nameHead; + yield return _nameBody; + yield return _nameHands; + yield return _nameLegs; + yield return _nameFeet; + yield return _nameEars; + yield return _nameNeck; + yield return _nameWrists; + yield return _nameRFinger; + yield return _nameLFinger; + yield return _nameMainhand; + yield return _nameOffhand; + yield return _nameGlasses; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + public readonly IEnumerable FilteredItemNames(EquipFlag item, BonusItemFlag bonusItem) + { + if (item.HasFlag(EquipFlag.Head)) + yield return _nameHead; + if (item.HasFlag(EquipFlag.Body)) + yield return _nameBody; + if (item.HasFlag(EquipFlag.Hands)) + yield return _nameHands; + if (item.HasFlag(EquipFlag.Legs)) + yield return _nameLegs; + if (item.HasFlag(EquipFlag.Feet)) + yield return _nameFeet; + if (item.HasFlag(EquipFlag.Ears)) + yield return _nameEars; + if (item.HasFlag(EquipFlag.Neck)) + yield return _nameNeck; + if (item.HasFlag(EquipFlag.Wrist)) + yield return _nameWrists; + if (item.HasFlag(EquipFlag.RFinger)) + yield return _nameRFinger; + if (item.HasFlag(EquipFlag.LFinger)) + yield return _nameLFinger; + if (item.HasFlag(EquipFlag.Mainhand)) + yield return _nameMainhand; + if (item.HasFlag(EquipFlag.Offhand)) + yield return _nameOffhand; + if (bonusItem.HasFlag(BonusItemFlag.Glasses)) + yield return _nameGlasses; + } public readonly FullEquipType MainhandType => _typeMainhand; From ab2a3f5bd9790e29d653bb20d4ae4b9a4208cb08 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 13 Feb 2025 16:30:35 +0100 Subject: [PATCH 601/786] Add new colors. --- Glamourer/Gui/Colors.cs | 58 +++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index b7f9737..e19639c 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -29,6 +29,9 @@ public enum ColorId TriStateNeutral, BattleNpc, EventNpc, + ModdedItemMarker, + ContainsItemsEnabled, + ContainsItemsDisabled, } public static class Colors @@ -39,32 +42,35 @@ public static class Colors => color switch { // @formatter:off - ColorId.NormalDesign => (0xFFFFFFFF, "Normal Design", "A design with no specific traits." ), - ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ), - ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that does not change equipment or customizations on a character." ), - ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ), - ColorId.ActorAvailable => (0xFF18C018, "Actor Available", "The header in the Actor tab panel if the currently selected actor exists in the game world at least once." ), - ColorId.ActorUnavailable => (0xFF1818C0, "Actor Unavailable", "The Header in the Actor tab panel if the currently selected actor does not exist in the game world." ), - ColorId.FolderExpanded => (0xFFFFF0C0, "Expanded Design Folder", "A design folder that is currently expanded." ), - ColorId.FolderCollapsed => (0xFFFFF0C0, "Collapsed Design Folder", "A design folder that is currently collapsed." ), - ColorId.FolderLine => (0xFFFFF0C0, "Expanded Design Folder Line", "The line signifying which descendants belong to an expanded design folder." ), - ColorId.EnabledAutoSet => (0xFFA0F0A0, "Enabled Automation Set", "An automation set that is currently enabled. Only one set can be enabled for each identifier at once." ), - ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ), - ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ), - ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ), - ColorId.HeaderButtons => (0xFFFFF0C0, "Header Buttons", "The text and border color of buttons in the header, like the Incognito toggle." ), - ColorId.FavoriteStarOn => (0xFF40D0D0, "Favored Item", "The color of the star for favored items and of the border in the unlock overview tab." ), - ColorId.FavoriteStarHovered => (0xFFD040D0, "Favorite Star Hovered", "The color of the star for favored items when it is hovered." ), - ColorId.FavoriteStarOff => (0x20808080, "Favorite Star Outline", "The color of the star for items that are not favored when it is not hovered." ), - ColorId.QuickDesignButton => (0x900A0A0A, "Quick Design Bar Button Background", "The color of button frames in the quick design bar." ), - ColorId.QuickDesignFrame => (0x90383838, "Quick Design Bar Combo Background", "The color of the combo background in the quick design bar." ), - ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ), - ColorId.TriStateCheck => (0xFF00D000, "Checkmark in Tri-State Checkboxes", "The color of the checkmark indicating positive change in tri-state checkboxes." ), - ColorId.TriStateCross => (0xFF0000D0, "Cross in Tri-State Checkboxes", "The color of the cross indicating negative change in tri-state checkboxes." ), - ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes." ), - ColorId.BattleNpc => (0xFFFFFFFF, "Battle NPC in NPC Tab", "The color of the names of battle NPCs in the NPC tab that do not have a more specific color assigned." ), - ColorId.EventNpc => (0xFFFFFFFF, "Event NPC in NPC Tab", "The color of the names of event NPCs in the NPC tab that do not have a more specific color assigned." ), - _ => (0x00000000, string.Empty, string.Empty ), + ColorId.NormalDesign => (0xFFFFFFFF, "Normal Design", "A design with no specific traits." ), + ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ), + ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that does not change equipment or customizations on a character." ), + ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ), + ColorId.ActorAvailable => (0xFF18C018, "Actor Available", "The header in the Actor tab panel if the currently selected actor exists in the game world at least once." ), + ColorId.ActorUnavailable => (0xFF1818C0, "Actor Unavailable", "The Header in the Actor tab panel if the currently selected actor does not exist in the game world." ), + ColorId.FolderExpanded => (0xFFFFF0C0, "Expanded Design Folder", "A design folder that is currently expanded." ), + ColorId.FolderCollapsed => (0xFFFFF0C0, "Collapsed Design Folder", "A design folder that is currently collapsed." ), + ColorId.FolderLine => (0xFFFFF0C0, "Expanded Design Folder Line", "The line signifying which descendants belong to an expanded design folder." ), + ColorId.EnabledAutoSet => (0xFFA0F0A0, "Enabled Automation Set", "An automation set that is currently enabled. Only one set can be enabled for each identifier at once." ), + ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ), + ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ), + ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ), + ColorId.HeaderButtons => (0xFFFFF0C0, "Header Buttons", "The text and border color of buttons in the header, like the Incognito toggle." ), + ColorId.FavoriteStarOn => (0xFF40D0D0, "Favored Item", "The color of the star for favored items and of the border in the unlock overview tab." ), + ColorId.FavoriteStarHovered => (0xFFD040D0, "Favorite Star Hovered", "The color of the star for favored items when it is hovered." ), + ColorId.FavoriteStarOff => (0x20808080, "Favorite Star Outline", "The color of the star for items that are not favored when it is not hovered." ), + ColorId.QuickDesignButton => (0x900A0A0A, "Quick Design Bar Button Background", "The color of button frames in the quick design bar." ), + ColorId.QuickDesignFrame => (0x90383838, "Quick Design Bar Combo Background", "The color of the combo background in the quick design bar." ), + ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ), + ColorId.TriStateCheck => (0xFF00D000, "Checkmark in Tri-State Checkboxes", "The color of the checkmark indicating positive change in tri-state checkboxes." ), + ColorId.TriStateCross => (0xFF0000D0, "Cross in Tri-State Checkboxes", "The color of the cross indicating negative change in tri-state checkboxes." ), + ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes." ), + ColorId.BattleNpc => (0xFFFFFFFF, "Battle NPC in NPC Tab", "The color of the names of battle NPCs in the NPC tab that do not have a more specific color assigned." ), + ColorId.EventNpc => (0xFFFFFFFF, "Event NPC in NPC Tab", "The color of the names of event NPCs in the NPC tab that do not have a more specific color assigned." ), + ColorId.ModdedItemMarker => (0xFFFF20FF, "Modded Item Marker", "The color of dot in the unlocks overview tab signaling that the item is modded in the currently selected Penumbra collection." ), + ColorId.ContainsItemsEnabled => (0xFFA0F0A0, "Enabled Mod Contains Design Items", "The color of enabled mods in the associated mod dropdown menu when they contain items used in this design." ), + ColorId.ContainsItemsDisabled => (0x80A0F0A0, "Disabled Mod Contains Design Items", "The color of disabled mods in the associated mod dropdown menu when they contain items used in this design." ), + _ => (0x00000000, string.Empty, string.Empty ), // @formatter:on }; From d56c2db547e64496e794e68ee1771c49096b0f58 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 13 Feb 2025 16:32:23 +0100 Subject: [PATCH 602/786] Add more mod association and modded utility. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 1 - .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/ModCombo.cs | 54 ++++---- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 16 ++- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 55 +++++++- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 105 ++++++++++++-- Glamourer/Interop/Penumbra/PenumbraService.cs | 130 +++++++++++------- OtterGui | 2 +- Penumbra.Api | 2 +- 9 files changed, 277 insertions(+), 90 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index dd198ae..748afea 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -17,7 +17,6 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Text; -using OtterGuiInternal.Structs; using Penumbra.GameData.Enums; using static Glamourer.Gui.Tabs.HeaderDrawer; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 021a396..feff657 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -15,7 +15,7 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelector selector, DesignManager manager, Configuration config) { - private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log); + private readonly ModCombo _modCombo = new(penumbra, Glamourer.Log, selector); private (Mod, ModSettings)[]? _copy; public void Draw() diff --git a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs index 53501b0..6ec9a1c 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs @@ -4,19 +4,18 @@ using ImGuiNET; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Raii; +using OtterGui.Text; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.DesignTab; -public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)> +public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings, int Count)> { - public ModCombo(PenumbraService penumbra, Logger log) - : base(penumbra.GetMods, MouseWheelType.None, log) - { - SearchByParts = false; - } + public ModCombo(PenumbraService penumbra, Logger log, DesignFileSystemSelector selector) + : base(() => penumbra.GetMods(selector.Selected?.FilteredItemNames.ToArray() ?? []), MouseWheelType.None, log) + => SearchByParts = false; - protected override string ToString((Mod Mod, ModSettings Settings) obj) + protected override string ToString((Mod Mod, ModSettings Settings, int Count) obj) => obj.Mod.Name; protected override bool IsVisible(int globalIndex, LowerString filter) @@ -24,36 +23,45 @@ public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)> protected override bool DrawSelectable(int globalIdx, bool selected) { - using var id = ImRaii.PushId(globalIdx); - var (mod, settings) = Items[globalIdx]; + using var id = ImUtf8.PushId(globalIdx); + var (mod, settings, count) = Items[globalIdx]; bool ret; - using (var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !settings.Enabled)) + var color = settings.Enabled + ? count > 0 + ? ColorId.ContainsItemsEnabled.Value() + : ImGui.GetColorU32(ImGuiCol.Text) + : count > 0 + ? ColorId.ContainsItemsDisabled.Value() + : ImGui.GetColorU32(ImGuiCol.TextDisabled); + using (ImRaii.PushColor(ImGuiCol.Text, color)) { - ret = ImGui.Selectable(mod.Name, selected); + ret = ImUtf8.Selectable(mod.Name, selected); } if (ImGui.IsItemHovered()) { using var style = ImRaii.PushStyle(ImGuiStyleVar.PopupBorderSize, 2 * ImGuiHelpers.GlobalScale); - using var tt = ImRaii.Tooltip(); + using var tt = ImUtf8.Tooltip(); var namesDifferent = mod.Name != mod.DirectoryName; ImGui.Dummy(new Vector2(300 * ImGuiHelpers.GlobalScale, 0)); - using (var group = ImRaii.Group()) + using (ImUtf8.Group()) { if (namesDifferent) - ImGui.TextUnformatted("Directory Name"); - ImGui.TextUnformatted("Enabled"); - ImGui.TextUnformatted("Priority"); + ImUtf8.Text("Directory Name"u8); + ImUtf8.Text("Enabled"u8); + ImUtf8.Text("Priority"u8); + ImUtf8.Text("Affected Design Items"u8); DrawSettingsLeft(settings); } ImGui.SameLine(Math.Max(ImGui.GetItemRectSize().X + 3 * ImGui.GetStyle().ItemSpacing.X, 150 * ImGuiHelpers.GlobalScale)); - using (var group = ImRaii.Group()) + using (ImUtf8.Group()) { if (namesDifferent) - ImGui.TextUnformatted(mod.DirectoryName); - ImGui.TextUnformatted(settings.Enabled.ToString()); - ImGui.TextUnformatted(settings.Priority.ToString()); + ImUtf8.Text(mod.DirectoryName); + ImUtf8.Text($"{settings.Enabled}"); + ImUtf8.Text($"{settings.Priority}"); + ImUtf8.Text($"{count}"); DrawSettingsRight(settings); } } @@ -65,7 +73,7 @@ public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)> { foreach (var setting in settings.Settings) { - ImGui.TextUnformatted(setting.Key); + ImUtf8.Text(setting.Key); for (var i = 1; i < setting.Value.Count; ++i) ImGui.NewLine(); } @@ -76,10 +84,10 @@ public sealed class ModCombo : FilterComboCache<(Mod Mod, ModSettings Settings)> foreach (var setting in settings.Settings) { if (setting.Value.Count == 0) - ImGui.TextUnformatted(""); + ImUtf8.Text(""u8); else foreach (var option in setting.Value) - ImGui.TextUnformatted(option); + ImUtf8.Text(option); } } } diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 9e894c4..a7afa21 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -5,11 +5,15 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Text; +using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.DesignTab; -public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager editor, DesignColors colors) +public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager editor, DesignColors colors, Configuration config) { + private readonly Button[] _leftButtons = []; + private readonly Button[] _rightButtons = [new IncognitoButton(config.Ephemeral)]; + private readonly DesignColorCombo _colorCombo = new(colors, true); public void Draw() @@ -17,8 +21,12 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e if (selector.SelectedPaths.Count == 0) return; - var width = ImGuiHelpers.ScaledVector2(145, 0); - ImGui.NewLine(); + HeaderDrawer.Draw(string.Empty, 0, ImGui.GetColorU32(ImGuiCol.FrameBg), _leftButtons, _rightButtons); + using var child = ImUtf8.Child("##MultiPanel"u8, default, true); + if (!child) + return; + + var width = ImGuiHelpers.ScaledVector2(145, 0); var treeNodePos = ImGui.GetCursorPos(); _numDesigns = DrawDesignList(); DrawCounts(treeNodePos); @@ -135,7 +143,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e { ImUtf8.TextFrameAligned("Multi Tagger:"u8); ImGui.SameLine(); - var offset = ImGui.GetItemRectSize().X; + var offset = ImGui.GetItemRectSize().X + ImGui.GetStyle().WindowPadding.X; ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X)); ImUtf8.InputText("##tag"u8, ref _tag, "Tag Name..."u8); diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index afc4f7b..903257b 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -1,8 +1,8 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface.Utility; -using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop; +using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; @@ -23,7 +23,8 @@ public class UnlockOverview( TextureService textures, CodeService codes, JobService jobs, - FavoriteManager favorites) + FavoriteManager favorites, + PenumbraService penumbra) { private static readonly Vector4 UnavailableTint = new(0.3f, 0.3f, 0.3f, 1.0f); @@ -32,6 +33,9 @@ public class UnlockOverview( private Gender _selected3 = Gender.Unknown; private BonusItemFlag _selected4 = BonusItemFlag.Unknown; + private uint _favoriteColor; + private uint _moddedColor; + private void DrawSelector() { using var child = ImRaii.Child("Selector", new Vector2(200 * ImGuiHelpers.GlobalScale, -1), true); @@ -90,6 +94,9 @@ public class UnlockOverview( if (!child) return; + _moddedColor = ColorId.ModdedItemMarker.Value(); + _favoriteColor = ColorId.FavoriteStarOn.Value(); + if (_selected1 is not FullEquipType.Unknown) DrawItems(); else if (_selected2 is not SubRace.Unknown && _selected3 is not Gender.Unknown) @@ -120,7 +127,7 @@ public class UnlockOverview( unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (favorites.Contains(_selected3, _selected2, customize.Index, customize.Value)) - ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), + ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), _favoriteColor, 12 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 6 * ImGuiHelpers.GlobalScale); if (hasIcon && ImGui.IsItemHovered()) @@ -192,9 +199,11 @@ public class UnlockOverview( ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (favorites.Contains(item)) - ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), + ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), _favoriteColor, 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); + var mods = DrawModdedMarker(item, iconSize); + // TODO handle clicking if (ImGui.IsItemHovered()) { @@ -206,9 +215,10 @@ public class UnlockOverview( ImUtf8.Text($"{item.Id.Id}"); ImUtf8.Text($"{item.PrimaryId.Id}-{item.Variant.Id}"); // TODO - ImUtf8.Text("Always Unlocked"); // : $"Unlocked on {time:g}" : "Not Unlocked."); + ImUtf8.Text("Always Unlocked"u8); // : $"Unlocked on {time:g}" : "Not Unlocked."); // TODO //tooltip.CreateTooltip(item, string.Empty, false); + DrawModTooltip(mods); } } } @@ -263,6 +273,8 @@ public class UnlockOverview( ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); + var mods = DrawModdedMarker(item, iconSize); + if (ImGui.IsItemClicked()) Glamourer.Messager.Chat.Print(new SeStringBuilder().AddItemLink(item.ItemId.Id, false).BuiltString); @@ -306,6 +318,7 @@ public class UnlockOverview( ImGui.TextUnformatted("Tradable"); if (item.Flags.HasFlag(ItemFlags.IsCrestWorthy)) ImGui.TextUnformatted("Can apply Crest"); + DrawModTooltip(mods); tooltip.CreateTooltip(item, string.Empty, false); } } @@ -316,4 +329,36 @@ public class UnlockOverview( private static int IconsPerRow(float iconWidth, float iconSpacing) => (int)(ImGui.GetContentRegionAvail().X / (iconWidth + iconSpacing)); + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private (string ModDirectory, string ModName)[] DrawModdedMarker(in EquipItem item, Vector2 iconSize) + { + var mods = penumbra.CheckCurrentChangedItem(item.Name); + if (mods.Length == 0) + return mods; + + var center = ImGui.GetItemRectMin() + new Vector2(iconSize.X * 0.85f, iconSize.Y * 0.15f); + ImGui.GetWindowDrawList().AddCircleFilled(center, iconSize.X * 0.1f, _moddedColor); + ImGui.GetWindowDrawList().AddCircle(center, iconSize.X * 0.1f, 0xFF000000); + return mods; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private void DrawModTooltip((string ModDirectory, string ModName)[] mods) + { + switch (mods.Length) + { + case 0: return; + case 1: + ImUtf8.Text("Modded by: "u8, _moddedColor); + ImGui.SameLine(0, 0); + ImUtf8.Text(mods[0].ModName); + return; + default: + ImUtf8.Text("Modded by:"u8, _moddedColor); + foreach (var (_, mod) in mods) + ImUtf8.BulletText(mod); + return; + } + } } diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 9651e85..6d9ce51 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -3,6 +3,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Events; using Glamourer.Interop; +using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; @@ -17,12 +18,16 @@ namespace Glamourer.Gui.Tabs.UnlocksTab; public class UnlockTable : Table, IDisposable { - private readonly ObjectUnlocked _event; + private readonly ObjectUnlocked _event; + private readonly PenumbraService _penumbra; + + private Guid _lastCurrentCollection = Guid.Empty; public UnlockTable(ItemManager items, TextureService textures, ItemUnlockManager itemUnlocks, - PenumbraChangedItemTooltip tooltip, ObjectUnlocked @event, JobService jobs, FavoriteManager favorites) + PenumbraChangedItemTooltip tooltip, ObjectUnlocked @event, JobService jobs, FavoriteManager favorites, PenumbraService penumbra) : base("ItemUnlockTable", new ItemList(items), new FavoriteColumn(favorites, @event) { Label = "F" }, + new ModdedColumn(penumbra) { Label = "M" }, new NameColumn(textures, tooltip) { Label = "Item Name..." }, new SlotColumn { Label = "Equip Slot" }, new TypeColumn { Label = "Item Type..." }, @@ -36,14 +41,40 @@ public class UnlockTable : Table, IDisposable new TradableColumn { Label = "Trade" } ) { - _event = @event; - Sortable = true; - Flags |= ImGuiTableFlags.Hideable | ImGuiTableFlags.Reorderable | ImGuiTableFlags.Resizable; + _event = @event; + _penumbra = penumbra; + Sortable = true; + Flags |= ImGuiTableFlags.Hideable | ImGuiTableFlags.Reorderable | ImGuiTableFlags.Resizable; _event.Subscribe(OnObjectUnlock, ObjectUnlocked.Priority.UnlockTable); + _penumbra.ModSettingChanged += OnModSettingsChanged; + + } + + private void OnModSettingsChanged(Penumbra.Api.Enums.ModSettingChange type, Guid collection, string mod, bool inherited) + { + if (collection != _lastCurrentCollection) + return; + + FilterDirty = true; + SortDirty = true; + } + + protected override void PreDraw() + { + var lastCurrentCollection = _penumbra.CurrentCollection.Id; + if (_lastCurrentCollection != lastCurrentCollection) + { + _lastCurrentCollection = lastCurrentCollection; + FilterDirty = true; + SortDirty = true; + } } public void Dispose() - => _event.Unsubscribe(OnObjectUnlock); + { + _event.Unsubscribe(OnObjectUnlock); + _penumbra.ModSettingChanged -= OnModSettingsChanged; + } private sealed class FavoriteColumn : YesNoColumn { @@ -77,6 +108,66 @@ public class UnlockTable : Table, IDisposable => _favorites.Contains(rhs).CompareTo(_favorites.Contains(lhs)); } + private sealed class ModdedColumn : YesNoColumn + { + public override float Width + => ImGui.GetFrameHeightWithSpacing(); + + private readonly PenumbraService _penumbra; + private readonly Dictionary _compareCache = []; + + public ModdedColumn(PenumbraService penumbra) + { + _penumbra = penumbra; + Flags |= ImGuiTableColumnFlags.NoResize; + } + + public override void PostSort() + { + _compareCache.Clear(); + } + + public override void DrawColumn(EquipItem item, int idx) + { + var value = _penumbra.CheckCurrentChangedItem(item.Name); + if (value.Length == 0) + return; + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ModdedItemMarker.Value()); + ImGuiUtil.Center(FontAwesomeIcon.Circle.ToIconString()); + } + + if (ImGui.IsItemHovered()) + { + using var tt = ImUtf8.Tooltip(); + foreach (var (_, mod) in value) + ImUtf8.BulletText(mod); + } + } + + public override bool FilterFunc(EquipItem item) + => FilterValue.HasFlag(_penumbra.CheckCurrentChangedItem(item.Name).Length > 0 ? YesNoFlag.Yes : YesNoFlag.No); + + public override int Compare(EquipItem lhs, EquipItem rhs) + { + if (!_compareCache.TryGetValue(lhs.Id, out var lhsCount)) + { + lhsCount = _penumbra.CheckCurrentChangedItem(lhs.Name).Length; + _compareCache[lhs.Id] = lhsCount; + } + + if (!_compareCache.TryGetValue(rhs.Id, out var rhsCount)) + { + rhsCount = _penumbra.CheckCurrentChangedItem(rhs.Name).Length; + _compareCache[rhs.Id] = rhsCount; + } + + return lhsCount.CompareTo(rhsCount); + } + } + private sealed class NameColumn : ColumnString { private readonly TextureService _textures; @@ -317,7 +408,6 @@ public class UnlockTable : Table, IDisposable { } } - private sealed class JobColumn : ColumnFlags { public override float Width @@ -415,7 +505,6 @@ public class UnlockTable : Table, IDisposable } } - private sealed class DyableColumn : ColumnFlags { [Flags] diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 27446ea..e0c445b 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -38,6 +38,7 @@ public class PenumbraService : IDisposable public const int RequiredPenumbraFeatureVersionTemp = 4; public const int RequiredPenumbraFeatureVersionTemp2 = 5; public const int RequiredPenumbraFeatureVersionTemp3 = 6; + public const int RequiredPenumbraFeatureVersionTemp4 = 7; private const int Key = -1610; @@ -49,30 +50,33 @@ public class PenumbraService : IDisposable private readonly EventSubscriber _createdCharacterBase; private readonly EventSubscriber _modSettingChanged; - private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; - private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; - private global::Penumbra.Api.IpcSubscribers.RedrawObject? _redraw; - private global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo? _drawObjectInfo; - private global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex? _cutsceneParent; - private global::Penumbra.Api.IpcSubscribers.GetCollectionForObject? _objectCollection; - private global::Penumbra.Api.IpcSubscribers.GetModList? _getMods; - private global::Penumbra.Api.IpcSubscribers.GetCollection? _currentCollection; - private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp? _getCurrentSettingsWithTemp; - private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings? _getCurrentSettings; - private global::Penumbra.Api.IpcSubscribers.GetAllModSettings? _getAllSettings; - private global::Penumbra.Api.IpcSubscribers.TryInheritMod? _inheritMod; - private global::Penumbra.Api.IpcSubscribers.TrySetMod? _setMod; - private global::Penumbra.Api.IpcSubscribers.TrySetModPriority? _setModPriority; - private global::Penumbra.Api.IpcSubscribers.TrySetModSetting? _setModSetting; - private global::Penumbra.Api.IpcSubscribers.TrySetModSettings? _setModSettings; - private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings? _setTemporaryModSettings; - private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer? _setTemporaryModSettingsPlayer; - private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings? _removeTemporaryModSettings; - private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer? _removeTemporaryModSettingsPlayer; - private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings? _removeAllTemporaryModSettings; - private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer? _removeAllTemporaryModSettingsPlayer; - private global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings? _queryTemporaryModSettings; - private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; + private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; + private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; + private global::Penumbra.Api.IpcSubscribers.RedrawObject? _redraw; + private global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo? _drawObjectInfo; + private global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex? _cutsceneParent; + private global::Penumbra.Api.IpcSubscribers.GetCollectionForObject? _objectCollection; + private global::Penumbra.Api.IpcSubscribers.GetModList? _getMods; + private global::Penumbra.Api.IpcSubscribers.GetCollection? _currentCollection; + private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp? _getCurrentSettingsWithTemp; + private global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings? _getCurrentSettings; + private global::Penumbra.Api.IpcSubscribers.GetAllModSettings? _getAllSettings; + private global::Penumbra.Api.IpcSubscribers.TryInheritMod? _inheritMod; + private global::Penumbra.Api.IpcSubscribers.TrySetMod? _setMod; + private global::Penumbra.Api.IpcSubscribers.TrySetModPriority? _setModPriority; + private global::Penumbra.Api.IpcSubscribers.TrySetModSetting? _setModSetting; + private global::Penumbra.Api.IpcSubscribers.TrySetModSettings? _setModSettings; + private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings? _setTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer? _setTemporaryModSettingsPlayer; + private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings? _removeTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer? _removeTemporaryModSettingsPlayer; + private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings? _removeAllTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer? _removeAllTemporaryModSettingsPlayer; + private global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings? _queryTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; + private global::Penumbra.Api.IpcSubscribers.GetChangedItems? _getChangedItems; + private IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)>? _changedItems; + private Func? _checkCurrentChangedItems; private readonly IDisposable _initializedEvent; private readonly IDisposable _disposedEvent; @@ -195,37 +199,58 @@ public class PenumbraService : IDisposable return ret[0]; } - public IReadOnlyList<(Mod Mod, ModSettings Settings)> GetMods() + public IReadOnlyList<(Mod Mod, ModSettings Settings, int Count)> GetMods(IReadOnlyList data) { if (!Available) return []; try { - var allMods = _getMods!.Invoke(); - var collection = _currentCollection!.Invoke(ApiCollectionType.Current); - if (_getAllSettings != null) + var allMods = _getMods!.Invoke(); + var currentCollection = _currentCollection!.Invoke(ApiCollectionType.Current); + var withSettings = WithSettings(allMods, currentCollection!.Value.Id); + var withCounts = WithCounts(withSettings, allMods.Count); + return OrderList(withCounts, allMods.Count); + + IEnumerable<(Mod Mod, ModSettings Settings)> WithSettings(Dictionary mods, Guid collection) { - var allSettings = _getAllSettings.Invoke(collection!.Value.Id, false, false, Key); - if (allSettings.Item1 is PenumbraApiEc.Success) - return allMods.Select(m => (new Mod(m.Value, m.Key), + if (_getAllSettings != null) + { + var allSettings = _getAllSettings.Invoke(collection, false, false, Key); + if (allSettings.Item1 is PenumbraApiEc.Success) + return mods.Select(m => (new Mod(m.Value, m.Key), allSettings.Item2!.TryGetValue(m.Key, out var s) - ? new ModSettings(s.Item3, s.Item2, s.Item1, s.Item4 && s.Item5, false) - : ModSettings.Empty)) - .OrderByDescending(p => p.Item2.Enabled) - .ThenBy(p => p.Item1.Name) - .ThenBy(p => p.Item1.DirectoryName) - .ThenByDescending(p => p.Item2.Priority) - .ToList(); + ? new ModSettings(s.Item3, s.Item2, s.Item1, s is { Item4: true, Item5: true }, false) + : ModSettings.Empty)); + } + + return mods.Select(m => (new Mod(m.Value, m.Key), GetSettings(collection, m.Key, m.Value, out _))); } - return allMods - .Select(m => (new Mod(m.Value, m.Key), GetSettings(collection!.Value.Id, m.Key, m.Value, out _))) - .OrderByDescending(p => p.Item2.Enabled) - .ThenBy(p => p.Item1.Name) - .ThenBy(p => p.Item1.DirectoryName) - .ThenByDescending(p => p.Item2.Priority) - .ToList(); + IEnumerable<(Mod Mod, ModSettings Settings, int Count)> WithCounts(IEnumerable<(Mod Mod, ModSettings Settings)> mods, int count) + { + if (_changedItems != null && _changedItems.Count == count) + return mods.Select((m, idx) => (m.Mod, m.Settings, CountItems(_changedItems[idx].ChangedItems, data))); + + return mods.Select(p => (p.Item1, p.Item2, CountItems(_getChangedItems!.Invoke(p.Item1.DirectoryName, p.Item1.Name), data))); + + static int CountItems(IReadOnlyDictionary dict, IReadOnlyList data) + => data.Count(dict.ContainsKey); + } + + static IReadOnlyList<(Mod Mod, ModSettings Settings, int Count)> OrderList( + IEnumerable<(Mod Mod, ModSettings Settings, int Count)> enumerable, int count) + { + var array = new (Mod Mod, ModSettings Settings, int Count)[count]; + var i = 0; + foreach (var t in enumerable.OrderByDescending(p => p.Item2.Enabled) + .ThenByDescending(p => p.Item3) + .ThenBy(p => p.Item1.Name) + .ThenBy(p => p.Item1.DirectoryName) + .ThenByDescending(p => p.Item2.Priority)) + array[i++] = t; + return array; + } } catch (Exception ex) { @@ -289,6 +314,9 @@ public class PenumbraService : IDisposable RemoveAllTemporarySettings(collection.Key); } + public (string ModDirectory, string ModName)[] CheckCurrentChangedItem(string changedItem) + => _checkCurrentChangedItems?.Invoke(changedItem) ?? []; + private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, ObjectIndex? index) { var ex = settings.Remove @@ -476,6 +504,7 @@ public class PenumbraService : IDisposable _setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface); _setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface); _openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface); + _getChangedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItems(_pluginInterface); if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp) { _setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface); @@ -488,10 +517,16 @@ public class PenumbraService : IDisposable if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) { _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); - if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) + if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp3) { _getCurrentSettingsWithTemp = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp(_pluginInterface); _getAllSettings = new global::Penumbra.Api.IpcSubscribers.GetAllModSettings(_pluginInterface); + if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp4) + { + _changedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItemAdapterList(_pluginInterface).Invoke(); + _checkCurrentChangedItems = + new global::Penumbra.Api.IpcSubscribers.CheckCurrentChangedItemFunc(_pluginInterface).Invoke(); + } } } } @@ -541,6 +576,9 @@ public class PenumbraService : IDisposable _removeAllTemporaryModSettings = null; _removeAllTemporaryModSettingsPlayer = null; _queryTemporaryModSettings = null; + _getChangedItems = null; + _changedItems = null; + _checkCurrentChangedItems = null; Available = false; Glamourer.Log.Debug("Glamourer detached from Penumbra."); } diff --git a/OtterGui b/OtterGui index 3c1260c..0b6085c 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 3c1260c9833303c2d33d12d6f77dc2b1afea3f34 +Subproject commit 0b6085ce720ffb7c78cf42d4e51861f34db27744 diff --git a/Penumbra.Api b/Penumbra.Api index c678090..70f0468 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit c67809057fac73a0fd407e3ad567f0aa6bc0bc37 +Subproject commit 70f046830cc7cd35b3480b12b7efe94182477fbb From f7d6e75a9b9e3687958c590cb90f8a3993ff62b8 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 13 Feb 2025 15:43:08 +0000 Subject: [PATCH 603/786] [CI] Updating repo.json for testing_1.3.6.1 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index d506898..76009f4 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.0", + "TestingAssemblyVersion": "1.3.6.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 11684609427ecfc84dedc4c4d826a3e3bae39148 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 17 Feb 2025 17:40:49 +0100 Subject: [PATCH 604/786] Add button to reset glamourer temporary settings to qdb. --- Glamourer/Gui/DesignQuickBar.cs | 43 +++- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 238 +++++++++--------- Glamourer/State/StateEditor.cs | 3 +- OtterGui | 2 +- 4 files changed, 167 insertions(+), 119 deletions(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index b58643c..1e2904c 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -7,11 +7,13 @@ using Dalamud.Plugin.Services; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Interop; +using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Text; using Penumbra.GameData.Actors; namespace Glamourer.Gui; @@ -26,6 +28,7 @@ public enum QdbButtons RevertEquip = 0x10, RevertCustomize = 0x20, ReapplyAutomation = 0x40, + ResetSettings = 0x80, } public sealed class DesignQuickBar : Window, IDisposable @@ -40,6 +43,7 @@ public sealed class DesignQuickBar : Window, IDisposable private readonly StateManager _stateManager; private readonly AutoDesignApplier _autoDesignApplier; private readonly ObjectManager _objects; + private readonly PenumbraService _penumbra; private readonly IKeyState _keyState; private readonly ImRaii.Style _windowPadding = new(); private readonly ImRaii.Color _windowColor = new(); @@ -47,7 +51,7 @@ public sealed class DesignQuickBar : Window, IDisposable private int _numButtons; public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, - ObjectManager objects, AutoDesignApplier autoDesignApplier) + ObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking) { _config = config; @@ -56,6 +60,7 @@ public sealed class DesignQuickBar : Window, IDisposable _keyState = keyState; _objects = objects; _autoDesignApplier = autoDesignApplier; + _penumbra = penumbra; IsOpen = _config.Ephemeral.ShowDesignQuickBar; DisableWindowSounds = true; Size = Vector2.Zero; @@ -122,6 +127,7 @@ public sealed class DesignQuickBar : Window, IDisposable DrawRevertAdvancedCustomization(buttonSize); DrawRevertAutomationButton(buttonSize); DrawReapplyAutomationButton(buttonSize); + DrawResetSettingsButton(buttonSize); } private ActorIdentifier _playerIdentifier; @@ -392,10 +398,41 @@ public sealed class DesignQuickBar : Window, IDisposable _stateManager.ResetEquip(state!, StateSource.Manual); } + private void DrawResetSettingsButton(Vector2 buttonSize) + { + if (!_config.QdbButtons.HasFlag(QdbButtons.ResetSettings)) + return; + + var available = 0; + var tooltip = string.Empty; + + if (_playerIdentifier.IsValid && _playerData.Valid) + { + available |= 1; + tooltip = $"Left-Click: Reset all temporary settings applied by Glamourer to the collection affecting {_playerIdentifier}."; + } + + if (_targetIdentifier.IsValid && _targetData.Valid) + { + if (available != 0) + tooltip += '\n'; + available |= 2; + tooltip += $"Right-Click: Reset all temporary settings applied by Glamourer to the collection affecting {_targetIdentifier}."; + } + + if (available == 0) + tooltip = "Neither player character nor target are available to identify their collections."; + + var (clicked, _, data, _) = ResolveTarget(FontAwesomeIcon.Cog, buttonSize, tooltip, available); + ImGui.SameLine(); + if (clicked) + _penumbra.RemoveAllTemporarySettings(data.Objects[0].Index); + } + private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip, int available) { - ImGuiUtil.DrawDisabledButton(icon.ToIconString(), buttonSize, tooltip, available == 0, true); + ImUtf8.IconButton(icon, tooltip, buttonSize, available == 0); if ((available & 1) == 1 && ImGui.IsItemClicked(ImGuiMouseButton.Left)) return (true, _playerIdentifier, _playerData, _playerState); if ((available & 2) == 2 && ImGui.IsItemClicked(ImGuiMouseButton.Right)) @@ -441,6 +478,8 @@ public sealed class DesignQuickBar : Window, IDisposable ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertEquip)) ++_numButtons; + if (_config.UseTemporarySettings && _config.QdbButtons.HasFlag(QdbButtons.ResetSettings)) + ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.ApplyDesign)) { ++_numButtons; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index ab40a48..4ee261b 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -9,7 +9,6 @@ using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.PalettePlus; using ImGuiNET; -using OtterGui; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Widgets; @@ -39,12 +38,12 @@ public class SettingsTab( public void DrawContent() { - using var child = ImRaii.Child("MainWindowChild"); + using var child = ImUtf8.Child("MainWindowChild"u8, default); if (!child) return; - Checkbox("Enable Auto Designs", - "Enable the application of designs associated to characters in the Automation tab to be applied automatically.", + Checkbox("Enable Auto Designs"u8, + "Enable the application of designs associated to characters in the Automation tab to be applied automatically."u8, config.EnableAutoDesigns, v => { config.EnableAutoDesigns = v; @@ -55,7 +54,7 @@ public class SettingsTab( ImGui.NewLine(); ImGui.NewLine(); - using (ImRaii.Child("SettingsChild")) + using (ImUtf8.Child("SettingsChild"u8, default)) { DrawBehaviorSettings(); DrawDesignDefaultSettings(); @@ -70,44 +69,44 @@ public class SettingsTab( private void DrawBehaviorSettings() { - if (!ImGui.CollapsingHeader("Glamourer Behavior")) + if (!ImUtf8.CollapsingHeader("Glamourer Behavior"u8)) return; - Checkbox("Always Apply Entire Weapon for Mainhand", - "When manually applying a mainhand item, will also apply a corresponding offhand and potentially gauntlets for certain fist weapons.", + Checkbox("Always Apply Entire Weapon for Mainhand"u8, + "When manually applying a mainhand item, will also apply a corresponding offhand and potentially gauntlets for certain fist weapons."u8, config.ChangeEntireItem, v => config.ChangeEntireItem = v); - Checkbox("Use Replacement Gear for Gear Unavailable to Your Race or Gender", - "Use different gender- and race-appropriate models as a substitute when detecting certain items not available for a characters current gender and race.", + Checkbox("Use Replacement Gear for Gear Unavailable to Your Race or Gender"u8, + "Use different gender- and race-appropriate models as a substitute when detecting certain items not available for a characters current gender and race."u8, config.UseRestrictedGearProtection, v => config.UseRestrictedGearProtection = v); - Checkbox("Do Not Apply Unobtained Items in Automation", - "Enable this if you want automatically applied designs to only consider items and customizations you have actually unlocked once, and skip those you have not.", + Checkbox("Do Not Apply Unobtained Items in Automation"u8, + "Enable this if you want automatically applied designs to only consider items and customizations you have actually unlocked once, and skip those you have not."u8, config.UnlockedItemMode, v => config.UnlockedItemMode = v); - Checkbox("Respect Manual Changes When Editing Automation", - "Whether changing any currently active automation group will respect manual changes to the character before re-applying the changed automation or not.", + Checkbox("Respect Manual Changes When Editing Automation"u8, + "Whether changing any currently active automation group will respect manual changes to the character before re-applying the changed automation or not."u8, config.RespectManualOnAutomationUpdate, v => config.RespectManualOnAutomationUpdate = v); - Checkbox("Enable Festival Easter-Eggs", - "Glamourer may do some fun things on specific dates. Disable this if you do not want your experience disrupted by this.", + Checkbox("Enable Festival Easter-Eggs"u8, + "Glamourer may do some fun things on specific dates. Disable this if you do not want your experience disrupted by this."u8, config.DisableFestivals == 0, v => config.DisableFestivals = v ? (byte)0 : (byte)2); - Checkbox("Auto-Reload Gear", - "Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection.", + Checkbox("Auto-Reload Gear"u8, + "Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection."u8, config.AutoRedrawEquipOnChanges, v => config.AutoRedrawEquipOnChanges = v); - Checkbox("Revert Manual Changes on Zone Change", - "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone.", + Checkbox("Revert Manual Changes on Zone Change"u8, + "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone."u8, config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); - Checkbox("Enable Advanced Customization Options", - "Enable the display and editing of advanced customization options like arbitrary colors.", + Checkbox("Enable Advanced Customization Options"u8, + "Enable the display and editing of advanced customization options like arbitrary colors."u8, config.UseAdvancedParameters, paletteChecker.SetAdvancedParameters); PaletteImportButton(); - Checkbox("Enable Advanced Dye Options", - "Enable the display and editing of advanced dyes (color sets) for all equipment", + Checkbox("Enable Advanced Dye Options"u8, + "Enable the display and editing of advanced dyes (color sets) for all equipment"u8, config.UseAdvancedDyes, v => config.UseAdvancedDyes = v); - Checkbox("Always Apply Associated Mods", - "Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n" - + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n" - + "If you enable this setting, you are aware that any resulting misconfiguration is your own fault.", + Checkbox("Always Apply Associated Mods"u8, + "Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n"u8 + + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n"u8 + + "If you enable this setting, you are aware that any resulting misconfiguration is your own fault."u8, config.AlwaysApplyAssociatedMods, v => config.AlwaysApplyAssociatedMods = v); - Checkbox("Use Temporary Mod Settings", - "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down.", config.UseTemporarySettings, + Checkbox("Use Temporary Mod Settings"u8, + "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down."u8, config.UseTemporarySettings, v => config.UseTemporarySettings = v); ImGui.NewLine(); } @@ -117,33 +116,34 @@ public class SettingsTab( if (!ImUtf8.CollapsingHeader("Design Defaults")) return; - Checkbox("Show in Quick Design Bar", "Newly created designs will be shown in the quick design bar by default.", + Checkbox("Show in Quick Design Bar"u8, "Newly created designs will be shown in the quick design bar by default."u8, config.DefaultDesignSettings.ShowQuickDesignBar, v => config.DefaultDesignSettings.ShowQuickDesignBar = v); - Checkbox("Reset Advanced Dyes", "Newly created designs will be configured to reset advanced dyes on application by default.", + Checkbox("Reset Advanced Dyes"u8, "Newly created designs will be configured to reset advanced dyes on application by default."u8, config.DefaultDesignSettings.ResetAdvancedDyes, v => config.DefaultDesignSettings.ResetAdvancedDyes = v); - Checkbox("Always Force Redraw", "Newly created designs will be configured to force character redraws on application by default.", + Checkbox("Always Force Redraw"u8, "Newly created designs will be configured to force character redraws on application by default."u8, config.DefaultDesignSettings.AlwaysForceRedrawing, v => config.DefaultDesignSettings.AlwaysForceRedrawing = v); - Checkbox("Reset Temporary Settings", "Newly created designs will be configured to clear all advanced settings applied by Glamourer to the collection by default.", + Checkbox("Reset Temporary Settings"u8, + "Newly created designs will be configured to clear all advanced settings applied by Glamourer to the collection by default."u8, config.DefaultDesignSettings.ResetTemporarySettings, v => config.DefaultDesignSettings.ResetTemporarySettings = v); } private void DrawInterfaceSettings() { - if (!ImGui.CollapsingHeader("Interface")) + if (!ImUtf8.CollapsingHeader("Interface"u8)) return; - EphemeralCheckbox("Show Quick Design Bar", - "Show a bar separate from the main window that allows you to quickly apply designs or revert your character and target.", + EphemeralCheckbox("Show Quick Design Bar"u8, + "Show a bar separate from the main window that allows you to quickly apply designs or revert your character and target."u8, config.Ephemeral.ShowDesignQuickBar, v => config.Ephemeral.ShowDesignQuickBar = v); - EphemeralCheckbox("Lock Quick Design Bar", "Prevent the quick design bar from being moved and lock it in place.", + EphemeralCheckbox("Lock Quick Design Bar"u8, "Prevent the quick design bar from being moved and lock it in place."u8, config.Ephemeral.LockDesignQuickBar, v => config.Ephemeral.LockDesignQuickBar = v); if (Widget.ModifiableKeySelector("Hotkey to Toggle Quick Design Bar", "Set a hotkey that opens or closes the quick design bar.", 100 * ImGuiHelpers.GlobalScale, config.ToggleQuickDesignBar, v => config.ToggleQuickDesignBar = v, _validKeys)) config.Save(); - Checkbox("Show Quick Design Bar in Main Window", - "Show the quick design bar in the tab selection part of the main window, too.", + Checkbox("Show Quick Design Bar in Main Window"u8, + "Show the quick design bar in the tab selection part of the main window, too."u8, config.ShowQuickBarInTabs, v => config.ShowQuickBarInTabs = v); DrawQuickDesignBoxes(); @@ -151,7 +151,7 @@ public class SettingsTab( ImGui.Separator(); ImGui.Dummy(Vector2.Zero); - Checkbox("Enable Game Context Menus", "Whether to show a Try On via Glamourer button on context menus for equippable items.", + Checkbox("Enable Game Context Menus"u8, "Whether to show a Try On via Glamourer button on context menus for equippable items."u8, config.EnableGameContextMenu, v => { config.EnableGameContextMenu = v; @@ -160,41 +160,41 @@ public class SettingsTab( else contextMenuService.Disable(); }); - Checkbox("Show Window when UI is Hidden", "Whether to show Glamourer windows even when the games UI is hidden.", + Checkbox("Show Window when UI is Hidden"u8, "Whether to show Glamourer windows even when the games UI is hidden."u8, config.ShowWindowWhenUiHidden, v => { config.ShowWindowWhenUiHidden = v; uiBuilder.DisableUserUiHide = v; }); - Checkbox("Hide Window in Cutscenes", "Whether the main Glamourer window should automatically be hidden when entering cutscenes or not.", + Checkbox("Hide Window in Cutscenes"u8, "Whether the main Glamourer window should automatically be hidden when entering cutscenes or not."u8, config.HideWindowInCutscene, v => { config.HideWindowInCutscene = v; uiBuilder.DisableCutsceneUiHide = !v; }); - EphemeralCheckbox("Lock Main Window", "Prevent the main window from being moved and lock it in place.", + EphemeralCheckbox("Lock Main Window"u8, "Prevent the main window from being moved and lock it in place."u8, config.Ephemeral.LockMainWindow, v => config.Ephemeral.LockMainWindow = v); - Checkbox("Open Main Window at Game Start", "Whether the main Glamourer window should be open or closed after launching the game.", + Checkbox("Open Main Window at Game Start"u8, "Whether the main Glamourer window should be open or closed after launching the game."u8, config.OpenWindowAtStart, v => config.OpenWindowAtStart = v); ImGui.Dummy(Vector2.Zero); ImGui.Separator(); ImGui.Dummy(Vector2.Zero); - Checkbox("Smaller Equip Display", "Use single-line display without icons and small dye buttons instead of double-line display.", + Checkbox("Smaller Equip Display"u8, "Use single-line display without icons and small dye buttons instead of double-line display."u8, config.SmallEquip, v => config.SmallEquip = v); DrawHeightUnitSettings(); - Checkbox("Show Application Checkboxes", - "Show the application checkboxes in the Customization and Equipment panels of the design tab, instead of only showing them under Application Rules.", + Checkbox("Show Application Checkboxes"u8, + "Show the application checkboxes in the Customization and Equipment panels of the design tab, instead of only showing them under Application Rules."u8, !config.HideApplyCheckmarks, v => config.HideApplyCheckmarks = !v); if (Widget.DoubleModifierSelector("Design Deletion Modifier", "A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale, config.DeleteDesignModifier, v => config.DeleteDesignModifier = v)) config.Save(); DrawRenameSettings(); - Checkbox("Auto-Open Design Folders", - "Have design folders open or closed as their default state after launching.", config.OpenFoldersByDefault, + Checkbox("Auto-Open Design Folders"u8, + "Have design folders open or closed as their default state after launching."u8, config.OpenFoldersByDefault, v => config.OpenFoldersByDefault = v); DrawFolderSortType(); @@ -202,32 +202,32 @@ public class SettingsTab( ImGui.Separator(); ImGui.Dummy(Vector2.Zero); - Checkbox("Allow Double-Clicking Designs to Apply", - "Tries to apply a design to the current player character When double-clicking it in the design selector.", + Checkbox("Allow Double-Clicking Designs to Apply"u8, + "Tries to apply a design to the current player character When double-clicking it in the design selector."u8, config.AllowDoubleClickToApply, v => config.AllowDoubleClickToApply = v); - Checkbox("Show all Application Rule Checkboxes for Automation", - "Show multiple separate application rule checkboxes for automated designs, instead of a single box for enabling or disabling.", + Checkbox("Show all Application Rule Checkboxes for Automation"u8, + "Show multiple separate application rule checkboxes for automated designs, instead of a single box for enabling or disabling."u8, config.ShowAllAutomatedApplicationRules, v => config.ShowAllAutomatedApplicationRules = v); - Checkbox("Show Unobtained Item Warnings", - "Show information whether you have unlocked all items and customizations in your automated design or not.", + Checkbox("Show Unobtained Item Warnings"u8, + "Show information whether you have unlocked all items and customizations in your automated design or not."u8, config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v); if (config.UseAdvancedParameters) { - Checkbox("Show Color Display Config", "Show the Color Display configuration options in the Advanced Customization panels.", + Checkbox("Show Color Display Config"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8, config.ShowColorConfig, v => config.ShowColorConfig = v); - Checkbox("Show Palette+ Import Button", - "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs.", + Checkbox("Show Palette+ Import Button"u8, + "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs."u8, config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v); using var id = ImRaii.PushId(1); PaletteImportButton(); } if (config.UseAdvancedDyes) - Checkbox("Keep Advanced Dye Window Attached", - "Keeps the advanced dye window expansion attached to the main window, or makes it freely movable.", + Checkbox("Keep Advanced Dye Window Attached"u8, + "Keeps the advanced dye window expansion attached to the main window, or makes it freely movable."u8, config.KeepAdvancedDyesAttached, v => config.KeepAdvancedDyesAttached = v); - Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general.", config.DebugMode, + Checkbox("Debug Mode"u8, "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general."u8, config.DebugMode, v => config.DebugMode = v); ImGui.NewLine(); } @@ -236,40 +236,48 @@ public class SettingsTab( { var showAuto = config.EnableAutoDesigns; var showAdvanced = config.UseAdvancedParameters || config.UseAdvancedDyes; - var numColumns = 7 - (showAuto ? 0 : 2) - (showAdvanced ? 0 : 1); + var numColumns = 8 - (showAuto ? 0 : 2) - (showAdvanced ? 0 : 1) - (config.UseTemporarySettings ? 0 : 1); ImGui.NewLine(); - ImGui.TextUnformatted("Show the Following Buttons in the Quick Design Bar:"); + ImUtf8.Text("Show the Following Buttons in the Quick Design Bar:"u8); ImGui.Dummy(Vector2.Zero); - using var table = ImRaii.Table("##tableQdb", numColumns, + using var table = ImUtf8.Table("##tableQdb"u8, numColumns, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Borders | ImGuiTableFlags.NoHostExtendX); if (!table) return; - var columns = new[] - { - (" Apply Design ", true, QdbButtons.ApplyDesign), - (" Revert All ", true, QdbButtons.RevertAll), - (" Revert to Auto ", showAuto, QdbButtons.RevertAutomation), - (" Reapply Auto ", showAuto, QdbButtons.ReapplyAutomation), - (" Revert Equip ", true, QdbButtons.RevertEquip), - (" Revert Customize ", true, QdbButtons.RevertCustomize), - (" Revert Advanced ", showAdvanced, QdbButtons.RevertAdvanced), - }; + ReadOnlySpan<(string, bool, QdbButtons)> columns = + [ + ("Apply Design", true, QdbButtons.ApplyDesign), + ("Revert All", true, QdbButtons.RevertAll), + ("Revert to Auto", showAuto, QdbButtons.RevertAutomation), + ("Reapply Auto", showAuto, QdbButtons.ReapplyAutomation), + ("Revert Equip", true, QdbButtons.RevertEquip), + ("Revert Customize", true, QdbButtons.RevertCustomize), + ("Revert Advanced", showAdvanced, QdbButtons.RevertAdvanced), + ("Reset Settings", config.UseTemporarySettings, QdbButtons.ResetSettings), + ]; - foreach (var (label, _, _) in columns.Where(t => t.Item2)) + for(var i = 0; i < columns.Length; ++i) { + if (!columns[i].Item2) + continue; + ImGui.TableNextColumn(); - ImGui.TableHeader(label); + ImUtf8.TableHeader(columns[i].Item1); } - foreach (var (_, _, flag) in columns.Where(t => t.Item2)) + for (var i = 0; i < columns.Length; ++i) { - using var id = ImRaii.PushId((int)flag); + if (!columns[i].Item2) + continue; + + var flag = columns[i].Item3; + using var id = ImUtf8.PushId((int)flag); ImGui.TableNextColumn(); var offset = (ImGui.GetContentRegionAvail().X - ImGui.GetFrameHeight()) / 2; ImGui.SetCursorPosX(ImGui.GetCursorPosX() + offset); var value = config.QdbButtons.HasFlag(flag); - if (!ImGui.Checkbox(string.Empty, ref value)) + if (!ImUtf8.Checkbox(""u8, ref value)) continue; var buttons = value ? config.QdbButtons | flag : config.QdbButtons & ~flag; @@ -287,31 +295,31 @@ public class SettingsTab( return; ImGui.SameLine(); - if (ImGui.Button("Import Palette+ to Designs")) + if (ImUtf8.Button("Import Palette+ to Designs"u8)) paletteImport.ImportDesigns(); - ImGuiUtil.HoverTooltip( + ImUtf8.HoverTooltip( $"Import all existing Palettes from your Palette+ Config into Designs at PalettePlus/[Name] if these do not exist. Existing Palettes are:\n\n\t - {string.Join("\n\t - ", paletteImport.Data.Keys)}"); } /// Draw the entire Color subsection. private void DrawColorSettings() { - if (!ImGui.CollapsingHeader("Colors")) + if (!ImUtf8.CollapsingHeader("Colors"u8)) return; - using (var tree = ImRaii.TreeNode("Custom Design Colors")) + using (var tree = ImUtf8.TreeNode("Custom Design Colors"u8)) { if (tree) designColorUi.Draw(); } - using (var tree = ImRaii.TreeNode("Color Settings")) + using (var tree = ImUtf8.TreeNode("Color Settings"u8)) { if (tree) foreach (var color in Enum.GetValues()) { var (defaultColor, name, description) = color.Data(); - var currentColor = config.Colors.TryGetValue(color, out var current) ? current : defaultColor; + var currentColor = config.Colors.GetValueOrDefault(color, defaultColor); if (Widget.ColorPicker(name, description, currentColor, c => config.Colors[color] = c, defaultColor)) config.Save(); } @@ -321,33 +329,33 @@ public class SettingsTab( } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private void Checkbox(string label, string tooltip, bool current, Action setter) + private void Checkbox(ReadOnlySpan label, ReadOnlySpan tooltip, bool current, Action setter) { - using var id = ImRaii.PushId(label); + using var id = ImUtf8.PushId(label); var tmp = current; - if (ImGui.Checkbox(string.Empty, ref tmp) && tmp != current) + if (ImUtf8.Checkbox(""u8, ref tmp) && tmp != current) { setter(tmp); config.Save(); } ImGui.SameLine(); - ImGuiUtil.LabeledHelpMarker(label, tooltip); + ImUtf8.LabeledHelpMarker(label, tooltip); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private void EphemeralCheckbox(string label, string tooltip, bool current, Action setter) + private void EphemeralCheckbox(ReadOnlySpan label, ReadOnlySpan tooltip, bool current, Action setter) { - using var id = ImRaii.PushId(label); + using var id = ImUtf8.PushId(label); var tmp = current; - if (ImGui.Checkbox(string.Empty, ref tmp) && tmp != current) + if (ImUtf8.Checkbox(""u8, ref tmp) && tmp != current) { setter(tmp); config.Ephemeral.Save(); } ImGui.SameLine(); - ImGuiUtil.LabeledHelpMarker(label, tooltip); + ImUtf8.LabeledHelpMarker(label, tooltip); } /// Different supported sort modes as a combo. @@ -355,29 +363,29 @@ public class SettingsTab( { var sortMode = config.SortMode; ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); - using (var combo = ImRaii.Combo("##sortMode", sortMode.Name)) + using (var combo = ImUtf8.Combo("##sortMode"u8, sortMode.Name)) { if (combo) foreach (var val in Configuration.Constants.ValidSortModes) { - if (ImGui.Selectable(val.Name, val.GetType() == sortMode.GetType()) && val.GetType() != sortMode.GetType()) + if (ImUtf8.Selectable(val.Name, val.GetType() == sortMode.GetType()) && val.GetType() != sortMode.GetType()) { config.SortMode = val; selector.SetFilterDirty(); config.Save(); } - ImGuiUtil.HoverTooltip(val.Description); + ImUtf8.HoverTooltip(val.Description); } } - ImGuiUtil.LabeledHelpMarker("Sort Mode", "Choose the sort mode for the mod selector in the designs tab."); + ImUtf8.LabeledHelpMarker("Sort Mode"u8, "Choose the sort mode for the mod selector in the designs tab."u8); } private void DrawRenameSettings() { ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); - using (var combo = ImRaii.Combo("##renameSettings", config.ShowRename.GetData().Name)) + using (var combo = ImUtf8.Combo("##renameSettings"u8, config.ShowRename.GetData().Name)) { if (combo) foreach (var value in Enum.GetValues()) @@ -390,7 +398,7 @@ public class SettingsTab( config.Save(); } - ImGuiUtil.HoverTooltip(desc); + ImUtf8.HoverTooltip(desc); } } @@ -399,19 +407,19 @@ public class SettingsTab( "Select which of the two renaming input fields are visible when opening the right-click context menu of a design in the design selector."; ImGuiComponents.HelpMarker(tt); ImGui.SameLine(); - ImGui.TextUnformatted("Rename Fields in Design Context Menu"); - ImGuiUtil.HoverTooltip(tt); + ImUtf8.Text("Rename Fields in Design Context Menu"u8); + ImUtf8.HoverTooltip(tt); } private void DrawHeightUnitSettings() { ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale); - using (var combo = ImRaii.Combo("##heightUnit", HeightDisplayTypeName(config.HeightDisplayType))) + using (var combo = ImUtf8.Combo("##heightUnit"u8, HeightDisplayTypeName(config.HeightDisplayType))) { if (combo) foreach (var type in Enum.GetValues()) { - if (ImGui.Selectable(HeightDisplayTypeName(type), type == config.HeightDisplayType) && type != config.HeightDisplayType) + if (ImUtf8.Selectable(HeightDisplayTypeName(type), type == config.HeightDisplayType) && type != config.HeightDisplayType) { config.HeightDisplayType = type; config.Save(); @@ -423,20 +431,20 @@ public class SettingsTab( const string tt = "Select how to display the height of characters in real-world units, if at all."; ImGuiComponents.HelpMarker(tt); ImGui.SameLine(); - ImGui.TextUnformatted("Character Height Display Type"); - ImGuiUtil.HoverTooltip(tt); + ImUtf8.Text("Character Height Display Type"u8); + ImUtf8.HoverTooltip(tt); } - private static string HeightDisplayTypeName(HeightDisplayType type) + private static ReadOnlySpan HeightDisplayTypeName(HeightDisplayType type) => type switch { - HeightDisplayType.None => "Do Not Display", - HeightDisplayType.Centimetre => "Centimetres (000.0 cm)", - HeightDisplayType.Metre => "Metres (0.00 m)", - HeightDisplayType.Wrong => "Inches (00.0 in)", - HeightDisplayType.WrongFoot => "Feet (0'00'')", - HeightDisplayType.Corgi => "Corgis (0.0 Corgis)", - HeightDisplayType.OlympicPool => "Olympic-size swimming Pools (0.000 Pools)", - _ => string.Empty, + HeightDisplayType.None => "Do Not Display"u8, + HeightDisplayType.Centimetre => "Centimetres (000.0 cm)"u8, + HeightDisplayType.Metre => "Metres (0.00 m)"u8, + HeightDisplayType.Wrong => "Inches (00.0 in)"u8, + HeightDisplayType.WrongFoot => "Feet (0'00'')"u8, + HeightDisplayType.Corgi => "Corgis (0.0 Corgis)"u8, + HeightDisplayType.OlympicPool => "Olympic-size swimming Pools (0.000 Pools)"u8, + _ => ""u8, }; } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 42058d2..fec5b13 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -407,7 +407,8 @@ public class StateEditor( } else if (!value.Revert) { - Editor.ChangeMaterialValue(state, idx, new MaterialValueState(ColorRow.Empty, value.Value, CharacterWeapon.Empty, source), + Editor.ChangeMaterialValue(state, idx, + new MaterialValueState(ColorRow.Empty, value.Value, CharacterWeapon.Empty, source), settings.Source, out _, settings.Key); } } diff --git a/OtterGui b/OtterGui index 0b6085c..332852f 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 0b6085ce720ffb7c78cf42d4e51861f34db27744 +Subproject commit 332852ffa81387b59f260781d7e5c967f24d8be2 From e5b2114ac250dec49e052ffc15ceae9955c0af4d Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 18 Feb 2025 14:13:13 +0000 Subject: [PATCH 605/786] [CI] Updating repo.json for testing_1.3.6.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 76009f4..992ca6a 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.1", + "TestingAssemblyVersion": "1.3.6.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 9e7679e70fecce6aa8abdb1a7b9eb769bd7a58e7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 19 Feb 2025 23:36:52 +0100 Subject: [PATCH 606/786] Move check for valid model to actor display instead of identifier list. --- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 7 ++++--- Glamourer/Interop/ObjectManager.cs | 2 +- OtterGui | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 76f0ba4..3269fd2 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -91,9 +91,10 @@ public class ActorSelector(ObjectManager objects, ActorManager actors, Ephemeral objects.Update(); _world = new WorldId(objects.Player.Valid ? objects.Player.HomeWorld : (ushort)0); - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); - var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); - var remainder = ImGuiClip.FilteredClippedDraw(objects.Identifiers, skips, CheckFilter, DrawSelectable); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); + var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); + var remainder = ImGuiClip.FilteredClippedDraw(objects.Identifiers.Where(p => p.Value.Objects.Any(a => a.Model)), skips, CheckFilter, + DrawSelectable); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index b185f4a..47f7ad5 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -83,7 +83,7 @@ public class ObjectManager( private void HandleIdentifier(ActorIdentifier identifier, Actor character) { - if (!character.Model || !identifier.IsValid) + if (!identifier.IsValid) return; if (!_identifiers.TryGetValue(identifier, out var data)) diff --git a/OtterGui b/OtterGui index 332852f..06422a8 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 332852ffa81387b59f260781d7e5c967f24d8be2 +Subproject commit 06422a893348a18a013e6dbc558370db8d21a446 From 5a9e9513f4cf62fff84237a6a68717e9d5857693 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 19 Feb 2025 23:42:05 +0100 Subject: [PATCH 607/786] Skip automatic updates from temporary settings when applied by design. --- .../Interop/Penumbra/ModSettingApplier.cs | 5 +++-- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 21 +++++++++++-------- .../Penumbra/PenumbraAutoRedrawSkip.cs | 15 +++++++++++++ Glamourer/State/StateEditor.cs | 2 +- 4 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 Glamourer/Interop/Penumbra/PenumbraAutoRedrawSkip.cs diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 5b27e6e..60f07e4 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -7,12 +7,12 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Penumbra; -public class ModSettingApplier(PenumbraService penumbra, Configuration config, ObjectManager objects, CollectionOverrideService overrides) +public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip autoRedrawSkip, Configuration config, ObjectManager objects, CollectionOverrideService overrides) : IService { private readonly HashSet _collectionTracker = []; - public void HandleStateApplication(ActorState state, MergedDesign design) + public void HandleStateApplication(ActorState state, MergedDesign design, bool skipAutoRedraw) { if (!config.AlwaysApplyAssociatedMods || (design.AssociatedMods.Count == 0 && !design.ResetTemporarySettings)) return; @@ -26,6 +26,7 @@ public class ModSettingApplier(PenumbraService penumbra, Configuration config, O } _collectionTracker.Clear(); + using var skip = autoRedrawSkip.SkipAutoUpdates(skipAutoRedraw); foreach (var actor in data.Objects) { var (collection, _, overridden) = overrides.GetCollection(actor, state.Identifier); diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index 3e48fe9..93d23c2 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -12,16 +12,18 @@ namespace Glamourer.Interop.Penumbra; public class PenumbraAutoRedraw : IDisposable, IRequiredService { - private const int WaitFrames = 5; - private readonly Configuration _config; - private readonly PenumbraService _penumbra; - private readonly StateManager _state; - private readonly ObjectManager _objects; - private readonly IFramework _framework; - private readonly StateChanged _stateChanged; + private const int WaitFrames = 5; + private readonly Configuration _config; + private readonly PenumbraService _penumbra; + private readonly StateManager _state; + private readonly ObjectManager _objects; + private readonly IFramework _framework; + private readonly StateChanged _stateChanged; + private readonly PenumbraAutoRedrawSkip _skip; + public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects, IFramework framework, - StateChanged stateChanged) + StateChanged stateChanged, PenumbraAutoRedrawSkip skip) { _penumbra = penumbra; _config = config; @@ -29,6 +31,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService _objects = objects; _framework = framework; _stateChanged = stateChanged; + _skip = skip; _penumbra.ModSettingChanged += OnModSettingChange; _framework.Update += OnFramework; _stateChanged.Subscribe(OnStateChanged, StateChanged.Priority.PenumbraAutoRedraw); @@ -94,7 +97,7 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService } }); } - else if (_config.AutoRedrawEquipOnChanges) + else if (_config.AutoRedrawEquipOnChanges && !_skip.Skip) { // Only update once per frame. var playerName = _penumbra.GetCurrentPlayerCollection(); diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedrawSkip.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedrawSkip.cs new file mode 100644 index 0000000..8ef522c --- /dev/null +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedrawSkip.cs @@ -0,0 +1,15 @@ +using OtterGui.Classes; +using OtterGui.Services; + +namespace Glamourer.Interop.Penumbra; + +public class PenumbraAutoRedrawSkip : IService +{ + private bool _skipAutoUpdates; + + public BoolSetter SkipAutoUpdates(bool skip) + => new(ref _skipAutoUpdates, skip); + + public bool Skip + => _skipAutoUpdates; +} diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index fec5b13..7eea607 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -262,7 +262,7 @@ public class StateEditor( public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings) { var state = (ActorState)data; - modApplier.HandleStateApplication(state, mergedDesign); + modApplier.HandleStateApplication(state, mergedDesign, true); if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize, mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key)) return; From c1f84b4303b76f935a1f6023f12308868dc4a00a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 20 Feb 2025 00:16:49 +0100 Subject: [PATCH 608/786] Differentiate between temporary settings through manual and automatic application. --- Glamourer/Automation/AutoDesignApplier.cs | 10 ++- Glamourer/Gui/DesignQuickBar.cs | 9 ++- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 77 +++++++++---------- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 5 +- Glamourer/Interop/ObjectManager.cs | 10 ++- .../Interop/Penumbra/ModSettingApplier.cs | 26 ++++--- Glamourer/Interop/Penumbra/PenumbraService.cs | 44 +++++++---- Glamourer/Services/CommandService.cs | 2 +- Glamourer/State/StateEditor.cs | 2 +- 9 files changed, 108 insertions(+), 77 deletions(-) diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 7f75674..545dff7 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -293,8 +293,16 @@ public sealed class AutoDesignApplier : IDisposable set.Designs.Where(d => d.IsActive(actor)) .SelectMany(d => d.Design.AllLinks(newApplication).Select(l => (l.Design, l.Flags & d.Type, d.Jobs.Flags))), state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods); - if (set.ResetTemporarySettings) + + if (_objects.IsInGPose && actor.IsGPoseOrCutscene) + { + mergedDesign.ResetTemporarySettings = false; + mergedDesign.AssociatedMods.Clear(); + } + else if (set.ResetTemporarySettings) + { mergedDesign.ResetTemporarySettings = true; + } _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false)); forcedRedraw = mergedDesign.ForcedRedraw; diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 1e2904c..b125b37 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -409,7 +409,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (_playerIdentifier.IsValid && _playerData.Valid) { available |= 1; - tooltip = $"Left-Click: Reset all temporary settings applied by Glamourer to the collection affecting {_playerIdentifier}."; + tooltip = $"Left-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting {_playerIdentifier}."; } if (_targetIdentifier.IsValid && _targetData.Valid) @@ -417,7 +417,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (available != 0) tooltip += '\n'; available |= 2; - tooltip += $"Right-Click: Reset all temporary settings applied by Glamourer to the collection affecting {_targetIdentifier}."; + tooltip += $"Right-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting {_targetIdentifier}."; } if (available == 0) @@ -426,7 +426,10 @@ public sealed class DesignQuickBar : Window, IDisposable var (clicked, _, data, _) = ResolveTarget(FontAwesomeIcon.Cog, buttonSize, tooltip, available); ImGui.SameLine(); if (clicked) - _penumbra.RemoveAllTemporarySettings(data.Objects[0].Index); + { + _penumbra.RemoveAllTemporarySettings(data.Objects[0].Index, StateSource.Manual); + _penumbra.RemoveAllTemporarySettings(data.Objects[0].Index, StateSource.Fixed); + } } private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip, diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index caa7d60..680e0e9 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -52,7 +52,7 @@ public class SetPanel( private void DrawPanel() { - using var child = ImRaii.Child("##Panel", -Vector2.One, true); + using var child = ImUtf8.Child("##Panel"u8, -Vector2.One, true); if (!child || !_selector.HasSelection) return; @@ -63,20 +63,20 @@ public class SetPanel( using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var enabled = Selection.Enabled; - if (ImGui.Checkbox("##Enabled", ref enabled)) + if (ImUtf8.Checkbox("##Enabled"u8, ref enabled)) _manager.SetState(_selector.SelectionIndex, enabled); - ImGuiUtil.LabeledHelpMarker("Enabled", - "Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time."); + ImUtf8.LabeledHelpMarker("Enabled"u8, + "Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time."u8); } using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game; - if (ImGui.Checkbox("##gameState", ref useGame)) + if (ImUtf8.Checkbox("##gameState"u8, ref useGame)) _manager.ChangeBaseState(_selector.SelectionIndex, useGame ? AutoDesignSet.Base.Game : AutoDesignSet.Base.Current); - ImGuiUtil.LabeledHelpMarker("Use Game State as Base", - "When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. " - + "Otherwise, they will be applied on top of the characters actual current look using Glamourer."); + ImUtf8.LabeledHelpMarker("Use Game State as Base"u8, + "When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. "u8 + + "Otherwise, they will be applied on top of the characters actual current look using Glamourer."u8); } } @@ -86,14 +86,14 @@ public class SetPanel( using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) { var editing = _config.ShowAutomationSetEditing; - if (ImGui.Checkbox("##Show Editing", ref editing)) + if (ImUtf8.Checkbox("##Show Editing"u8, ref editing)) { _config.ShowAutomationSetEditing = editing; _config.Save(); } - ImGuiUtil.LabeledHelpMarker("Show Editing", - "Show options to change the name or the associated character or NPC of this design set."); + ImUtf8.LabeledHelpMarker("Show Editing"u8, + "Show options to change the name or the associated character or NPC of this design set."u8); } using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) @@ -102,8 +102,8 @@ public class SetPanel( if (ImGui.Checkbox("##resetSettings", ref resetSettings)) _manager.ChangeResetSettings(_selector.SelectionIndex, resetSettings); - ImGuiUtil.LabeledHelpMarker("Reset Temporary Settings", - "Always reset all temporary settings applied by Glamourer when this automation set is applied, regardless of active designs."); + ImUtf8.LabeledHelpMarker("Reset Temporary Settings"u8, + "Always reset all temporary settings applied by Glamourer when this automation set is applied, regardless of active designs."u8); } } @@ -160,42 +160,42 @@ public class SetPanel( (false, false) => 4, }; - using var table = ImRaii.Table("SetTable", numRows, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY); + using var table = ImUtf8.Table("SetTable"u8, numRows, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY); if (!table) return; - ImGui.TableSetupColumn("##del", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight()); - ImGui.TableSetupColumn("##Index", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); + ImUtf8.TableSetupColumn("##del"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight()); + ImUtf8.TableSetupColumn("##Index"u8, ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); if (singleRow) { - ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale); + ImUtf8.TableSetupColumn("Design"u8, ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale); if (_config.ShowAllAutomatedApplicationRules) - ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed, + ImUtf8.TableSetupColumn("Application"u8, ImGuiTableColumnFlags.WidthFixed, 6 * ImGui.GetFrameHeight() + 10 * ImGuiHelpers.GlobalScale); else - ImGui.TableSetupColumn("Use", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X); + ImUtf8.TableSetupColumn("Use"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X); } else { - ImGui.TableSetupColumn("Design / Job Restrictions", ImGuiTableColumnFlags.WidthFixed, 250 * ImGuiHelpers.GlobalScale); + ImUtf8.TableSetupColumn("Design / Job Restrictions"u8, ImGuiTableColumnFlags.WidthFixed, 250 * ImGuiHelpers.GlobalScale); if (_config.ShowAllAutomatedApplicationRules) - ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed, + ImUtf8.TableSetupColumn("Application"u8, ImGuiTableColumnFlags.WidthFixed, 3 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale); else - ImGui.TableSetupColumn("Use", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X); + ImUtf8.TableSetupColumn("Use"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X); } if (singleRow) - ImGui.TableSetupColumn("Job Restrictions", ImGuiTableColumnFlags.WidthStretch); + ImUtf8.TableSetupColumn("Job Restrictions"u8, ImGuiTableColumnFlags.WidthStretch); if (_config.ShowUnlockedItemWarnings) - ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, 2 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale); + ImUtf8.TableSetupColumn(""u8, ImGuiTableColumnFlags.WidthFixed, 2 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale); ImGui.TableHeadersRow(); foreach (var (design, idx) in Selection.Designs.WithIndex()) { - using var id = ImRaii.PushId(idx); + using var id = ImUtf8.PushId(idx); ImGui.TableNextColumn(); var keyValid = _config.DeleteDesignModifier.IsActive(); var tt = keyValid @@ -205,7 +205,7 @@ public class SetPanel( if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, !keyValid, true)) _endAction = () => _manager.DeleteDesign(Selection, idx); ImGui.TableNextColumn(); - ImGui.Selectable($"#{idx + 1:D2}"); + ImUtf8.Selectable($"#{idx + 1:D2}"); DrawDragDrop(Selection, idx); ImGui.TableNextColumn(); DrawRandomEditing(Selection, design, idx); @@ -234,8 +234,7 @@ public class SetPanel( ImGui.TableNextColumn(); ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted("New"); + ImUtf8.TextFrameAligned("New"u8); ImGui.TableNextColumn(); _designCombo.Draw(Selection, null, -1); ImGui.TableNextRow(); @@ -250,20 +249,20 @@ public class SetPanel( private void DrawConditions(AutoDesign design, int idx) { var usingGearset = design.GearsetIndex >= 0; - if (ImGui.Button($"{(usingGearset ? "Gearset:" : "Jobs:")}##usingGearset")) + if (ImUtf8.Button($"{(usingGearset ? "Gearset:" : "Jobs:")}##usingGearset")) { usingGearset = !usingGearset; _manager.ChangeGearsetCondition(Selection, idx, (short)(usingGearset ? 0 : -1)); } - ImGuiUtil.HoverTooltip("Click to switch between Job and Gearset restrictions."); + ImUtf8.HoverTooltip("Click to switch between Job and Gearset restrictions."u8); ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); if (usingGearset) { var set = 1 + (_tmpGearset == int.MaxValue || _whichIndex != idx ? design.GearsetIndex : _tmpGearset); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - if (ImGui.InputInt("##whichGearset", ref set, 0, 0)) + if (ImUtf8.InputScalar("##whichGearset"u8, ref set)) { _whichIndex = idx; _tmpGearset = Math.Clamp(set, 1, 100); @@ -361,12 +360,12 @@ public class SetPanel( ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color); } - ImGuiUtil.HoverTooltip(sb.ToString()); + ImUtf8.HoverTooltip($"{sb}"); } else { ImGuiUtil.DrawTextButton(string.Empty, size, 0); - ImGuiUtil.HoverTooltip(good); + ImUtf8.HoverTooltip(good); } } } @@ -374,7 +373,7 @@ public class SetPanel( private void DrawDragDrop(AutoDesignSet set, int index) { const string dragDropLabel = "DesignDragDrop"; - using (var target = ImRaii.DragDropTarget()) + using (var target = ImUtf8.DragDropTarget()) { if (target.Success && ImGuiUtil.IsDropping(dragDropLabel)) { @@ -388,11 +387,11 @@ public class SetPanel( } } - using (var source = ImRaii.DragDropSource()) + using (var source = ImUtf8.DragDropSource()) { if (source) { - ImGui.TextUnformatted($"Moving design #{index + 1:D2}..."); + ImUtf8.Text($"Moving design #{index + 1:D2}..."); if (ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0)) { _dragIndex = index; @@ -415,16 +414,16 @@ public class SetPanel( } style.Pop(); - ImGuiUtil.HoverTooltip("Toggle all application modes at once."); + ImUtf8.HoverTooltip("Toggle all application modes at once."u8); if (_config.ShowAllAutomatedApplicationRules) { void Box(int idx) { var (type, description) = ApplicationTypeExtensions.Types[idx]; var value = design.Type.HasFlag(type); - if (ImGui.Checkbox($"##{(byte)type}", ref value)) + if (ImUtf8.Checkbox($"##{(byte)type}", ref value)) newType = value ? newType | type : newType & ~type; - ImGuiUtil.HoverTooltip(description); + ImUtf8.HoverTooltip(description); } ImGui.SameLine(); diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index feff657..5856fcc 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -4,6 +4,7 @@ using Dalamud.Interface.Utility; using Dalamud.Utility; using Glamourer.Designs; using Glamourer.Interop.Penumbra; +using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -83,7 +84,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect public void ApplyAll() { foreach (var (mod, settings) in selector.Selected!.AssociatedMods) - penumbra.SetMod(mod, settings); + penumbra.SetMod(mod, settings, StateSource.Manual); } private void DrawTable() @@ -218,7 +219,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect if (ImGuiUtil.DrawDisabledButton("Apply", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, !penumbra.Available)) { - var text = penumbra.SetMod(mod, settings); + var text = penumbra.SetMod(mod, settings, StateSource.Manual); if (text.Length > 0) Glamourer.Messager.NotificationMessage(text, NotificationType.Warning, false); } diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs index 47f7ad5..471a49b 100644 --- a/Glamourer/Interop/ObjectManager.cs +++ b/Glamourer/Interop/ObjectManager.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Control; +using Glamourer.Events; using Glamourer.Interop.Structs; using OtterGui.Log; using Penumbra.GameData.Actors; @@ -24,8 +25,11 @@ public class ObjectManager( => LastFrame; private DateTime _identifierUpdate; - public bool IsInGPose { get; private set; } - public ushort World { get; private set; } + + public bool IsInGPose + => clientState.IsGPosing; + + public ushort World { get; private set; } private readonly Dictionary _identifiers = new(200); private readonly Dictionary _allWorldIdentifiers = new(200); @@ -76,8 +80,6 @@ public class ObjectManager( HandleIdentifier(identifier, actor); } - var gPose = GPosePlayer; - IsInGPose = gPose.Utf8Name.Length > 0; return true; } diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 60f07e4..82a840c 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -12,7 +12,7 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip { private readonly HashSet _collectionTracker = []; - public void HandleStateApplication(ActorState state, MergedDesign design, bool skipAutoRedraw) + public void HandleStateApplication(ActorState state, MergedDesign design, StateSource source, bool skipAutoRedraw, bool respectManual) { if (!config.AlwaysApplyAssociatedMods || (design.AssociatedMods.Count == 0 && !design.ResetTemporarySettings)) return; @@ -36,10 +36,10 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip if (!_collectionTracker.Add(collection)) continue; - var index = ResetOldSettings(collection, actor, design.ResetTemporarySettings); + var index = ResetOldSettings(collection, actor, source, design.ResetTemporarySettings, respectManual); foreach (var (mod, setting) in design.AssociatedMods) { - var message = penumbra.SetMod(mod, setting, collection, index); + var message = penumbra.SetMod(mod, setting, source, collection, index); if (message.Length > 0) Glamourer.Log.Verbose($"[Mod Applier] Error applying mod settings: {message}"); else @@ -50,7 +50,7 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip } public (List Messages, int Applied, Guid Collection, string Name, bool Overridden) ApplyModSettings( - IReadOnlyDictionary settings, Actor actor, bool resetOther) + IReadOnlyDictionary settings, Actor actor, StateSource source, bool resetOther) { var (collection, name, overridden) = overrides.GetCollection(actor); if (collection == Guid.Empty) @@ -59,10 +59,10 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip var messages = new List(); var appliedMods = 0; - var index = ResetOldSettings(collection, actor, resetOther); + var index = ResetOldSettings(collection, actor, source, resetOther, true); foreach (var (mod, setting) in settings) { - var message = penumbra.SetMod(mod, setting, collection, index); + var message = penumbra.SetMod(mod, setting, source, collection, index); if (message.Length > 0) messages.Add($"Error applying mod settings: {message}"); else @@ -73,16 +73,24 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ObjectIndex? ResetOldSettings(Guid collection, Actor actor, bool resetOther) + private ObjectIndex? ResetOldSettings(Guid collection, Actor actor, StateSource source, bool resetOther, bool respectManual) { ObjectIndex? index = actor.Valid ? actor.Index : null; if (!resetOther) return index; if (index == null) - penumbra.RemoveAllTemporarySettings(collection); + { + penumbra.RemoveAllTemporarySettings(collection, source); + if (!respectManual && source.IsFixed()) + penumbra.RemoveAllTemporarySettings(collection, StateSource.Manual); + } else - penumbra.RemoveAllTemporarySettings(index.Value); + { + penumbra.RemoveAllTemporarySettings(index.Value, source); + if (!respectManual && source.IsFixed()) + penumbra.RemoveAllTemporarySettings(index.Value, StateSource.Manual); + } return index; } } diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index e0c445b..d9b4d27 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin; using Glamourer.Events; +using Glamourer.State; using OtterGui.Classes; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; @@ -40,7 +41,10 @@ public class PenumbraService : IDisposable public const int RequiredPenumbraFeatureVersionTemp3 = 6; public const int RequiredPenumbraFeatureVersionTemp4 = 7; - private const int Key = -1610; + private const int KeyFixed = -1610; + private const string NameFixed = "Glamourer (Automation)"; + private const int KeyManual = -6160; + private const string NameManual = "Glamourer (Manually)"; private readonly IDalamudPluginInterface _pluginInterface; private readonly Configuration _config; @@ -160,7 +164,7 @@ public class PenumbraService : IDisposable if (_getCurrentSettingsWithTemp != null) { source = string.Empty; - var (ec, tuple) = _getCurrentSettingsWithTemp!.Invoke(collection, modDirectory, modName, false, false, Key); + var (ec, tuple) = _getCurrentSettingsWithTemp!.Invoke(collection, modDirectory, modName, false, false, KeyFixed); if (ec is not PenumbraApiEc.Success) return ModSettings.Empty; @@ -216,7 +220,7 @@ public class PenumbraService : IDisposable { if (_getAllSettings != null) { - var allSettings = _getAllSettings.Invoke(collection, false, false, Key); + var allSettings = _getAllSettings.Invoke(collection, false, false, KeyFixed); if (allSettings.Item1 is PenumbraApiEc.Success) return mods.Select(m => (new Mod(m.Value, m.Key), allSettings.Item2!.TryGetValue(m.Key, out var s) @@ -276,7 +280,7 @@ public class PenumbraService : IDisposable /// Try to set all mod settings as desired. Only sets when the mod should be enabled. /// If it is disabled, ignore all other settings. /// - public string SetMod(Mod mod, ModSettings settings, Guid? collectionInput = null, ObjectIndex? index = null) + public string SetMod(Mod mod, ModSettings settings, StateSource source, Guid? collectionInput = null, ObjectIndex? index = null) { if (!Available) return "Penumbra is not available."; @@ -286,7 +290,7 @@ public class PenumbraService : IDisposable { var collection = collectionInput ?? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value.Id; if (_config.UseTemporarySettings && _setTemporaryModSettings != null) - SetModTemporary(sb, mod, settings, collection, index); + SetModTemporary(sb, mod, settings, collection, index, source); else SetModPermanent(sb, mod, settings, collection); @@ -298,37 +302,43 @@ public class PenumbraService : IDisposable } } - public void RemoveAllTemporarySettings(Guid collection) - => _removeAllTemporaryModSettings?.Invoke(collection, Key); + public void RemoveAllTemporarySettings(Guid collection, StateSource source) + => _removeAllTemporaryModSettings?.Invoke(collection, source.IsFixed() ? KeyFixed : KeyManual); - public void RemoveAllTemporarySettings(ObjectIndex index) - => _removeAllTemporaryModSettingsPlayer?.Invoke(index.Index, Key); + public void RemoveAllTemporarySettings(ObjectIndex index, StateSource source) + => _removeAllTemporaryModSettingsPlayer?.Invoke(index.Index, source.IsFixed() ? KeyFixed : KeyManual); - public void ClearAllTemporarySettings() + public void ClearAllTemporarySettings(bool fix, bool manual) { if (!Available || _removeAllTemporaryModSettings == null) return; var collections = _collections!.Invoke(); foreach (var collection in collections) - RemoveAllTemporarySettings(collection.Key); + { + if (fix) + RemoveAllTemporarySettings(collection.Key, StateSource.Fixed); + if (manual) + RemoveAllTemporarySettings(collection.Key, StateSource.Manual); + } } public (string ModDirectory, string ModName)[] CheckCurrentChangedItem(string changedItem) => _checkCurrentChangedItems?.Invoke(changedItem) ?? []; - private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, ObjectIndex? index) + private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, ObjectIndex? index, StateSource source) { + var (key, name) = source.IsFixed() ? (KeyFixed, NameFixed) : (KeyManual, NameManual); var ex = settings.Remove ? index.HasValue - ? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, Key) - : _removeTemporaryModSettings!.Invoke(collection, mod.DirectoryName, Key) + ? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, key) + : _removeTemporaryModSettings!.Invoke(collection, mod.DirectoryName, key) : index.HasValue ? _setTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, settings.ForceInherit, settings.Enabled, settings.Priority, - settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), "Glamourer", Key) + settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key) : _setTemporaryModSettings!.Invoke(collection, mod.DirectoryName, settings.ForceInherit, settings.Enabled, settings.Priority, - settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), "Glamourer", Key); + settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key); switch (ex) { case PenumbraApiEc.InvalidArgument: @@ -586,7 +596,7 @@ public class PenumbraService : IDisposable public void Dispose() { - ClearAllTemporarySettings(); + ClearAllTemporarySettings(true, true); Unattach(); _tooltipSubscriber.Dispose(); _clickSubscriber.Dispose(); diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index bffc072..4b8ea3d 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -694,7 +694,7 @@ public class CommandService : IDisposable, IApiService if (!applyMods || design is not Design d) return; - var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor, d.ResetTemporarySettings); + var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor, StateSource.Manual, d.ResetTemporarySettings); foreach (var message in messages) Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 7eea607..1fa1ffe 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -262,7 +262,7 @@ public class StateEditor( public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings) { var state = (ActorState)data; - modApplier.HandleStateApplication(state, mergedDesign, true); + modApplier.HandleStateApplication(state, mergedDesign, settings.Source, true, settings.RespectManual); if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize, mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key)) return; From 8a7ec45bbf215602b82b94970e9d8c5a80761e0a Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 19 Feb 2025 23:19:21 +0000 Subject: [PATCH 609/786] [CI] Updating repo.json for testing_1.3.6.3 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 992ca6a..31b5654 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.2", + "TestingAssemblyVersion": "1.3.6.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 0c8110e15e0f2200c9d95a27485f3bf50a3e6df4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 21 Feb 2025 00:10:30 +0100 Subject: [PATCH 610/786] Highlight existing advanced dyes in state and design. --- Glamourer/Gui/Colors.cs | 2 + Glamourer/Gui/Equipment/BonusDrawData.cs | 6 +- Glamourer/Gui/Equipment/EquipDrawData.cs | 14 +-- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 89 +++++++++++-------- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 35 ++++---- .../Interop/Material/MaterialValueManager.cs | 20 +++++ 6 files changed, 106 insertions(+), 60 deletions(-) diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index e19639c..98deace 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -32,6 +32,7 @@ public enum ColorId ModdedItemMarker, ContainsItemsEnabled, ContainsItemsDisabled, + AdvancedDyeActive, } public static class Colors @@ -70,6 +71,7 @@ public static class Colors ColorId.ModdedItemMarker => (0xFFFF20FF, "Modded Item Marker", "The color of dot in the unlocks overview tab signaling that the item is modded in the currently selected Penumbra collection." ), ColorId.ContainsItemsEnabled => (0xFFA0F0A0, "Enabled Mod Contains Design Items", "The color of enabled mods in the associated mod dropdown menu when they contain items used in this design." ), ColorId.ContainsItemsDisabled => (0x80A0F0A0, "Disabled Mod Contains Design Items", "The color of disabled mods in the associated mod dropdown menu when they contain items used in this design." ), + ColorId.AdvancedDyeActive => (0xFF58DDFF, "Advanced Dyes Active", "The highlight color for the advanced dye button and marker if any advanced dyes are active for this slot." ), _ => (0x00000000, string.Empty, string.Empty ), // @formatter:on }; diff --git a/Glamourer/Gui/Equipment/BonusDrawData.cs b/Glamourer/Gui/Equipment/BonusDrawData.cs index e19287a..c14de2a 100644 --- a/Glamourer/Gui/Equipment/BonusDrawData.cs +++ b/Glamourer/Gui/Equipment/BonusDrawData.cs @@ -1,4 +1,5 @@ using Glamourer.Designs; +using Glamourer.Interop.Material; using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -9,10 +10,11 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) { private IDesignEditor _editor = null!; private object _object = null!; - public readonly BonusItemFlag Slot = slot; + public readonly BonusItemFlag Slot = slot; public bool Locked; public bool DisplayApplication; public bool AllowRevert; + public bool HasAdvancedDyes; public readonly bool IsDesign => _object is Design; @@ -42,6 +44,7 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) CurrentApply = design.DoApplyBonusItem(slot), Locked = design.WriteProtected(), DisplayApplication = true, + HasAdvancedDyes = design.GetMaterialDataRef().CheckExistence(MaterialValueIndex.FromSlot(slot)), }; public static BonusDrawData FromState(StateManager manager, ActorState state, BonusItemFlag slot) @@ -53,5 +56,6 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) DisplayApplication = false, GameItem = state.BaseData.BonusItem(slot), AllowRevert = true, + HasAdvancedDyes = state.Materials.CheckExistence(MaterialValueIndex.FromSlot(slot)), }; } diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index b72e234..d13b9e4 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -1,4 +1,5 @@ using Glamourer.Designs; +using Glamourer.Interop.Material; using Glamourer.State; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -9,7 +10,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) { private IDesignEditor _editor = null!; private object _object = null!; - public readonly EquipSlot Slot = slot; + public readonly EquipSlot Slot = slot; public bool Locked; public bool DisplayApplication; public bool AllowRevert; @@ -29,15 +30,13 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) public readonly void SetApplyItem(bool value) { var manager = (DesignManager)_editor; - var design = (Design)_object; - manager.ChangeApplyItem(design, Slot, value); + manager.ChangeApplyItem((Design)_object, Slot, value); } public readonly void SetApplyStain(bool value) { var manager = (DesignManager)_editor; - var design = (Design)_object; - manager.ChangeApplyStains(design, Slot, value); + manager.ChangeApplyStains((Design)_object, Slot, value); } public EquipItem CurrentItem = designData.Item(slot); @@ -46,6 +45,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) public StainIds GameStains = default; public bool CurrentApply; public bool CurrentApplyStain; + public bool HasAdvancedDyes; public readonly Gender CurrentGender = designData.Customize.Gender; public readonly Race CurrentRace = designData.Customize.Race; @@ -58,6 +58,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) CurrentApply = design.DoApplyEquip(slot), CurrentApplyStain = design.DoApplyStain(slot), Locked = design.WriteProtected(), + HasAdvancedDyes = design.GetMaterialDataRef().CheckExistence(MaterialValueIndex.FromSlot(slot)), DisplayApplication = true, }; @@ -70,6 +71,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) DisplayApplication = false, GameItem = state.BaseData.Item(slot), GameStains = state.BaseData.Stain(slot), + HasAdvancedDyes = state.Materials.CheckExistence(MaterialValueIndex.FromSlot(slot)), AllowRevert = true, }; -} \ No newline at end of file +} diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index c8a4b11..2bd84e7 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -3,6 +3,7 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; using Glamourer.Events; using Glamourer.Gui.Materials; +using Glamourer.Interop.Material; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; @@ -63,6 +64,7 @@ public class EquipmentDrawer private Vector2 _iconSize; private float _comboLength; + private uint _advancedMaterialColor; public void Prepare() { @@ -74,7 +76,8 @@ public class EquipmentDrawer .Max(i => ImGui.CalcTextSize($"{i.Item2.Name} ({i.Item2.ModelString})").X) / ImGuiHelpers.GlobalScale; - _requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale; + _requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale; + _advancedMaterialColor = ColorId.AdvancedDyeActive.Value(); } private bool VerifyRestrictedGear(EquipDrawData data) @@ -91,7 +94,7 @@ public class EquipmentDrawer if (_config.HideApplyCheckmarks) equipDrawData.DisplayApplication = false; - using var id = ImRaii.PushId((int)equipDrawData.Slot); + using var id = ImUtf8.PushId((int)equipDrawData.Slot); var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -106,7 +109,7 @@ public class EquipmentDrawer if (_config.HideApplyCheckmarks) bonusDrawData.DisplayApplication = false; - using var id = ImRaii.PushId(100 + (int)bonusDrawData.Slot); + using var id = ImUtf8.PushId(100 + (int)bonusDrawData.Slot); var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -127,7 +130,7 @@ public class EquipmentDrawer offhand.DisplayApplication = false; } - using var id = ImRaii.PushId("Weapons"); + using var id = ImUtf8.PushId("Weapons"u8); var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); @@ -174,7 +177,7 @@ public class EquipmentDrawer change = true; } - ImGuiUtil.HoverTooltip($"{_config.DeleteDesignModifier.ToString()} and Right-click to clear."); + ImUtf8.HoverTooltip($"{_config.DeleteDesignModifier.ToString()} and Right-click to clear."); } return change; @@ -196,14 +199,13 @@ public class EquipmentDrawer } else if (equipDrawData.IsState) { - _advancedDyes.DrawButton(equipDrawData.Slot); + _advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); } if (VerifyRestrictedGear(equipDrawData)) label += " (Restricted)"; - ImGui.SameLine(); - ImGui.TextUnformatted(label); + DrawEquipLabel(equipDrawData is { IsDesign: true, HasAdvancedDyes: true }, label); } private void DrawBonusItemSmall(in BonusDrawData bonusDrawData) @@ -218,11 +220,10 @@ public class EquipmentDrawer } else if (bonusDrawData.IsState) { - _advancedDyes.DrawButton(bonusDrawData.Slot); + _advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); } - ImGui.SameLine(); - ImGui.TextUnformatted(label); + DrawEquipLabel(bonusDrawData is { IsDesign: true, HasAdvancedDyes: true }, label); } private void DrawWeaponsSmall(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) @@ -239,12 +240,12 @@ public class EquipmentDrawer } else if (mainhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.MainHand); + _advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); } if (allWeapons) mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})"; - WeaponHelpMarker(mainhandLabel); + WeaponHelpMarker(mainhand is { IsDesign: true, HasAdvancedDyes: true }, mainhandLabel); if (offhand.CurrentItem.Type is FullEquipType.Unknown) return; @@ -261,10 +262,10 @@ public class EquipmentDrawer } else if (offhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.OffHand); + _advancedDyes.DrawButton(EquipSlot.OffHand, offhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); } - WeaponHelpMarker(offhandLabel); + WeaponHelpMarker(offhand is { IsDesign: true, HasAdvancedDyes: true }, offhandLabel); } #endregion @@ -285,8 +286,8 @@ public class EquipmentDrawer DrawApply(equipDrawData); } - ImGui.SameLine(); - ImGui.TextUnformatted(label); + DrawEquipLabel(equipDrawData is { IsDesign: true, HasAdvancedDyes: true }, label); + DrawStain(equipDrawData, false); if (equipDrawData.DisplayApplication) { @@ -295,13 +296,13 @@ public class EquipmentDrawer } else if (equipDrawData.IsState) { - _advancedDyes.DrawButton(equipDrawData.Slot); + _advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); } if (VerifyRestrictedGear(equipDrawData)) { ImGui.SameLine(); - ImGui.TextUnformatted("(Restricted)"); + ImUtf8.Text("(Restricted)"u8); } } @@ -319,11 +320,10 @@ public class EquipmentDrawer } else if (bonusDrawData.IsState) { - _advancedDyes.DrawButton(bonusDrawData.Slot); + _advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u); } - ImGui.SameLine(); - ImGui.TextUnformatted(label); + DrawEquipLabel(bonusDrawData is { IsDesign: true, HasAdvancedDyes: true }, label); } private void DrawWeaponsNormal(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) @@ -334,7 +334,7 @@ public class EquipmentDrawer mainhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.MainHand); var left = ImGui.IsItemClicked(ImGuiMouseButton.Left); ImGui.SameLine(); - using (ImRaii.Group()) + using (ImUtf8.Group()) { DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, false, left); if (mainhand.DisplayApplication) @@ -343,7 +343,8 @@ public class EquipmentDrawer DrawApply(mainhand); } - WeaponHelpMarker(mainhandLabel, allWeapons ? mainhand.CurrentItem.Type.ToName() : null); + WeaponHelpMarker(mainhand is { IsDesign: true, HasAdvancedDyes: true }, mainhandLabel, + allWeapons ? mainhand.CurrentItem.Type.ToName() : null); DrawStain(mainhand, false); if (mainhand.DisplayApplication) @@ -353,7 +354,7 @@ public class EquipmentDrawer } else if (mainhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.MainHand); + _advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); } } @@ -364,7 +365,7 @@ public class EquipmentDrawer var right = ImGui.IsItemClicked(ImGuiMouseButton.Right); left = ImGui.IsItemClicked(ImGuiMouseButton.Left); ImGui.SameLine(); - using (ImRaii.Group()) + using (ImUtf8.Group()) { DrawOffhand(mainhand, offhand, out var offhandLabel, false, right, left); if (offhand.DisplayApplication) @@ -373,7 +374,7 @@ public class EquipmentDrawer DrawApply(offhand); } - WeaponHelpMarker(offhandLabel); + WeaponHelpMarker(offhand is { IsDesign: true, HasAdvancedDyes: true }, offhandLabel); DrawStain(offhand, false); if (offhand.DisplayApplication) @@ -381,9 +382,9 @@ public class EquipmentDrawer ImGui.SameLine(); DrawApplyStain(offhand); } - else if (mainhand.IsState) + else if (offhand.IsState) { - _advancedDyes.DrawButton(EquipSlot.OffHand); + _advancedDyes.DrawButton(EquipSlot.OffHand, offhand.HasAdvancedDyes ? _advancedMaterialColor : 0u); } } } @@ -468,14 +469,16 @@ public class EquipmentDrawer UiHelpers.OpenCombo($"##{combo.Label}"); using var disabled = ImRaii.Disabled(data.Locked); - var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id.BonusItem, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, + var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id.BonusItem, + small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); if (change) data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); - if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, EquipItem.BonusItemNothing(data.Slot), out var item)) + if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, EquipItem.BonusItemNothing(data.Slot), + out var item)) data.SetItem(item); } @@ -502,7 +505,7 @@ public class EquipmentDrawer (false, true, _) => ("Right-click to clear.\nControl and mouse wheel to scroll.", clearItem, true), (false, false, _) => ("Control and mouse wheel to scroll.", default, false), }; - ImGuiUtil.HoverTooltip(tt); + ImUtf8.HoverTooltip(tt); return clicked && valid; } @@ -544,8 +547,8 @@ public class EquipmentDrawer } } - if (unknown && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) - ImGui.SetTooltip("The weapon type could not be identified, thus changing it to other weapons of that type is not possible."); + if (unknown) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "The weapon type could not be identified, thus changing it to other weapons of that type is not possible."u8); } private void DrawOffhand(in EquipDrawData mainhand, in EquipDrawData offhand, out string label, bool small, bool clear, bool open) @@ -595,14 +598,14 @@ public class EquipmentDrawer #endregion - private static void WeaponHelpMarker(string label, string? type = null) + private void WeaponHelpMarker(bool hasAdvancedDyes, string label, string? type = null) { ImGui.SameLine(); ImGuiComponents.HelpMarker( "Changing weapons to weapons of different types can cause crashes, freezes, soft- and hard locks and cheating, " + "thus it is only allowed to change weapons to other weapons of the same type."); - ImGui.SameLine(); - ImGui.TextUnformatted(label); + DrawEquipLabel(hasAdvancedDyes, label); + if (type == null) return; @@ -610,4 +613,16 @@ public class EquipmentDrawer pos.Y += ImGui.GetFrameHeightWithSpacing(); ImGui.GetWindowDrawList().AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), $"({type})"); } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private void DrawEquipLabel(bool hasAdvancedDyes, string label) + { + ImGui.SameLine(); + using (ImRaii.PushColor(ImGuiCol.Text, _advancedMaterialColor, hasAdvancedDyes)) + { + ImUtf8.Text(label); + } + if (hasAdvancedDyes) + ImUtf8.HoverTooltip("This design has advanced dyes setup for this slot."u8); + } } diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index b842d7f..4fa93c1 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -51,28 +51,29 @@ public sealed unsafe class AdvancedDyePopup( return true; } - public void DrawButton(EquipSlot slot) - => DrawButton(MaterialValueIndex.FromSlot(slot)); + public void DrawButton(EquipSlot slot, uint color) + => DrawButton(MaterialValueIndex.FromSlot(slot), color); - public void DrawButton(BonusItemFlag slot) - => DrawButton(MaterialValueIndex.FromSlot(slot)); + public void DrawButton(BonusItemFlag slot, uint color) + => DrawButton(MaterialValueIndex.FromSlot(slot), color); - private void DrawButton(MaterialValueIndex index) + private void DrawButton(MaterialValueIndex index, uint color) { if (!config.UseAdvancedDyes) return; ImGui.SameLine(); - using var id = ImRaii.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); + using var id = ImUtf8.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); var isOpen = index == _drawIndex; - using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen) - .Push(ImGuiCol.Text, ColorId.HeaderButtons.Value(), isOpen) - .Push(ImGuiCol.Border, ColorId.HeaderButtons.Value(), isOpen)) + var (textColor, buttonColor) = isOpen + ? (ColorId.HeaderButtons.Value(), ImGui.GetColorU32(ImGuiCol.ButtonActive)) + : (color, 0u); + + using (ImRaii.PushColor(ImGuiCol.Border, textColor, isOpen)) { using var frame = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, isOpen); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Palette.ToIconString(), new Vector2(ImGui.GetFrameHeight()), - string.Empty, false, true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Palette, ""u8, default, false, textColor, buttonColor)) { _forceFocus = true; _selectedMaterial = byte.MaxValue; @@ -80,7 +81,7 @@ public sealed unsafe class AdvancedDyePopup( } } - ImGuiUtil.HoverTooltip("Open advanced dyes for this slot."); + ImUtf8.HoverTooltip("Open advanced dyes for this slot."u8); } private (string Path, string GamePath) ResourceName(MaterialValueIndex index) @@ -197,7 +198,7 @@ public sealed unsafe class AdvancedDyePopup( var width = 7 * ImGui.GetFrameHeight() // Buttons + 3 * ImGui.GetStyle().ItemSpacing.X // around text + 7 * ImGui.GetStyle().ItemInnerSpacing.X - + 200 * ImGuiHelpers.GlobalScale // Drags + + 200 * ImGuiHelpers.GlobalScale // Drags + 7 * UiBuilder.MonoFont.GetCharAdvance(' ') * ImGuiHelpers.GlobalScale // Row + 2 * ImGui.GetStyle().WindowPadding.X; var height = 19 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + 3 * ImGui.GetStyle().ItemSpacing.Y; @@ -305,8 +306,9 @@ public sealed unsafe class AdvancedDyePopup( { EquipSlot.MainHand => _state.ModelData.Weapon(EquipSlot.MainHand), EquipSlot.OffHand => _state.ModelData.Weapon(EquipSlot.OffHand), - EquipSlot.Unknown => _state.ModelData.BonusItem((index.SlotIndex - 16u).ToBonusSlot()).Armor().ToWeapon(0), // TODO: Handle better - _ => _state.ModelData.Armor(slot).ToWeapon(0), + EquipSlot.Unknown => + _state.ModelData.BonusItem((index.SlotIndex - 16u).ToBonusSlot()).Armor().ToWeapon(0), // TODO: Handle better + _ => _state.ModelData.Armor(slot).ToWeapon(0), }; value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual); } @@ -392,7 +394,8 @@ public sealed unsafe class AdvancedDyePopup( { var tmp = value; var minValue = ImGui.GetIO().KeyCtrl ? 0f : (float)Half.Epsilon; - if (!ImUtf8.DragScalar("##Gloss"u8, ref tmp, "%.1f G"u8, 0.001f, minValue, Math.Max(0.01f, 0.005f * value), ImGuiSliderFlags.AlwaysClamp)) + if (!ImUtf8.DragScalar("##Gloss"u8, ref tmp, "%.1f G"u8, 0.001f, minValue, Math.Max(0.01f, 0.005f * value), + ImGuiSliderFlags.AlwaysClamp)) return false; var tmp2 = Math.Clamp(tmp, minValue, (float)Half.MaxValue); diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index f1ec440..be52b9c 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -6,6 +6,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Structs; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using static Penumbra.GameData.Files.ShpkFile; namespace Glamourer.Interop.Material; @@ -280,6 +282,24 @@ public readonly struct MaterialValueManager return true; } + public bool CheckExistence(MaterialValueIndex index) + { + if (_values.Count == 0) + return false; + + var key = index.Key; + var idx = Search(key); + if (idx >= 0) + return true; + + idx = ~idx; + if (idx >= _values.Count) + return false; + + var nextItem = MaterialValueIndex.FromKey(_values[idx].Key); + return nextItem.DrawObject == index.DrawObject && nextItem.SlotIndex == index.SlotIndex; + } + public bool RemoveValue(MaterialValueIndex index) => RemoveValue(index.Key); From 425c9471fbaa422d1185dbde7d6eff91133bdb09 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 23 Feb 2025 23:19:24 +0100 Subject: [PATCH 611/786] Highlight materials with advanced dyes even if inactive, allow removing inactive. --- Glamourer/Gui/Equipment/BonusDrawData.cs | 4 +- Glamourer/Gui/Equipment/EquipDrawData.cs | 4 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 75 ++++++++++--------- .../Interop/Material/MaterialValueManager.cs | 24 ++++-- 4 files changed, 63 insertions(+), 44 deletions(-) diff --git a/Glamourer/Gui/Equipment/BonusDrawData.cs b/Glamourer/Gui/Equipment/BonusDrawData.cs index c14de2a..067c0c6 100644 --- a/Glamourer/Gui/Equipment/BonusDrawData.cs +++ b/Glamourer/Gui/Equipment/BonusDrawData.cs @@ -44,7 +44,7 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) CurrentApply = design.DoApplyBonusItem(slot), Locked = design.WriteProtected(), DisplayApplication = true, - HasAdvancedDyes = design.GetMaterialDataRef().CheckExistence(MaterialValueIndex.FromSlot(slot)), + HasAdvancedDyes = design.GetMaterialDataRef().CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)), }; public static BonusDrawData FromState(StateManager manager, ActorState state, BonusItemFlag slot) @@ -56,6 +56,6 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData) DisplayApplication = false, GameItem = state.BaseData.BonusItem(slot), AllowRevert = true, - HasAdvancedDyes = state.Materials.CheckExistence(MaterialValueIndex.FromSlot(slot)), + HasAdvancedDyes = state.Materials.CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)), }; } diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index d13b9e4..77f4533 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -58,7 +58,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) CurrentApply = design.DoApplyEquip(slot), CurrentApplyStain = design.DoApplyStain(slot), Locked = design.WriteProtected(), - HasAdvancedDyes = design.GetMaterialDataRef().CheckExistence(MaterialValueIndex.FromSlot(slot)), + HasAdvancedDyes = design.GetMaterialDataRef().CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)), DisplayApplication = true, }; @@ -71,7 +71,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) DisplayApplication = false, GameItem = state.BaseData.Item(slot), GameStains = state.BaseData.Stain(slot), - HasAdvancedDyes = state.Materials.CheckExistence(MaterialValueIndex.FromSlot(slot)), + HasAdvancedDyes = state.Materials.CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)), AllowRevert = true, }; } diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 4fa93c1..d6d7420 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -8,7 +8,6 @@ using Glamourer.Designs; using Glamourer.Interop.Material; using Glamourer.State; using ImGuiNET; -using OtterGui; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; @@ -102,11 +101,12 @@ public sealed unsafe class AdvancedDyePopup( private void DrawTabBar(ReadOnlySpan> textures, ReadOnlySpan> materials, ref bool firstAvailable) { - using var bar = ImRaii.TabBar("tabs"); + using var bar = ImUtf8.TabBar("tabs"u8); if (!bar) return; - var table = new ColorTable.Table(); + var table = new ColorTable.Table(); + var highLightColor = ColorId.AdvancedDyeActive.Value(); for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i) { var index = _drawIndex!.Value with { MaterialIndex = i }; @@ -125,17 +125,30 @@ public sealed unsafe class AdvancedDyePopup( if (available) firstAvailable = false; - using var tab = _label.TabItem(i, select); + var hasAdvancedDyes = _state.Materials.CheckExistenceMaterial(index); + using var c = ImRaii.PushColor(ImGuiCol.Text, highLightColor, hasAdvancedDyes); + using var tab = _label.TabItem(i, select); + c.Pop(); if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { using var enabled = ImRaii.Enabled(); var (path, gamePath) = ResourceName(index); + using var tt = ImUtf8.Tooltip(); + if (gamePath.Length == 0 || path.Length == 0) - ImGui.SetTooltip("This material does not exist."); + ImUtf8.Text("This material does not exist."u8); else if (!available) - ImGui.SetTooltip($"This material does not have an associated color set.\n\n{gamePath}\n{path}"); + ImUtf8.Text($"This material does not have an associated color set.\n\n{gamePath}\n{path}"); else - ImGui.SetTooltip($"{gamePath}\n{path}"); + ImUtf8.Text($"{gamePath}\n{path}"); + + if (hasAdvancedDyes && !available) + { + ImUtf8.Text("\nRight-Click to remove ineffective advanced dyes."u8); + if (ImGui.IsMouseClicked(ImGuiMouseButton.Right)) + for (byte row = 0; row < ColorTable.NumRows; ++row) + stateManager.ResetMaterialValue(_state, index with { RowIndex = row }, ApplySettings.Game); + } } if ((tab.Success || select is ImGuiTabItemFlags.SetSelected) && available) @@ -174,7 +187,7 @@ public sealed unsafe class AdvancedDyePopup( DrawTabBar(textures, materials, ref firstAvailable); if (firstAvailable) - ImGui.TextUnformatted("No Editable Materials available."); + ImUtf8.Text("No Editable Materials available."u8); } private void DrawWindow(ReadOnlySpan> textures, ReadOnlySpan> materials) @@ -256,25 +269,23 @@ public sealed unsafe class AdvancedDyePopup( { using var id = ImRaii.PushId(100); var buttonSize = new Vector2(ImGui.GetFrameHeight()); - ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), buttonSize, "Highlight all affected colors on the character.", - false, true); + ImUtf8.IconButton(FontAwesomeIcon.Crosshairs, "Highlight all affected colors on the character."u8, buttonSize); if (ImGui.IsItemHovered()) preview.OnHover(materialIndex with { RowIndex = byte.MaxValue }, _actor.Index, table); ImGui.SameLine(); ImGui.AlignTextToFramePadding(); using (ImRaii.PushFont(UiBuilder.MonoFont)) { - ImGui.TextUnformatted("All Color Row Pairs (1-16)"); + ImUtf8.Text("All Color Row Pairs (1-16)"u8); } var spacing = ImGui.GetStyle().ItemInnerSpacing.X; ImGui.SameLine(ImGui.GetWindowSize().X - 3 * buttonSize.X - 2 * spacing - ImGui.GetStyle().WindowPadding.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this table to your clipboard.", false, - true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Export this table to your clipboard."u8, buttonSize)) ColorRowClipboard.Table = table; ImGui.SameLine(0, spacing); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize, - "Import an exported table from your clipboard onto this table.", !ColorRowClipboard.IsTableSet, true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported table from your clipboard onto this table."u8, buttonSize, + !ColorRowClipboard.IsTableSet)) for (var idx = 0; idx < ColorTable.NumRows; ++idx) { var row = ColorRowClipboard.Table[idx]; @@ -288,15 +299,14 @@ public sealed unsafe class AdvancedDyePopup( } ImGui.SameLine(0, spacing); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this table to game state.", !_anyChanged, - true)) + if (ImUtf8.IconButton(FontAwesomeIcon.UndoAlt, "Reset this table to game state."u8, buttonSize, !_anyChanged)) for (byte i = 0; i < ColorTable.NumRows; ++i) stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = i }, ApplySettings.Game); } private void DrawRow(ref ColorTableRow row, MaterialValueIndex index, in ColorTable.Table table) { - using var id = ImRaii.PushId(index.RowIndex); + using var id = ImUtf8.PushId(index.RowIndex); var changed = _state.Materials.TryGetValue(index, out var value); if (!changed) { @@ -319,8 +329,7 @@ public sealed unsafe class AdvancedDyePopup( } var buttonSize = new Vector2(ImGui.GetFrameHeight()); - ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), buttonSize, "Highlight the affected colors on the character.", - false, true); + ImUtf8.IconButton(FontAwesomeIcon.Crosshairs, "Highlight the affected colors on the character."u8, buttonSize); if (ImGui.IsItemHovered()) preview.OnHover(index, _actor.Index, table); @@ -330,28 +339,28 @@ public sealed unsafe class AdvancedDyePopup( { var rowIndex = index.RowIndex / 2 + 1; var rowSuffix = (index.RowIndex & 1) == 0 ? 'A' : 'B'; - ImGui.TextUnformatted($"Row {rowIndex,2}{rowSuffix}"); + ImUtf8.Text($"Row {rowIndex,2}{rowSuffix}"); } ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); - var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, - v => value.Model.Diffuse = v, "D"); + var applied = ImUtf8.ColorPicker("##diffuse"u8, "Change the diffuse value for this row."u8, value.Model.Diffuse, + v => value.Model.Diffuse = v, "D"u8); var spacing = ImGui.GetStyle().ItemInnerSpacing; ImGui.SameLine(0, spacing.X); - applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular, - v => value.Model.Specular = v, "S"); + applied |= ImUtf8.ColorPicker("##specular"u8, "Change the specular value for this row."u8, value.Model.Specular, + v => value.Model.Specular = v, "S"u8); ImGui.SameLine(0, spacing.X); - applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive, - v => value.Model.Emissive = v, "E"); + applied |= ImUtf8.ColorPicker("##emissive"u8, "Change the emissive value for this row."u8, value.Model.Emissive, + v => value.Model.Emissive = v, "E"u8); ImGui.SameLine(0, spacing.X); if (_mode is not ColorRow.Mode.Dawntrail) { ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); applied |= DragGloss(ref value.Model.GlossStrength); - ImGuiUtil.HoverTooltip("Change the gloss strength for this row."); + ImUtf8.HoverTooltip("Change the gloss strength for this row."u8); } else { @@ -363,7 +372,7 @@ public sealed unsafe class AdvancedDyePopup( { ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); applied |= DragSpecularStrength(ref value.Model.SpecularStrength); - ImGuiUtil.HoverTooltip("Change the specular strength for this row."); + ImUtf8.HoverTooltip("Change the specular strength for this row."u8); } else { @@ -371,19 +380,17 @@ public sealed unsafe class AdvancedDyePopup( } ImGui.SameLine(0, spacing.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", false, - true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Export this row to your clipboard."u8, buttonSize)) ColorRowClipboard.Row = value.Model; ImGui.SameLine(0, spacing.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize, - "Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet, true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported row from your clipboard onto this row."u8, buttonSize, !ColorRowClipboard.IsSet)) { value.Model = ColorRowClipboard.Row; applied = true; } ImGui.SameLine(0, spacing.X); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this row to game state.", !changed, true)) + if (ImUtf8.IconButton(FontAwesomeIcon.UndoAlt, "Reset this row to game state."u8, buttonSize, !changed)) stateManager.ResetMaterialValue(_state, index, ApplySettings.Game); if (applied) diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index be52b9c..520dacf 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -282,22 +282,34 @@ public readonly struct MaterialValueManager return true; } - public bool CheckExistence(MaterialValueIndex index) + public bool CheckExistenceSlot(MaterialValueIndex index) + { + var key = CheckExistence(index); + return key.Valid && key.DrawObject == index.DrawObject && key.SlotIndex == index.SlotIndex; + } + + public bool CheckExistenceMaterial(MaterialValueIndex index) + { + var key = CheckExistence(index); + return key.Valid && key.DrawObject == index.DrawObject && key.SlotIndex == index.SlotIndex && key.MaterialIndex == index.MaterialIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private MaterialValueIndex CheckExistence(MaterialValueIndex index) { if (_values.Count == 0) - return false; + return MaterialValueIndex.Invalid; var key = index.Key; var idx = Search(key); if (idx >= 0) - return true; + return index; idx = ~idx; if (idx >= _values.Count) - return false; + return MaterialValueIndex.Invalid; - var nextItem = MaterialValueIndex.FromKey(_values[idx].Key); - return nextItem.DrawObject == index.DrawObject && nextItem.SlotIndex == index.SlotIndex; + return MaterialValueIndex.FromKey(_values[idx].Key); } public bool RemoveValue(MaterialValueIndex index) From 528aae7eeeeb49639e0470bd869ce1de205fcf73 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 2 Mar 2025 13:24:51 +0100 Subject: [PATCH 612/786] Remove PalettePlusChecker and config options to disable Advanced Customization and Advanced Dyes. --- Glamourer/Api/StateApi.cs | 2 +- Glamourer/Configuration.cs | 2 - Glamourer/Designs/ApplicationRules.cs | 3 - Glamourer/Glamourer.cs | 2 - Glamourer/Gui/DesignQuickBar.cs | 6 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 6 -- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 3 - Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 64 +++++++------------ Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 53 +++++++-------- Glamourer/Interop/Material/MaterialManager.cs | 3 - .../Interop/PalettePlus/PalettePlusChecker.cs | 49 -------------- Glamourer/State/StateApplier.cs | 26 +++----- Glamourer/State/StateListener.cs | 17 ++--- Glamourer/State/StateManager.cs | 2 +- 14 files changed, 66 insertions(+), 172 deletions(-) delete mode 100644 Glamourer/Interop/PalettePlus/PalettePlusChecker.cs diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index b2fdc9b..c27abb7 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -314,7 +314,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable if (!state.CanUnlock(key)) return (GlamourerApiEc.InvalidKey, null); - return (GlamourerApiEc.Success, _converter.ShareJObject(state, ApplicationRules.AllWithConfig(_config))); + return (GlamourerApiEc.Success, _converter.ShareJObject(state, ApplicationRules.All)); } private (GlamourerApiEc, string?) ConvertBase64(ActorState? state, uint key) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 4b59191..019bb2b 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -55,8 +55,6 @@ public class Configuration : IPluginConfiguration, ISavable public bool ShowQuickBarInTabs { get; set; } = true; public bool OpenWindowAtStart { get; set; } = false; public bool ShowWindowWhenUiHidden { get; set; } = false; - public bool UseAdvancedParameters { get; set; } = true; - public bool UseAdvancedDyes { get; set; } = true; public bool KeepAdvancedDyesAttached { get; set; } = true; public bool ShowPalettePlusImport { get; set; } = true; public bool UseFloatForColors { get; set; } = true; diff --git a/Glamourer/Designs/ApplicationRules.cs b/Glamourer/Designs/ApplicationRules.cs index 703a3ae..0df4feb 100644 --- a/Glamourer/Designs/ApplicationRules.cs +++ b/Glamourer/Designs/ApplicationRules.cs @@ -19,9 +19,6 @@ public readonly struct ApplicationRules(ApplicationCollection application, bool public static ApplicationRules AllButParameters(ActorState state) => new(ApplicationCollection.All with { Parameters = ComputeParameters(state.ModelData, state.BaseData, All.Parameters) }, true); - public static ApplicationRules AllWithConfig(Configuration config) - => new(ApplicationCollection.All with { Parameters = config.UseAdvancedParameters ? All.Parameters : 0 }, config.UseAdvancedDyes); - public static ApplicationRules NpcFromModifiers(bool ctrl, bool shift) { var equip = ctrl || !shift ? EquipFlagExtensions.All : 0; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index cf0b278..5d38e3a 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -69,8 +69,6 @@ public class Glamourer : IDalamudPlugin sb.Append($"> **`Auto-Reload Gear: `** {config.AutoRedrawEquipOnChanges}\n"); sb.Append($"> **`Revert on Zone Change:`** {config.RevertManualChangesOnZoneChange}\n"); sb.Append($"> **`Festival Easter-Eggs: `** {config.DisableFestivals}\n"); - sb.Append($"> **`Advanced Customize: `** {config.UseAdvancedParameters}\n"); - sb.Append($"> **`Advanced Dye: `** {config.UseAdvancedDyes}\n"); sb.Append($"> **`Apply Entire Weapon: `** {config.ChangeEntireItem}\n"); sb.Append($"> **`Apply Associated Mods:`** {config.AlwaysApplyAssociatedMods}\n"); sb.Append($"> **`Show QDB: `** {config.Ephemeral.ShowDesignQuickBar}\n"); diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index b125b37..cabd888 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -11,7 +11,6 @@ using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; -using OtterGui; using OtterGui.Classes; using OtterGui.Text; using Penumbra.GameData.Actors; @@ -304,9 +303,6 @@ public sealed class DesignQuickBar : Window, IDisposable private void DrawRevertAdvancedCustomization(Vector2 buttonSize) { - if (!_config.UseAdvancedParameters) - return; - if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) return; @@ -475,7 +471,7 @@ public sealed class DesignQuickBar : Window, IDisposable ++_numButtons; } - if ((_config.UseAdvancedParameters || _config.UseAdvancedDyes) && _config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) + if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize)) ++_numButtons; diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index d6d7420..6d0bb70 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -38,9 +38,6 @@ public sealed unsafe class AdvancedDyePopup( private bool ShouldBeDrawn() { - if (!config.UseAdvancedDyes) - return false; - if (_drawIndex is not { Valid: true }) return false; @@ -58,9 +55,6 @@ public sealed unsafe class AdvancedDyePopup( private void DrawButton(MaterialValueIndex index, uint color) { - if (!config.UseAdvancedDyes) - return; - ImGui.SameLine(); using var id = ImUtf8.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); var isOpen = index == _drawIndex; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 9c8f3cf..e56266d 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -239,9 +239,6 @@ public class ActorPanel private void DrawParameterHeader() { - if (!_config.UseAdvancedParameters) - return; - using var h = ImUtf8.CollapsingHeaderId("Advanced Customizations"u8); if (!h) return; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 748afea..554e671 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -180,10 +180,7 @@ public class DesignPanel private void DrawCustomizeParameters() { - if (!_config.UseAdvancedParameters) - return; - - using var h = ImRaii.CollapsingHeader("Advanced Customizations"); + using var h = ImUtf8.CollapsingHeaderId("Advanced Customizations"u8); if (!h) return; @@ -192,10 +189,7 @@ public class DesignPanel private void DrawMaterialValues() { - if (!_config.UseAdvancedDyes) - return; - - using var h = ImRaii.CollapsingHeader("Advanced Dyes"); + using var h = ImUtf8.CollapsingHeaderId("Advanced Dyes"u8); if (!h) return; @@ -204,7 +198,7 @@ public class DesignPanel private void DrawCustomizeApplication() { - using var id = ImRaii.PushId("Customizations"); + using var id = ImUtf8.PushId("Customizations"u8); var set = _selector.Selected!.CustomizeSet; var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : @@ -219,55 +213,52 @@ public class DesignPanel } var applyClan = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Clan); - if (ImGui.Checkbox($"Apply {CustomizeIndex.Clan.ToDefaultName()}", ref applyClan)) + if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Clan.ToDefaultName()}", ref applyClan)) _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Clan, applyClan); var applyGender = _selector.Selected!.DoApplyCustomize(CustomizeIndex.Gender); - if (ImGui.Checkbox($"Apply {CustomizeIndex.Gender.ToDefaultName()}", ref applyGender)) + if (ImUtf8.Checkbox($"Apply {CustomizeIndex.Gender.ToDefaultName()}", ref applyGender)) _manager.ChangeApplyCustomize(_selector.Selected!, CustomizeIndex.Gender, applyGender); foreach (var index in CustomizationExtensions.All.Where(set.IsAvailable)) { var apply = _selector.Selected!.DoApplyCustomize(index); - if (ImGui.Checkbox($"Apply {set.Option(index)}", ref apply)) + if (ImUtf8.Checkbox($"Apply {set.Option(index)}", ref apply)) _manager.ChangeApplyCustomize(_selector.Selected!, index, apply); } } private void DrawCrestApplication() { - using var id = ImRaii.PushId("Crests"); + using var id = ImUtf8.PushId("Crests"u8); var flags = (uint)_selector.Selected!.Application.Crest; var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); foreach (var flag in CrestExtensions.AllRelevantSet) { var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(flag); - if (ImGui.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) + if (ImUtf8.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) _manager.ChangeApplyCrest(_selector.Selected!, flag, apply); } } private void DrawApplicationRules() { - using var h = ImRaii.CollapsingHeader("Application Rules"); + using var h = ImUtf8.CollapsingHeaderId("Application Rules"u8); if (!h) return; using var disabled = ImRaii.Disabled(_selector.Selected!.WriteProtected()); - using (var _ = ImRaii.Group()) + using (var _ = ImUtf8.Group()) { DrawCustomizeApplication(); ImUtf8.IconDummy(); DrawCrestApplication(); ImUtf8.IconDummy(); - if (_config.UseAdvancedParameters) - { - DrawMetaApplication(); - ImUtf8.IconDummy(); - DrawBonusSlotApplication(); - } + DrawMetaApplication(); + ImUtf8.IconDummy(); + DrawBonusSlotApplication(); } ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); @@ -276,20 +267,20 @@ public class DesignPanel void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable slots) { var flags = (uint)(allFlags & _selector.Selected!.Application.Equip); - using var id = ImRaii.PushId(label); + using var id = ImUtf8.PushId(label); var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); if (stain) foreach (var slot in slots) { var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : _selector.Selected!.DoApplyStain(slot); - if (ImGui.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange) + if (ImUtf8.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange) _manager.ChangeApplyStains(_selector.Selected!, slot, apply); } else foreach (var slot in slots) { var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selector.Selected!.DoApplyEquip(slot); - if (ImGui.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange) + if (ImUtf8.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange) _manager.ChangeApplyItem(_selector.Selected!, slot, apply); } } @@ -311,16 +302,7 @@ public class DesignPanel EquipSlotExtensions.FullSlots); ImUtf8.IconDummy(); - if (_config.UseAdvancedParameters) - { - DrawParameterApplication(); - } - else - { - DrawMetaApplication(); - ImUtf8.IconDummy(); - DrawBonusSlotApplication(); - } + DrawParameterApplication(); } } @@ -334,7 +316,7 @@ public class DesignPanel private void DrawMetaApplication() { - using var id = ImRaii.PushId("Meta"); + using var id = ImUtf8.PushId("Meta"); const uint all = (uint)MetaExtensions.All; var flags = (uint)_selector.Selected!.Application.Meta; var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); @@ -342,7 +324,7 @@ public class DesignPanel foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(MetaLabels)) { var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selector.Selected!.DoApplyMeta(index); - if (ImGui.Checkbox(label, ref apply) || bigChange) + if (ImUtf8.Checkbox(label, ref apply) || bigChange) _manager.ChangeApplyMeta(_selector.Selected!, index, apply); } } @@ -368,20 +350,20 @@ public class DesignPanel private void DrawParameterApplication() { - using var id = ImRaii.PushId("Parameter"); + using var id = ImUtf8.PushId("Parameter"); var flags = (uint)_selector.Selected!.Application.Parameters; var bigChange = ImGui.CheckboxFlags("Apply All Customize Parameters", ref flags, (uint)CustomizeParameterExtensions.All); foreach (var flag in CustomizeParameterExtensions.AllFlags) { var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : _selector.Selected!.DoApplyParameter(flag); - if (ImGui.Checkbox($"Apply {flag.ToName()}", ref apply) || bigChange) + if (ImUtf8.Checkbox($"Apply {flag.ToName()}", ref apply) || bigChange) _manager.ChangeApplyParameter(_selector.Selected!, flag, apply); } } public void Draw() { - using var group = ImRaii.Group(); + using var group = ImUtf8.Group(); if (_selector.SelectedPaths.Count > 1) { _multiDesignPanel.Draw(); @@ -419,10 +401,12 @@ public class DesignPanel using var table = ImUtf8.Table("##Panel", 1, ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, ImGui.GetContentRegionAvail()); if (!table || _selector.Selected == null) return; + ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableNextColumn(); if (_selector.Selected == null) return; + ImGui.Dummy(Vector2.Zero); DrawButtonRow(); ImGui.TableNextColumn(); diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 4ee261b..fd4f3f5 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -24,7 +24,6 @@ public class SettingsTab( IKeyState keys, DesignColorUi designColorUi, PaletteImport paletteImport, - PalettePlusChecker paletteChecker, CollectionOverrideDrawer overrides, CodeDrawer codeDrawer, Glamourer glamourer, @@ -93,20 +92,15 @@ public class SettingsTab( Checkbox("Revert Manual Changes on Zone Change"u8, "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone."u8, config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); - Checkbox("Enable Advanced Customization Options"u8, - "Enable the display and editing of advanced customization options like arbitrary colors."u8, - config.UseAdvancedParameters, paletteChecker.SetAdvancedParameters); PaletteImportButton(); - Checkbox("Enable Advanced Dye Options"u8, - "Enable the display and editing of advanced dyes (color sets) for all equipment"u8, - config.UseAdvancedDyes, v => config.UseAdvancedDyes = v); Checkbox("Always Apply Associated Mods"u8, "Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n"u8 + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n"u8 + "If you enable this setting, you are aware that any resulting misconfiguration is your own fault."u8, config.AlwaysApplyAssociatedMods, v => config.AlwaysApplyAssociatedMods = v); Checkbox("Use Temporary Mod Settings"u8, - "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down."u8, config.UseTemporarySettings, + "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down."u8, + config.UseTemporarySettings, v => config.UseTemporarySettings = v); ImGui.NewLine(); } @@ -152,7 +146,7 @@ public class SettingsTab( ImGui.Dummy(Vector2.Zero); Checkbox("Enable Game Context Menus"u8, "Whether to show a Try On via Glamourer button on context menus for equippable items."u8, - config.EnableGameContextMenu, v => + config.EnableGameContextMenu, v => { config.EnableGameContextMenu = v; if (v) @@ -161,12 +155,13 @@ public class SettingsTab( contextMenuService.Disable(); }); Checkbox("Show Window when UI is Hidden"u8, "Whether to show Glamourer windows even when the games UI is hidden."u8, - config.ShowWindowWhenUiHidden, v => + config.ShowWindowWhenUiHidden, v => { config.ShowWindowWhenUiHidden = v; uiBuilder.DisableUserUiHide = v; }); - Checkbox("Hide Window in Cutscenes"u8, "Whether the main Glamourer window should automatically be hidden when entering cutscenes or not."u8, + Checkbox("Hide Window in Cutscenes"u8, + "Whether the main Glamourer window should automatically be hidden when entering cutscenes or not."u8, config.HideWindowInCutscene, v => { @@ -177,13 +172,13 @@ public class SettingsTab( config.Ephemeral.LockMainWindow, v => config.Ephemeral.LockMainWindow = v); Checkbox("Open Main Window at Game Start"u8, "Whether the main Glamourer window should be open or closed after launching the game."u8, - config.OpenWindowAtStart, v => config.OpenWindowAtStart = v); + config.OpenWindowAtStart, v => config.OpenWindowAtStart = v); ImGui.Dummy(Vector2.Zero); ImGui.Separator(); ImGui.Dummy(Vector2.Zero); Checkbox("Smaller Equip Display"u8, "Use single-line display without icons and small dye buttons instead of double-line display."u8, - config.SmallEquip, v => config.SmallEquip = v); + config.SmallEquip, v => config.SmallEquip = v); DrawHeightUnitSettings(); Checkbox("Show Application Checkboxes"u8, "Show the application checkboxes in the Customization and Equipment panels of the design tab, instead of only showing them under Application Rules."u8, @@ -211,23 +206,22 @@ public class SettingsTab( Checkbox("Show Unobtained Item Warnings"u8, "Show information whether you have unlocked all items and customizations in your automated design or not."u8, config.ShowUnlockedItemWarnings, v => config.ShowUnlockedItemWarnings = v); - if (config.UseAdvancedParameters) + Checkbox("Show Color Display Config"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8, + config.ShowColorConfig, v => config.ShowColorConfig = v); + Checkbox("Show Palette+ Import Button"u8, + "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs."u8, + config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v); + using (ImRaii.PushId(1)) { - Checkbox("Show Color Display Config"u8, "Show the Color Display configuration options in the Advanced Customization panels."u8, - config.ShowColorConfig, v => config.ShowColorConfig = v); - Checkbox("Show Palette+ Import Button"u8, - "Show the import button that allows you to import Palette+ palettes onto a design in the Advanced Customization options section for designs."u8, - config.ShowPalettePlusImport, v => config.ShowPalettePlusImport = v); - using var id = ImRaii.PushId(1); PaletteImportButton(); } - if (config.UseAdvancedDyes) - Checkbox("Keep Advanced Dye Window Attached"u8, - "Keeps the advanced dye window expansion attached to the main window, or makes it freely movable."u8, - config.KeepAdvancedDyesAttached, v => config.KeepAdvancedDyesAttached = v); + Checkbox("Keep Advanced Dye Window Attached"u8, + "Keeps the advanced dye window expansion attached to the main window, or makes it freely movable."u8, + config.KeepAdvancedDyesAttached, v => config.KeepAdvancedDyesAttached = v); - Checkbox("Debug Mode"u8, "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general."u8, config.DebugMode, + Checkbox("Debug Mode"u8, "Show the debug tab. Only useful for debugging or advanced use. Not recommended in general."u8, + config.DebugMode, v => config.DebugMode = v); ImGui.NewLine(); } @@ -235,8 +229,7 @@ public class SettingsTab( private void DrawQuickDesignBoxes() { var showAuto = config.EnableAutoDesigns; - var showAdvanced = config.UseAdvancedParameters || config.UseAdvancedDyes; - var numColumns = 8 - (showAuto ? 0 : 2) - (showAdvanced ? 0 : 1) - (config.UseTemporarySettings ? 0 : 1); + var numColumns = 8 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); ImGui.NewLine(); ImUtf8.Text("Show the Following Buttons in the Quick Design Bar:"u8); ImGui.Dummy(Vector2.Zero); @@ -253,11 +246,11 @@ public class SettingsTab( ("Reapply Auto", showAuto, QdbButtons.ReapplyAutomation), ("Revert Equip", true, QdbButtons.RevertEquip), ("Revert Customize", true, QdbButtons.RevertCustomize), - ("Revert Advanced", showAdvanced, QdbButtons.RevertAdvanced), + ("Revert Advanced", true, QdbButtons.RevertAdvanced), ("Reset Settings", config.UseTemporarySettings, QdbButtons.ResetSettings), ]; - for(var i = 0; i < columns.Length; ++i) + for (var i = 0; i < columns.Length; ++i) { if (!columns[i].Item2) continue; @@ -291,7 +284,7 @@ public class SettingsTab( private void PaletteImportButton() { - if (!config.UseAdvancedParameters || !config.ShowPalettePlusImport) + if (!config.ShowPalettePlusImport) return; ImGui.SameLine(); diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index f3c1875..81373c5 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -41,9 +41,6 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainIds stain, ref nint ret) { - if (!_config.UseAdvancedDyes) - return; - var actor = _penumbra.GameObjectFromDrawObject(characterBase); var validType = FindType(characterBase, actor, out var type); var (slotId, materialId) = FindMaterial(characterBase, material); diff --git a/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs b/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs deleted file mode 100644 index a5a5ed9..0000000 --- a/Glamourer/Interop/PalettePlus/PalettePlusChecker.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Plugin; -using OtterGui.Services; -using Notification = OtterGui.Classes.Notification; - -namespace Glamourer.Interop.PalettePlus; - -public sealed class PalettePlusChecker : IRequiredService, IDisposable -{ - private readonly Timer _paletteTimer; - private readonly Configuration _config; - private readonly IDalamudPluginInterface _pluginInterface; - - public PalettePlusChecker(Configuration config, IDalamudPluginInterface pluginInterface) - { - _config = config; - _pluginInterface = pluginInterface; - _paletteTimer = new Timer(_ => PalettePlusCheck(), null, TimeSpan.FromSeconds(30), Timeout.InfiniteTimeSpan); - } - - public void Dispose() - => _paletteTimer.Dispose(); - - public void SetAdvancedParameters(bool value) - { - _config.UseAdvancedParameters = value; - PalettePlusCheck(); - } - - private void PalettePlusCheck() - { - if (!_config.UseAdvancedParameters) - return; - - try - { - var subscriber = _pluginInterface.GetIpcSubscriber("PalettePlus.ApiVersion"); - subscriber.InvokeFunc(); - Glamourer.Messager.AddMessage(new Notification( - "You currently have Palette+ installed. This conflicts with Glamourers advanced options and will cause invalid state.\n\n" - + "Please uninstall Palette+ and restart your game. Palette+ is deprecated and no longer supported by Mare Synchronos.", - NotificationType.Warning, 10000)); - } - catch - { - // ignored - } - } -} diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 2e086d6..be85580 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -25,7 +25,6 @@ public class StateApplier( MetaService _metaService, ObjectManager _objects, CrestService _crests, - Configuration _config, DirectXService _directX) { /// Simply force a redraw regardless of conditions. @@ -291,30 +290,26 @@ public class StateApplier( } /// Change the customize parameters on models. Can change multiple at once. - public void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values, bool force) + public void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values) { - if (!force && !_config.UseAdvancedParameters || flags == 0) + if (flags == 0) return; foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) actor.Model.ApplyParameterData(flags, values); } - /// + /// public ActorData ChangeParameters(ActorState state, CustomizeParameterFlag flags, bool apply) { var data = GetData(state); if (apply) - ChangeParameters(data, flags, state.ModelData.Parameters, state.IsLocked); + ChangeParameters(data, flags, state.ModelData.Parameters); return data; } - public unsafe void ChangeMaterialValue(ActorState state, ActorData data, MaterialValueIndex changedIndex, ColorRow? changedValue, - bool force) + public unsafe void ChangeMaterialValue(ActorState state, ActorData data, MaterialValueIndex changedIndex, ColorRow? changedValue) { - if (!force && !_config.UseAdvancedDyes) - return; - foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true })) { if (!changedIndex.TryGetTexture(actor, out var texture)) @@ -341,16 +336,13 @@ public class StateApplier( { var data = GetData(state); if (apply) - ChangeMaterialValue(state, data, index, state.Materials.TryGetValue(index, out var v) ? v.Model : null, state.IsLocked); + ChangeMaterialValue(state, data, index, state.Materials.TryGetValue(index, out var v) ? v.Model : null); return data; } - public unsafe void ChangeMaterialValues(ActorData data, in StateMaterialManager materials, bool force) + public unsafe void ChangeMaterialValues(ActorData data, in StateMaterialManager materials) { - if (!force && !_config.UseAdvancedDyes) - return; - var groupedMaterialValues = materials.Values.Select(p => (MaterialValueIndex.FromKey(p.Key), p.Value)) .GroupBy(p => (p.Item1.DrawObject, p.Item1.SlotIndex, p.Item1.MaterialIndex)); @@ -410,8 +402,8 @@ public class StateApplier( ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); ChangeCrests(actors, state.ModelData.CrestVisibility); - ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked); - ChangeMaterialValues(actors, state.Materials, state.IsLocked); + ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters); + ChangeMaterialValues(actors, state.Materials); } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 3a6d6ef..88ec0b5 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -41,7 +41,7 @@ public class StateListener : IDisposable private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly VisorStateChanged _visorState; private readonly WeaponVisibilityChanged _weaponVisibility; - private readonly StateFinalized _stateFinalized; + private readonly StateFinalized _stateFinalized; private readonly AutoDesignApplier _autoDesignApplier; private readonly FunModule _funModule; private readonly HumanModelList _humans; @@ -88,7 +88,7 @@ public class StateListener : IDisposable _condition = condition; _crestService = crestService; _bonusSlotUpdating = bonusSlotUpdating; - _stateFinalized = stateFinalized; + _stateFinalized = stateFinalized; Subscribe(); } @@ -225,7 +225,7 @@ public class StateListener : IDisposable // then we do not want to use our restricted gear protection // since we assume the player has that gear modded to availability. var locked = false; - if (actor.Identifier(_actors, out var identifier) + if (actor.Identifier(_actors, out var identifier) && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor); @@ -889,7 +889,7 @@ public class StateListener : IDisposable case StateSource.Manual: if (state.BaseData.Parameters.Set(flag, newValue)) _manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game); - else if (_config.UseAdvancedParameters) + else model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; case StateSource.IpcManual: @@ -900,8 +900,7 @@ public class StateListener : IDisposable break; case StateSource.Fixed: state.BaseData.Parameters.Set(flag, newValue); - if (_config.UseAdvancedParameters) - model.ApplySingleParameterData(flag, state.ModelData.Parameters); + model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; case StateSource.IpcFixed: state.BaseData.Parameters.Set(flag, newValue); @@ -910,14 +909,12 @@ public class StateListener : IDisposable case StateSource.Pending: state.BaseData.Parameters.Set(flag, newValue); state.Sources[flag] = StateSource.Manual; - if (_config.UseAdvancedParameters) - model.ApplySingleParameterData(flag, state.ModelData.Parameters); + model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; case StateSource.IpcPending: state.BaseData.Parameters.Set(flag, newValue); state.Sources[flag] = StateSource.IpcManual; - if (_config.UseAdvancedParameters) - model.ApplySingleParameterData(flag, state.ModelData.Parameters); + model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; } } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 9b71586..2dda310 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -297,7 +297,7 @@ public sealed class StateManager( { actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); foreach (var (idx, mat) in state.Materials.Values) - Applier.ChangeMaterialValue(state, actors, MaterialValueIndex.FromKey(idx), mat.Game, true); + Applier.ChangeMaterialValue(state, actors, MaterialValueIndex.FromKey(idx), mat.Game); } state.Materials.Clear(); From 94f6e870e6c96e21bc06221dc8249aa94311e261 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 2 Mar 2025 13:33:41 +0100 Subject: [PATCH 613/786] Update Submodules. --- OtterGui | 2 +- Penumbra.GameData | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OtterGui b/OtterGui index 06422a8..c347d29 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 06422a893348a18a013e6dbc558370db8d21a446 +Subproject commit c347d29d980b0191d1d071170cf2ec229e3efdcf diff --git a/Penumbra.GameData b/Penumbra.GameData index 33fea10..a21c146 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 33fea10e18ec9f8a5b309890de557fcb25780086 +Subproject commit a21c146790b370bd58b0f752385ae153f7e769c0 From 7015737d887a9918f0d5f5175d98df9e6a81ee73 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 2 Mar 2025 13:38:58 +0100 Subject: [PATCH 614/786] Meh. --- Glamourer/GameData/NpcCustomizeSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index 3cc19cd..725f80f 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -330,9 +330,9 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList /// Check decal files for existence. private static void CheckFacepaintFiles(IDataManager data, BitArray facepaints) { - for (var i = 0; i < 128; ++i) + for (byte i = 0; i < 128; ++i) { - var path = GamePaths.Character.Tex.DecalPath("face", (PrimaryId)i); + var path = GamePaths.Tex.FaceDecal(i); if (data.FileExists(path)) facepaints[i] = true; } From 311207977623c800449299fc6f243a1f246e4a19 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 2 Mar 2025 12:40:58 +0000 Subject: [PATCH 615/786] [CI] Updating repo.json for testing_1.3.6.5 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 31b5654..fe2a7c2 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.3", + "TestingAssemblyVersion": "1.3.6.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c96f009fb431f0093015e8c9a0bec42df3f466f6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 2 Mar 2025 16:02:02 +0100 Subject: [PATCH 616/786] Slightly speed up QDB drawing. --- Glamourer/Gui/DesignQuickBar.cs | 130 +++++++++++++++++++------------- 1 file changed, 77 insertions(+), 53 deletions(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index cabd888..d2dba01 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -48,6 +48,7 @@ public sealed class DesignQuickBar : Window, IDisposable private readonly ImRaii.Color _windowColor = new(); private DateTime _keyboardToggle = DateTime.UnixEpoch; private int _numButtons; + private readonly StringBuilder _tooltipBuilder = new(512); public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, ObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) @@ -107,7 +108,7 @@ public sealed class DesignQuickBar : Window, IDisposable private void Draw(float width) { - using var group = ImRaii.Group(); + using var group = ImUtf8.Group(); var spacing = ImGui.GetStyle().ItemInnerSpacing; using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); var buttonSize = new Vector2(ImGui.GetFrameHeight()); @@ -149,33 +150,38 @@ public sealed class DesignQuickBar : Window, IDisposable { var design = _designCombo.Design as Design; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); + if (design == null) { - tooltip = "No design selected."; + _tooltipBuilder.Append("No design selected."); } else { if (_playerIdentifier.IsValid && _playerData.Valid) { available |= 1; - tooltip = $"Left-Click: Apply {design.ResolveName(_config.Ephemeral.IncognitoMode)} to yourself."; + _tooltipBuilder.Append("Left-Click: Apply ") + .Append(design.ResolveName(_config.Ephemeral.IncognitoMode)) + .Append(" to yourself."); } if (_targetIdentifier.IsValid && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Apply {design.ResolveName(_config.Ephemeral.IncognitoMode)} to {_targetIdentifier}."; + _tooltipBuilder.Append("Right-Click: Apply ") + .Append(design.ResolveName(_config.Ephemeral.IncognitoMode)) + .Append(" to {_targetIdentifier}."); } if (available == 0) - tooltip = "Neither player character nor target available."; + _tooltipBuilder.Append("Neither player character nor target available."); } - var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.PlayCircle, size, tooltip, available); + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.PlayCircle, size, available); ImGui.SameLine(); if (!clicked) return; @@ -197,25 +203,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false }) { available |= 1; - tooltip = "Left-Click: Revert the player character to their game state."; + _tooltipBuilder.Append("Left-Click: Revert the player character to their game state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false }) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Revert {_targetIdentifier} to their game state."; + _tooltipBuilder.Append("Right-Click: Revert ") + .Append(_targetIdentifier) + .Append(" to their game state."); } if (available == 0) - tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); - var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available); + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, available); ImGui.SameLine(); if (clicked) _stateManager.ResetState(state!, StateSource.Manual, isFinal: true); @@ -230,26 +239,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Revert the player character to their automation state."; + _tooltipBuilder.Append("Left-Click: Revert the player character to their automation state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Revert {_targetIdentifier} to their automation state."; + _tooltipBuilder.Append("Right-Click: Revert ") + .Append(_targetIdentifier) + .Append(" to their automation state."); } if (available == 0) - tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); - var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, tooltip, available); + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, available); ImGui.SameLine(); if (!clicked) return; @@ -270,26 +281,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Reapply the player character's current automation on top of their current state."; + _tooltipBuilder.Append("Left-Click: Reapply the player character's current automation on top of their current state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Reapply {_targetIdentifier}'s current automation on top of their current state."; + _tooltipBuilder.Append("Right-Click: Reapply ") + .Append(_targetIdentifier) + .Append("'s current automation on top of their current state."); } if (available == 0) - tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); - var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.Repeat, buttonSize, tooltip, available); + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.Repeat, buttonSize, available); ImGui.SameLine(); if (!clicked) return; @@ -307,26 +320,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Revert the advanced customizations and dyes of the player character to their game state."; + _tooltipBuilder.Append("Left-Click: Revert the advanced customizations and dyes of the player character to their game state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Revert the advanced customizations and dyes of {_targetIdentifier} to their game state."; + _tooltipBuilder.Append("Right-Click: Revert the advanced customizations and dyes of ") + .Append(_targetIdentifier) + .Append(" to their game state."); } if (available == 0) - tooltip = "Neither player character nor target are available or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available or their state is locked."); - var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, tooltip, available); + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, available); ImGui.SameLine(); if (clicked) _stateManager.ResetAdvancedState(state!, StateSource.Manual); @@ -338,26 +353,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Revert the customizations of the player character to their game state."; + _tooltipBuilder.Append("Left-Click: Revert the customizations of the player character to their game state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Revert the customizations of {_targetIdentifier} to their game state."; + _tooltipBuilder.Append("Right-Click: Revert the customizations of ") + .Append(_targetIdentifier) + .Append(" to their game state."); } if (available == 0) - tooltip = "Neither player character nor target are available or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available or their state is locked."); - var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.User, buttonSize, tooltip, available); + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.User, buttonSize, available); ImGui.SameLine(); if (clicked) _stateManager.ResetCustomize(state!, StateSource.Manual); @@ -369,26 +386,28 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - tooltip = "Left-Click: Revert the equipment of the player character to its game state."; + _tooltipBuilder.Append("Left-Click: Revert the equipment of the player character to its game state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Revert the equipment of {_targetIdentifier} to its game state."; + _tooltipBuilder.Append("Right-Click: Revert the equipment of ") + .Append(_targetIdentifier) + .Append(" to its game state."); } if (available == 0) - tooltip = "Neither player character nor target are available or their state is locked."; + _tooltipBuilder.Append("Neither player character nor target are available or their state is locked."); - var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Vest, buttonSize, tooltip, available); + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Vest, buttonSize, available); ImGui.SameLine(); if (clicked) _stateManager.ResetEquip(state!, StateSource.Manual); @@ -400,26 +419,30 @@ public sealed class DesignQuickBar : Window, IDisposable return; var available = 0; - var tooltip = string.Empty; + _tooltipBuilder.Clear(); if (_playerIdentifier.IsValid && _playerData.Valid) { available |= 1; - tooltip = $"Left-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting {_playerIdentifier}."; + _tooltipBuilder.Append("Left-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") + .Append(_playerIdentifier) + .Append('.'); } if (_targetIdentifier.IsValid && _targetData.Valid) { if (available != 0) - tooltip += '\n'; + _tooltipBuilder.Append('\n'); available |= 2; - tooltip += $"Right-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting {_targetIdentifier}."; + _tooltipBuilder.Append("Right-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") + .Append(_targetIdentifier) + .Append('.'); } if (available == 0) - tooltip = "Neither player character nor target are available to identify their collections."; + _tooltipBuilder.Append("Neither player character nor target are available to identify their collections."); - var (clicked, _, data, _) = ResolveTarget(FontAwesomeIcon.Cog, buttonSize, tooltip, available); + var (clicked, _, data, _) = ResolveTarget(FontAwesomeIcon.Cog, buttonSize, available); ImGui.SameLine(); if (clicked) { @@ -428,10 +451,11 @@ public sealed class DesignQuickBar : Window, IDisposable } } - private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip, - int available) + private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, int available) { - ImUtf8.IconButton(icon, tooltip, buttonSize, available == 0); + var enumerator = _tooltipBuilder.GetChunks(); + var span = enumerator.MoveNext() ? enumerator.Current.Span : []; + ImUtf8.IconButton(icon, span, buttonSize, available == 0); if ((available & 1) == 1 && ImGui.IsItemClicked(ImGuiMouseButton.Left)) return (true, _playerIdentifier, _playerData, _playerState); if ((available & 2) == 2 && ImGui.IsItemClicked(ImGuiMouseButton.Right)) From 6e685b96d11da86fbb19037da075763183f41da3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 3 Mar 2025 16:31:09 +0100 Subject: [PATCH 617/786] Make all panels configurable. --- Glamourer/Configuration.cs | 3 + Glamourer/DesignPanelFlag.cs | 96 +++++++++++++++++++ Glamourer/Glamourer.cs | 1 + Glamourer/Gui/Materials/AdvancedDyePopup.cs | 3 + Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 15 ++- .../Gui/Tabs/DesignTab/DesignDetailTab.cs | 9 +- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 14 ++- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 14 ++- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 7 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 24 +++-- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 15 +++ 11 files changed, 176 insertions(+), 25 deletions(-) create mode 100644 Glamourer/DesignPanelFlag.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 019bb2b..ef51544 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -66,6 +66,9 @@ public class Configuration : IPluginConfiguration, ISavable public bool AllowDoubleClickToApply { get; set; } = false; public bool RespectManualOnAutomationUpdate { get; set; } = false; + public DesignPanelFlag HideDesignPanel { get; set; } = 0; + public DesignPanelFlag AutoExpandDesignPanel { get; set; } = 0; + public DefaultDesignSettings DefaultDesignSettings { get; set; } = new(); public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre; diff --git a/Glamourer/DesignPanelFlag.cs b/Glamourer/DesignPanelFlag.cs new file mode 100644 index 0000000..db84173 --- /dev/null +++ b/Glamourer/DesignPanelFlag.cs @@ -0,0 +1,96 @@ +using Glamourer.Designs; +using ImGuiNET; +using OtterGui.Text; +using OtterGui.Text.EndObjects; + +namespace Glamourer; + +[Flags] +public enum DesignPanelFlag : uint +{ + Customization = 0x0001, + Equipment = 0x0002, + AdvancedCustomizations = 0x0004, + AdvancedDyes = 0x0008, + AppearanceDetails = 0x0010, + DesignDetails = 0x0020, + ModAssociations = 0x0040, + DesignLinks = 0x0080, + ApplicationRules = 0x0100, + DebugData = 0x0200, +} + +public static class DesignPanelFlagExtensions +{ + public static ReadOnlySpan ToName(this DesignPanelFlag flag) + => flag switch + { + DesignPanelFlag.Customization => "Customization"u8, + DesignPanelFlag.Equipment => "Equipment"u8, + DesignPanelFlag.AdvancedCustomizations => "Advanced Customization"u8, + DesignPanelFlag.AdvancedDyes => "Advanced Dyes"u8, + DesignPanelFlag.DesignDetails => "Design Details"u8, + DesignPanelFlag.ApplicationRules => "Application Rules"u8, + DesignPanelFlag.ModAssociations => "Mod Associations"u8, + DesignPanelFlag.DesignLinks => "Design Links"u8, + DesignPanelFlag.DebugData => "Debug Data"u8, + DesignPanelFlag.AppearanceDetails => "Appearance Details"u8, + _ => ""u8, + }; + + public static CollapsingHeader Header(this DesignPanelFlag flag, Configuration config) + { + if (config.HideDesignPanel.HasFlag(flag)) + return new CollapsingHeader() + { + Disposed = true, + }; + + var expand = config.AutoExpandDesignPanel.HasFlag(flag); + return ImUtf8.CollapsingHeaderId(flag.ToName(), expand ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None); + } + + public static void DrawTable(ReadOnlySpan label, DesignPanelFlag hidden, DesignPanelFlag expanded, Action setterHide, + Action setterExpand) + { + var checkBoxWidth = Math.Max(ImGui.GetFrameHeight(), ImUtf8.CalcTextSize("Expand"u8).X); + var textWidth = ImUtf8.CalcTextSize(DesignPanelFlag.AdvancedCustomizations.ToName()).X; + var tableSize = 2 * (textWidth + 2 * checkBoxWidth) + 10 * ImGui.GetStyle().CellPadding.X + 2 * ImGui.GetStyle().WindowPadding.X + 2 * ImGui.GetStyle().FrameBorderSize; + using var table = ImUtf8.Table(label, 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Borders, new Vector2(tableSize, 6 * ImGui.GetFrameHeight())); + if (!table) + return; + + var headerColor = ImGui.GetColorU32(ImGuiCol.TableHeaderBg); + var checkBoxOffset = (checkBoxWidth - ImGui.GetFrameHeight()) / 2; + ImUtf8.TableSetupColumn("Panel##1"u8, ImGuiTableColumnFlags.WidthFixed, textWidth); + ImUtf8.TableSetupColumn("Show##1"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth); + ImUtf8.TableSetupColumn("Expand##1"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth); + ImUtf8.TableSetupColumn("Panel##2"u8, ImGuiTableColumnFlags.WidthFixed, textWidth); + ImUtf8.TableSetupColumn("Show##2"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth); + ImUtf8.TableSetupColumn("Expand##2"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth); + + ImGui.TableHeadersRow(); + foreach (var panel in Enum.GetValues()) + { + using var id = ImUtf8.PushId((int)panel); + ImGui.TableNextColumn(); + ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, headerColor); + ImUtf8.TextFrameAligned(panel.ToName()); + var isShown = !hidden.HasFlag(panel); + var isExpanded = expanded.HasFlag(panel); + + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + checkBoxOffset); + if (ImUtf8.Checkbox("##show"u8, ref isShown)) + setterHide.Invoke(isShown ? hidden & ~panel : hidden | panel); + ImUtf8.HoverTooltip( + "Show this panel and associated functionality in all relevant tabs.\n\nToggling this off does NOT disable any functionality, just the display of it, so hide panels at your own risk."u8); + + ImGui.TableNextColumn(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + checkBoxOffset); + if (ImUtf8.Checkbox("##expand"u8, ref isExpanded)) + setterExpand.Invoke(isExpanded ? expanded | panel : expanded & ~panel); + ImUtf8.HoverTooltip("Expand this panel by default in all relevant tabs."u8); + } + } +} diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 5d38e3a..9191c4f 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -71,6 +71,7 @@ public class Glamourer : IDalamudPlugin sb.Append($"> **`Festival Easter-Eggs: `** {config.DisableFestivals}\n"); sb.Append($"> **`Apply Entire Weapon: `** {config.ChangeEntireItem}\n"); sb.Append($"> **`Apply Associated Mods:`** {config.AlwaysApplyAssociatedMods}\n"); + sb.Append($"> **`Hidden Panels: `** {config.HideDesignPanel}\n"); sb.Append($"> **`Show QDB: `** {config.Ephemeral.ShowDesignQuickBar}\n"); sb.Append($"> **`QDB Hotkey: `** {config.ToggleQuickDesignBar}\n"); sb.Append($"> **`Smaller Equip Display:`** {config.SmallEquip}\n"); diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 6d0bb70..21e5ef9 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -55,6 +55,9 @@ public sealed unsafe class AdvancedDyePopup( private void DrawButton(MaterialValueIndex index, uint color) { + if (config.HideDesignPanel.HasFlag(DesignPanelFlag.AdvancedDyes)) + return; + ImGui.SameLine(); using var id = ImUtf8.PushId(index.SlotIndex | ((int)index.DrawObject << 8)); var isOpen = index == _drawIndex; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index e56266d..0071b1f 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -59,7 +59,7 @@ public class ActorPanel ICondition conditions, DictModelChara modelChara, CustomizeParameterDrawer parameterDrawer, - AdvancedDyePopup advancedDyes, + AdvancedDyePopup advancedDyes, EditorHistory editorHistory) { _selector = selector; @@ -157,6 +157,7 @@ public class ActorPanel using var table = ImUtf8.Table("##Panel", 1, ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, ImGui.GetContentRegionAvail()); if (!table || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state)) return; + ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableNextColumn(); ImGui.Dummy(Vector2.Zero); @@ -191,10 +192,14 @@ public class ActorPanel private void DrawCustomizationsHeader() { + if (_config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization)) + return; + var header = _state!.ModelData.ModelId == 0 ? "Customization" : $"Customization (Model Id #{_state.ModelData.ModelId})###Customization"; - using var h = ImUtf8.CollapsingHeaderId(header); + var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); + using var h = ImUtf8.CollapsingHeaderId(header, expand ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None); if (!h) return; @@ -207,7 +212,7 @@ public class ActorPanel private void DrawEquipmentHeader() { - using var h = ImUtf8.CollapsingHeaderId("Equipment"u8); + using var h = DesignPanelFlag.Equipment.Header(_config); if (!h) return; @@ -239,7 +244,7 @@ public class ActorPanel private void DrawParameterHeader() { - using var h = ImUtf8.CollapsingHeaderId("Advanced Customizations"u8); + using var h = DesignPanelFlag.AdvancedCustomizations.Header(_config); if (!h) return; @@ -251,7 +256,7 @@ public class ActorPanel if (!_config.DebugMode) return; - using var h = ImUtf8.CollapsingHeaderId("Debug Data"u8); + using var h = DesignPanelFlag.DebugData.Header(_config); if (!h) return; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 1469deb..cbf7acd 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -14,6 +14,7 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class DesignDetailTab { private readonly SaveService _saveService; + private readonly Configuration _config; private readonly DesignFileSystemSelector _selector; private readonly DesignFileSystem _fileSystem; private readonly DesignManager _manager; @@ -30,19 +31,20 @@ public class DesignDetailTab private DesignFileSystem.Leaf? _changeLeaf; public DesignDetailTab(SaveService saveService, DesignFileSystemSelector selector, DesignManager manager, DesignFileSystem fileSystem, - DesignColors colors) + DesignColors colors, Configuration config) { _saveService = saveService; _selector = selector; _manager = manager; _fileSystem = fileSystem; _colors = colors; + _config = config; _colorCombo = new DesignColorCombo(_colors, false); } public void Draw() { - using var h = ImUtf8.CollapsingHeaderId("Design Details"u8); + using var h = DesignPanelFlag.DesignDetails.Header(_config); if (!h) return; @@ -159,7 +161,8 @@ public class DesignDetailTab ImGui.TableNextColumn(); if (ImUtf8.Checkbox("##ResetTemporarySettings"u8, ref resetTemporarySettings)) _manager.ChangeResetTemporarySettings(_selector.Selected!, resetTemporarySettings); - ImUtf8.HoverTooltip("Set this design to reset any temporary settings previously applied to the associated collection when it is applied through any means."u8); + ImUtf8.HoverTooltip( + "Set this design to reset any temporary settings previously applied to the associated collection when it is applied through any means."u8); ImUtf8.DrawFrameColumn("Color"u8); var colorName = _selector.Selected!.Color.Length == 0 ? DesignColors.AutomaticName : _selector.Selected!.Color; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index 5a8c41c..b81ffdf 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -10,7 +10,12 @@ using OtterGui.Services; namespace Glamourer.Gui.Tabs.DesignTab; -public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, LinkDesignCombo _combo, DesignColors _colorManager) : IUiService +public class DesignLinkDrawer( + DesignLinkManager _linkManager, + DesignFileSystemSelector _selector, + LinkDesignCombo _combo, + DesignColors _colorManager, + Configuration config) : IUiService { private int _dragDropIndex = -1; private LinkOrder _dragDropOrder = LinkOrder.None; @@ -19,12 +24,15 @@ public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSe public void Draw() { - using var header = ImRaii.CollapsingHeader("Design Links"); + using var h = DesignPanelFlag.DesignLinks.Header(config); + if (h.Disposed) + return; + ImGuiUtil.HoverTooltip( "Design links are links to other designs that will be applied to characters or during automation according to the rules set.\n" + "They apply from top to bottom just like automated design sets, so anything set by an earlier design will not not be set again by later designs, and order is important.\n" + "If a linked design links to other designs, they will also be applied, so circular links are prohibited. "); - if (!header) + if (!h) return; DrawList(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 554e671..f576223 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -101,7 +101,7 @@ public class DesignPanel private void DrawEquipment() { - using var h = ImRaii.CollapsingHeader("Equipment"); + using var h = DesignPanelFlag.Equipment.Header(_config); if (!h) return; @@ -156,10 +156,14 @@ public class DesignPanel private void DrawCustomize() { + if (_config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization)) + return; + var header = _selector.Selected!.DesignData.ModelId == 0 ? "Customization" : $"Customization (Model Id #{_selector.Selected!.DesignData.ModelId})###Customization"; - using var h = ImRaii.CollapsingHeader(header); + var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); + using var h = ImUtf8.CollapsingHeaderId(header, expand ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None); if (!h) return; @@ -180,7 +184,7 @@ public class DesignPanel private void DrawCustomizeParameters() { - using var h = ImUtf8.CollapsingHeaderId("Advanced Customizations"u8); + using var h = DesignPanelFlag.AdvancedCustomizations.Header(_config); if (!h) return; @@ -189,7 +193,7 @@ public class DesignPanel private void DrawMaterialValues() { - using var h = ImUtf8.CollapsingHeaderId("Advanced Dyes"u8); + using var h = DesignPanelFlag.AdvancedDyes.Header(_config); if (!h) return; @@ -244,7 +248,7 @@ public class DesignPanel private void DrawApplicationRules() { - using var h = ImUtf8.CollapsingHeaderId("Application Rules"u8); + using var h = DesignPanelFlag.ApplicationRules.Header(_config); if (!h) return; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 5856fcc..f172735 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -21,7 +21,10 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect public void Draw() { - using var h = ImRaii.CollapsingHeader("Mod Associations"); + using var h = DesignPanelFlag.ModAssociations.Header(config); + if (h.Disposed) + return; + ImGuiUtil.HoverTooltip( "This tab can store information about specific mods associated with this design.\n\n" + "It does NOT change any mod settings automatically, though there is functionality to apply desired mod settings manually.\n" @@ -98,7 +101,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect ImUtf8.TableSetupColumn("Mod Name"u8, ImGuiTableColumnFlags.WidthStretch); if (config.UseTemporarySettings) ImUtf8.TableSetupColumn("Remove"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Remove"u8).X); - ImUtf8.TableSetupColumn("Inherit"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Inherit"u8).X); + ImUtf8.TableSetupColumn("Inherit"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Inherit"u8).X); ImUtf8.TableSetupColumn("State"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("State"u8).X); ImUtf8.TableSetupColumn("Priority"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Priority"u8).X); ImUtf8.TableSetupColumn("##Options"u8, ImGuiTableColumnFlags.WidthFixed, ImUtf8.CalcTextSize("Applym"u8).X); diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index aeb96f6..1151e86 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -19,6 +19,7 @@ namespace Glamourer.Gui.Tabs.NpcTab; public class NpcPanel { + private readonly Configuration _config; private readonly DesignColorCombo _colorCombo; private string _newName = string.Empty; private DesignBase? _newDesign; @@ -42,7 +43,8 @@ public class NpcPanel DesignManager designManager, StateManager state, ObjectManager objects, - DesignColors colors) + DesignColors colors, + Configuration config) { _selector = selector; _favorites = favorites; @@ -53,6 +55,7 @@ public class NpcPanel _state = state; _objects = objects; _colors = colors; + _config = config; _colorCombo = new DesignColorCombo(colors, true); _leftButtons = [ @@ -139,9 +142,14 @@ public class NpcPanel private void DrawCustomization() { - using var h = _selector.Selection.ModelId == 0 - ? ImUtf8.CollapsingHeaderId("Customization"u8) - : ImUtf8.CollapsingHeaderId($"Customization (Model Id #{_selector.Selection.ModelId})###Customization"); + if (_config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization)) + return; + + var header = _selector.Selection.ModelId == 0 + ? "Customization" + : $"Customization (Model Id #{_selector.Selection.ModelId})###Customization"; + var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization); + using var h = ImUtf8.CollapsingHeaderId(header, expand ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None); if (!h) return; @@ -151,7 +159,7 @@ public class NpcPanel private void DrawEquipment() { - using var h = ImUtf8.CollapsingHeaderId("Equipment"u8); + using var h = DesignPanelFlag.Equipment.Header(_config); if (!h) return; @@ -190,7 +198,9 @@ public class NpcPanel private void DrawApplyToSelf() { var (id, data) = _objects.PlayerData; - if (!ImUtf8.ButtonEx("Apply to Yourself"u8, "Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8, Vector2.Zero, !data.Valid)) + if (!ImUtf8.ButtonEx("Apply to Yourself"u8, + "Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations."u8, + Vector2.Zero, !data.Valid)) return; if (_state.GetOrCreate(id, data.Objects[0], out var state)) @@ -221,7 +231,7 @@ public class NpcPanel private void DrawAppearanceInfo() { - using var h = ImUtf8.CollapsingHeaderId("Appearance Details"u8); + using var h = DesignPanelFlag.AppearanceDetails.Header(_config); if (!h) return; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index fd4f3f5..d6d2c15 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -136,6 +136,7 @@ public class SettingsTab( 100 * ImGuiHelpers.GlobalScale, config.ToggleQuickDesignBar, v => config.ToggleQuickDesignBar = v, _validKeys)) config.Save(); + Checkbox("Show Quick Design Bar in Main Window"u8, "Show the quick design bar in the tab selection part of the main window, too."u8, config.ShowQuickBarInTabs, v => config.ShowQuickBarInTabs = v); @@ -193,6 +194,20 @@ public class SettingsTab( v => config.OpenFoldersByDefault = v); DrawFolderSortType(); + ImGui.NewLine(); + ImUtf8.Text("Show the following panels in their respective tabs:"u8); + ImGui.Dummy(Vector2.Zero); + DesignPanelFlagExtensions.DrawTable("##panelTable"u8, config.HideDesignPanel, config.AutoExpandDesignPanel, v => + { + config.HideDesignPanel = v; + config.Save(); + }, v => + { + config.AutoExpandDesignPanel = v; + config.Save(); + }); + + ImGui.Dummy(Vector2.Zero); ImGui.Separator(); ImGui.Dummy(Vector2.Zero); From b9e4c144c20f560d7ddc0f0aafb1124bdd3faacf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 3 Mar 2025 17:55:38 +0100 Subject: [PATCH 618/786] Add some buttons to set design application rules to some presets. --- Glamourer/Designs/DesignManager.cs | 34 +++++++++ Glamourer/Gui/Materials/MaterialDrawer.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 84 +++++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index f931489..f9429ad 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -3,6 +3,7 @@ using Glamourer.Designs.History; using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; +using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; @@ -448,6 +449,39 @@ public sealed class DesignManager : DesignEditor DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, new ApplicationTransaction(flag, !value, value)); } + /// Change multiple application values at once. + public void ChangeApplyMulti(Design design, bool? equipment, bool? customization, bool? bonus, bool? parameters, bool? meta, bool? stains, + bool? materials, bool? crest) + { + if (equipment is { } e) + foreach (var f in EquipSlotExtensions.FullSlots) + ChangeApplyItem(design, f, e); + if (stains is { } s) + foreach (var f in EquipSlotExtensions.FullSlots) + ChangeApplyStains(design, f, s); + if (customization is { } c) + foreach (var f in CustomizationExtensions.All.Where(design.CustomizeSet.IsAvailable).Prepend(CustomizeIndex.Clan) + .Prepend(CustomizeIndex.Gender)) + ChangeApplyCustomize(design, f, c); + if (bonus is { } b) + foreach (var f in BonusExtensions.AllFlags) + ChangeApplyBonusItem(design, f, b); + if (meta is { } m) + foreach (var f in MetaExtensions.AllRelevant) + ChangeApplyMeta(design, f, m); + if (crest is { } cr) + foreach (var f in CrestExtensions.AllRelevantSet) + ChangeApplyCrest(design, f, cr); + + if (parameters is { } p) + foreach (var f in CustomizeParameterExtensions.AllFlags) + ChangeApplyParameter(design, f, p); + + if (materials is { } ma) + foreach (var (key, _) in design.GetMaterialData()) + ChangeApplyMaterialValue(design, MaterialValueIndex.FromKey(key), ma); + } + #endregion public void UndoDesignChange(Design design) diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 1b5e65a..5b3af31 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -44,7 +44,7 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) private void DrawName(MaterialValueIndex index) { - using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); + using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0.05f, 0.5f)); ImUtf8.TextFramed(index.ToString(), 0, new Vector2((GlossWidth + SpecularStrengthWidth) * ImGuiHelpers.GlobalScale + _spacing, 0), borderColor: ImGui.GetColorU32(ImGuiCol.Text)); } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index f576223..caea8fe 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -254,6 +254,8 @@ public class DesignPanel using var disabled = ImRaii.Disabled(_selector.Selected!.WriteProtected()); + DrawAllButtons(); + using (var _ = ImUtf8.Group()) { DrawCustomizeApplication(); @@ -310,6 +312,88 @@ public class DesignPanel } } + private void DrawAllButtons() + { + var enabled = _config.DeleteDesignModifier.IsActive(); + bool? equip = null; + bool? customize = null; + var size = new Vector2(200 * ImUtf8.GlobalScale, 0); + if (ImUtf8.ButtonEx("Disable Everything"u8, + "Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size, + !enabled)) + { + equip = false; + customize = false; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Enable Everything"u8, + "Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size, + !enabled)) + { + equip = true; + customize = true; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + + if (ImUtf8.ButtonEx("Equipment Only"u8, + "Enable application of anything related to gear, disable anything that is not related to gear."u8, size, + !enabled)) + { + equip = true; + customize = false; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Customization Only"u8, + "Enable application of anything related to customization, disable anything that is not related to customization."u8, size, + !enabled)) + { + equip = false; + customize = true; + } + + if (ImUtf8.ButtonEx("Default Application"u8, + "Set the application rules to the default values as if the design was newly created, without any advanced features or wetness."u8, + size, + !enabled)) + { + _manager.ChangeApplyMulti(_selector.Selected!, true, true, true, false, true, true, false, true); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, false); + } + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Disable Advanced"u8, "Disable all advanced dyes and customizations but keep everything else as is."u8, + size, + !enabled)) + _manager.ChangeApplyMulti(_selector.Selected!, null, null, null, false, null, null, false, null); + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); + + if (equip is null && customize is null) + return; + + _manager.ChangeApplyMulti(_selector.Selected!, equip, customize, equip, customize, null, equip, equip, equip); + if (equip.HasValue) + { + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, equip.Value); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.VisorState, equip.Value); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.WeaponState, equip.Value); + } + + if (customize.HasValue) + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, customize.Value); + } + private static readonly IReadOnlyList MetaLabels = [ "Apply Wetness", From 3fd6108fa1749c4e65958b8b3a4d2f5b4f437c9b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 3 Mar 2025 18:12:42 +0100 Subject: [PATCH 619/786] Add buttons to remove, enable and disable multiple advanced dyes at once in a design. --- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Gui/Materials/MaterialDrawer.cs | 48 +++++++++++++++++++++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index f9429ad..ef01e98 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -478,7 +478,7 @@ public sealed class DesignManager : DesignEditor ChangeApplyParameter(design, f, p); if (materials is { } ma) - foreach (var (key, _) in design.GetMaterialData()) + foreach (var (key, _) in design.GetMaterialData().ToArray()) ChangeApplyMaterialValue(design, MaterialValueIndex.FromKey(key), ma); } diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 5b3af31..0c4433d 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -35,6 +35,10 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) + (GlossWidth + SpecularStrengthWidth) * ImGuiHelpers.GlobalScale + 6 * _spacing + ImUtf8.CalcTextSize("Revert"u8).X; + DrawMultiButtons(design); + ImUtf8.Dummy(0); + ImGui.Separator(); + ImUtf8.Dummy(0); if (available > 1.95 * colorWidth) DrawSingleRow(design); else @@ -42,6 +46,39 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) DrawNew(design); } + private void DrawMultiButtons(Design design) + { + var any = design.Materials.Count > 0; + var disabled = !_config.DeleteDesignModifier.IsActive(); + var size = new Vector2(200 * ImUtf8.GlobalScale, 0); + if (ImUtf8.ButtonEx("Enable All Advanced Dyes"u8, + any + ? "Enable the application of all contained advanced dyes without deleting them."u8 + : "This design does not contain any advanced dyes."u8, size, + !any || disabled)) + _designManager.ChangeApplyMulti(design, null, null, null, null, null, null, true, null); + ; + if (disabled && any) + ImUtf8.HoverTooltip($"Hold {_config.DeleteDesignModifier} while clicking to enable."); + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Disable All Advanced Dyes"u8, + any + ? "Disable the application of all contained advanced dyes without deleting them."u8 + : "This design does not contain any advanced dyes."u8, size, + !any || disabled)) + _designManager.ChangeApplyMulti(design, null, null, null, null, null, null, false, null); + if (disabled && any) + ImUtf8.HoverTooltip($"Hold {_config.DeleteDesignModifier} while clicking to disable."); + + if (ImUtf8.ButtonEx("Delete All Advanced Dyes"u8, any ? ""u8 : "This design does not contain any advanced dyes."u8, size, + !any || disabled)) + while (design.Materials.Count > 0) + _designManager.ChangeMaterialValue(design, MaterialValueIndex.FromKey(design.Materials[0].Item1), null); + + if (disabled && any) + ImUtf8.HoverTooltip($"Hold {_config.DeleteDesignModifier} while clicking to delete."); + } + private void DrawName(MaterialValueIndex index) { using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0.05f, 0.5f)); @@ -139,7 +176,7 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) "If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."u8); } - public sealed class MaterialSlotCombo; + public sealed class MaterialSlotCombo; public void DrawNew(Design design) { @@ -165,22 +202,27 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) { ImGui.SetNextItemWidth(ImUtf8.CalcTextSize("Material AA"u8).X); var format = $"Material {(char)('A' + _newMaterialIdx)}"; - if (ImUtf8.DragScalar("##Material"u8, ref _newMaterialIdx, format, 0, MaterialService.MaterialsPerModel - 1, 0.01f)) + if (ImUtf8.DragScalar("##Material"u8, ref _newMaterialIdx, format, 0, MaterialService.MaterialsPerModel - 1, 0.01f, + ImGuiSliderFlags.NoInput)) { _newMaterialIdx = Math.Clamp(_newMaterialIdx, 0, MaterialService.MaterialsPerModel - 1); _newKey = _newKey with { MaterialIndex = (byte)_newMaterialIdx }; } + + ImUtf8.HoverTooltip("Drag this to the left or right to change its value."u8); } private void DrawRowIdxDrag() { ImGui.SetNextItemWidth(ImUtf8.CalcTextSize("Row 0000"u8).X); var format = $"Row {_newRowIdx / 2 + 1}{(char)(_newRowIdx % 2 + 'A')}"; - if (ImUtf8.DragScalar("##Row"u8, ref _newRowIdx, format, 0, ColorTable.NumRows - 1, 0.01f)) + if (ImUtf8.DragScalar("##Row"u8, ref _newRowIdx, format, 0, ColorTable.NumRows - 1, 0.01f, ImGuiSliderFlags.NoInput)) { _newRowIdx = Math.Clamp(_newRowIdx, 0, ColorTable.NumRows - 1); _newKey = _newKey with { RowIndex = (byte)_newRowIdx }; } + + ImUtf8.HoverTooltip("Drag this to the left or right to change its value."u8); } private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row, bool disabled) From 71e15474b2822a9403701458889a93a8f60397c9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 3 Mar 2025 18:32:52 +0100 Subject: [PATCH 620/786] Add deletion of advanced dyes to multi design selection. --- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 49 ++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index a7afa21..1e52fb5 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -1,6 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Designs; +using Glamourer.Interop.Material; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -37,6 +38,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e DrawMultiResetSettings(offset); DrawMultiResetDyes(offset); DrawMultiForceRedraw(offset); + DrawAdvancedButtons(offset); } private void DrawCounts(Vector2 treeNodePos) @@ -57,11 +59,13 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e private void ResetCounts() { - _numQuickDesignEnabled = 0; - _numDesignsLocked = 0; - _numDesignsForcedRedraw = 0; - _numDesignsResetSettings = 0; - _numDesignsResetDyes = 0; + _numQuickDesignEnabled = 0; + _numDesignsLocked = 0; + _numDesignsForcedRedraw = 0; + _numDesignsResetSettings = 0; + _numDesignsResetDyes = 0; + _numDesignsWithAdvancedDyes = 0; + _numAdvancedDyes = 0; } private bool CountLeaves(DesignFileSystem.IPath path) @@ -79,6 +83,12 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ++_numDesignsForcedRedraw; if (l.Value.ResetAdvancedDyes) ++_numDesignsResetDyes; + if (l.Value.Materials.Count > 0) + { + ++_numDesignsWithAdvancedDyes; + _numAdvancedDyes += l.Value.Materials.Count; + } + return true; } @@ -135,6 +145,8 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e private int _numDesignsForcedRedraw; private int _numDesignsResetSettings; private int _numDesignsResetDyes; + private int _numAdvancedDyes; + private int _numDesignsWithAdvancedDyes; private int _numDesigns; private readonly List _addDesigns = []; private readonly List<(Design, int)> _removeDesigns = []; @@ -294,7 +306,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e private void DrawMultiColor(Vector2 width, float offset) { - ImUtf8.TextFrameAligned("Multi Colors:"); + ImUtf8.TextFrameAligned("Multi Colors:"u8); ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); _colorCombo.Draw("##color", _colorCombo.CurrentSelection ?? string.Empty, "Select a design color.", ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X), ImGui.GetTextLineHeight()); @@ -330,6 +342,31 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ImGui.Separator(); } + private void DrawAdvancedButtons(float offset) + { + ImUtf8.TextFrameAligned("Delete Adv."u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var enabled = config.DeleteDesignModifier.IsActive(); + var tt = _numDesignsWithAdvancedDyes is 0 + ? "No selected designs contain any advanced dyes." + : $"Delete {_numAdvancedDyes} advanced dyes from {_numDesignsWithAdvancedDyes} of the selected designs."; + if (ImUtf8.ButtonEx("Delete All Advanced Dyes"u8, tt, new Vector2(ImGui.GetContentRegionAvail().X, 0), + !enabled || _numDesignsWithAdvancedDyes is 0)) + + foreach (var design in selector.SelectedPaths.OfType()) + { + while (design.Value.Materials.Count > 0) + editor.ChangeMaterialValue(design.Value, MaterialValueIndex.FromKey(design.Value.Materials[0].Item1), null); + } + + if (!enabled && _numDesignsWithAdvancedDyes is not 0) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking to delete."); + ImGui.Separator(); + } + + private void DrawApplicationButtons(Vector2 width, float offset) + { } + private void UpdateTagCache() { _addDesigns.Clear(); From 87f1b613f969f5c558acc51220f6d878cd638127 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 4 Mar 2025 00:18:41 +0100 Subject: [PATCH 621/786] Add application shortcuts to multi design panel. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 4 + .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 104 +++++++++++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index caea8fe..9fed566 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -360,6 +360,8 @@ public class DesignPanel equip = false; customize = true; } + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); if (ImUtf8.ButtonEx("Default Application"u8, "Set the application rules to the default values as if the design was newly created, without any advanced features or wetness."u8, @@ -369,6 +371,8 @@ public class DesignPanel _manager.ChangeApplyMulti(_selector.Selected!, true, true, true, false, true, true, false, true); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, false); } + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); ImGui.SameLine(); if (ImUtf8.ButtonEx("Disable Advanced"u8, "Disable all advanced dyes and customizations but keep everything else as is."u8, diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 1e52fb5..e0523f7 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -39,6 +39,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e DrawMultiResetDyes(offset); DrawMultiForceRedraw(offset); DrawAdvancedButtons(offset); + DrawApplicationButtons(offset); } private void DrawCounts(Vector2 treeNodePos) @@ -364,8 +365,107 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ImGui.Separator(); } - private void DrawApplicationButtons(Vector2 width, float offset) - { } + private void DrawApplicationButtons(float offset) + { + ImUtf8.TextFrameAligned("Application"u8); + ImGui.SameLine(offset, ImGui.GetStyle().ItemSpacing.X); + var width = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var enabled = config.DeleteDesignModifier.IsActive(); + bool? equip = null; + bool? customize = null; + var group = ImUtf8.Group(); + if (ImUtf8.ButtonEx("Disable Everything"u8, + _numDesigns > 0 + ? $"Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." + : "No designs selected.", width, !enabled)) + { + equip = false; + customize = false; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Enable Everything"u8, + _numDesigns > 0 + ? $"Enable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness for all {_numDesigns} designs." + : "No designs selected.", width, !enabled)) + { + equip = true; + customize = true; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + if (ImUtf8.ButtonEx("Equipment Only"u8, + _numDesigns > 0 + ? $"Enable application of anything related to gear, disable anything that is not related to gear for all {_numDesigns} designs." + : "No designs selected.", width, !enabled)) + { + equip = true; + customize = false; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Customization Only"u8, + _numDesigns > 0 + ? $"Enable application of anything related to customization, disable anything that is not related to customization for all {_numDesigns} designs." + : "No designs selected.", width, !enabled)) + { + equip = false; + customize = true; + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + if (ImUtf8.ButtonEx("Default Application"u8, + _numDesigns > 0 + ? $"Set the application rules to the default values as if the {_numDesigns} were newly created,without any advanced features or wetness." + : "No designs selected.", width, !enabled)) + foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + { + editor.ChangeApplyMulti(design, true, true, true, false, true, true, false, true); + editor.ChangeApplyMeta(design, MetaIndex.Wetness, false); + } + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Disable Advanced"u8, _numDesigns > 0 + ? $"Disable all advanced dyes and customizations but keep everything else as is for all {_numDesigns} designs." + : "No designs selected.", width, !enabled)) + foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + editor.ChangeApplyMulti(design, null, null, null, false, null, null, false, null); + + if (!enabled) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {config.DeleteDesignModifier} while clicking."); + + group.Dispose(); + ImGui.Separator(); + if (equip is null && customize is null) + return; + + foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) + { + editor.ChangeApplyMulti(design, equip, customize, equip, customize, null, equip, equip, equip); + if (equip.HasValue) + { + editor.ChangeApplyMeta(design, MetaIndex.HatState, equip.Value); + editor.ChangeApplyMeta(design, MetaIndex.VisorState, equip.Value); + editor.ChangeApplyMeta(design, MetaIndex.WeaponState, equip.Value); + } + + if (customize.HasValue) + editor.ChangeApplyMeta(design, MetaIndex.Wetness, customize.Value); + } + } private void UpdateTagCache() { From ae093506c19b4a0ad3bc994db86e3fb03b651888 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 3 Mar 2025 23:21:09 +0000 Subject: [PATCH 622/786] [CI] Updating repo.json for testing_1.3.6.6 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index fe2a7c2..18c30c3 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.5", + "TestingAssemblyVersion": "1.3.6.6", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.6/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 2026069ed35f38dd38b12ca703c9d1784219abce Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 4 Mar 2025 15:34:39 +0100 Subject: [PATCH 623/786] Make Glamourer able to export and import color sets to and from Penumbra, maybe. --- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 72 +++++++++++++++++-- .../Interop/Material/MaterialValueManager.cs | 2 - 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 21e5ef9..e15872b 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -1,4 +1,5 @@ using Dalamud.Interface; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; @@ -16,6 +17,7 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; using Penumbra.String; +using Notification = OtterGui.Classes.Notification; namespace Glamourer.Gui.Materials; @@ -262,6 +264,61 @@ public sealed unsafe class AdvancedDyePopup( DrawAllRow(materialIndex, table); } + private static void CopyToClipboard(in ColorTable.Table table) + { + try + { + fixed (ColorTable.Table* ptr = &table) + { + var data = new ReadOnlySpan(ptr, sizeof(ColorTable.Table)); + var base64 = Convert.ToBase64String(data); + ImGui.SetClipboardText(base64); + } + } + catch (Exception ex) + { + Glamourer.Log.Error($"Could not copy color table to clipboard:\n{ex}"); + } + } + + private static bool ImportFromClipboard(out ColorTable.Table table) + { + try + { + var base64 = ImGui.GetClipboardText(); + if (base64.Length > 0) + { + var data = Convert.FromBase64String(base64); + if (sizeof(ColorTable.Table) <= data.Length) + { + table = new ColorTable.Table(); + fixed (ColorTable.Table* tPtr = &table) + { + fixed (byte* ptr = data) + { + new ReadOnlySpan(ptr, sizeof(ColorTable.Table)).CopyTo(new Span(tPtr, sizeof(ColorTable.Table))); + return true; + } + } + } + } + + if (ColorRowClipboard.IsTableSet) + { + table = ColorRowClipboard.Table; + return true; + } + } + catch (Exception ex) + { + Glamourer.Messager.AddMessage(new Notification(ex, "Could not paste color table from clipboard.", + "Could not paste color table from clipboard.", NotificationType.Error)); + } + + table = default; + return false; + } + private void DrawAllRow(MaterialValueIndex materialIndex, in ColorTable.Table table) { using var id = ImRaii.PushId(100); @@ -279,13 +336,17 @@ public sealed unsafe class AdvancedDyePopup( var spacing = ImGui.GetStyle().ItemInnerSpacing.X; ImGui.SameLine(ImGui.GetWindowSize().X - 3 * buttonSize.X - 2 * spacing - ImGui.GetStyle().WindowPadding.X); if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Export this table to your clipboard."u8, buttonSize)) + { ColorRowClipboard.Table = table; + CopyToClipboard(table); + } + ImGui.SameLine(0, spacing); - if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported table from your clipboard onto this table."u8, buttonSize, - !ColorRowClipboard.IsTableSet)) + if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported table from your clipboard onto this table."u8, buttonSize) + && ImportFromClipboard(out var newTable)) for (var idx = 0; idx < ColorTable.NumRows; ++idx) { - var row = ColorRowClipboard.Table[idx]; + var row = newTable[idx]; var internalRow = new ColorRow(row); var slot = materialIndex.ToEquipSlot(); var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand @@ -336,7 +397,7 @@ public sealed unsafe class AdvancedDyePopup( { var rowIndex = index.RowIndex / 2 + 1; var rowSuffix = (index.RowIndex & 1) == 0 ? 'A' : 'B'; - ImUtf8.Text($"Row {rowIndex,2}{rowSuffix}"); + ImUtf8.Text($"Row {rowIndex,2}{rowSuffix}"); } ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2); @@ -380,7 +441,8 @@ public sealed unsafe class AdvancedDyePopup( if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Export this row to your clipboard."u8, buttonSize)) ColorRowClipboard.Row = value.Model; ImGui.SameLine(0, spacing.X); - if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported row from your clipboard onto this row."u8, buttonSize, !ColorRowClipboard.IsSet)) + if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported row from your clipboard onto this row."u8, buttonSize, + !ColorRowClipboard.IsSet)) { value.Model = ColorRowClipboard.Row; applied = true; diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 520dacf..01cb479 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -6,8 +6,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Structs; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using static Penumbra.GameData.Files.ShpkFile; namespace Glamourer.Interop.Material; From c9f00c636996c18c384ae47520682fcf1f5a4b61 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 4 Mar 2025 16:28:48 +0100 Subject: [PATCH 624/786] Mark designs containing advanced dyes, mod associations or links in automation. --- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 51 +++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 680e0e9..94329e5 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -30,10 +30,10 @@ public class SetPanel( Configuration _config, RandomRestrictionDrawer _randomDrawer) { - private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log); + private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log); private readonly HeaderDrawer.Button[] _rightButtons = [new HeaderDrawer.IncognitoButton(_config.Ephemeral)]; - private string? _tempName; - private int _dragIndex = -1; + private string? _tempName; + private int _dragIndex = -1; private Action? _endAction; @@ -178,7 +178,8 @@ public class SetPanel( } else { - ImUtf8.TableSetupColumn("Design / Job Restrictions"u8, ImGuiTableColumnFlags.WidthFixed, 250 * ImGuiHelpers.GlobalScale); + ImUtf8.TableSetupColumn("Design / Job Restrictions"u8, ImGuiTableColumnFlags.WidthFixed, + 250 * ImGuiHelpers.GlobalScale - (ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ScrollbarSize : 0)); if (_config.ShowAllAutomatedApplicationRules) ImUtf8.TableSetupColumn("Application"u8, ImGuiTableColumnFlags.WidthFixed, 3 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale); @@ -205,8 +206,8 @@ public class SetPanel( if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, !keyValid, true)) _endAction = () => _manager.DeleteDesign(Selection, idx); ImGui.TableNextColumn(); - ImUtf8.Selectable($"#{idx + 1:D2}"); - DrawDragDrop(Selection, idx); + DrawSelectable(idx, design.Design); + ImGui.TableNextColumn(); DrawRandomEditing(Selection, design, idx); _designCombo.Draw(Selection, design, idx); @@ -243,6 +244,44 @@ public class SetPanel( _endAction = null; } + private void DrawSelectable(int idx, IDesignStandIn design) + { + var highlight = 0u; + var sb = new StringBuilder(); + if (design is Design d) + { + var count = design.AllLinks(true).Count(); + if (count > 1) + { + sb.AppendLine($"This design contains {count - 1} links to other designs."); + highlight = ColorId.HeaderButtons.Value(); + } + + count = d.AssociatedMods.Count; + if (count > 0) + { + sb.AppendLine($"This design contains {count} mod associations."); + highlight = ColorId.ModdedItemMarker.Value(); + } + + count = design.GetMaterialData().Count(p => p.Item2.Enabled); + if (count > 0) + { + sb.AppendLine($"This design contains {count} enabled advanced dyes."); + highlight = ColorId.AdvancedDyeActive.Value(); + } + } + + using (ImRaii.PushColor(ImGuiCol.Text, highlight, highlight != 0)) + { + ImUtf8.Selectable($"#{idx + 1:D2}"); + } + + ImUtf8.HoverTooltip($"{sb}"); + + DrawDragDrop(Selection, idx); + } + private int _tmpGearset = int.MaxValue; private int _whichIndex = -1; From 773682838e553e966d0dd75e0294113c8e54f305 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 9 Mar 2025 13:37:27 +0100 Subject: [PATCH 625/786] Make customize buttons not change advanced customization to On by default. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 9fed566..04e51ce 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -386,7 +386,7 @@ public class DesignPanel if (equip is null && customize is null) return; - _manager.ChangeApplyMulti(_selector.Selected!, equip, customize, equip, customize, null, equip, equip, equip); + _manager.ChangeApplyMulti(_selector.Selected!, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); if (equip.HasValue) { _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, equip.Value); diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index e0523f7..2235160 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -454,7 +454,7 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) { - editor.ChangeApplyMulti(design, equip, customize, equip, customize, null, equip, equip, equip); + editor.ChangeApplyMulti(design, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); if (equip.HasValue) { editor.ChangeApplyMeta(design, MetaIndex.HatState, equip.Value); From e93c3b7bb8872d73a68362190112789dde0825c1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 9 Mar 2025 23:12:21 +0100 Subject: [PATCH 626/786] Update submodules. --- OtterGui | 2 +- Penumbra.GameData | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OtterGui b/OtterGui index c347d29..13f1a90 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit c347d29d980b0191d1d071170cf2ec229e3efdcf +Subproject commit 13f1a90b88d2b8572480748a209f957b70d6a46f diff --git a/Penumbra.GameData b/Penumbra.GameData index a21c146..96163f7 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit a21c146790b370bd58b0f752385ae153f7e769c0 +Subproject commit 96163f79e13c7d52cc36cdd82ab4e823763f4f31 From 3dee511b9adb9a1da98442619d9e8093dab0caa5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 9 Mar 2025 23:13:33 +0100 Subject: [PATCH 627/786] Update some obsoletes. --- Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs | 2 +- Glamourer/Interop/InventoryService.cs | 2 +- Glamourer/Unlocks/ItemUnlockManager.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs index b021656..f57a57e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs @@ -23,7 +23,7 @@ public unsafe class InventoryPanel : IGameDataDrawer ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)inventory:X}"); var equip = inventory->GetInventoryContainer(InventoryType.EquippedItems); - if (equip == null || equip->Loaded == 0) + if (equip == null || equip->IsLoaded) return; ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)equip:X}"); diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 4b98d46..c30ae06 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -182,7 +182,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService // Invoked after calling Original, so the item is already moved. var inventory = manager->GetInventoryContainer(targetContainer); - if (inventory == null || inventory->Loaded == 0 || inventory->Size <= targetSlot) + if (inventory == null || inventory->IsLoaded || inventory->Size <= targetSlot) return false; var item = inventory->GetInventorySlot((int)targetSlot); diff --git a/Glamourer/Unlocks/ItemUnlockManager.cs b/Glamourer/Unlocks/ItemUnlockManager.cs index 0fc1675..6708267 100644 --- a/Glamourer/Unlocks/ItemUnlockManager.cs +++ b/Glamourer/Unlocks/ItemUnlockManager.cs @@ -168,7 +168,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionaryGetInventoryContainer(type); - if (container != null && container->Loaded != 0 && _currentInventoryIndex < container->Size) + if (container != null && container->IsLoaded && _currentInventoryIndex < container->Size) { Glamourer.Log.Excessive($"[UnlockScanner] Scanning {_currentInventory} {type} {_currentInventoryIndex}/{container->Size}."); var item = container->GetInventorySlot(_currentInventoryIndex++); From 381b2a1b8b198f1569b23139e225f738842f2731 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 9 Mar 2025 23:14:19 +0100 Subject: [PATCH 628/786] 1.3.7.0 --- Glamourer/Gui/GlamourerChangelog.cs | 63 +++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 4c8b365..7033fdd 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -40,6 +40,7 @@ public class GlamourerChangelog Add1_3_4_0(Changelog); Add1_3_5_0(Changelog); Add1_3_6_0(Changelog); + Add1_3_7_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -60,32 +61,67 @@ public class GlamourerChangelog } } + private static void Add1_3_7_0(Changelog log) + => log.NextVersion("Version 1.3.7.0") + .RegisterImportant( + "The option to disable advanced customizations or advanced dyes has been removed. The functionality can no longer be disabled entirely, you can just decide not to use it, and to hide it.") + .RegisterHighlight( + "You can now configure which panels (like Customization, Equipment, Advanced Customization etc.) are displayed at all, and which are expanded by default. This does not disable any functionality.") + .RegisterHighlight( + "The Unlocks tab now shows whether items are modded in the currently selected collection in Penumbra in Overview mode and shows and can filter and sort for it in Detailed mode.") + .RegisterEntry("Added an optional button to the Quick Design Bar to reset all temporary settings applied by Glamourer.") + .RegisterHighlight( + "Any existing advanced dyes will now be highlighted on the corresponding Advanced Dye buttons in the actors panel and on the corresponding equip slot name in the design panel.") + .RegisterEntry("This also affects currently inactive advanced dyes, which can now be manually removed on the inactive materials.", + 1) + .RegisterHighlight( + "In the design list of an automation set, the design indices are now highlighted if a design contains advanced dyes, mod associations, or links to other designs.") + .RegisterHighlight("Some quality of life improvements:") + .RegisterEntry("Added some buttons for some application rule presets to the Application Rules panel.", 1) + .RegisterEntry("Added some buttons to enable, disable or delete all advanced dyes in a design.", 1) + .RegisterEntry("Some of those buttons are also available in multi-design selection to apply to all selected designs at once.", 1) + .RegisterEntry( + "A copied material color set from Penumbra should now be able to be imported into a advanced dye color set, as well as the other way around.") + .RegisterEntry( + "Automatically applied character updates when applying a design with mod associations and temporary settings are now skipped to prevent some issues with GPose. This should not affect anything else.") + .RegisterEntry("Glamourer now differentiates between temporary settings applied through manual or automatic application."); + + private static void Add1_3_6_0(Changelog log) => log.NextVersion("Version 1.3.6.0") .RegisterHighlight("Added some new multi design selection functionality to change design settings of many designs at once.") .RegisterEntry("Also added the number of selected designs and folders to the multi design selection display.", 1) .RegisterEntry("Glamourer will now use temporary settings when saving mod associations, if they exist in Penumbra.") - .RegisterEntry("Actually added the checkbox to reset all temporary settings to Automation Sets (functionality was there, just not exposed to the UI...).") - .RegisterEntry("Adapted the behavior for identified copies of characters that have a different state than the character itself to deal with the associated Penumbra changes.") - .RegisterEntry("Added '/glamour resetdesign' as a command, that re-applies automation but resets randomly chosen designs (Thanks Diorik).") + .RegisterEntry( + "Actually added the checkbox to reset all temporary settings to Automation Sets (functionality was there, just not exposed to the UI...).") + .RegisterEntry( + "Adapted the behavior for identified copies of characters that have a different state than the character itself to deal with the associated Penumbra changes.") + .RegisterEntry( + "Added '/glamour resetdesign' as a command, that re-applies automation but resets randomly chosen designs (Thanks Diorik).") .RegisterEntry("All existing facepaints should now be accepted in designs, including NPC facepaints.") - .RegisterEntry("Overwriting a design with your characters current state will now discard any prior advanced dyes and only add those from the current state.") + .RegisterEntry( + "Overwriting a design with your characters current state will now discard any prior advanced dyes and only add those from the current state.") .RegisterEntry("Fixed an issue with racial mount and accessory scaling when changing zones on a changed race.") .RegisterEntry("Fixed issues with the detection of gear set changes in certain circumstances (Thanks Cordelia).") .RegisterEntry("Fixed an issue with the Force to Inherit checkbox in mod associations.") - .RegisterEntry("Added a new IPC event that fires only when Glamourer finalizes its current changes to a character (for/from Cordelia).") + .RegisterEntry( + "Added a new IPC event that fires only when Glamourer finalizes its current changes to a character (for/from Cordelia).") .RegisterEntry("Added new IPC to set a meta flag on actors. (for/from Cordelia)."); private static void Add1_3_5_0(Changelog log) => log.NextVersion("Version 1.3.5.0") - .RegisterHighlight("Added the usage of the new Temporary Mod Setting functionality from Penumbra to apply mod associations. This is on by default but can be turned back to permanent changes in the settings.") + .RegisterHighlight( + "Added the usage of the new Temporary Mod Setting functionality from Penumbra to apply mod associations. This is on by default but can be turned back to permanent changes in the settings.") .RegisterEntry("Designs now have a setting to always reset all prior temporary settings made by Glamourer on application.", 1) - .RegisterEntry("Automation Sets also have a setting to do this, independently of the designs contained in them.", 1) + .RegisterEntry("Automation Sets also have a setting to do this, independently of the designs contained in them.", 1) .RegisterHighlight("More NPC customization options should now be accepted as valid for designs, regardless of clan/gender.") .RegisterHighlight("The 'Apply' chat command had the currently selected design and the current quick bar design added as choices.") - .RegisterEntry("The application buttons for designs, NPCs or actors should now stick at the top of their respective panels even when scrolling down.") + .RegisterEntry( + "The application buttons for designs, NPCs or actors should now stick at the top of their respective panels even when scrolling down.") .RegisterHighlight("Randomly chosen designs should now stay across loading screens or redrawing. (1.3.4.3)") - .RegisterEntry("In automation, Random designs now have an option to always choose another design, including during loading screens or redrawing.", 1) + .RegisterEntry( + "In automation, Random designs now have an option to always choose another design, including during loading screens or redrawing.", + 1) .RegisterEntry("Fixed an issue where disabling auto designs did not work as expected.") .RegisterEntry("Fixed the inversion of application flags in IPC calls.") .RegisterEntry("Fixed an issue with the scaling of the Advanced Dye popup with increased font sizes.") @@ -110,15 +146,18 @@ public class GlamourerChangelog => log.NextVersion("Version 1.3.2.0") .RegisterEntry("Fixed an issue with weapon hiding when leaving GPose or changing zones.") .RegisterEntry("Added support for unnamed items to be previewed from Penumbra.") - .RegisterEntry("Item combos filters now check if the model string starts with the current filter, instead of checking if the primary ID contains the current filter.") + .RegisterEntry( + "Item combos filters now check if the model string starts with the current filter, instead of checking if the primary ID contains the current filter.") .RegisterEntry("Improved the handling of bonus items (glasses) in designs.") .RegisterEntry("Imported .chara files now import bonus items.") - .RegisterEntry("Added a Debug Data rider in the Actors tab that is visible if Debug Mode is enabled and (currently) contains some IDs.") + .RegisterEntry( + "Added a Debug Data rider in the Actors tab that is visible if Debug Mode is enabled and (currently) contains some IDs.") .RegisterEntry("Fixed bonus items not reverting correctly in some cases.") .RegisterEntry("Fixed an issue with the RNG in cheat codes and events skipping some possible entries.") .RegisterEntry("Fixed the chat log context menu for glamourer Try-On.") .RegisterEntry("Fixed some issues with cheat code sets.") - .RegisterEntry("Made the popped out Advanced Dye Window and Unlocks Window non-docking as that caused issues when docked to the main Glamourer window.") + .RegisterEntry( + "Made the popped out Advanced Dye Window and Unlocks Window non-docking as that caused issues when docked to the main Glamourer window.") .RegisterEntry("Refreshed NPC name associations.") .RegisterEntry("Removed a now useless cheat code.") .RegisterEntry("Added API for Bonus Items. (1.3.1.1)"); From 99a8e1e8c5613bafb1ce9cf6f04d7dfe575b6d7b Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 9 Mar 2025 22:17:20 +0000 Subject: [PATCH 629/786] [CI] Updating repo.json for 1.3.7.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 18c30c3..cc9fb7a 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.6.0", - "TestingAssemblyVersion": "1.3.6.6", + "AssemblyVersion": "1.3.7.0", + "TestingAssemblyVersion": "1.3.7.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.6.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.3.6.6/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 25517525c90a8a16992577f6a150231357506db6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 10 Mar 2025 23:55:24 +0100 Subject: [PATCH 630/786] Keep temporary mod settings manually applied by Glamourer when redrawing with automation changes. --- .../Gui/Tabs/DesignTab/ModAssociationsTab.cs | 4 +- .../Interop/Penumbra/ModSettingApplier.cs | 4 +- Glamourer/Interop/Penumbra/PenumbraService.cs | 41 +++++++++++++++---- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index f172735..80000d9 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -87,7 +87,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect public void ApplyAll() { foreach (var (mod, settings) in selector.Selected!.AssociatedMods) - penumbra.SetMod(mod, settings, StateSource.Manual); + penumbra.SetMod(mod, settings, StateSource.Manual, false); } private void DrawTable() @@ -222,7 +222,7 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect if (ImGuiUtil.DrawDisabledButton("Apply", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, !penumbra.Available)) { - var text = penumbra.SetMod(mod, settings, StateSource.Manual); + var text = penumbra.SetMod(mod, settings, StateSource.Manual, false); if (text.Length > 0) Glamourer.Messager.NotificationMessage(text, NotificationType.Warning, false); } diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 82a840c..1d39f79 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -39,7 +39,7 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip var index = ResetOldSettings(collection, actor, source, design.ResetTemporarySettings, respectManual); foreach (var (mod, setting) in design.AssociatedMods) { - var message = penumbra.SetMod(mod, setting, source, collection, index); + var message = penumbra.SetMod(mod, setting, source, respectManual, collection, index); if (message.Length > 0) Glamourer.Log.Verbose($"[Mod Applier] Error applying mod settings: {message}"); else @@ -62,7 +62,7 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip var index = ResetOldSettings(collection, actor, source, resetOther, true); foreach (var (mod, setting) in settings) { - var message = penumbra.SetMod(mod, setting, source, collection, index); + var message = penumbra.SetMod(mod, setting, source, false, collection, index); if (message.Length > 0) messages.Add($"Error applying mod settings: {message}"); else diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index d9b4d27..e04af7c 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -41,9 +41,9 @@ public class PenumbraService : IDisposable public const int RequiredPenumbraFeatureVersionTemp3 = 6; public const int RequiredPenumbraFeatureVersionTemp4 = 7; - private const int KeyFixed = -1610; - private const string NameFixed = "Glamourer (Automation)"; - private const int KeyManual = -6160; + private const int KeyFixed = -1610; + private const string NameFixed = "Glamourer (Automation)"; + private const int KeyManual = -6160; private const string NameManual = "Glamourer (Manually)"; private readonly IDalamudPluginInterface _pluginInterface; @@ -77,6 +77,7 @@ public class PenumbraService : IDisposable private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings? _removeAllTemporaryModSettings; private global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer? _removeAllTemporaryModSettingsPlayer; private global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings? _queryTemporaryModSettings; + private global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettingsPlayer? _queryTemporaryModSettingsPlayer; private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; private global::Penumbra.Api.IpcSubscribers.GetChangedItems? _getChangedItems; private IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)>? _changedItems; @@ -280,7 +281,8 @@ public class PenumbraService : IDisposable /// Try to set all mod settings as desired. Only sets when the mod should be enabled. /// If it is disabled, ignore all other settings. /// - public string SetMod(Mod mod, ModSettings settings, StateSource source, Guid? collectionInput = null, ObjectIndex? index = null) + public string SetMod(Mod mod, ModSettings settings, StateSource source, bool respectManual, Guid? collectionInput = null, + ObjectIndex? index = null) { if (!Available) return "Penumbra is not available."; @@ -290,7 +292,7 @@ public class PenumbraService : IDisposable { var collection = collectionInput ?? _currentCollection!.Invoke(ApiCollectionType.Current)!.Value.Id; if (_config.UseTemporarySettings && _setTemporaryModSettings != null) - SetModTemporary(sb, mod, settings, collection, index, source); + SetModTemporary(sb, mod, settings, collection, respectManual, index, source); else SetModPermanent(sb, mod, settings, collection); @@ -326,9 +328,29 @@ public class PenumbraService : IDisposable public (string ModDirectory, string ModName)[] CheckCurrentChangedItem(string changedItem) => _checkCurrentChangedItems?.Invoke(changedItem) ?? []; - private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, ObjectIndex? index, StateSource source) + private void SetModTemporary(StringBuilder sb, Mod mod, ModSettings settings, Guid collection, bool respectManual, ObjectIndex? index, + StateSource source) { var (key, name) = source.IsFixed() ? (KeyFixed, NameFixed) : (KeyManual, NameManual); + // Check for existing manual settings and do not apply fixed on top of them if respecting manual changes. + if (key is KeyFixed && respectManual) + { + var existingSource = string.Empty; + var ec = index.HasValue + ? _queryTemporaryModSettingsPlayer?.Invoke(index.Value.Index, mod.DirectoryName, out _, + out existingSource, key, mod.Name) + ?? PenumbraApiEc.InvalidArgument + : _queryTemporaryModSettings?.Invoke(collection, mod.DirectoryName, out _, + out existingSource, key, mod.Name) + ?? PenumbraApiEc.InvalidArgument; + if (ec is PenumbraApiEc.Success && existingSource is NameManual) + { + Glamourer.Log.Debug( + $"Skipped applying mod settings for [{mod.Name}] through automation because manual settings from Glamourer existed."); + return; + } + } + var ex = settings.Remove ? index.HasValue ? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, key) @@ -391,9 +413,7 @@ public class PenumbraService : IDisposable : _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list); switch (ec) { - case PenumbraApiEc.OptionGroupMissing: - sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}."); - break; + case PenumbraApiEc.OptionGroupMissing: sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}."); break; case PenumbraApiEc.OptionMissing: sb.AppendLine($"Could not find all desired options in the option group {setting} in mod {mod.Name}."); break; @@ -527,6 +547,8 @@ public class PenumbraService : IDisposable if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) { _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); + _queryTemporaryModSettingsPlayer = + new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettingsPlayer(_pluginInterface); if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp3) { _getCurrentSettingsWithTemp = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp(_pluginInterface); @@ -586,6 +608,7 @@ public class PenumbraService : IDisposable _removeAllTemporaryModSettings = null; _removeAllTemporaryModSettingsPlayer = null; _queryTemporaryModSettings = null; + _queryTemporaryModSettingsPlayer = null; _getChangedItems = null; _changedItems = null; _checkCurrentChangedItems = null; From 750d4f9eca78f1dae713065d099780a1bade9789 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 10 Mar 2025 22:59:41 +0000 Subject: [PATCH 631/786] [CI] Updating repo.json for 1.3.7.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index cc9fb7a..f2b213e 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.7.0", - "TestingAssemblyVersion": "1.3.7.0", + "AssemblyVersion": "1.3.7.1", + "TestingAssemblyVersion": "1.3.7.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From fd0d761b9270698557e13cda343015fa8e30016f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 11 Mar 2025 00:58:00 +0100 Subject: [PATCH 632/786] Fix small issue with invisible customizations applying. --- Glamourer/Automation/ApplicationType.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs index 8871a0e..58f296e 100644 --- a/Glamourer/Automation/ApplicationType.cs +++ b/Glamourer/Automation/ApplicationType.cs @@ -47,7 +47,13 @@ public static class ApplicationTypeExtensions } public static ApplicationCollection ApplyWhat(this ApplicationType type, IDesignStandIn designStandIn) - => designStandIn is not DesignBase design ? type.Collection() : type.Collection().Restrict(design.Application); + { + if(designStandIn is not DesignBase design) + return type.Collection(); + var ret = type.Collection().Restrict(design.Application); + ret.CustomizeRaw = ret.CustomizeRaw.FixApplication(design.CustomizeSet); + return ret; + } public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand; public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet; From 18ff905746225c81f42ae4d021a7db45d884f1de Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 11 Mar 2025 18:06:22 +0100 Subject: [PATCH 633/786] Add a chat command to clear temporary settings made by Glamourer. --- Glamourer/Services/CommandService.cs | 90 ++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 4b8ea3d..fa6e63f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -41,12 +41,13 @@ public class CommandService : IDisposable, IApiService private readonly DesignManager _designManager; private readonly DesignConverter _converter; private readonly DesignResolver _resolver; + private readonly PenumbraService _penumbra; public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier, ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemSelector designSelector, - QuickDesignCombo quickDesignCombo, DesignResolver resolver) + QuickDesignCombo quickDesignCombo, DesignResolver resolver, PenumbraService penumbra) { _commands = commands; _mainWindow = mainWindow; @@ -63,6 +64,7 @@ public class CommandService : IDisposable, IApiService _items = items; _customizeService = customizeService; _resolver = resolver; + _penumbra = penumbra; _commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer) { HelpMessage = "Open or close the Glamourer window." }); _commands.AddHandler(ApplyCommandString, @@ -122,8 +124,9 @@ public class CommandService : IDisposable, IApiService "reapply" => ReapplyState(argument), "revert" => Revert(argument), "reapplyautomation" => ReapplyAutomation(argument, "reapplyautomation", false, false), - "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true, false), - "resetdesign" => ReapplyAutomation(argument, "resetdesign", false, true), + "reverttoautomation" => ReapplyAutomation(argument, "reverttoautomation", true, false), + "resetdesign" => ReapplyAutomation(argument, "resetdesign", false, true), + "clearsettings" => ClearSettings(argument), "automation" => SetAutomation(argument), "copy" => CopyState(argument), "save" => SaveState(argument), @@ -154,6 +157,8 @@ public class CommandService : IDisposable, IApiService "Reverts a given character to its supposed state using automated designs. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder().AddCommand("resetdesign", "Reapplies the current automation and resets the random design. Use without arguments for help.").BuiltString); + _chat.Print(new SeStringBuilder() + .AddCommand("clearsettings", "Clears all temporary settings applied by Glamourer. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder() .AddCommand("copy", "Copy the current state of a character to clipboard. Use without arguments for help.").BuiltString); _chat.Print(new SeStringBuilder() @@ -168,6 +173,62 @@ public class CommandService : IDisposable, IApiService return true; } + private bool ClearSettings(string argument) + { + var argumentList = argument.Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (argumentList.Length < 1) + { + _chat.Print(new SeStringBuilder().AddText("Use with /glamour clearsettings ").AddGreen("[Character Identifier]").AddText(" | ") + .AddPurple("").AddText(" | ").AddBlue("").BuiltString); + PlayerIdentifierHelp(false, true); + _chat.Print(new SeStringBuilder().AddText(" 》 The character identifier specifies the collection to clear settings from. It also accepts '").AddGreen("all").AddText("' to clear all collections.").BuiltString); + _chat.Print(new SeStringBuilder().AddText(" 》 The booleans are optional and default to 'true', the ").AddPurple("first") + .AddText(" determines whether ").AddPurple("manually").AddText(" applied settings are cleared, the ").AddBlue("second") + .AddText(" determines whether ").AddBlue("automatically").AddText(" applied settings are cleared.").BuiltString); + return false; + } + + var clearManual = true; + var clearAutomatic = true; + if (argumentList.Length > 1 && bool.TryParse(argumentList[1], out var m)) + clearManual = m; + if (argumentList.Length > 2 && bool.TryParse(argumentList[2], out var a)) + clearAutomatic = a; + + if (!clearManual && !clearAutomatic) + return true; + + if (argumentList[0].ToLowerInvariant() is "all") + { + _penumbra.ClearAllTemporarySettings(clearAutomatic, clearManual); + return true; + } + + if (!IdentifierHandling(argumentList[0], out var identifiers, false, true)) + return false; + + var set = new HashSet(); + foreach (var id in identifiers) + { + if (!_objects.TryGetValue(id, out var data) || !data.Valid) + continue; + + foreach (var obj in data.Objects) + { + var guid = _penumbra.GetActorCollection(obj, out _); + if (!set.Add(guid)) + continue; + + if (clearManual) + _penumbra.RemoveAllTemporarySettings(guid, StateSource.Manual); + if (clearAutomatic) + _penumbra.RemoveAllTemporarySettings(guid, StateSource.Fixed); + } + } + + return true; + } + private bool SetAutomation(string arguments) { var argumentList = arguments.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); @@ -283,21 +344,11 @@ public class CommandService : IDisposable, IApiService { switch (char.ToLowerInvariant(character)) { - case 'c': - applicationFlags |= ApplicationType.Customizations; - break; - case 'e': - applicationFlags |= ApplicationType.Armor; - break; - case 'a': - applicationFlags |= ApplicationType.Accessories; - break; - case 'd': - applicationFlags |= ApplicationType.GearCustomization; - break; - case 'w': - applicationFlags |= ApplicationType.Weapons; - break; + case 'c': applicationFlags |= ApplicationType.Customizations; break; + case 'e': applicationFlags |= ApplicationType.Armor; break; + case 'a': applicationFlags |= ApplicationType.Accessories; break; + case 'd': applicationFlags |= ApplicationType.GearCustomization; break; + case 'w': applicationFlags |= ApplicationType.Weapons; break; default: _chat.Print(new SeStringBuilder().AddText("The value ").AddPurple(split2[1], true) .AddText(" is not a valid set of application flags.").BuiltString); @@ -694,7 +745,8 @@ public class CommandService : IDisposable, IApiService if (!applyMods || design is not Design d) return; - var (messages, appliedMods, _, name, overridden) = _modApplier.ApplyModSettings(d.AssociatedMods, actor, StateSource.Manual, d.ResetTemporarySettings); + var (messages, appliedMods, _, name, overridden) = + _modApplier.ApplyModSettings(d.AssociatedMods, actor, StateSource.Manual, d.ResetTemporarySettings); foreach (var message in messages) Glamourer.Messager.Chat.Print($"Error applying mod settings: {message}"); From 22babad7896ac3ae227087e8dbafce45a0420a28 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 27 Mar 2025 18:11:08 +0100 Subject: [PATCH 634/786] Update. --- .github/workflows/release.yml | 2 +- .github/workflows/test_release.yml | 2 +- Glamourer.Api | 2 +- Glamourer.sln | 48 ++++---- Glamourer/Glamourer.csproj | 64 +---------- Glamourer/Glamourer.json | 2 +- Glamourer/Gui/DesignQuickBar.cs | 2 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 2 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 2 +- Glamourer/Interop/Material/DirectXService.cs | 11 +- Glamourer/Interop/Material/MaterialService.cs | 12 +- Glamourer/Interop/Material/PrepareColorSet.cs | 2 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 4 +- Glamourer/packages.lock.json | 108 ++++++++++++++++++ OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 18 files changed, 160 insertions(+), 111 deletions(-) create mode 100644 Glamourer/packages.lock.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bac600a..18435ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '8.x.x' + dotnet-version: '9.x.x' - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/.github/workflows/test_release.yml b/.github/workflows/test_release.yml index b9d3672..6316776 100644 --- a/.github/workflows/test_release.yml +++ b/.github/workflows/test_release.yml @@ -15,7 +15,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '8.x.x' + dotnet-version: '9.x.x' - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/Glamourer.Api b/Glamourer.Api index 9f9bdf0..5e5c867 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 9f9bdf0873899d2e45fabaca446bb1624303b418 +Subproject commit 5e5c867a095eecac0dd494b30a33298a65e46426 diff --git a/Glamourer.sln b/Glamourer.sln index 4ac3356..78c32ec 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -29,30 +29,30 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.Build.0 = Release|Any CPU - {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.Build.0 = Release|Any CPU - {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.Build.0 = Release|Any CPU - {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.Build.0 = Release|Any CPU - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.Build.0 = Release|Any CPU - {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.Build.0 = Release|Any CPU + {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|x64 + {01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|x64 + {01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|x64 + {01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.Build.0 = Release|x64 + {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.ActiveCfg = Debug|x64 + {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.Build.0 = Debug|x64 + {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.ActiveCfg = Release|x64 + {29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.Build.0 = Release|x64 + {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.ActiveCfg = Debug|x64 + {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.Build.0 = Debug|x64 + {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.ActiveCfg = Release|x64 + {C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.Build.0 = Release|x64 + {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.ActiveCfg = Debug|x64 + {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.Build.0 = Debug|x64 + {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.ActiveCfg = Release|x64 + {AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.Build.0 = Release|x64 + {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.ActiveCfg = Debug|x64 + {EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.Build.0 = Debug|x64 + {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.ActiveCfg = Release|x64 + {EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.Build.0 = Release|x64 + {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.ActiveCfg = Debug|x64 + {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.Build.0 = Debug|x64 + {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.ActiveCfg = Release|x64 + {9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 9a1b95b..5fa2c5d 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,38 +1,13 @@ - + - net8.0-windows - preview - x64 Glamourer Glamourer 9.0.0.1 9.0.0.1 - SoftOtter Glamourer - Copyright © 2023 - true - Library + Copyright © 2025 4 - true - enable bin\$(Configuration)\ - $(MSBuildWarningsAsMessages);MSB3277 - true - false - false - - - - true - full - false - DEBUG;TRACE - - - - pdbonly - true - TRACE @@ -47,41 +22,6 @@
- - $(AppData)\XIVLauncher\addon\Hooks\dev\ - - - - - $(DalamudLibPath)Dalamud.dll - False - - - $(DalamudLibPath)FFXIVClientStructs.dll - False - - - $(DalamudLibPath)ImGui.NET.dll - False - - - $(DalamudLibPath)ImGuiScene.dll - False - - - $(DalamudLibPath)Lumina.dll - False - - - $(DalamudLibPath)Lumina.Excel.dll - False - - - $(DalamudLibPath)Newtonsoft.Json.dll - False - - - diff --git a/Glamourer/Glamourer.json b/Glamourer/Glamourer.json index 1e9edf7..3127d7d 100644 --- a/Glamourer/Glamourer.json +++ b/Glamourer/Glamourer.json @@ -8,7 +8,7 @@ "AssemblyVersion": "9.0.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 11, + "DalamudApiLevel": 12, "ImageUrls": null, "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png" } \ No newline at end of file diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index d2dba01..c9ace0c 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -173,7 +173,7 @@ public sealed class DesignQuickBar : Window, IDisposable available |= 2; _tooltipBuilder.Append("Right-Click: Apply ") .Append(design.ResolveName(_config.Ephemeral.IncognitoMode)) - .Append(" to {_targetIdentifier}."); + .Append(" to ").Append(_config.Ephemeral.IncognitoMode ? _targetIdentifier.Incognito(null) : _targetIdentifier.ToName()); } if (available == 0) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index e15872b..ec25378 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -94,7 +94,7 @@ public sealed unsafe class AdvancedDyePopup( : ByteString.FromSpanUnsafe(materialHandle->ResourceHandle.FileName.AsSpan(), true).ToString(); var gamePath = modelHandle == null ? string.Empty - : modelHandle->GetMaterialFileNameBySlotAsString(index.MaterialIndex); + : modelHandle->GetMaterialFileNameBySlot(index.MaterialIndex).ToString(); return (path, gamePath); } diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index 68ba18e..cf7e1f3 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -29,7 +29,7 @@ public sealed class PenumbraChangedItemTooltip : IDisposable .Select(p => new KeyValuePair(p.First, p.Second)); public ChangedItemType LastType { get; private set; } = ChangedItemType.None; - public uint LastId { get; private set; } = 0; + public uint LastId { get; private set; } public DateTime LastTooltip { get; private set; } = DateTime.MinValue; public DateTime LastClick { get; private set; } = DateTime.MinValue; diff --git a/Glamourer/Interop/Material/DirectXService.cs b/Glamourer/Interop/Material/DirectXService.cs index a809a34..8006a2f 100644 --- a/Glamourer/Interop/Material/DirectXService.cs +++ b/Glamourer/Interop/Material/DirectXService.cs @@ -1,12 +1,9 @@ using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using Lumina.Data.Files; using OtterGui.Services; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.String.Functions; using SharpGen.Runtime; using Vortice.Direct3D11; -using Vortice.DXGI; using MapFlags = Vortice.Direct3D11.MapFlags; using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture; @@ -14,7 +11,7 @@ namespace Glamourer.Interop.Material; public unsafe class DirectXService(IFramework framework) : IService { - private readonly object _lock = new(); + private readonly object _lock = new(); private readonly ConcurrentDictionary _textures = []; /// Generate a color table the way the game does inside the original texture, and release the original. @@ -32,9 +29,7 @@ public unsafe class DirectXService(IFramework framework) : IService lock (_lock) { - using var texture = new SafeTextureHandle(Device.Instance()->CreateTexture2D(textureSize, 1, - (uint)TexFile.TextureFormat.R16G16B16A16F, - (uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7), false); + using var texture = new SafeTextureHandle(MaterialService.CreateColorTableTexture(), false); if (texture.IsInvalid) return false; @@ -119,7 +114,7 @@ public unsafe class DirectXService(IFramework framework) : IService { var desc = resource.Description1; - if (desc.Format is not Format.R16G16B16A16_Float + if (desc.Format is not Vortice.DXGI.Format.R16G16B16A16_Float || desc.Width != MaterialService.TextureWidth || desc.Height != MaterialService.TextureHeight || map.DepthPitch != map.RowPitch * desc.Height) diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index f7ffe0f..a5f2b36 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -9,18 +9,24 @@ namespace Glamourer.Interop.Material; public static unsafe class MaterialService { + private const TextureFormat Format = TextureFormat.R16G16B16A16_FLOAT; + private const TextureFlags Flags = TextureFlags.TextureType2D | TextureFlags.Managed | TextureFlags.Immutable; + public const int TextureWidth = 8; public const int TextureHeight = ColorTable.NumRows; public const int MaterialsPerModel = 10; - public static bool GenerateNewColorTable(in ColorTable.Table colorTable, out Texture* texture) + public static Texture* CreateColorTableTexture() { var textureSize = stackalloc int[2]; textureSize[0] = TextureWidth; textureSize[1] = TextureHeight; + return Device.Instance()->CreateTexture2D(textureSize, 1, Format, Flags, 7); + } - texture = Device.Instance()->CreateTexture2D(textureSize, 1, (uint)TexFile.TextureFormat.R16G16B16A16F, - (uint)(TexFile.Attribute.TextureType2D | TexFile.Attribute.Managed | TexFile.Attribute.Immutable), 7); + public static bool GenerateNewColorTable(in ColorTable.Table colorTable, out Texture* texture) + { + texture = CreateColorTableTexture(); if (texture == null) return false; diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index b44246b..4d1feef 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -133,7 +133,7 @@ public sealed unsafe class PrepareColorSet public static ColorRow.Mode GetMode(MaterialResourceHandle* handle) => handle == null ? ColorRow.Mode.Dawntrail - : handle->ShpkNameSpan.SequenceEqual("characterlegacy.shpk"u8) + : handle->ShpkName.AsSpan().SequenceEqual("characterlegacy.shpk"u8) ? ColorRow.Mode.Legacy : ColorRow.Mode.Dawntrail; diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index 18f3cac..b58385e 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -190,7 +190,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable ? "Eternal Bond" : x.Value.HintItem.ValueNullable?.Name.ExtractText().Replace("Modern Aesthetics - ", string.Empty) ?? string.Empty; - ret.TryAdd(hair, (x.Value.Data, name)); + ret.TryAdd(hair, (x.Value.UnlockLink, name)); } } @@ -201,7 +201,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable { var name = x.Value.HintItem.ValueNullable?.Name.ExtractText().Replace("Modern Cosmetics - ", string.Empty) ?? string.Empty; - ret.TryAdd(paint, (x.Value.Data, name)); + ret.TryAdd(paint, (x.Value.UnlockLink, name)); } } } diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json new file mode 100644 index 0000000..e6f2fe5 --- /dev/null +++ b/Glamourer/packages.lock.json @@ -0,0 +1,108 @@ +{ + "version": 1, + "dependencies": { + "net9.0-windows7.0": { + "DalamudPackager": { + "type": "Direct", + "requested": "[12.0.0, )", + "resolved": "12.0.0", + "contentHash": "J5TJLV3f16T/E2H2P17ClWjtfEBPpq3yxvqW46eN36JCm6wR+EaoaYkqG9Rm5sHqs3/nK/vKjWWyvEs/jhKoXw==" + }, + "DotNet.ReproducibleBuilds": { + "type": "Direct", + "requested": "[1.2.25, )", + "resolved": "1.2.25", + "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg==" + }, + "Vortice.Direct3D11": { + "type": "Direct", + "requested": "[3.4.2-beta, )", + "resolved": "3.4.2-beta", + "contentHash": "CWVMTF7ebylzzXbQXVp5C9UpBB/L+EpX2OxSdb2wlzcsdEmrev/Ith8wVs0WjZ6DbA0WiiybnYAWqB5v0nOO/A==", + "dependencies": { + "SharpGen.Runtime": "2.1.2-beta", + "Vortice.DXGI": "3.4.2-beta" + } + }, + "JetBrains.Annotations": { + "type": "Transitive", + "resolved": "2024.3.0", + "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "9.0.2", + "contentHash": "ZffbJrskOZ40JTzcTyKwFHS5eACSWp2bUQBBApIgGV+es8RaTD4OxUG7XxFr3RIPLXtYQ1jQzF2DjKB5fZn7Qg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "9.0.2", + "contentHash": "MNe7GSTBf3jQx5vYrXF0NZvn6l7hUKF6J54ENfAgCO8y6xjN1XUmKKWG464LP2ye6QqDiA1dkaWEZBYnhoZzjg==" + }, + "SharpGen.Runtime": { + "type": "Transitive", + "resolved": "2.1.2-beta", + "contentHash": "nqZAjfEG1jX1ivvdZLsi6Pkt0DiOJyuOgRgldNFsmjXFPhxUbXQibofLSwuDZidL2kkmtTF8qLoRIeqeVdXgYw==" + }, + "SharpGen.Runtime.COM": { + "type": "Transitive", + "resolved": "2.1.2-beta", + "contentHash": "HBCrb6HfnUWx9v5/GjJeBr5DuodZLnHlFQQYXPrQs1Hbe1c6Wd0uCXf+SJp4hW8fQNxjXEu0FgiyHGlA/SRzRw==", + "dependencies": { + "SharpGen.Runtime": "2.1.2-beta" + } + }, + "Vortice.DirectX": { + "type": "Transitive", + "resolved": "3.4.2-beta", + "contentHash": "EwDbemXkmEiDGZVDem25uiEcZBYOMb+wzePuta+M/k2LXrQVGPknZhZUK56+QlHhI+Ducf/d+J75wgBzEjKi2g==", + "dependencies": { + "SharpGen.Runtime": "2.1.2-beta", + "SharpGen.Runtime.COM": "2.1.2-beta", + "Vortice.Mathematics": "1.7.6" + } + }, + "Vortice.DXGI": { + "type": "Transitive", + "resolved": "3.4.2-beta", + "contentHash": "T4S3pp6l/SGJ6SH3ebCbodN/bimGOkIBiIYKeBpVEis7+/ac1XIjyzgSTJ5XsH3o3hSH7DqSbP6Yo6mL9nyFQA==", + "dependencies": { + "SharpGen.Runtime": "2.1.2-beta", + "Vortice.DirectX": "3.4.2-beta" + } + }, + "Vortice.Mathematics": { + "type": "Transitive", + "resolved": "1.7.6", + "contentHash": "W8FNv850lPGxmHphwLyi1qnUlQHZBxh/62EenFJTaY6acPP29Fk0xMQJI60G+YNlsVJb3fSoriuW+ong5sM5UQ==" + }, + "glamourer.api": { + "type": "Project" + }, + "ottergui": { + "type": "Project", + "dependencies": { + "JetBrains.Annotations": "[2024.3.0, )", + "Microsoft.Extensions.DependencyInjection": "[9.0.2, )" + } + }, + "penumbra.api": { + "type": "Project" + }, + "penumbra.gamedata": { + "type": "Project", + "dependencies": { + "OtterGui": "[1.0.0, )", + "Penumbra.Api": "[5.6.1, )", + "Penumbra.String": "[1.0.6, )" + } + }, + "penumbra.string": { + "type": "Project" + } + } + } +} \ No newline at end of file diff --git a/OtterGui b/OtterGui index 13f1a90..3396ee1 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 13f1a90b88d2b8572480748a209f957b70d6a46f +Subproject commit 3396ee176fa72ad2dfb2de3294f7125ebce4dae5 diff --git a/Penumbra.Api b/Penumbra.Api index 70f0468..2cbf4ba 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 70f046830cc7cd35b3480b12b7efe94182477fbb +Subproject commit 2cbf4bace53a5749d3eab1ff03025a6e6bd9fc37 diff --git a/Penumbra.GameData b/Penumbra.GameData index 96163f7..9ae4a97 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 96163f79e13c7d52cc36cdd82ab4e823763f4f31 +Subproject commit 9ae4a97110fff005a54213815086ce950d4d8b2d diff --git a/Penumbra.String b/Penumbra.String index 4eb7c11..2896c05 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 4eb7c118cdac5873afb97cb04719602f061f03b7 +Subproject commit 2896c0561f60827f97408650d52a15c38f4d9d10 From 00cb1b6643615fa360a5d4c11a44e0481f0820d9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 13:52:15 +0100 Subject: [PATCH 635/786] Use CS sig. --- Glamourer/Interop/ScalingService.cs | 5 +++-- Penumbra.GameData | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Glamourer/Interop/ScalingService.cs b/Glamourer/Interop/ScalingService.cs index 68dc2e8..2a89a25 100644 --- a/Glamourer/Interop/ScalingService.cs +++ b/Glamourer/Interop/ScalingService.cs @@ -27,6 +27,9 @@ public unsafe class ScalingService : IDisposable interop.HookFromAddress((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); _calculateHeightHook = interop.HookFromAddress((nint)ModelContainer.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); + _placeMinionHook = interop.HookFromAddress((nint)Companion.MemberFunctionPointers.PlaceCompanion, PlaceMinionDetour); + //_updateOrnamentHook = + // interop.HookFromAddress((nint)Ornament.MemberFunctionPointers.UpdateOrnament, UpdateOrnamentDetour); _setupMountHook.Enable(); _updateOrnamentHook.Enable(); @@ -55,8 +58,6 @@ public unsafe class ScalingService : IDisposable private readonly Hook _calculateHeightHook; - // TODO: Use client structs sig. - [Signature(Sigs.PlaceMinion, DetourName = nameof(PlaceMinionDetour))] private readonly Hook _placeMinionHook = null!; private void SetupMountDetour(MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4) diff --git a/Penumbra.GameData b/Penumbra.GameData index 9ae4a97..8ec54ed 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 9ae4a97110fff005a54213815086ce950d4d8b2d +Subproject commit 8ec54ed4b114e2ffb1d46596785fd5ec382d7ebd From d6d592f099a8a51065465f95c1a7dcd216b06026 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 13:58:19 +0100 Subject: [PATCH 636/786] 1.3.8.0 --- Glamourer.sln | 1 + Glamourer/Gui/GlamourerChangelog.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/Glamourer.sln b/Glamourer.sln index 78c32ec..e2915d5 100644 --- a/Glamourer.sln +++ b/Glamourer.sln @@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .github\workflows\release.yml = .github\workflows\release.yml + Glamourer\Glamourer.json = Glamourer\Glamourer.json repo.json = repo.json .github\workflows\test_release.yml = .github\workflows\test_release.yml EndProjectSection diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 7033fdd..e12b32e 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -41,6 +41,7 @@ public class GlamourerChangelog Add1_3_5_0(Changelog); Add1_3_6_0(Changelog); Add1_3_7_0(Changelog); + Add1_3_8_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -61,6 +62,18 @@ public class GlamourerChangelog } } + private static void Add1_3_8_0(Changelog log) + => log.NextVersion("Version 1.3.8.0") + .RegisterImportant("Updated Glamourer for update 7.20 and Dalamud API 12.") + .RegisterEntry( + "This is not thoroughly tested, but I decided to push to stable instead of testing because otherwise a lot of people would just go to testing just for early access again despite having no business doing so.", + 1) + .RegisterEntry( + "I also do not use most of the functionality of Glamourer myself, so I am unable to even encounter most issues myself.", 1) + .RegisterEntry("If you encounter any issues, please report them quickly on the discord.", 1) + .RegisterEntry("Added a chat command to clear temporary settings applied by Glamourer to Penumbra.") + .RegisterEntry("Fixed small issues with customizations not applicable to your race still applying."); + private static void Add1_3_7_0(Changelog log) => log.NextVersion("Version 1.3.7.0") .RegisterImportant( From 9d3dfbbece555b3505b86e63a1ac68d5700ad283 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 14:05:53 +0100 Subject: [PATCH 637/786] use staging build for release for now. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18435ae..327b75b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | From 296f1e90b5c0c3b18b3f79ba68a5bd5fd338a089 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 14:13:34 +0100 Subject: [PATCH 638/786] :shrug: --- Glamourer/Glamourer.csproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 5fa2c5d..a4325fb 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -10,10 +10,6 @@ bin\$(Configuration)\
- - OnOutputUpdated - - @@ -62,8 +58,4 @@ PreserveNewest - - - - \ No newline at end of file From d75d70bee577680da38b95dad877b1da92838c1e Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 28 Mar 2025 13:16:18 +0000 Subject: [PATCH 639/786] [CI] Updating repo.json for 1.3.8.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index f2b213e..2eabfd0 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.7.1", - "TestingAssemblyVersion": "1.3.7.1", + "AssemblyVersion": "1.3.8.0", + "TestingAssemblyVersion": "1.3.8.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 11, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.7.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From d398381b52f1d22625adfce15e0ad044c7146e95 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 14:17:16 +0100 Subject: [PATCH 640/786] Revert Dalamud staging on release, and update api level. --- .github/workflows/release.yml | 2 +- repo.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 327b75b..18435ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | diff --git a/repo.json b/repo.json index 2eabfd0..fd45265 100644 --- a/repo.json +++ b/repo.json @@ -21,8 +21,8 @@ "TestingAssemblyVersion": "1.3.8.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 11, - "TestingDalamudApiLevel": 11, + "DalamudApiLevel": 12, + "TestingDalamudApiLevel": 12, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1, From b0abf865cb2e189f3864295a7d875f6e4b2e4f55 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 15:54:10 +0100 Subject: [PATCH 641/786] Change build step. --- Glamourer/Glamourer.csproj | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index a4325fb..c3e46ab 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -12,6 +12,10 @@ + + + PreserveNewest + @@ -52,10 +56,4 @@ $(GitCommitHash)
- - - - PreserveNewest - - \ No newline at end of file From b1d00e981240221fb3a98f7c3f545c7c6f2fb1e1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 15:54:26 +0100 Subject: [PATCH 642/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 8ec54ed..8592159 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 8ec54ed4b114e2ffb1d46596785fd5ec382d7ebd +Subproject commit 859215989da41a4ccb59a5ce390223570a69c94e From 361ed536a3056bbd7705cb6dcd16e5c1589bfa1b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 17:22:57 +0100 Subject: [PATCH 643/786] Fix issue with NPC automation due to missing job detection. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 8592159..ff9b731 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 859215989da41a4ccb59a5ce390223570a69c94e +Subproject commit ff9b731c39494851cfde3d580cd99364b6c04d7c From 76eaa75d04e7cf4373476e8a2dacaf6266e460ba Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 17:23:25 +0100 Subject: [PATCH 644/786] Update Penumbra.Api. --- Penumbra.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.Api b/Penumbra.Api index 2cbf4ba..bd56d82 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 2cbf4bace53a5749d3eab1ff03025a6e6bd9fc37 +Subproject commit bd56d82816b8366e19dddfb2dc7fd7f167e264ee From 782c4446b2ae78602c90331c6ee1bf8f9af24897 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Mar 2025 17:25:34 +0100 Subject: [PATCH 645/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index ff9b731..e717a66 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit ff9b731c39494851cfde3d580cd99364b6c04d7c +Subproject commit e717a66f33b0656a7c5c971ffa2f63fd96477d94 From 381b23fe0cbb9d8ed21c129c35f84113687ac21f Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 28 Mar 2025 16:29:25 +0000 Subject: [PATCH 646/786] [CI] Updating repo.json for 1.3.8.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index fd45265..d29a69f 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.0", - "TestingAssemblyVersion": "1.3.8.0", + "AssemblyVersion": "1.3.8.1", + "TestingAssemblyVersion": "1.3.8.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From b1e65e6f9d509c84454c0657da8f3936e9ca9f2d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 29 Mar 2025 18:04:13 +0100 Subject: [PATCH 647/786] Fix some offsets. --- Glamourer/Interop/Material/MaterialManager.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 81373c5..e065e91 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -197,12 +197,11 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// private static CharacterWeapon GetTempSlot(Weapon* weapon) { - // TODO: Use ClientStructs - var changedData = *(void**)((byte*)weapon + 0xA40); + var changedData = weapon->ChangedData; if (changedData == null) return new CharacterWeapon(weapon->ModelSetId, weapon->SecondaryId, (Variant)weapon->Variant, StainIds.FromWeapon(*weapon)); - return new CharacterWeapon(weapon->ModelSetId, *(SecondaryId*)changedData, ((Variant*)changedData)[2], - new StainIds(((StainId*)changedData)[3], ((StainId*)changedData)[4])); + return new CharacterWeapon(weapon->ModelSetId, changedData->SecondaryId, changedData->Variant, + new StainIds(changedData->Stain0, changedData->Stain1)); } } From 4fca1600cab8c954789171eb81feeecf5968c5dc Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 29 Mar 2025 17:06:25 +0000 Subject: [PATCH 648/786] [CI] Updating repo.json for 1.3.8.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index d29a69f..bba0aa8 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.1", - "TestingAssemblyVersion": "1.3.8.1", + "AssemblyVersion": "1.3.8.2", + "TestingAssemblyVersion": "1.3.8.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 6b3a64ce14ad1f033c23afe17b641021eaffdd73 Mon Sep 17 00:00:00 2001 From: keifufu Date: Mon, 31 Mar 2025 13:00:51 +0200 Subject: [PATCH 649/786] fix linux build --- Glamourer/Glamourer.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index c3e46ab..90c743e 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -26,7 +26,7 @@ - + @@ -56,4 +56,4 @@ $(GitCommitHash) - \ No newline at end of file + From a40a6905bec869940c371cc4adeac096ddc4de62 Mon Sep 17 00:00:00 2001 From: keifufu Date: Mon, 31 Mar 2025 13:02:19 +0200 Subject: [PATCH 650/786] newline be gone --- Glamourer/Glamourer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 90c743e..9dabbb2 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -56,4 +56,4 @@ $(GitCommitHash) - + \ No newline at end of file From 95bc52b2bc14758a547d8405a57d31917a42b5c0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 2 Apr 2025 23:45:02 +0200 Subject: [PATCH 651/786] Check for valid humanity. --- Glamourer/Interop/Material/MaterialManager.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index e065e91..ff30483 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -157,7 +157,11 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// Find the type of the given draw object by checking the actors pointers. private static bool FindType(CharacterBase* characterBase, Actor actor, out MaterialValueIndex.DrawObjectType type) { - type = MaterialValueIndex.DrawObjectType.Human; + type = MaterialValueIndex.DrawObjectType.Invalid; + if (!((Model)characterBase).IsHuman) + return false; + + type = type = MaterialValueIndex.DrawObjectType.Human; if (!actor.Valid) return false; From 90813ce0300751749a2bba70001be03a4ba338b1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 2 Apr 2025 23:45:28 +0200 Subject: [PATCH 652/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index e717a66..ab63da8 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit e717a66f33b0656a7c5c971ffa2f63fd96477d94 +Subproject commit ab63da8047f3d99240159bb1b17dbcb61d77326a From b98cb31fd2b03819c283d95ef674ecb7d7f7973c Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 2 Apr 2025 21:47:24 +0000 Subject: [PATCH 653/786] [CI] Updating repo.json for 1.3.8.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index bba0aa8..fae7a8f 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.2", - "TestingAssemblyVersion": "1.3.8.2", + "AssemblyVersion": "1.3.8.3", + "TestingAssemblyVersion": "1.3.8.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 096d82741d16e9e88e626c950b83c5a07f94f20c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 3 Apr 2025 01:04:58 +0200 Subject: [PATCH 654/786] Fix previous fix for weapons. --- Glamourer/Interop/Material/MaterialManager.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index ff30483..9eccb29 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -157,19 +157,23 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable /// Find the type of the given draw object by checking the actors pointers. private static bool FindType(CharacterBase* characterBase, Actor actor, out MaterialValueIndex.DrawObjectType type) { - type = MaterialValueIndex.DrawObjectType.Invalid; - if (!((Model)characterBase).IsHuman) - return false; - - type = type = MaterialValueIndex.DrawObjectType.Human; if (!actor.Valid) + { + type = MaterialValueIndex.DrawObjectType.Invalid; return false; + } - if (actor.Model.AsCharacterBase == characterBase) + if (actor.Model.AsCharacterBase == characterBase && ((Model)characterBase).IsHuman) + { + type = MaterialValueIndex.DrawObjectType.Human; return true; + } if (!actor.AsObject->IsCharacter()) + { + type = MaterialValueIndex.DrawObjectType.Invalid; return false; + } if (actor.AsCharacter->DrawData.WeaponData[0].DrawObject == characterBase) { @@ -183,6 +187,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable return true; } + type = MaterialValueIndex.DrawObjectType.Invalid; return false; } From 118f51cc647e82ec2910a3678d856e95fba457b3 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 2 Apr 2025 23:07:10 +0000 Subject: [PATCH 655/786] [CI] Updating repo.json for 1.3.8.4 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index fae7a8f..b00ebe5 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.3", - "TestingAssemblyVersion": "1.3.8.3", + "AssemblyVersion": "1.3.8.4", + "TestingAssemblyVersion": "1.3.8.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 46f8818cee7cae30c5ac2d444290b389c85350a9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 4 Apr 2025 22:35:42 +0200 Subject: [PATCH 656/786] Add Incognito Modifier. --- Glamourer/Configuration.cs | 1 + Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 2 +- Glamourer/Gui/Tabs/HeaderDrawer.cs | 28 ++++++++++++++----- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 4 +++ 7 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index ef51544..b1b9ec2 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -75,6 +75,7 @@ public class Configuration : IPluginConfiguration, ISavable public RenameField ShowRename { get; set; } = RenameField.BothDataPrio; public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); + public DoubleModifier IncognitoModifier { get; set; } = new(ModifierHotkey.Control); public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; public QdbButtons QdbButtons { get; set; } = diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 0071b1f..0381a17 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -87,7 +87,7 @@ public class ActorPanel _rightButtons = [ new LockedButton(this), - new HeaderDrawer.IncognitoButton(_config.Ephemeral), + new HeaderDrawer.IncognitoButton(_config), ]; } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 94329e5..badeaeb 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -31,7 +31,7 @@ public class SetPanel( RandomRestrictionDrawer _randomDrawer) { private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log); - private readonly HeaderDrawer.Button[] _rightButtons = [new HeaderDrawer.IncognitoButton(_config.Ephemeral)]; + private readonly HeaderDrawer.Button[] _rightButtons = [new HeaderDrawer.IncognitoButton(_config)]; private string? _tempName; private int _dragIndex = -1; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 04e51ce..3543b68 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -89,7 +89,7 @@ public class DesignPanel _rightButtons = [ new LockButton(this), - new IncognitoButton(_config.Ephemeral), + new IncognitoButton(_config), ]; } diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 2235160..2a5b3e1 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -13,7 +13,7 @@ namespace Glamourer.Gui.Tabs.DesignTab; public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager editor, DesignColors colors, Configuration config) { private readonly Button[] _leftButtons = []; - private readonly Button[] _rightButtons = [new IncognitoButton(config.Ephemeral)]; + private readonly Button[] _rightButtons = [new IncognitoButton(config)]; private readonly DesignColorCombo _colorCombo = new(colors, true); diff --git a/Glamourer/Gui/Tabs/HeaderDrawer.cs b/Glamourer/Gui/Tabs/HeaderDrawer.cs index 0e9237d..d6baeb9 100644 --- a/Glamourer/Gui/Tabs/HeaderDrawer.cs +++ b/Glamourer/Gui/Tabs/HeaderDrawer.cs @@ -44,22 +44,36 @@ public static class HeaderDrawer } } - public sealed class IncognitoButton(EphemeralConfig config) : Button + public sealed class IncognitoButton(Configuration config) : Button { protected override string Description - => config.IncognitoMode - ? "Toggle incognito mode off." - : "Toggle incognito mode on."; + { + get + { + var hold = config.IncognitoModifier.IsActive(); + return (config.Ephemeral.IncognitoMode, hold) + switch + { + (true, true) => "Toggle incognito mode off.", + (false, true) => "Toggle incognito mode on.", + (true, false) => $"Toggle incognito mode off.\n\nHold {config.IncognitoModifier} while clicking to toggle.", + (false, false) => $"Toggle incognito mode on.\n\nHold {config.IncognitoModifier} while clicking to toggle.", + }; + } + } protected override FontAwesomeIcon Icon - => config.IncognitoMode + => config.Ephemeral.IncognitoMode ? FontAwesomeIcon.EyeSlash : FontAwesomeIcon.Eye; protected override void OnClick() { - config.IncognitoMode = !config.IncognitoMode; - config.Save(); + if (!config.IncognitoModifier.IsActive()) + return; + + config.Ephemeral.IncognitoMode = !config.Ephemeral.IncognitoMode; + config.Ephemeral.Save(); } } diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index d6d2c15..11af9b9 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -188,6 +188,10 @@ public class SettingsTab( "A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale, config.DeleteDesignModifier, v => config.DeleteDesignModifier = v)) config.Save(); + if (Widget.DoubleModifierSelector("Incognito Modifier", + "A modifier you need to hold while clicking the Incognito button for it to take effect.", 100 * ImGuiHelpers.GlobalScale, + config.IncognitoModifier, v => config.IncognitoModifier = v)) + config.Save(); DrawRenameSettings(); Checkbox("Auto-Open Design Folders"u8, "Have design folders open or closed as their default state after launching."u8, config.OpenFoldersByDefault, From 8fe0ac81952b5650f80207270ec1610e74062333 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 5 Apr 2025 15:12:27 +0200 Subject: [PATCH 657/786] Fix weapon color set issue. --- Glamourer/Interop/Material/PrepareColorSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 4d1feef..bd60a60 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -119,7 +119,7 @@ public sealed unsafe class PrepareColorSet case MaterialValueIndex.DrawObjectType.Human: return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stains : StainIds.None; case MaterialValueIndex.DrawObjectType.Mainhand: - var mainhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject; + var mainhand = (Model)actor.AsCharacter->DrawData.WeaponData[0].DrawObject; return mainhand.IsWeapon ? StainIds.FromWeapon(*mainhand.AsWeapon) : StainIds.None; case MaterialValueIndex.DrawObjectType.Offhand: var offhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject; From c0ad4aab510689f01ab07028a87b468003e68baa Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 5 Apr 2025 18:48:39 +0200 Subject: [PATCH 658/786] Update for new ActorObjectManager. --- Glamourer/Api/ApiHelpers.cs | 12 +- Glamourer/Api/StateApi.cs | 37 ++-- Glamourer/Automation/AutoDesignApplier.cs | 28 ++- Glamourer/Designs/History/EditorHistory.cs | 2 +- Glamourer/Events/StateChanged.cs | 1 + Glamourer/Events/StateFinalized.cs | 1 + Glamourer/Glamourer.cs | 6 +- Glamourer/Gui/DesignQuickBar.cs | 48 ++-- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 16 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 8 +- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 11 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- .../Gui/Tabs/AutomationTab/SetSelector.cs | 26 +-- .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 14 +- .../DebugTab/AdvancedCustomizationDrawer.cs | 8 +- .../Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 44 ++-- .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 5 +- .../Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 62 +++--- .../Gui/Tabs/DebugTab/ObjectManagerPanel.cs | 61 ++--- .../Gui/Tabs/DebugTab/RetainedStatePanel.cs | 3 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 10 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 6 +- .../SettingsTab/CollectionOverrideDrawer.cs | 13 +- Glamourer/Interop/ContextMenuService.cs | 15 +- Glamourer/Interop/ObjectManager.cs | 209 ------------------ .../Interop/Penumbra/ModSettingApplier.cs | 3 +- .../Interop/Penumbra/PenumbraAutoRedraw.cs | 8 +- Glamourer/Interop/Structs/ActorData.cs | 47 ---- Glamourer/Services/CommandService.cs | 46 ++-- Glamourer/Services/DesignApplier.cs | 15 +- Glamourer/Services/ServiceManager.cs | 2 + Glamourer/State/FunModule.cs | 39 ++-- Glamourer/State/StateApplier.cs | 8 +- Glamourer/State/StateEditor.cs | 1 + Glamourer/State/StateListener.cs | 21 +- Glamourer/Unlocks/CustomizeUnlockManager.cs | 9 +- OtterGui | 2 +- Penumbra.GameData | 2 +- 38 files changed, 273 insertions(+), 578 deletions(-) delete mode 100644 Glamourer/Interop/ObjectManager.cs delete mode 100644 Glamourer/Interop/Structs/ActorData.cs diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index ed58500..238d349 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -6,12 +6,12 @@ using OtterGui.Log; using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.String; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Api; -public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService +public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal IEnumerable FindExistingStates(string actorName) @@ -27,7 +27,7 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal GlamourerApiEc FindExistingState(int objectIndex, out ActorState? state) { - var actor = objects[objectIndex]; + var actor = objects.Objects[objectIndex]; var identifier = actor.GetIdentifier(actors); if (!identifier.IsValid) { @@ -42,7 +42,7 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal ActorState? FindState(int objectIndex) { - var actor = objects[objectIndex]; + var actor = objects.Objects[objectIndex]; var identifier = actor.GetIdentifier(actors); if (identifier.IsValid && stateManager.GetOrCreate(identifier, actor, out var state)) return state; @@ -73,10 +73,8 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM if (objectName.Length == 0 || !ByteString.FromString(objectName, out var byteString)) return []; - objects.Update(); - return stateManager.Values.Where(state => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString) - .Concat(objects.Identifiers + .Concat(objects .Where(kvp => kvp.Key is { IsValid: true, Type: IdentifierType.Player } && kvp.Key.PlayerName == byteString) .SelectWhere(kvp => { diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index c27abb7..43dc453 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -4,34 +4,32 @@ using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Events; -using Glamourer.Interop.Structs; using Glamourer.State; using Newtonsoft.Json.Linq; using OtterGui.Services; using Penumbra.GameData.Interop; -using ObjectManager = Glamourer.Interop.ObjectManager; using StateChanged = Glamourer.Events.StateChanged; namespace Glamourer.Api; public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { - private readonly ApiHelpers _helpers; - private readonly StateManager _stateManager; - private readonly DesignConverter _converter; - private readonly Configuration _config; - private readonly AutoDesignApplier _autoDesigns; - private readonly ObjectManager _objects; - private readonly StateChanged _stateChanged; - private readonly StateFinalized _stateFinalized; - private readonly GPoseService _gPose; + private readonly ApiHelpers _helpers; + private readonly StateManager _stateManager; + private readonly DesignConverter _converter; + private readonly Configuration _config; + private readonly AutoDesignApplier _autoDesigns; + private readonly ActorObjectManager _objects; + private readonly StateChanged _stateChanged; + private readonly StateFinalized _stateFinalized; + private readonly GPoseService _gPose; public StateApi(ApiHelpers helpers, StateManager stateManager, DesignConverter converter, Configuration config, AutoDesignApplier autoDesigns, - ObjectManager objects, + ActorObjectManager objects, StateChanged stateChanged, StateFinalized stateFinalized, GPoseService gPose) @@ -219,7 +217,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable if (!state.CanUnlock(key)) return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args); - RevertToAutomation(_objects[objectIndex], state, key, flags); + RevertToAutomation(_objects.Objects[objectIndex], state, key, flags); return ApiHelpers.Return(GlamourerApiEc.Success, args); } @@ -272,15 +270,9 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; switch (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) { - case ApplyFlag.Equipment: - _stateManager.ResetEquip(state, source, key); - break; - case ApplyFlag.Customization: - _stateManager.ResetCustomize(state, source, key); - break; - case ApplyFlag.Equipment | ApplyFlag.Customization: - _stateManager.ResetState(state, source, key); - break; + case ApplyFlag.Equipment: _stateManager.ResetEquip(state, source, key); break; + case ApplyFlag.Customization: _stateManager.ResetCustomize(state, source, key); break; + case ApplyFlag.Equipment | ApplyFlag.Customization: _stateManager.ResetState(state, source, key); break; } ApiHelpers.Lock(state, key, flags); @@ -288,7 +280,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private GlamourerApiEc RevertToAutomation(ActorState state, uint key, ApplyFlag flags) { - _objects.Update(); if (!_objects.TryGetValue(state.Identifier, out var actors) || !actors.Valid) return GlamourerApiEc.ActorNotFound; diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 545dff7..a61a004 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -11,29 +11,28 @@ using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Automation; public sealed class AutoDesignApplier : IDisposable { - private readonly Configuration _config; - private readonly AutoDesignManager _manager; - private readonly StateManager _state; - private readonly JobService _jobs; - private readonly EquippedGearset _equippedGearset; - private readonly ActorManager _actors; - private readonly AutomationChanged _event; - private readonly ObjectManager _objects; - private readonly WeaponLoading _weapons; - private readonly HumanModelList _humans; - private readonly DesignMerger _designMerger; - private readonly IClientState _clientState; + private readonly Configuration _config; + private readonly AutoDesignManager _manager; + private readonly StateManager _state; + private readonly JobService _jobs; + private readonly EquippedGearset _equippedGearset; + private readonly ActorManager _actors; + private readonly AutomationChanged _event; + private readonly ActorObjectManager _objects; + private readonly WeaponLoading _weapons; + private readonly HumanModelList _humans; + private readonly DesignMerger _designMerger; + private readonly IClientState _clientState; private readonly JobChangeState _jobChangeState; public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors, - AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, + AutomationChanged @event, ActorObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState) { _config = config; @@ -154,7 +153,6 @@ public sealed class AutoDesignApplier : IDisposable if (newSet is not { Enabled: true }) return; - _objects.Update(); foreach (var id in newSet.Identifiers) { if (_objects.TryGetValue(id, out var data)) diff --git a/Glamourer/Designs/History/EditorHistory.cs b/Glamourer/Designs/History/EditorHistory.cs index 6382b94..58bce3d 100644 --- a/Glamourer/Designs/History/EditorHistory.cs +++ b/Glamourer/Designs/History/EditorHistory.cs @@ -1,8 +1,8 @@ using Glamourer.Api.Enums; using Glamourer.Events; -using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Services; +using Penumbra.GameData.Interop; namespace Glamourer.Designs.History; diff --git a/Glamourer/Events/StateChanged.cs b/Glamourer/Events/StateChanged.cs index c704195..2bcc6fe 100644 --- a/Glamourer/Events/StateChanged.cs +++ b/Glamourer/Events/StateChanged.cs @@ -3,6 +3,7 @@ using Glamourer.Designs.History; using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Classes; +using Penumbra.GameData.Interop; namespace Glamourer.Events; diff --git a/Glamourer/Events/StateFinalized.cs b/Glamourer/Events/StateFinalized.cs index e8548e9..0ccaa8b 100644 --- a/Glamourer/Events/StateFinalized.cs +++ b/Glamourer/Events/StateFinalized.cs @@ -2,6 +2,7 @@ using Glamourer.Api; using Glamourer.Api.Enums; using Glamourer.Interop.Structs; using OtterGui.Classes; +using Penumbra.GameData.Interop; namespace Glamourer.Events; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 9191c4f..1e2a62d 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -6,12 +6,10 @@ using Glamourer.Gui; using Glamourer.Interop; using Glamourer.Services; using Glamourer.State; -using OtterGui; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Services; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Files; +using Penumbra.GameData.Interop; namespace Glamourer; @@ -82,7 +80,7 @@ public class Glamourer : IDalamudPlugin var designManager = _services.GetService(); var autoManager = _services.GetService(); var stateManager = _services.GetService(); - var objectManager = _services.GetService(); + var objectManager = _services.GetService(); var currentPlayer = objectManager.PlayerData.Identifier; var states = stateManager.Where(kvp => objectManager.ContainsKey(kvp.Key)).ToList(); diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index c9ace0c..5112d97 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -6,14 +6,13 @@ using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; using Glamourer.Automation; using Glamourer.Designs; -using Glamourer.Interop; using Glamourer.Interop.Penumbra; -using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; using OtterGui.Classes; using OtterGui.Text; using Penumbra.GameData.Actors; +using Penumbra.GameData.Interop; namespace Glamourer.Gui; @@ -37,21 +36,21 @@ public sealed class DesignQuickBar : Window, IDisposable ? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove : ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing; - private readonly Configuration _config; - private readonly QuickDesignCombo _designCombo; - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly ObjectManager _objects; - private readonly PenumbraService _penumbra; - private readonly IKeyState _keyState; - private readonly ImRaii.Style _windowPadding = new(); - private readonly ImRaii.Color _windowColor = new(); - private DateTime _keyboardToggle = DateTime.UnixEpoch; - private int _numButtons; - private readonly StringBuilder _tooltipBuilder = new(512); + private readonly Configuration _config; + private readonly QuickDesignCombo _designCombo; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly ActorObjectManager _objects; + private readonly PenumbraService _penumbra; + private readonly IKeyState _keyState; + private readonly ImRaii.Style _windowPadding = new(); + private readonly ImRaii.Color _windowColor = new(); + private DateTime _keyboardToggle = DateTime.UnixEpoch; + private int _numButtons; + private readonly StringBuilder _tooltipBuilder = new(512); public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, - ObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) + ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking) { _config = config; @@ -222,7 +221,8 @@ public sealed class DesignQuickBar : Window, IDisposable } if (available == 0) - _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); + _tooltipBuilder.Append( + "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, available); ImGui.SameLine(); @@ -258,9 +258,10 @@ public sealed class DesignQuickBar : Window, IDisposable } if (available == 0) - _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); + _tooltipBuilder.Append( + "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); - var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, available); + var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, available); ImGui.SameLine(); if (!clicked) return; @@ -300,7 +301,8 @@ public sealed class DesignQuickBar : Window, IDisposable } if (available == 0) - _tooltipBuilder.Append("Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); + _tooltipBuilder.Append( + "Neither player character nor target are available, have state modified by Glamourer, or their state is locked."); var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.Repeat, buttonSize, available); ImGui.SameLine(); @@ -424,7 +426,9 @@ public sealed class DesignQuickBar : Window, IDisposable if (_playerIdentifier.IsValid && _playerData.Valid) { available |= 1; - _tooltipBuilder.Append("Left-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") + _tooltipBuilder + .Append( + "Left-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") .Append(_playerIdentifier) .Append('.'); } @@ -434,7 +438,9 @@ public sealed class DesignQuickBar : Window, IDisposable if (available != 0) _tooltipBuilder.Append('\n'); available |= 2; - _tooltipBuilder.Append("Right-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") + _tooltipBuilder + .Append( + "Right-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ") .Append(_targetIdentifier) .Append('.'); } diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index cf7e1f3..1723333 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -1,6 +1,5 @@ using Glamourer.Designs; using Glamourer.Events; -using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; @@ -9,18 +8,19 @@ using OtterGui.Raii; using Penumbra.Api.Enums; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Gui; public sealed class PenumbraChangedItemTooltip : IDisposable { - private readonly PenumbraService _penumbra; - private readonly StateManager _stateManager; - private readonly ItemManager _items; - private readonly ObjectManager _objects; - private readonly CustomizeService _customize; - private readonly GPoseService _gpose; + private readonly PenumbraService _penumbra; + private readonly StateManager _stateManager; + private readonly ItemManager _items; + private readonly ActorObjectManager _objects; + private readonly CustomizeService _customize; + private readonly GPoseService _gpose; private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2]; @@ -33,7 +33,7 @@ public sealed class PenumbraChangedItemTooltip : IDisposable public DateTime LastTooltip { get; private set; } = DateTime.MinValue; public DateTime LastClick { get; private set; } = DateTime.MinValue; - public PenumbraChangedItemTooltip(PenumbraService penumbra, StateManager stateManager, ItemManager items, ObjectManager objects, + public PenumbraChangedItemTooltip(PenumbraService penumbra, StateManager stateManager, ItemManager items, ActorObjectManager objects, CustomizeService customize, GPoseService gpose) { _penumbra = penumbra; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 0381a17..5419f23 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -10,7 +10,6 @@ using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Materials; using Glamourer.Interop; -using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; using OtterGui; @@ -22,7 +21,6 @@ using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Gui.Tabs.ActorTab; @@ -35,7 +33,7 @@ public class ActorPanel private readonly AutoDesignApplier _autoDesignApplier; private readonly Configuration _config; private readonly DesignConverter _converter; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly DesignManager _designManager; private readonly ImportService _importService; private readonly ICondition _conditions; @@ -53,7 +51,7 @@ public class ActorPanel AutoDesignApplier autoDesignApplier, Configuration config, DesignConverter converter, - ObjectManager objects, + ActorObjectManager objects, DesignManager designManager, ImportService importService, ICondition conditions, @@ -106,7 +104,7 @@ public class ActorPanel { using var group = ImRaii.Group(); (_identifier, _data) = _selector.Selection; - _lockedRedraw = _identifier.Type is IdentifierType.Special + _lockedRedraw = _identifier.Type is IdentifierType.Special || _objects.IsInLobby || _conditions[ConditionFlag.OccupiedInCutSceneEvent]; (_actorName, _actor) = GetHeaderName(); DrawHeader(); diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index 3269fd2..e46d651 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -1,7 +1,4 @@ -using System.Security.AccessControl; -using Dalamud.Interface; -using Glamourer.Interop; -using Glamourer.Interop.Structs; +using Dalamud.Interface; using ImGuiNET; using OtterGui; using OtterGui.Classes; @@ -9,11 +6,12 @@ using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.ActorTab; -public class ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config) +public class ActorSelector(ActorObjectManager objects, ActorManager actors, EphemeralConfig config) { private ActorIdentifier _identifier = ActorIdentifier.Invalid; @@ -89,11 +87,10 @@ public class ActorSelector(ObjectManager objects, ActorManager actors, Ephemeral if (!child) return; - objects.Update(); _world = new WorldId(objects.Player.Valid ? objects.Player.HomeWorld : (ushort)0); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight()); - var remainder = ImGuiClip.FilteredClippedDraw(objects.Identifiers.Where(p => p.Value.Objects.Any(a => a.Model)), skips, CheckFilter, + var remainder = ImGuiClip.FilteredClippedDraw(objects.Where(p => p.Value.Objects.Any(a => a.Model)), skips, CheckFilter, DrawSelectable); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight()); } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index badeaeb..7f576b3 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -434,7 +434,7 @@ public class SetPanel( if (ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0)) { _dragIndex = index; - _selector._dragDesignIndex = index; + _selector.DragDesignIndex = index; } } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 950b735..09ee6aa 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -2,12 +2,11 @@ using Dalamud.Interface.Utility; using Glamourer.Automation; using Glamourer.Events; -using Glamourer.Interop; using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; -using Penumbra.GameData.Actors; +using Penumbra.GameData.Interop; using Penumbra.String; using ImGuiClip = OtterGui.ImGuiClip; @@ -18,8 +17,7 @@ public class SetSelector : IDisposable private readonly Configuration _config; private readonly AutoDesignManager _manager; private readonly AutomationChanged _event; - private readonly ActorManager _actors; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly List<(AutoDesignSet, int)> _list = []; public AutoDesignSet? Selection { get; private set; } @@ -38,14 +36,13 @@ public class SetSelector : IDisposable private int _dragIndex = -1; private Action? _endAction; - internal int _dragDesignIndex = -1; + internal int DragDesignIndex = -1; - public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorManager actors, ObjectManager objects) + public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorObjectManager objects) { _manager = manager; _event = @event; _config = config; - _actors = actors; _objects = objects; _event.Subscribe(OnAutomationChange, AutomationChanged.Priority.SetSelector); } @@ -94,7 +91,7 @@ public class SetSelector : IDisposable } private LowerString _filter = LowerString.Empty; - private uint _enabledFilter = 0; + private uint _enabledFilter; private float _width; private Vector2 _defaultItemSpacing; private Vector2 _selectableSize; @@ -177,7 +174,6 @@ public class SetSelector : IDisposable UpdateList(); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing); _selectableSize = new Vector2(0, 2 * ImGui.GetTextLineHeight() + ImGui.GetStyle().ItemSpacing.Y); - _objects.Update(); ImGuiClip.ClippedDraw(_list, DrawSetSelectable, _selectableSize.Y + 2 * ImGui.GetStyle().ItemSpacing.Y); _endAction?.Invoke(); _endAction = null; @@ -186,7 +182,7 @@ public class SetSelector : IDisposable private void DrawSetSelectable((AutoDesignSet Set, int Index) pair) { using var id = ImRaii.PushId(pair.Index); - using (var color = ImRaii.PushColor(ImGuiCol.Text, pair.Set.Enabled ? ColorId.EnabledAutoSet.Value() : ColorId.DisabledAutoSet.Value())) + using (ImRaii.PushColor(ImGuiCol.Text, pair.Set.Enabled ? ColorId.EnabledAutoSet.Value() : ColorId.DisabledAutoSet.Value())) { if (ImGui.Selectable(GetSetName(pair.Set, pair.Index), pair.Set == Selection, ImGuiSelectableFlags.None, _selectableSize)) { @@ -285,9 +281,9 @@ public class SetSelector : IDisposable private void NewSetButton(Vector2 size) { - var id = _actors.GetCurrentPlayer(); + var id = _objects.Actors.GetCurrentPlayer(); if (!id.IsValid) - id = _actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue); + id = _objects.Actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, $"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true)) _manager.AddDesignSet("New Automation Set", id); @@ -332,15 +328,15 @@ public class SetSelector : IDisposable } else if (ImGuiUtil.IsDropping("DesignDragDrop")) { - if (_dragDesignIndex >= 0) + if (DragDesignIndex >= 0) { - var idx = _dragDesignIndex; + var idx = DragDesignIndex; var setTo = set; var setFrom = Selection!; _endAction = () => _manager.MoveDesignToSet(setFrom, idx, setTo); } - _dragDesignIndex = -1; + DragDesignIndex = -1; } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index f5fe088..b4bdc2a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -1,18 +1,17 @@ using Dalamud.Interface; using Glamourer.GameData; using Glamourer.Designs; -using Glamourer.Interop; -using Glamourer.Interop.Structs; using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.DebugTab; -public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer +public class ActiveStatePanel(StateManager _stateManager, ActorObjectManager _objectManager) : IGameDataDrawer { public string Label => $"Active Actors ({_stateManager.Count})###Active Actors"; @@ -22,8 +21,7 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM public void Draw() { - _objectManager.Update(); - foreach (var (identifier, actors) in _objectManager.Identifiers) + foreach (var (identifier, actors) in _objectManager) { if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Trash.ToIconString()}##{actors.Label}", new Vector2(ImGui.GetFrameHeight()), string.Empty, !_stateManager.ContainsKey(identifier), true)) @@ -66,13 +64,15 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM static string ItemString(in DesignData data, EquipSlot slot) { var item = data.Item(slot); - return $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; + return + $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; } static string BonusItemString(in DesignData data, BonusItemFlag slot) { var item = data.BonusItem(slot); - return $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; + return + $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; } PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state.Sources[MetaIndex.ModelId]); diff --git a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs index 6f6d27a..5a02621 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs @@ -1,13 +1,13 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using Glamourer.Interop; using ImGuiNET; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.DebugTab; -public unsafe class AdvancedCustomizationDrawer(ObjectManager objects) : IGameDataDrawer +public unsafe class AdvancedCustomizationDrawer(ActorObjectManager objects) : IGameDataDrawer { public string Label => "Advanced Customizations"; @@ -31,8 +31,8 @@ public unsafe class AdvancedCustomizationDrawer(ObjectManager objects) : IGameDa return; } - DrawCBuffer("Customize"u8, model.AsHuman->CustomizeParameterCBuffer, 0); - DrawCBuffer("Decal"u8, model.AsHuman->DecalColorCBuffer, 1); + DrawCBuffer("Customize"u8, model.AsHuman->CustomizeParameterCBuffer, 0); + DrawCBuffer("Decal"u8, model.AsHuman->DecalColorCBuffer, 1); DrawCBuffer("Unk1"u8, *(ConstantBuffer**)((byte*)model.AsHuman + 0xBA0), 2); DrawCBuffer("Unk2"u8, *(ConstantBuffer**)((byte*)model.AsHuman + 0xBA8), 3); } diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index 62f93e9..100cc9c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -3,24 +3,25 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Designs; -using Glamourer.Interop; using Glamourer.Services; using Glamourer.State; using ImGuiNET; using OtterGui; +using OtterGui.Text; using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Tabs.DebugTab; public unsafe class GlamourPlatePanel : IGameDataDrawer { - private readonly DesignManager _design; - private readonly ItemManager _items; - private readonly StateManager _state; - private readonly ObjectManager _objects; + private readonly DesignManager _design; + private readonly ItemManager _items; + private readonly StateManager _state; + private readonly ActorObjectManager _objects; public string Label => "Glamour Plates"; @@ -28,7 +29,8 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer public bool Disabled => false; - public GlamourPlatePanel(IGameInteropProvider interop, ItemManager items, DesignManager design, StateManager state, ObjectManager objects) + public GlamourPlatePanel(IGameInteropProvider interop, ItemManager items, DesignManager design, StateManager state, + ActorObjectManager objects) { _items = items; _design = design; @@ -42,24 +44,24 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer var manager = MirageManager.Instance(); using (ImRaii.Group()) { - ImGui.TextUnformatted("Address:"); - ImGui.TextUnformatted("Number of Glamour Plates:"); - ImGui.TextUnformatted("Glamour Plates Requested:"); - ImGui.TextUnformatted("Glamour Plates Loaded:"); - ImGui.TextUnformatted("Is Applying Glamour Plates:"); + ImUtf8.Text("Address:"u8); + ImUtf8.Text("Number of Glamour Plates:"u8); + ImUtf8.Text("Glamour Plates Requested:"u8); + ImUtf8.Text("Glamour Plates Loaded:"u8); + ImUtf8.Text("Is Applying Glamour Plates:"u8); } ImGui.SameLine(); using (ImRaii.Group()) { - ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)manager:X}"); - ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlates.Length.ToString()); - ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesRequested.ToString()); + ImUtf8.CopyOnClickSelectable($"0x{(ulong)manager:X}"); + ImUtf8.Text(manager == null ? "-" : manager->GlamourPlates.Length.ToString()); + ImUtf8.Text(manager == null ? "-" : manager->GlamourPlatesRequested.ToString()); ImGui.SameLine(); - if (ImGui.SmallButton("Request Update")) + if (ImUtf8.SmallButton("Request Update"u8)) RequestGlamour(); - ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesLoaded.ToString()); - ImGui.TextUnformatted(manager == null ? "-" : manager->IsApplyingGlamourPlate.ToString()); + ImUtf8.Text(manager == null ? "-" : manager->GlamourPlatesLoaded.ToString()); + ImUtf8.Text(manager == null ? "-" : manager->IsApplyingGlamourPlate.ToString()); } if (manager == null) @@ -71,12 +73,12 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer for (var i = 0; i < manager->GlamourPlates.Length; ++i) { - using var tree = ImRaii.TreeNode($"Plate #{i + 1:D2}"); + using var tree = ImUtf8.TreeNode($"Plate #{i + 1:D2}"); if (!tree) continue; ref var plate = ref manager->GlamourPlates[i]; - if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled)) + if (ImUtf8.ButtonEx("Apply to Player"u8, ""u8, Vector2.Zero, !enabled)) { var design = CreateDesign(plate); _state.ApplyDesign(state!, design, ApplySettings.Manual with { IsFinal = true }); @@ -85,14 +87,14 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer using (ImRaii.Group()) { foreach (var slot in EquipSlotExtensions.FullSlots) - ImGui.TextUnformatted(slot.ToName()); + ImUtf8.Text(slot.ToName()); } ImGui.SameLine(); using (ImRaii.Group()) { foreach (var (_, index) in EquipSlotExtensions.FullSlots.WithIndex()) - ImGui.TextUnformatted($"{plate.ItemIds[index]:D6}, {StainIds.FromGlamourPlate(plate, index)}"); + ImUtf8.Text($"{plate.ItemIds[index]:D6}, {StainIds.FromGlamourPlate(plate, index)}"); } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index c1b5847..fc4799f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -11,12 +11,11 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Gui.Tabs.DebugTab; public unsafe class ModelEvaluationPanel( - ObjectManager _objectManager, + ActorObjectManager _objectManager, VisorService _visorService, UpdateSlotService _updateSlotService, ChangeCustomizeService _changeCustomizeService, @@ -34,7 +33,7 @@ public unsafe class ModelEvaluationPanel( public void Draw() { ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); - var actor = _objectManager[_gameObjectIndex]; + var actor = _objectManager.Objects[_gameObjectIndex]; var model = actor.Model; using var table = ImRaii.Table("##evaluationTable", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); ImGui.TableNextColumn(); diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 04537b5..966bc6f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -3,18 +3,18 @@ using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; using Glamourer.GameData; -using Glamourer.Interop; using Glamourer.State; using ImGuiNET; -using OtterGui; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) +public class NpcAppearancePanel(NpcCombo npcCombo, StateManager stateManager, ActorObjectManager objectManager, DesignConverter designConverter) : IGameDataDrawer { public string Label @@ -28,9 +28,9 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM public void Draw() { - ImGui.Checkbox("Compare Customize (or Gear)", ref _customizeOrGear); + ImUtf8.Checkbox("Compare Customize (or Gear)"u8, ref _customizeOrGear); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - var resetScroll = ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64); + var resetScroll = ImUtf8.InputText("##npcFilter"u8, ref _npcFilter, "Filter..."u8); using var table = ImRaii.Table("npcs", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, new Vector2(-1, 400 * ImGuiHelpers.GlobalScale)); @@ -40,19 +40,19 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM if (resetScroll) ImGui.SetScrollY(0); - ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300); - ImGui.TableSetupColumn("Kind", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Model", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Visor", ImGuiTableColumnFlags.WidthFixed); - ImGui.TableSetupColumn("Compare", ImGuiTableColumnFlags.WidthStretch); + ImUtf8.TableSetupColumn("Button"u8, ImGuiTableColumnFlags.WidthFixed); + ImUtf8.TableSetupColumn("Name"u8, ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300); + ImUtf8.TableSetupColumn("Kind"u8, ImGuiTableColumnFlags.WidthFixed); + ImUtf8.TableSetupColumn("Id"u8, ImGuiTableColumnFlags.WidthFixed); + ImUtf8.TableSetupColumn("Model"u8, ImGuiTableColumnFlags.WidthFixed); + ImUtf8.TableSetupColumn("Visor"u8, ImGuiTableColumnFlags.WidthFixed); + ImUtf8.TableSetupColumn("Compare"u8, ImGuiTableColumnFlags.WidthStretch); ImGui.TableNextColumn(); var skips = ImGuiClip.GetNecessarySkips(ImGui.GetFrameHeightWithSpacing()); ImGui.TableNextRow(); var idx = 0; - var remainder = ImGuiClip.FilteredClippedDraw(_npcCombo.Items, skips, + var remainder = ImGuiClip.FilteredClippedDraw(npcCombo.Items, skips, d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), DrawData); ImGui.TableNextColumn(); ImGuiClip.DrawEndDummy(remainder, ImGui.GetFrameHeightWithSpacing()); @@ -61,43 +61,31 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM void DrawData(NpcData data) { using var id = ImRaii.PushId(idx++); - var disabled = !_state.GetOrCreate(_objectManager.Player, out var state); + var disabled = !stateManager.GetOrCreate(objectManager.Player, out var state); ImGui.TableNextColumn(); - if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled)) + if (ImUtf8.ButtonEx("Apply"u8, ""u8, Vector2.Zero, disabled)) { - foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true)) - _state.ChangeEquip(state!, slot, item, stain, ApplySettings.Manual); - _state.ChangeMetaState(state!, MetaIndex.VisorState, data.VisorToggled, ApplySettings.Manual); - _state.ChangeEntireCustomize(state!, data.Customize, CustomizeFlagExtensions.All, ApplySettings.Manual); + foreach (var (slot, item, stain) in designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true)) + stateManager.ChangeEquip(state!, slot, item, stain, ApplySettings.Manual); + stateManager.ChangeMetaState(state!, MetaIndex.VisorState, data.VisorToggled, ApplySettings.Manual); + stateManager.ChangeEntireCustomize(state!, data.Customize, CustomizeFlagExtensions.All, ApplySettings.Manual); } - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(data.Name); + ImUtf8.DrawFrameColumn(data.Name); - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(data.Kind is ObjectKind.BattleNpc ? "B" : "E"); + ImUtf8.DrawFrameColumn(data.Kind is ObjectKind.BattleNpc ? "B" : "E"); - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(data.Id.Id.ToString()); + ImUtf8.DrawFrameColumn(data.Id.Id.ToString()); - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(data.ModelId.ToString()); + ImUtf8.DrawFrameColumn(data.ModelId.ToString()); using (_ = ImRaii.PushFont(UiBuilder.IconFont)) { - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(data.VisorToggled ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); + ImUtf8.DrawFrameColumn(data.VisorToggled ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); } using var mono = ImRaii.PushFont(UiBuilder.MonoFont); - ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(_customizeOrGear ? data.Customize.ToString() : data.WriteGear()); + ImUtf8.DrawFrameColumn(_customizeOrGear ? data.Customize.ToString() : data.WriteGear()); } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs index e519ea5..a4d1224 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -1,13 +1,13 @@ -using Glamourer.Interop; -using ImGuiNET; +using ImGuiNET; using OtterGui; -using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Actors; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.DebugTab; -public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _actors) : IGameDataDrawer +public class ObjectManagerPanel(ActorObjectManager _objectManager, ActorManager _actors) : IGameDataDrawer { public string Label => "Object Manager"; @@ -19,44 +19,45 @@ public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _acto public void Draw() { - _objectManager.Update(); - using (var table = ImRaii.Table("##data", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit)) + _objectManager.Objects.DrawDebug(); + + using (var table = ImUtf8.Table("##data"u8, 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit)) { if (!table) return; - ImGuiUtil.DrawTableColumn("Last Update"); - ImGuiUtil.DrawTableColumn(_objectManager.LastUpdate.ToString(CultureInfo.InvariantCulture)); + ImUtf8.DrawTableColumn("World"u8); + ImUtf8.DrawTableColumn(_actors.Finished ? _actors.Data.ToWorldName(_objectManager.World) : "Service Missing"); + ImUtf8.DrawTableColumn(_objectManager.World.ToString()); + + ImUtf8.DrawTableColumn("Player Character"u8); + ImUtf8.DrawTableColumn($"{_objectManager.Player.Utf8Name} ({_objectManager.Player.Index})"); + ImGui.TableNextColumn(); + ImUtf8.CopyOnClickSelectable(_objectManager.Player.ToString()); + + ImUtf8.DrawTableColumn("In GPose"u8); + ImUtf8.DrawTableColumn(_objectManager.IsInGPose.ToString()); ImGui.TableNextColumn(); - ImGuiUtil.DrawTableColumn("World"); - ImGuiUtil.DrawTableColumn(_actors.Finished ? _actors.Data.ToWorldName(_objectManager.World) : "Service Missing"); - ImGuiUtil.DrawTableColumn(_objectManager.World.ToString()); - - ImGuiUtil.DrawTableColumn("Player Character"); - ImGuiUtil.DrawTableColumn($"{_objectManager.Player.Utf8Name} ({_objectManager.Player.Index})"); - ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(_objectManager.Player.ToString()); - - ImGuiUtil.DrawTableColumn("In GPose"); - ImGuiUtil.DrawTableColumn(_objectManager.IsInGPose.ToString()); + ImUtf8.DrawTableColumn("In Lobby"u8); + ImUtf8.DrawTableColumn(_objectManager.IsInLobby.ToString()); ImGui.TableNextColumn(); if (_objectManager.IsInGPose) { - ImGuiUtil.DrawTableColumn("GPose Player"); - ImGuiUtil.DrawTableColumn($"{_objectManager.GPosePlayer.Utf8Name} ({_objectManager.GPosePlayer.Index})"); + ImUtf8.DrawTableColumn("GPose Player"u8); + ImUtf8.DrawTableColumn($"{_objectManager.GPosePlayer.Utf8Name} ({_objectManager.GPosePlayer.Index})"); ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(_objectManager.GPosePlayer.ToString()); + ImUtf8.CopyOnClickSelectable(_objectManager.GPosePlayer.ToString()); } - ImGuiUtil.DrawTableColumn("Number of Players"); - ImGuiUtil.DrawTableColumn(_objectManager.Count.ToString()); + ImUtf8.DrawTableColumn("Number of Players"u8); + ImUtf8.DrawTableColumn(_objectManager.Count.ToString()); ImGui.TableNextColumn(); } - var filterChanged = ImGui.InputTextWithHint("##Filter", "Filter...", ref _objectFilter, 64); - using var table2 = ImRaii.Table("##data2", 3, + var filterChanged = ImUtf8.InputText("##Filter"u8, ref _objectFilter, "Filter..."u8); + using var table2 = ImUtf8.Table("##data2"u8, 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, new Vector2(-1, 20 * ImGui.GetTextLineHeightWithSpacing())); if (!table2) @@ -69,13 +70,13 @@ public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _acto var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); ImGui.TableNextRow(); - var remainder = ImGuiClip.FilteredClippedDraw(_objectManager.Identifiers, skips, + var remainder = ImGuiClip.FilteredClippedDraw(_objectManager, skips, p => p.Value.Label.Contains(_objectFilter, StringComparison.OrdinalIgnoreCase), p => { - ImGuiUtil.DrawTableColumn(p.Key.ToString()); - ImGuiUtil.DrawTableColumn(p.Value.Label); - ImGuiUtil.DrawTableColumn(string.Join(", ", p.Value.Objects.OrderBy(a => a.Index).Select(a => a.Index.ToString()))); + ImUtf8.DrawTableColumn(p.Key.ToString()); + ImUtf8.DrawTableColumn(p.Value.Label); + ImUtf8.DrawTableColumn(string.Join(", ", p.Value.Objects.OrderBy(a => a.Index).Select(a => a.Index.ToString()))); }); ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeightWithSpacing()); } diff --git a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs index 2abc1db..21f0c50 100644 --- a/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/RetainedStatePanel.cs @@ -3,10 +3,11 @@ using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; +using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.DebugTab; -public class RetainedStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer +public class RetainedStatePanel(StateManager _stateManager, ActorObjectManager _objectManager) : IGameDataDrawer { public string Label => "Retained States (Inactive Actors)"; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 3543b68..65014a4 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -18,6 +18,7 @@ using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.DesignTab; @@ -28,7 +29,7 @@ public class DesignPanel private readonly DesignFileSystemSelector _selector; private readonly CustomizationDrawer _customizationDrawer; private readonly DesignManager _manager; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly StateManager _state; private readonly EquipmentDrawer _equipmentDrawer; private readonly ModAssociationsTab _modAssociations; @@ -48,7 +49,7 @@ public class DesignPanel public DesignPanel(DesignFileSystemSelector selector, CustomizationDrawer customizationDrawer, DesignManager manager, - ObjectManager objects, + ActorObjectManager objects, StateManager state, EquipmentDrawer equipmentDrawer, ModAssociationsTab modAssociations, @@ -360,6 +361,7 @@ public class DesignPanel equip = false; customize = true; } + if (!enabled) ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); @@ -371,6 +373,7 @@ public class DesignPanel _manager.ChangeApplyMulti(_selector.Selected!, true, true, true, false, true, true, false, true); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, false); } + if (!enabled) ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteDesignModifier} while clicking."); @@ -386,7 +389,8 @@ public class DesignPanel if (equip is null && customize is null) return; - _manager.ChangeApplyMulti(_selector.Selected!, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); + _manager.ChangeApplyMulti(_selector.Selected!, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, + equip, equip, equip); if (equip.HasValue) { _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, equip.Value); diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 1151e86..c6166a3 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -5,7 +5,6 @@ using Glamourer.Designs; using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Tabs.DesignTab; -using Glamourer.Interop; using Glamourer.State; using ImGuiNET; using OtterGui; @@ -13,6 +12,7 @@ using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.NpcTab; @@ -30,7 +30,7 @@ public class NpcPanel private readonly DesignConverter _converter; private readonly DesignManager _designManager; private readonly StateManager _state; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly DesignColors _colors; private readonly Button[] _leftButtons; private readonly Button[] _rightButtons; @@ -42,7 +42,7 @@ public class NpcPanel DesignConverter converter, DesignManager designManager, StateManager state, - ObjectManager objects, + ActorObjectManager objects, DesignColors colors, Configuration config) { diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs index d976d28..87490db 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -6,14 +6,14 @@ using OtterGui; using OtterGui.Raii; using OtterGui.Services; using Penumbra.GameData.Actors; -using ObjectManager = Glamourer.Interop.ObjectManager; +using Penumbra.GameData.Interop; namespace Glamourer.Gui.Tabs.SettingsTab; public class CollectionOverrideDrawer( CollectionOverrideService collectionOverrides, Configuration config, - ObjectManager objects, + ActorObjectManager objects, ActorManager actors, PenumbraService penumbra, CollectionCombo combo) : IService @@ -61,7 +61,8 @@ public class CollectionOverrideDrawer( DrawActorIdentifier(idx, actor); ImGui.TableNextColumn(); - if (combo.Draw("##collection", name, $"Select the overriding collection. Current GUID:", ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight())) + if (combo.Draw("##collection", name, $"Select the overriding collection. Current GUID:", ImGui.GetContentRegionAvail().X, + ImGui.GetTextLineHeight())) { var (guid, _, newName) = combo.CurrentSelection; collectionOverrides.ChangeOverride(idx, guid, newName); @@ -69,7 +70,7 @@ public class CollectionOverrideDrawer( if (ImGui.IsItemHovered()) { - using var tt = ImRaii.Tooltip(); + using var tt = ImRaii.Tooltip(); using var font = ImRaii.PushFont(UiBuilder.MonoFont); ImGui.TextUnformatted($" {collection}"); } @@ -102,7 +103,7 @@ public class CollectionOverrideDrawer( return; using var tt2 = ImRaii.Tooltip(); - using var f = ImRaii.PushFont(UiBuilder.MonoFont); + using var f = ImRaii.PushFont(UiBuilder.MonoFont); ImGui.TextUnformatted(collection.ToString()); } @@ -146,7 +147,7 @@ public class CollectionOverrideDrawer( } catch (ActorIdentifierFactory.IdentifierParseError e) { - _exception = e; + _exception = e; _identifiers = []; } diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 71a9280..1f85612 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -5,6 +5,7 @@ using Glamourer.Designs; using Glamourer.Services; using Glamourer.State; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Interop; @@ -13,16 +14,16 @@ public class ContextMenuService : IDisposable { public const int ChatLogContextItemId = 0x958; - private readonly ItemManager _items; - private readonly IContextMenu _contextMenu; - private readonly StateManager _state; - private readonly ObjectManager _objects; - private EquipItem _lastItem; - private readonly StainId[] _lastStains = new StainId[StainId.NumStains]; + private readonly ItemManager _items; + private readonly IContextMenu _contextMenu; + private readonly StateManager _state; + private readonly ActorObjectManager _objects; + private EquipItem _lastItem; + private readonly StainId[] _lastStains = new StainId[StainId.NumStains]; private readonly MenuItem _inventoryItem; - public ContextMenuService(ItemManager items, StateManager state, ObjectManager objects, Configuration config, + public ContextMenuService(ItemManager items, StateManager state, ActorObjectManager objects, Configuration config, IContextMenu context) { _contextMenu = context; diff --git a/Glamourer/Interop/ObjectManager.cs b/Glamourer/Interop/ObjectManager.cs deleted file mode 100644 index 471a49b..0000000 --- a/Glamourer/Interop/ObjectManager.cs +++ /dev/null @@ -1,209 +0,0 @@ -using Dalamud.Game.ClientState.Objects; -using Dalamud.Plugin; -using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.Game.Control; -using Glamourer.Events; -using Glamourer.Interop.Structs; -using OtterGui.Log; -using Penumbra.GameData.Actors; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Interop; - -namespace Glamourer.Interop; - -public class ObjectManager( - IFramework framework, - IClientState clientState, - IObjectTable objects, - IDalamudPluginInterface pi, - Logger log, - ActorManager actors, - ITargetManager targets) - : global::Penumbra.GameData.Interop.ObjectManager(pi, log, framework, objects) -{ - public DateTime LastUpdate - => LastFrame; - - private DateTime _identifierUpdate; - - public bool IsInGPose - => clientState.IsGPosing; - - public ushort World { get; private set; } - - private readonly Dictionary _identifiers = new(200); - private readonly Dictionary _allWorldIdentifiers = new(200); - private readonly Dictionary _nonOwnedIdentifiers = new(200); - - public IReadOnlyDictionary Identifiers - => _identifiers; - - public override bool Update() - { - if (!base.Update() && _identifierUpdate >= LastUpdate) - return false; - - _identifierUpdate = LastUpdate; - World = (ushort)(Player.Valid ? Player.HomeWorld : 0); - _identifiers.Clear(); - _allWorldIdentifiers.Clear(); - _nonOwnedIdentifiers.Clear(); - - foreach (var actor in BattleNpcs.Concat(CutsceneCharacters)) - { - if (actor.Identifier(actors, out var identifier)) - HandleIdentifier(identifier, actor); - } - - void AddSpecial(ScreenActor idx, string label) - { - var actor = this[(int)idx]; - if (actor.Identifier(actors, out var ident)) - { - var data = new ActorData(actor, label); - _identifiers.Add(ident, data); - } - } - - AddSpecial(ScreenActor.CharacterScreen, "Character Screen Actor"); - AddSpecial(ScreenActor.ExamineScreen, "Examine Screen Actor"); - AddSpecial(ScreenActor.FittingRoom, "Fitting Room Actor"); - AddSpecial(ScreenActor.DyePreview, "Dye Preview Actor"); - AddSpecial(ScreenActor.Portrait, "Portrait Actor"); - AddSpecial(ScreenActor.Card6, "Card Actor 6"); - AddSpecial(ScreenActor.Card7, "Card Actor 7"); - AddSpecial(ScreenActor.Card8, "Card Actor 8"); - - foreach (var actor in EventNpcs) - { - if (actor.Identifier(actors, out var identifier)) - HandleIdentifier(identifier, actor); - } - - return true; - } - - private void HandleIdentifier(ActorIdentifier identifier, Actor character) - { - if (!identifier.IsValid) - return; - - if (!_identifiers.TryGetValue(identifier, out var data)) - { - data = new ActorData(character, identifier.ToString()); - _identifiers[identifier] = data; - } - else - { - data.Objects.Add(character); - } - - if (identifier.Type is IdentifierType.Player or IdentifierType.Owned) - { - var allWorld = actors.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, - identifier.Kind, - identifier.DataId); - - if (!_allWorldIdentifiers.TryGetValue(allWorld, out var allWorldData)) - { - allWorldData = new ActorData(character, allWorld.ToString()); - _allWorldIdentifiers[allWorld] = allWorldData; - } - else - { - allWorldData.Objects.Add(character); - } - } - - if (identifier.Type is IdentifierType.Owned) - { - var nonOwned = actors.CreateNpc(identifier.Kind, identifier.DataId); - if (!_nonOwnedIdentifiers.TryGetValue(nonOwned, out var nonOwnedData)) - { - nonOwnedData = new ActorData(character, nonOwned.ToString()); - _nonOwnedIdentifiers[nonOwned] = nonOwnedData; - } - else - { - nonOwnedData.Objects.Add(character); - } - } - } - - public Actor GPosePlayer - => this[(int)ScreenActor.GPosePlayer]; - - public Actor Player - => this[0]; - - public unsafe Actor Target - => clientState.IsGPosing ? TargetSystem.Instance()->GPoseTarget : TargetSystem.Instance()->Target; - - public Actor Focus - => targets.FocusTarget?.Address ?? nint.Zero; - - public Actor MouseOver - => targets.MouseOverTarget?.Address ?? nint.Zero; - - public (ActorIdentifier Identifier, ActorData Data) PlayerData - { - get - { - Update(); - return Player.Identifier(actors, out var ident) && _identifiers.TryGetValue(ident, out var data) - ? (ident, data) - : (ident, ActorData.Invalid); - } - } - - public (ActorIdentifier Identifier, ActorData Data) TargetData - { - get - { - Update(); - return Target.Identifier(actors, out var ident) && _identifiers.TryGetValue(ident, out var data) - ? (ident, data) - : (ident, ActorData.Invalid); - } - } - - /// Also handles All Worlds players and non-owned NPCs. - public bool ContainsKey(ActorIdentifier key) - => Identifiers.ContainsKey(key) || _allWorldIdentifiers.ContainsKey(key) || _nonOwnedIdentifiers.ContainsKey(key); - - public bool TryGetValue(ActorIdentifier key, out ActorData value) - => Identifiers.TryGetValue(key, out value); - - public bool TryGetValueAllWorld(ActorIdentifier key, out ActorData value) - => _allWorldIdentifiers.TryGetValue(key, out value); - - public bool TryGetValueNonOwned(ActorIdentifier key, out ActorData value) - => _nonOwnedIdentifiers.TryGetValue(key, out value); - - public ActorData this[ActorIdentifier key] - => Identifiers[key]; - - public IEnumerable Keys - => Identifiers.Keys; - - public IEnumerable Values - => Identifiers.Values; - - public bool GetName(string lowerName, out Actor actor) - { - (actor, var ret) = lowerName switch - { - "" => (Actor.Null, true), - "" => (Player, true), - "self" => (Player, true), - "" => (Target, true), - "target" => (Target, true), - "" => (Focus, true), - "focus" => (Focus, true), - "" => (MouseOver, true), - "mouseover" => (MouseOver, true), - _ => (Actor.Null, false), - }; - return ret; - } -} diff --git a/Glamourer/Interop/Penumbra/ModSettingApplier.cs b/Glamourer/Interop/Penumbra/ModSettingApplier.cs index 1d39f79..b94be09 100644 --- a/Glamourer/Interop/Penumbra/ModSettingApplier.cs +++ b/Glamourer/Interop/Penumbra/ModSettingApplier.cs @@ -7,7 +7,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Penumbra; -public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip autoRedrawSkip, Configuration config, ObjectManager objects, CollectionOverrideService overrides) +public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip autoRedrawSkip, Configuration config, ActorObjectManager objects, CollectionOverrideService overrides) : IService { private readonly HashSet _collectionTracker = []; @@ -17,7 +17,6 @@ public class ModSettingApplier(PenumbraService penumbra, PenumbraAutoRedrawSkip if (!config.AlwaysApplyAssociatedMods || (design.AssociatedMods.Count == 0 && !design.ResetTemporarySettings)) return; - objects.Update(); if (!objects.TryGetValue(state.Identifier, out var data)) { Glamourer.Log.Verbose( diff --git a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs index 93d23c2..4e3c8e3 100644 --- a/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs +++ b/Glamourer/Interop/Penumbra/PenumbraAutoRedraw.cs @@ -2,11 +2,11 @@ using Glamourer.Api.Enums; using Glamourer.Designs.History; using Glamourer.Events; -using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Classes; using OtterGui.Services; using Penumbra.Api.Enums; +using Penumbra.GameData.Interop; namespace Glamourer.Interop.Penumbra; @@ -16,13 +16,14 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService private readonly Configuration _config; private readonly PenumbraService _penumbra; private readonly StateManager _state; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly IFramework _framework; private readonly StateChanged _stateChanged; private readonly PenumbraAutoRedrawSkip _skip; - public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ObjectManager objects, IFramework framework, + public PenumbraAutoRedraw(PenumbraService penumbra, Configuration config, StateManager state, ActorObjectManager objects, + IFramework framework, StateChanged stateChanged, PenumbraAutoRedrawSkip skip) { _penumbra = penumbra; @@ -78,7 +79,6 @@ public class PenumbraAutoRedraw : IDisposable, IRequiredService { _framework.RunOnFrameworkThread(() => { - _objects.Update(); foreach (var (id, state) in _state) { if (!_objects.TryGetValue(id, out var actors) || !actors.Valid) diff --git a/Glamourer/Interop/Structs/ActorData.cs b/Glamourer/Interop/Structs/ActorData.cs deleted file mode 100644 index 5cfbcbe..0000000 --- a/Glamourer/Interop/Structs/ActorData.cs +++ /dev/null @@ -1,47 +0,0 @@ -using OtterGui.Log; -using Penumbra.GameData.Interop; - -namespace Glamourer.Interop.Structs; - -/// -/// A single actor with its label and the list of associated game objects. -/// -public readonly struct ActorData -{ - public readonly List Objects; - public readonly string Label; - - public bool Valid - => Objects.Count > 0; - - public ActorData(Actor actor, string label) - { - Objects = [actor]; - Label = label; - } - - public static readonly ActorData Invalid = new(false); - - private ActorData(bool _) - { - Objects = []; - Label = string.Empty; - } - - public LazyString ToLazyString(string invalid) - { - var objects = Objects; - return Valid - ? new LazyString(() => string.Join(", ", objects.Select(o => o.ToString()))) - : new LazyString(() => invalid); - } - - private ActorData(List objects, string label) - { - Objects = objects; - Label = label; - } - - public ActorData OnlyGPose() - => new(Objects.Where(o => o.IsGPoseOrCutscene).ToList(), Label); -} diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index fa6e63f..ed51a06 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -17,7 +17,6 @@ using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.Services; @@ -26,24 +25,24 @@ public class CommandService : IDisposable, IApiService private const string MainCommandString = "/glamourer"; private const string ApplyCommandString = "/glamour"; - private readonly ICommandManager _commands; - private readonly MainWindow _mainWindow; - private readonly IChatGui _chat; - private readonly ActorManager _actors; - private readonly ObjectManager _objects; - private readonly StateManager _stateManager; - private readonly AutoDesignApplier _autoDesignApplier; - private readonly AutoDesignManager _autoDesignManager; - private readonly Configuration _config; - private readonly ModSettingApplier _modApplier; - private readonly ItemManager _items; - private readonly CustomizeService _customizeService; - private readonly DesignManager _designManager; - private readonly DesignConverter _converter; - private readonly DesignResolver _resolver; - private readonly PenumbraService _penumbra; + private readonly ICommandManager _commands; + private readonly MainWindow _mainWindow; + private readonly IChatGui _chat; + private readonly ActorManager _actors; + private readonly ActorObjectManager _objects; + private readonly StateManager _stateManager; + private readonly AutoDesignApplier _autoDesignApplier; + private readonly AutoDesignManager _autoDesignManager; + private readonly Configuration _config; + private readonly ModSettingApplier _modApplier; + private readonly ItemManager _items; + private readonly CustomizeService _customizeService; + private readonly DesignManager _designManager; + private readonly DesignConverter _converter; + private readonly DesignResolver _resolver; + private readonly PenumbraService _penumbra; - public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ObjectManager objects, + public CommandService(ICommandManager commands, MainWindow mainWindow, IChatGui chat, ActorManager actors, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, StateManager stateManager, DesignManager designManager, DesignConverter converter, DesignFileSystem designFileSystem, AutoDesignManager autoDesignManager, Configuration config, ModSettingApplier modApplier, ItemManager items, RandomDesignGenerator randomDesign, CustomizeService customizeService, DesignFileSystemSelector designSelector, @@ -181,7 +180,9 @@ public class CommandService : IDisposable, IApiService _chat.Print(new SeStringBuilder().AddText("Use with /glamour clearsettings ").AddGreen("[Character Identifier]").AddText(" | ") .AddPurple("").AddText(" | ").AddBlue("").BuiltString); PlayerIdentifierHelp(false, true); - _chat.Print(new SeStringBuilder().AddText(" 》 The character identifier specifies the collection to clear settings from. It also accepts '").AddGreen("all").AddText("' to clear all collections.").BuiltString); + _chat.Print(new SeStringBuilder() + .AddText(" 》 The character identifier specifies the collection to clear settings from. It also accepts '").AddGreen("all") + .AddText("' to clear all collections.").BuiltString); _chat.Print(new SeStringBuilder().AddText(" 》 The booleans are optional and default to 'true', the ").AddPurple("first") .AddText(" determines whether ").AddPurple("manually").AddText(" applied settings are cleared, the ").AddBlue("second") .AddText(" determines whether ").AddBlue("automatically").AddText(" applied settings are cleared.").BuiltString); @@ -372,7 +373,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(argument, out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_objects.TryGetValue(identifier, out var data)) @@ -425,7 +425,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(argument, out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_objects.TryGetValue(identifier, out var data)) @@ -485,7 +484,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(split[1], out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_objects.TryGetValue(identifier, out var actors)) @@ -568,7 +566,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(split[1], out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_objects.TryGetValue(identifier, out var actors)) @@ -716,7 +713,6 @@ public class CommandService : IDisposable, IApiService if (!_resolver.GetDesign(split[0], out var design, true) || !IdentifierHandling(split2[0], out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_objects.TryGetValue(identifier, out var actors)) @@ -794,7 +790,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(argument, out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_stateManager.TryGetValue(identifier, out var state) @@ -835,7 +830,6 @@ public class CommandService : IDisposable, IApiService if (!IdentifierHandling(split[1], out var identifiers, false, true)) return false; - _objects.Update(); foreach (var identifier in identifiers) { if (!_stateManager.TryGetValue(identifier, out var state) diff --git a/Glamourer/Services/DesignApplier.cs b/Glamourer/Services/DesignApplier.cs index e0134d4..f0a9ba4 100644 --- a/Glamourer/Services/DesignApplier.cs +++ b/Glamourer/Services/DesignApplier.cs @@ -1,13 +1,12 @@ using Glamourer.Designs; -using Glamourer.Interop; -using Glamourer.Interop.Structs; using Glamourer.State; using OtterGui.Services; using Penumbra.GameData.Actors; +using Penumbra.GameData.Interop; namespace Glamourer.Services; -public sealed class DesignApplier(StateManager stateManager, ObjectManager objects) : IService +public sealed class DesignApplier(StateManager stateManager, ActorObjectManager objects) : IService { public void ApplyToPlayer(DesignBase design) { @@ -34,16 +33,10 @@ public sealed class DesignApplier(StateManager stateManager, ObjectManager objec } public void Apply(ActorIdentifier actor, DesignBase design) - { - objects.Update(); - Apply(actor, objects.TryGetValue(actor, out var d) ? d : ActorData.Invalid, design, ApplySettings.ManualWithLinks); - } + => Apply(actor, objects.TryGetValue(actor, out var d) ? d : ActorData.Invalid, design, ApplySettings.ManualWithLinks); public void Apply(ActorIdentifier actor, DesignBase design, ApplySettings settings) - { - objects.Update(); - Apply(actor, objects.TryGetValue(actor, out var d) ? d : ActorData.Invalid, design, settings); - } + => Apply(actor, objects.TryGetValue(actor, out var d) ? d : ActorData.Invalid, design, settings); public void Apply(ActorIdentifier actor, ActorData data, DesignBase design) => Apply(actor, data, design, ApplySettings.ManualWithLinks); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index baae507..78a0bf0 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -27,6 +27,7 @@ using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.Services; @@ -102,6 +103,7 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton(p => new CutsceneResolver(p.GetRequiredService().CutsceneParent)) .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 1ca5c48..52394a2 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -11,7 +11,6 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; using CustomizeIndex = Penumbra.GameData.Enums.CustomizeIndex; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.State; @@ -35,7 +34,7 @@ public unsafe class FunModule : IDisposable private readonly StateManager _stateManager; private readonly DesignConverter _designConverter; private readonly DesignManager _designManager; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly NpcCustomizeSet _npcs; private readonly StainId[] _stains; @@ -69,7 +68,7 @@ public unsafe class FunModule : IDisposable => OnDayChange(DateTime.Now.Day, DateTime.Now.Month, DateTime.Now.Year); public FunModule(CodeService codes, CustomizeService customizations, ItemManager items, Configuration config, - GenericPopupWindow popupWindow, StateManager stateManager, ObjectManager objects, DesignConverter designConverter, + GenericPopupWindow popupWindow, StateManager stateManager, ActorObjectManager objects, DesignConverter designConverter, DesignManager designManager, NpcCustomizeSet npcs) { _codes = codes; @@ -125,9 +124,7 @@ public unsafe class FunModule : IDisposable switch (_codes.Masked(CodeService.GearCodes)) { - case CodeService.CodeFlag.Emperor: - SetRandomItem(slot, ref armor); - break; + case CodeService.CodeFlag.Emperor: SetRandomItem(slot, ref armor); break; case CodeService.CodeFlag.Elephants: case CodeService.CodeFlag.Dolphins: case CodeService.CodeFlag.World when actor.Index != 0: @@ -137,9 +134,7 @@ public unsafe class FunModule : IDisposable switch (_codes.Masked(CodeService.DyeCodes)) { - case CodeService.CodeFlag.Clown: - SetRandomDye(ref armor); - break; + case CodeService.CodeFlag.Clown: SetRandomDye(ref armor); break; } } @@ -306,9 +301,7 @@ public unsafe class FunModule : IDisposable SetDolphin(EquipSlot.Body, ref armor[1]); SetDolphin(EquipSlot.Head, ref armor[0]); break; - case CodeService.CodeFlag.World when actor.Index != 0: - _worldSets.Apply(actor, _rng, armor); - break; + case CodeService.CodeFlag.World when actor.Index != 0: _worldSets.Apply(actor, _rng, armor); break; } switch (_codes.Masked(CodeService.DyeCodes)) @@ -368,17 +361,17 @@ public unsafe class FunModule : IDisposable private static IReadOnlyList DolphinBodies => [ - new CharacterArmor(6089, 1, new StainIds(4)), // Toad - new CharacterArmor(6089, 1, new StainIds(4)), // Toad - new CharacterArmor(6089, 1, new StainIds(4)), // Toad - new CharacterArmor(6023, 1, new StainIds(4)), // Swine - new CharacterArmor(6023, 1, new StainIds(4)), // Swine - new CharacterArmor(6023, 1, new StainIds(4)), // Swine - new CharacterArmor(6133, 1, new StainIds(4)), // Gaja - new CharacterArmor(6182, 1, new StainIds(3)), // Imp - new CharacterArmor(6182, 1, new StainIds(3)), // Imp - new CharacterArmor(6182, 1, new StainIds(4)), // Imp - new CharacterArmor(6182, 1, new StainIds(4)), // Imp + new(6089, 1, new StainIds(4)), // Toad + new(6089, 1, new StainIds(4)), // Toad + new(6089, 1, new StainIds(4)), // Toad + new(6023, 1, new StainIds(4)), // Swine + new(6023, 1, new StainIds(4)), // Swine + new(6023, 1, new StainIds(4)), // Swine + new(6133, 1, new StainIds(4)), // Gaja + new(6182, 1, new StainIds(3)), // Imp + new(6182, 1, new StainIds(3)), // Imp + new(6182, 1, new StainIds(4)), // Imp + new(6182, 1, new StainIds(4)), // Imp ]; private void SetDolphin(EquipSlot slot, ref CharacterArmor armor) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index be85580..698151f 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -7,6 +7,7 @@ using Glamourer.Interop.Structs; using Glamourer.Services; using Penumbra.Api.Enums; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.State; @@ -23,7 +24,7 @@ public class StateApplier( ItemManager _items, PenumbraService _penumbra, MetaService _metaService, - ObjectManager _objects, + ActorObjectManager _objects, CrestService _crests, DirectXService _directX) { @@ -411,8 +412,5 @@ public class StateApplier( } private ActorData GetData(ActorState state) - { - _objects.Update(); - return _objects.TryGetValue(state.Identifier, out var data) ? data : ActorData.Invalid; - } + => _objects.TryGetValue(state.Identifier, out var data) ? data : ActorData.Invalid; } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 1fa1ffe..986bdc2 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -9,6 +9,7 @@ using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; namespace Glamourer.State; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 88ec0b5..24f641c 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -15,7 +15,6 @@ using Penumbra.GameData.DataContainers; using Glamourer.Designs; using Penumbra.GameData.Interop; using Glamourer.Api.Enums; -using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.State; @@ -28,7 +27,7 @@ public class StateListener : IDisposable { private readonly Configuration _config; private readonly ActorManager _actors; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly StateManager _manager; private readonly StateApplier _applier; private readonly ItemManager _items; @@ -61,7 +60,7 @@ public class StateListener : IDisposable public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, EquipSlotUpdating equipSlotUpdating, GearsetDataLoaded gearsetDataLoaded, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, - FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, + FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ActorObjectManager objects, GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateFinalized stateFinalized) { @@ -205,9 +204,7 @@ public class StateListener : IDisposable } break; - case UpdateState.NoChange: - customize = state.ModelData.Customize; - break; + case UpdateState.NoChange: customize = state.ModelData.Customize; break; } } @@ -262,9 +259,7 @@ public class StateListener : IDisposable item = state.ModelData.BonusItem(slot).Armor(); break; // Use current model data. - case UpdateState.NoChange: - item = state.ModelData.BonusItem(slot).Armor(); - break; + case UpdateState.NoChange: item = state.ModelData.BonusItem(slot).Armor(); break; case UpdateState.Transformed: break; } } @@ -279,7 +274,6 @@ public class StateListener : IDisposable if (!actor.Identifier(_actors, out var identifier)) return; - _objects.Update(); if (_objects.TryGetValue(identifier, out var actors) && actors.Valid) _stateFinalized.Invoke(StateFinalizationType.Gearset, actors); } @@ -287,7 +281,6 @@ public class StateListener : IDisposable private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items) { - _objects.Update(); var (identifier, objects) = _objects.PlayerData; if (!identifier.IsValid || !_manager.TryGetValue(identifier, out var state)) return; @@ -310,7 +303,7 @@ public class StateListener : IDisposable var stainChanged = current.Stains == changed.Stains && !state.Sources[slot, true].IsFixed(); - switch ((itemChanged, stainChanged)) + switch (itemChanged, stainChanged) { case (true, true): _manager.ChangeEquip(state, slot, currentItem, current.Stains, ApplySettings.Game); @@ -376,9 +369,7 @@ public class StateListener : IDisposable else apply = true; break; - case UpdateState.NoChange: - apply = true; - break; + case UpdateState.NoChange: apply = true; break; } var baseType = slot is EquipSlot.OffHand ? state.BaseData.MainhandType.Offhand() : state.BaseData.MainhandType; diff --git a/Glamourer/Unlocks/CustomizeUnlockManager.cs b/Glamourer/Unlocks/CustomizeUnlockManager.cs index b58385e..bd13f99 100644 --- a/Glamourer/Unlocks/CustomizeUnlockManager.cs +++ b/Glamourer/Unlocks/CustomizeUnlockManager.cs @@ -1,16 +1,15 @@ using Dalamud.Game; using Dalamud.Hooking; using Dalamud.Plugin.Services; -using Dalamud.Utility; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.UI; using Glamourer.GameData; using Glamourer.Events; -using Glamourer.Interop; using Glamourer.Services; using Lumina.Excel.Sheets; using Penumbra.GameData; using Penumbra.GameData.Enums; +using Penumbra.GameData.Interop; namespace Glamourer.Unlocks; @@ -19,7 +18,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable private readonly SaveService _saveService; private readonly IClientState _clientState; private readonly ObjectUnlocked _event; - private readonly ObjectManager _objects; + private readonly ActorObjectManager _objects; private readonly Dictionary _unlocked = new(); public readonly IReadOnlyDictionary Unlockable; @@ -28,7 +27,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable => _unlocked; public CustomizeUnlockManager(SaveService saveService, CustomizeService customizations, IDataManager gameData, - IClientState clientState, ObjectUnlocked @event, IGameInteropProvider interop, ObjectManager objects) + IClientState clientState, ObjectUnlocked @event, IGameInteropProvider interop, ActorObjectManager objects) { interop.InitializeFromAttributes(this); _saveService = saveService; @@ -177,7 +176,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable IDataManager gameData) { var ret = new Dictionary(); - var sheet = gameData.GetExcelSheet(ClientLanguage.English)!; + var sheet = gameData.GetExcelSheet(ClientLanguage.English); foreach (var (clan, gender) in CustomizeManager.AllSets()) { var list = customizations.Manager.GetSet(clan, gender); diff --git a/OtterGui b/OtterGui index 3396ee1..f53fd22 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 3396ee176fa72ad2dfb2de3294f7125ebce4dae5 +Subproject commit f53fd227a242435ce44a9fe9c5e847d0ca788869 diff --git a/Penumbra.GameData b/Penumbra.GameData index ab63da8..801e98a 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit ab63da8047f3d99240159bb1b17dbcb61d77326a +Subproject commit 801e98a2956d707a25fd19bdfa46dc674c95365d From aad978f5f68bb36eb43f78f18c45ce7160c993e3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 8 Apr 2025 22:53:59 +0200 Subject: [PATCH 659/786] Pass actor in GearsetDataLoaded instead of looking it up. --- Glamourer/Events/GearsetDataLoaded.cs | 2 +- Glamourer/Interop/UpdateSlotService.cs | 2 +- Glamourer/State/StateListener.cs | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index 4750939..620bdab 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -11,7 +11,7 @@ namespace Glamourer.Events; /// /// public sealed class GearsetDataLoaded() - : EventWrapper(nameof(GearsetDataLoaded)) + : EventWrapper(nameof(GearsetDataLoaded)) { public enum Priority { diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index ffa6581..3ef99d9 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -120,7 +120,7 @@ public unsafe class UpdateSlotService : IDisposable { var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); var drawObject = drawDataContainer->OwnerObject->DrawObject; - GearsetDataLoadedEvent.Invoke(drawObject); + GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject, drawObject); Glamourer.Log.Excessive($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); return ret; } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 24f641c..90520b2 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -264,10 +264,9 @@ public class StateListener : IDisposable } } - private void OnGearsetDataLoaded(Model model) + private void OnGearsetDataLoaded(Actor actor, Model model) { - var actor = _penumbra.GameObjectFromDrawObject(model); - if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + if (!actor.Valid || (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)) return; // ensure actor and state are valid. From 7ed42005dd8e8a2563a6fcaf4820a6a325a7943f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 9 Apr 2025 15:11:04 +0200 Subject: [PATCH 660/786] Force higher Penumbra API version and use better IPC for cutscene parents and game objects. --- Glamourer/Interop/Penumbra/PenumbraService.cs | 94 ++++++++----------- Penumbra.Api | 2 +- 2 files changed, 40 insertions(+), 56 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index e04af7c..3f93551 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -34,12 +34,8 @@ public readonly record struct ModSettings(Dictionary> Setti public class PenumbraService : IDisposable { - public const int RequiredPenumbraBreakingVersion = 5; - public const int RequiredPenumbraFeatureVersion = 3; - public const int RequiredPenumbraFeatureVersionTemp = 4; - public const int RequiredPenumbraFeatureVersionTemp2 = 5; - public const int RequiredPenumbraFeatureVersionTemp3 = 6; - public const int RequiredPenumbraFeatureVersionTemp4 = 7; + public const int RequiredPenumbraBreakingVersion = 5; + public const int RequiredPenumbraFeatureVersion = 8; private const int KeyFixed = -1610; private const string NameFixed = "Glamourer (Automation)"; @@ -57,8 +53,6 @@ public class PenumbraService : IDisposable private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; private global::Penumbra.Api.IpcSubscribers.RedrawObject? _redraw; - private global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo? _drawObjectInfo; - private global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex? _cutsceneParent; private global::Penumbra.Api.IpcSubscribers.GetCollectionForObject? _objectCollection; private global::Penumbra.Api.IpcSubscribers.GetModList? _getMods; private global::Penumbra.Api.IpcSubscribers.GetCollection? _currentCollection; @@ -82,6 +76,8 @@ public class PenumbraService : IDisposable private global::Penumbra.Api.IpcSubscribers.GetChangedItems? _getChangedItems; private IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)>? _changedItems; private Func? _checkCurrentChangedItems; + private Func? _checkCutsceneParent; + private Func? _getGameObject; private readonly IDisposable _initializedEvent; private readonly IDisposable _disposedEvent; @@ -453,11 +449,11 @@ public class PenumbraService : IDisposable /// Obtain the game object corresponding to a draw object. public Actor GameObjectFromDrawObject(Model drawObject) - => Available ? _drawObjectInfo!.Invoke(drawObject.Address).Item1 : Actor.Null; + => _getGameObject?.Invoke(drawObject.Address) ?? Actor.Null; /// Obtain the parent of a cutscene actor if it is known. public short CutsceneParent(ushort idx) - => (short)(Available ? _cutsceneParent!.Invoke(idx) : -1); + => (short)(_checkCutsceneParent?.Invoke(idx) ?? -1); /// Try to redraw the given actor. public void RedrawObject(Actor actor, RedrawType settings) @@ -519,49 +515,37 @@ public class PenumbraService : IDisposable _creatingCharacterBase.Enable(); _createdCharacterBase.Enable(); _modSettingChanged.Enable(); - _collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface); - _collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface); - _redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface); - _drawObjectInfo = new global::Penumbra.Api.IpcSubscribers.GetDrawObjectInfo(_pluginInterface); - _cutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndex(_pluginInterface); - _objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface); - _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); - _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface); - _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface); - _inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface); - _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface); - _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface); - _setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface); - _setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface); - _openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface); - _getChangedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItems(_pluginInterface); - if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp) - { - _setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface); - _setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface); - _removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface); - _removeTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer(_pluginInterface); - _removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface); - _removeAllTemporaryModSettingsPlayer = - new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface); - if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp2) - { - _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); - _queryTemporaryModSettingsPlayer = - new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettingsPlayer(_pluginInterface); - if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp3) - { - _getCurrentSettingsWithTemp = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp(_pluginInterface); - _getAllSettings = new global::Penumbra.Api.IpcSubscribers.GetAllModSettings(_pluginInterface); - if (CurrentMinor >= RequiredPenumbraFeatureVersionTemp4) - { - _changedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItemAdapterList(_pluginInterface).Invoke(); - _checkCurrentChangedItems = - new global::Penumbra.Api.IpcSubscribers.CheckCurrentChangedItemFunc(_pluginInterface).Invoke(); - } - } - } - } + _collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface); + _collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface); + _redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface); + _checkCutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndexFunc(_pluginInterface).Invoke(); + _getGameObject = new global::Penumbra.Api.IpcSubscribers.GetGameObjectFromDrawObjectFunc(_pluginInterface).Invoke(); + _objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface); + _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); + _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface); + _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface); + _inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface); + _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface); + _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface); + _setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface); + _setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface); + _openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface); + _getChangedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItems(_pluginInterface); + _setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface); + _setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface); + _removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface); + _removeTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer(_pluginInterface); + _removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface); + _removeAllTemporaryModSettingsPlayer = + new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface); + _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); + _queryTemporaryModSettingsPlayer = + new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettingsPlayer(_pluginInterface); + _getCurrentSettingsWithTemp = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettingsWithTemp(_pluginInterface); + _getAllSettings = new global::Penumbra.Api.IpcSubscribers.GetAllModSettings(_pluginInterface); + _changedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItemAdapterList(_pluginInterface).Invoke(); + _checkCurrentChangedItems = + new global::Penumbra.Api.IpcSubscribers.CheckCurrentChangedItemFunc(_pluginInterface).Invoke(); Available = true; _penumbraReloaded.Invoke(); @@ -587,8 +571,8 @@ public class PenumbraService : IDisposable _collectionByIdentifier = null; _collections = null; _redraw = null; - _drawObjectInfo = null; - _cutsceneParent = null; + _getGameObject = null; + _checkCutsceneParent = null; _objectCollection = null; _getMods = null; _currentCollection = null; diff --git a/Penumbra.Api b/Penumbra.Api index bd56d82..47bd542 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit bd56d82816b8366e19dddfb2dc7fd7f167e264ee +Subproject commit 47bd5424d04c667d0df1ac1dd1eeb3e50b476c2c From 6c556d6a610e05d3900b4202c8e733d92a25747c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 10 Apr 2025 16:20:15 +0200 Subject: [PATCH 661/786] Update API. --- Penumbra.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.Api b/Penumbra.Api index 47bd542..f578091 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 47bd5424d04c667d0df1ac1dd1eeb3e50b476c2c +Subproject commit f578091fa579fb098c21036b492ff6e6088184c9 From bfce99859ff7540ddd52d00723015645534f6660 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 10 Apr 2025 16:39:04 +0200 Subject: [PATCH 662/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 801e98a..e10d8f3 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 801e98a2956d707a25fd19bdfa46dc674c95365d +Subproject commit e10d8f33a676ff4544d7ca05a93d555416f41222 From 4f6fb44f79ef88b2cedffada8a0cf6231e52bbb3 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 10 Apr 2025 14:42:24 +0000 Subject: [PATCH 663/786] [CI] Updating repo.json for 1.3.8.5 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index b00ebe5..3e8e6dc 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.4", - "TestingAssemblyVersion": "1.3.8.4", + "AssemblyVersion": "1.3.8.5", + "TestingAssemblyVersion": "1.3.8.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.4/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 155a9d62660f57a6c7b7156199be4c055976df49 Mon Sep 17 00:00:00 2001 From: Caraxi Date: Tue, 15 Apr 2025 14:26:30 +0930 Subject: [PATCH 664/786] Fix `SetMetaState` and `SetMetaStateName` not being registered --- Glamourer/Api/IpcProviders.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 704f008..bd4da53 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -36,6 +36,8 @@ public sealed class IpcProviders : IDisposable, IApiService (a, b, c, d, e, f) => (int)api.Items.SetItemName(a, (ApiEquipSlot)b, c, [d], e, (ApplyFlag)f)), IpcSubscribers.SetBonusItem.Provider(pi, api.Items), IpcSubscribers.SetBonusItemName.Provider(pi, api.Items), + IpcSubscribers.SetMetaState.Provider(pi, api.Items), + IpcSubscribers.SetMetaStateName.Provider(pi, api.Items), IpcSubscribers.GetState.Provider(pi, api.State), IpcSubscribers.GetStateName.Provider(pi, api.State), IpcSubscribers.GetStateBase64.Provider(pi, api.State), From 325b54031c431616dcadb09969b8fa39e6043dfc Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 19 Apr 2025 22:28:00 +0200 Subject: [PATCH 665/786] Update libraries. --- Glamourer/Api/ApiHelpers.cs | 1 + Glamourer/Automation/AutoDesignManager.cs | 1 + Glamourer/Configuration.cs | 1 + Glamourer/Designs/DesignBase64Migration.cs | 1 + Glamourer/Designs/DesignColors.cs | 1 + Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Designs/Links/DesignLinkLoader.cs | 2 +- Glamourer/GameData/CustomizeSet.cs | 1 + Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs | 1 + Glamourer/Gui/DesignCombo.cs | 1 + Glamourer/Gui/Equipment/BonusItemCombo.cs | 1 + Glamourer/Gui/Equipment/EquipmentDrawer.cs | 1 + Glamourer/Gui/Equipment/ItemCombo.cs | 2 +- Glamourer/Gui/Equipment/WeaponCombo.cs | 1 + Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs | 1 + Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 1 + Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs | 1 + Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs | 1 + Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs | 1 + Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs | 1 + Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 1 + Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs | 1 + Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs | 1 + Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs | 1 + Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs | 1 + Glamourer/Interop/Material/UpdateColorSets.cs | 4 ++-- Glamourer/Services/CollectionOverrideService.cs | 1 + Glamourer/Services/CommandService.cs | 1 + Glamourer/State/FunModule.cs | 1 + OtterGui | 2 +- Penumbra.GameData | 2 +- 31 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index 238d349..45d84b9 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -2,6 +2,7 @@ using Glamourer.Designs; using Glamourer.State; using OtterGui; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Services; using Penumbra.GameData.Actors; diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 6635a89..7a4511b 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -10,6 +10,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Filesystem; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index b1b9ec2..4339186 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -8,6 +8,7 @@ using Glamourer.Services; using Newtonsoft.Json; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Filesystem; using OtterGui.Widgets; using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; diff --git a/Glamourer/Designs/DesignBase64Migration.cs b/Glamourer/Designs/DesignBase64Migration.cs index cab9e27..8cd137f 100644 --- a/Glamourer/Designs/DesignBase64Migration.cs +++ b/Glamourer/Designs/DesignBase64Migration.cs @@ -1,6 +1,7 @@ using Glamourer.Api.Enums; using Glamourer.Services; using OtterGui; +using OtterGui.Extensions; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index 96592bf..172e10f 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; namespace Glamourer.Designs; diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index ef01e98..fff9565 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -8,7 +8,7 @@ using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui; +using OtterGui.Extensions; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; diff --git a/Glamourer/Designs/Links/DesignLinkLoader.cs b/Glamourer/Designs/Links/DesignLinkLoader.cs index fc7a26d..24138a8 100644 --- a/Glamourer/Designs/Links/DesignLinkLoader.cs +++ b/Glamourer/Designs/Links/DesignLinkLoader.cs @@ -1,6 +1,6 @@ using Dalamud.Interface.ImGuiNotification; -using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Services; using Notification = OtterGui.Classes.Notification; diff --git a/Glamourer/GameData/CustomizeSet.cs b/Glamourer/GameData/CustomizeSet.cs index d0193aa..8795c19 100644 --- a/Glamourer/GameData/CustomizeSet.cs +++ b/Glamourer/GameData/CustomizeSet.cs @@ -1,4 +1,5 @@ using OtterGui; +using OtterGui.Extensions; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Race = Penumbra.GameData.Enums.Race; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index 486fdb4..eabb6f0 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -3,6 +3,7 @@ using Glamourer.GameData; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index dccfa44..a871cf1 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -8,6 +8,7 @@ using Glamourer.Events; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Equipment/BonusItemCombo.cs b/Glamourer/Gui/Equipment/BonusItemCombo.cs index c333a87..892d9f1 100644 --- a/Glamourer/Gui/Equipment/BonusItemCombo.cs +++ b/Glamourer/Gui/Equipment/BonusItemCombo.cs @@ -5,6 +5,7 @@ using ImGuiNET; using Lumina.Excel.Sheets; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Raii; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 2bd84e7..0d2e8dc 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -8,6 +8,7 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Text.EndObjects; diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index f7c75e1..14f2e8a 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -3,8 +3,8 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; using Lumina.Excel.Sheets; -using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index 37d9d3c..e96f721 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -3,6 +3,7 @@ using Glamourer.Unlocks; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs index 530e04a..8a08437 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs @@ -3,6 +3,7 @@ using Dalamud.Utility; using ImGuiNET; using OtterGui; using OtterGui.Custom; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Widgets; using Penumbra.GameData.DataContainers; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 7f576b3..1600837 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -8,6 +8,7 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 09ee6aa..96730e8 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -5,6 +5,7 @@ using Glamourer.Events; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Raii; using Penumbra.GameData.Interop; using Penumbra.String; diff --git a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs index 98b7d9e..df39f45 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs @@ -1,6 +1,7 @@ using Glamourer.Automation; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index b562ecf..ede3e9e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -2,6 +2,7 @@ using Glamourer.Designs; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index c893f4c..26be8ce 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -3,6 +3,7 @@ using Glamourer.Designs; using Glamourer.Services; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index 100cc9c..aafc21a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -7,6 +7,7 @@ using Glamourer.Services; using Glamourer.State; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Text; using Penumbra.GameData; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs index e59be09..9444ff7 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -1,6 +1,7 @@ using Glamourer.Designs; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 80000d9..bab25f0 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -8,6 +8,7 @@ using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Text.Widget; diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 2a5b3e1..3567fb6 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -4,6 +4,7 @@ using Glamourer.Designs; using Glamourer.Interop.Material; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using static Glamourer.Gui.Tabs.HeaderDrawer; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs index b3f0cef..847b65e 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs @@ -1,6 +1,7 @@ using Glamourer.GameData; using ImGuiNET; using OtterGui; +using OtterGui.Extensions; using OtterGui.Raii; using ImGuiClip = OtterGui.ImGuiClip; diff --git a/Glamourer/Interop/Material/UpdateColorSets.cs b/Glamourer/Interop/Material/UpdateColorSets.cs index a96c60f..e503bc6 100644 --- a/Glamourer/Interop/Material/UpdateColorSets.cs +++ b/Glamourer/Interop/Material/UpdateColorSets.cs @@ -8,7 +8,7 @@ public sealed class UpdateColorSets : FastHook { public delegate void Delegate(Model model, uint unk); - private readonly ThreadLocal _updatingModel = new(); + private readonly ThreadLocal _updatingModel = new(() => Model.Null); public UpdateColorSets(HookManager hooks) => Task = hooks.CreateHook("Update Color Sets", Sigs.UpdateColorSets, Detour, true); @@ -17,7 +17,7 @@ public sealed class UpdateColorSets : FastHook { _updatingModel.Value = model; Task.Result.Original(model, unk); - _updatingModel.Value = nint.Zero; + _updatingModel.Value = Model.Null; } public Model Get() diff --git a/Glamourer/Services/CollectionOverrideService.cs b/Glamourer/Services/CollectionOverrideService.cs index fcc9998..99635d8 100644 --- a/Glamourer/Services/CollectionOverrideService.cs +++ b/Glamourer/Services/CollectionOverrideService.cs @@ -3,6 +3,7 @@ using Glamourer.Interop.Penumbra; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; +using OtterGui.Extensions; using OtterGui.Filesystem; using OtterGui.Services; using Penumbra.GameData.Actors; diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index ed51a06..3d40fa9 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -12,6 +12,7 @@ using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 52394a2..ae7160c 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -7,6 +7,7 @@ using Glamourer.Services; using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Extensions; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; diff --git a/OtterGui b/OtterGui index f53fd22..d13d700 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit f53fd227a242435ce44a9fe9c5e847d0ca788869 +Subproject commit d13d700796648f2a9279250052c4aa8ebeca221f diff --git a/Penumbra.GameData b/Penumbra.GameData index e10d8f3..62bbce5 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit e10d8f33a676ff4544d7ca05a93d555416f41222 +Subproject commit 62bbce5981e961a91322ca1a7d3bb5be25f67185 From c7d1620c1e2f3024c5bb80330ca4681da255c4f6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 19 Apr 2025 23:09:48 +0200 Subject: [PATCH 666/786] Update GameData. --- Glamourer/Services/DalamudServices.cs | 3 +++ Penumbra.GameData | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Glamourer/Services/DalamudServices.cs b/Glamourer/Services/DalamudServices.cs index 02df634..85783b9 100644 --- a/Glamourer/Services/DalamudServices.cs +++ b/Glamourer/Services/DalamudServices.cs @@ -6,6 +6,8 @@ using OtterGui.Services; namespace Glamourer.Services; +#pragma warning disable SeStringEvaluator + public class DalamudServices { public static void AddServices(ServiceManager services, IDalamudPluginInterface pi) @@ -28,5 +30,6 @@ public class DalamudServices services.AddDalamudService(pi); services.AddDalamudService(pi); services.AddDalamudService(pi); + services.AddDalamudService(pi); } } diff --git a/Penumbra.GameData b/Penumbra.GameData index 62bbce5..002260d 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 62bbce5981e961a91322ca1a7d3bb5be25f67185 +Subproject commit 002260d9815e571f1496c50374f5b712818e9880 From 9a684c9ff5a89e339b529d704be8ed0c2a2351c2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 29 Apr 2025 23:50:29 +0200 Subject: [PATCH 667/786] Implement GetDesignListExtended. --- Glamourer.Api | 2 +- Glamourer/Api/DesignsApi.cs | 7 ++++++- Glamourer/Api/GlamourerApi.cs | 2 +- Glamourer/Api/IpcProviders.cs | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 5e5c867..2158bd4 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 5e5c867a095eecac0dd494b30a33298a65e46426 +Subproject commit 2158bd4bbcb6cefe3ce48e6d8b32e134cbee9a91 diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index 08f5ddc..0ee0b64 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -6,11 +6,16 @@ using OtterGui.Services; namespace Glamourer.Api; -public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager stateManager) : IGlamourerApiDesigns, IApiService +public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager stateManager, DesignFileSystem fileSystem, DesignColors color) + : IGlamourerApiDesigns, IApiService { public Dictionary GetDesignList() => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text); + public Dictionary GetDesignListExtended() + => designs.Designs.ToDictionary(d => d.Identifier, + d => (d.Name.Text, fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign)); + public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags) { var args = ApiHelpers.Args("Design", designId, "Index", objectIndex, "Key", key, "Flags", flags); diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 62107a9..9aaf72f 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -6,7 +6,7 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; - public const int CurrentApiVersionMinor = 4; + public const int CurrentApiVersionMinor = 5; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 704f008..70ba47f 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -24,6 +24,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.ApiVersion.Provider(pi, api), IpcSubscribers.GetDesignList.Provider(pi, api.Designs), + IpcSubscribers.GetDesignListExtended.Provider(pi, api.Designs), IpcSubscribers.ApplyDesign.Provider(pi, api.Designs), IpcSubscribers.ApplyDesignName.Provider(pi, api.Designs), From b53124e7084c1036675c87ab0c922fdba4a63bb7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 1 May 2025 23:02:08 +0200 Subject: [PATCH 668/786] Temporarily use custom address for ReadStainingTemplate. --- Glamourer/Interop/Material/PrepareColorSet.cs | 14 +++++++++----- OtterGui | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index bd60a60..dfa6811 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -1,4 +1,5 @@ using Dalamud.Hooking; +using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; @@ -27,7 +28,8 @@ public sealed unsafe class PrepareColorSet : base("Prepare Color Set ") { _updateColorSets = updateColorSets; - _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); + hooks.Provider.InitializeFromAttributes(this); + _task = hooks.CreateHook(Name, Sigs.PrepareColorSet, Detour, true); } private readonly Task> _task; @@ -49,6 +51,10 @@ public sealed unsafe class PrepareColorSet private delegate Texture* Delegate(MaterialResourceHandle* material, StainId stainId1, StainId stainId2); + // TODO use CS when stabilized in Dalamud. + [Signature("E8 ?? ?? ?? ?? 48 8B FB EB 07")] + private static delegate* unmanaged _readStainingTemplate = null; + private Texture* Detour(MaterialResourceHandle* material, StainId stainId1, StainId stainId2) { Glamourer.Log.Excessive($"[{Name}] Triggered with 0x{(nint)material:X} {stainId1.Id} {stainId2.Id}."); @@ -78,12 +84,10 @@ public sealed unsafe class PrepareColorSet if (GetDyeTable(material, out var dyeTable)) { if (stainIds.Stain1.Id != 0) - ((delegate* unmanaged)MaterialResourceHandle.MemberFunctionPointers - .ReadStainingTemplate)(material, dyeTable, stainIds.Stain1.Id, (Half*)(&newTable), 0); + _readStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)(&newTable), 0); if (stainIds.Stain2.Id != 0) - ((delegate* unmanaged)MaterialResourceHandle.MemberFunctionPointers - .ReadStainingTemplate)(material, dyeTable, stainIds.Stain2.Id, (Half*)(&newTable), 1); + _readStainingTemplate(material, dyeTable, stainIds.Stain2.Id, (Half*)(&newTable), 1); } table = newTable; diff --git a/OtterGui b/OtterGui index d13d700..abfce28 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit d13d700796648f2a9279250052c4aa8ebeca221f +Subproject commit abfce28e0bacdea841f029f59bc19971c43814d8 From 39636f5293d76d42c6b3b84b82bbb73a08826ec4 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 1 May 2025 21:04:23 +0000 Subject: [PATCH 669/786] [CI] Updating repo.json for 1.3.8.6 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 3e8e6dc..3f1f29b 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.5", - "TestingAssemblyVersion": "1.3.8.5", + "AssemblyVersion": "1.3.8.6", + "TestingAssemblyVersion": "1.3.8.6", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.5/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a6073e2a4214cac6142b0e794a8cb6966120bb90 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 3 May 2025 23:36:03 +0200 Subject: [PATCH 670/786] Update old PR slightly. --- .../Designs/Special/RandomDesignGenerator.cs | 41 +++++++++++-------- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 8 ++-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/Glamourer/Designs/Special/RandomDesignGenerator.cs b/Glamourer/Designs/Special/RandomDesignGenerator.cs index b34a8ba..b1e1e7c 100644 --- a/Glamourer/Designs/Special/RandomDesignGenerator.cs +++ b/Glamourer/Designs/Special/RandomDesignGenerator.cs @@ -5,22 +5,29 @@ namespace Glamourer.Designs.Special; public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService { - private readonly Random _rng = new(); - private WeakReference _lastDesign = new(null, false); + private readonly Random _rng = new(); + private readonly WeakReference _lastDesign = new(null!, false); public Design? Design(IReadOnlyList localDesigns) { - if (localDesigns.Count == 0) + if (localDesigns.Count is 0) return null; - int idx; - do - idx = _rng.Next(0, localDesigns.Count); - while (config.PreventRandomRepeats && localDesigns.Count > 1 && _lastDesign.TryGetTarget(out var lastDesign) && lastDesign == localDesigns[idx]); - - Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {localDesigns[idx].Incognito}."); - _lastDesign.SetTarget(localDesigns[idx]); - return localDesigns[idx]; + var idx = _rng.Next(0, localDesigns.Count); + if (localDesigns.Count is 1) + { + _lastDesign.SetTarget(localDesigns[idx]); + return localDesigns[idx]; + } + + if (config.PreventRandomRepeats && _lastDesign.TryGetTarget(out var lastDesign)) + while (lastDesign == localDesigns[idx]) + idx = _rng.Next(0, localDesigns.Count); + + var design = localDesigns[idx]; + Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {design.Incognito}."); + _lastDesign.SetTarget(design); + return design; } public Design? Design() @@ -31,12 +38,12 @@ public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileS public Design? Design(IReadOnlyList predicates) { - if (predicates.Count == 0) - return Design(); - if (predicates.Count == 1) - return Design(predicates[0]); - - return Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList()); + return predicates.Count switch + { + 0 => Design(), + 1 => Design(predicates[0]), + _ => Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList()), + }; } public Design? Design(string restrictions) diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index a16c29e..746bb47 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -102,8 +102,8 @@ public class SettingsTab( "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down."u8, config.UseTemporarySettings, v => config.UseTemporarySettings = v); - Checkbox("Prevent Random Design Repeats", - "When using random designs, prevent the same design from being chosen twice in a row.", + Checkbox("Prevent Random Design Repeats"u8, + "When using random designs, prevent the same design from being chosen twice in a row."u8, config.PreventRandomRepeats, v => config.PreventRandomRepeats = v); ImGui.NewLine(); } @@ -250,8 +250,8 @@ public class SettingsTab( private void DrawQuickDesignBoxes() { - var showAuto = config.EnableAutoDesigns; - var numColumns = 8 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); + var showAuto = config.EnableAutoDesigns; + var numColumns = 8 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); ImGui.NewLine(); ImUtf8.Text("Show the Following Buttons in the Quick Design Bar:"u8); ImGui.Dummy(Vector2.Zero); From fcb0660deffa383d040567350bbab87aad777cf6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 4 May 2025 00:36:39 +0200 Subject: [PATCH 671/786] Implement new IPC methods and API 1.6 --- Glamourer.Api | 2 +- Glamourer/Api/DesignsApi.cs | 66 ++++++++++++++++++- Glamourer/Api/GlamourerApi.cs | 2 +- Glamourer/Api/IpcProviders.cs | 5 ++ .../DebugTab/IpcTester/DesignIpcTester.cs | 45 +++++++++++++ OtterGui | 2 +- 6 files changed, 118 insertions(+), 4 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 2158bd4..9c86a9d 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 2158bd4bbcb6cefe3ce48e6d8b32e134cbee9a91 +Subproject commit 9c86a9d6847f68e75679fe57d34f53f680d949c6 diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index 0ee0b64..e21e6cb 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -2,11 +2,18 @@ using Glamourer.Api.Enums; using Glamourer.Designs; using Glamourer.State; +using Newtonsoft.Json.Linq; using OtterGui.Services; namespace Glamourer.Api; -public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager stateManager, DesignFileSystem fileSystem, DesignColors color) +public class DesignsApi( + ApiHelpers helpers, + DesignManager designs, + StateManager stateManager, + DesignFileSystem fileSystem, + DesignColors color, + DesignConverter converter) : IGlamourerApiDesigns, IApiService { public Dictionary GetDesignList() @@ -16,6 +23,11 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager => designs.Designs.ToDictionary(d => d.Identifier, d => (d.Name.Text, fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign)); + public (string DisplayName, string FullPath, uint DisplayColor, bool ShowInQdb) GetExtendedDesignData(Guid designId) + => designs.Designs.ByIdentifier(designId) is { } d + ? (d.Name.Text, fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign) + : (string.Empty, string.Empty, 0, false); + public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags) { var args = ApiHelpers.Args("Design", designId, "Index", objectIndex, "Key", key, "Flags", flags); @@ -71,4 +83,56 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager return ApiHelpers.Return(GlamourerApiEc.Success, args); } + + public (GlamourerApiEc, Guid) AddDesign(string designInput, string name) + { + var args = ApiHelpers.Args("DesignData", designInput, "Name", name); + + if (converter.FromBase64(designInput, true, true, out _) is not { } designBase) + try + { + var jObj = JObject.Parse(designInput); + designBase = converter.FromJObject(jObj, true, true); + if (designBase is null) + return (ApiHelpers.Return(GlamourerApiEc.CouldNotParse, args), Guid.Empty); + } + catch (Exception ex) + { + Glamourer.Log.Error($"Failure parsing data for AddDesign due to\n{ex}"); + return (ApiHelpers.Return(GlamourerApiEc.CouldNotParse, args), Guid.Empty); + } + + try + { + var design = designBase is Design d + ? designs.CreateClone(d, name, true) + : designs.CreateClone(designBase, name, true); + return (ApiHelpers.Return(GlamourerApiEc.Success, args), design.Identifier); + } + catch (Exception ex) + { + Glamourer.Log.Error($"Unknown error creating design via IPC:\n{ex}"); + return (ApiHelpers.Return(GlamourerApiEc.UnknownError, args), Guid.Empty); + } + } + + public GlamourerApiEc DeleteDesign(Guid designId) + { + var args = ApiHelpers.Args("DesignId", designId); + if (designs.Designs.ByIdentifier(designId) is not { } design) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + designs.Delete(design); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public string? GetDesignBase64(Guid designId) + => designs.Designs.ByIdentifier(designId) is { } design + ? converter.ShareBase64(design) + : null; + + public JObject? GetDesignJObject(Guid designId) + => designs.Designs.ByIdentifier(designId) is { } design + ? converter.ShareJObject(design) + : null; } diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 9aaf72f..14c0512 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -6,7 +6,7 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; - public const int CurrentApiVersionMinor = 5; + public const int CurrentApiVersionMinor = 6; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 8058818..2701f18 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -25,8 +25,13 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.GetDesignList.Provider(pi, api.Designs), IpcSubscribers.GetDesignListExtended.Provider(pi, api.Designs), + IpcSubscribers.GetExtendedDesignData.Provider(pi, api.Designs), IpcSubscribers.ApplyDesign.Provider(pi, api.Designs), IpcSubscribers.ApplyDesignName.Provider(pi, api.Designs), + IpcSubscribers.AddDesign.Provider(pi, api.Designs), + IpcSubscribers.DeleteDesign.Provider(pi, api.Designs), + IpcSubscribers.GetDesignBase64.Provider(pi, api.Designs), + IpcSubscribers.GetDesignJObject.Provider(pi, api.Designs), IpcSubscribers.SetItem.Provider(pi, api.Items), IpcSubscribers.SetItemName.Provider(pi, api.Items), diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs index 918c7ad..9e11b99 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs @@ -7,6 +7,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; @@ -15,6 +16,7 @@ public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiServi private Dictionary _designs = []; private int _gameObjectIndex; private string _gameObjectName = string.Empty; + private string _designName = string.Empty; private uint _key; private ApplyFlag _flags = ApplyFlagEx.DesignDefault; private Guid? _design; @@ -30,6 +32,7 @@ public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiServi IpcTesterHelpers.IndexInput(ref _gameObjectIndex); IpcTesterHelpers.KeyInput(ref _key); IpcTesterHelpers.NameInput(ref _gameObjectName); + ImUtf8.InputText("##designName"u8, ref _designName, "Design Name..."u8); ImGuiUtil.GuidInput("##identifier", "Design Identifier...", string.Empty, ref _design, ref _designText, ImGui.GetContentRegionAvail().X); IpcTesterHelpers.DrawFlagInput(ref _flags); @@ -54,6 +57,48 @@ public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiServi IpcTesterHelpers.DrawIntro(ApplyDesignName.Label); if (ImGuiUtil.DrawDisabledButton("Apply##Name", Vector2.Zero, string.Empty, !_design.HasValue)) _lastError = new ApplyDesignName(pluginInterface).Invoke(_design!.Value, _gameObjectName, _key, _flags); + + IpcTesterHelpers.DrawIntro(GetExtendedDesignData.Label); + if (_design.HasValue) + { + var (display, path, color, draw) = new GetExtendedDesignData(pluginInterface).Invoke(_design.Value); + if (path.Length > 0) + ImUtf8.Text($"{display} ({path}){(draw ? " in QDB"u8 : ""u8)}", color); + else + ImUtf8.Text("No Data"u8); + } + else + { + ImUtf8.Text("No Data"u8); + } + + IpcTesterHelpers.DrawIntro(GetDesignBase64.Label); + if (ImUtf8.Button("To Clipboard##Base64"u8) && _design.HasValue) + { + var data = new GetDesignBase64(pluginInterface).Invoke(_design.Value); + ImUtf8.SetClipboardText(data); + } + + IpcTesterHelpers.DrawIntro(AddDesign.Label); + if (ImUtf8.Button("Add from Clipboard"u8)) + try + { + var data = ImUtf8.GetClipboardText(); + _lastError = new AddDesign(pluginInterface).Invoke(data, _designName, out var newDesign); + if (_lastError is GlamourerApiEc.Success) + { + _design = newDesign; + _designText = newDesign.ToString(); + } + } + catch + { + _lastError = GlamourerApiEc.UnknownError; + } + + IpcTesterHelpers.DrawIntro(DeleteDesign.Label); + if (ImUtf8.Button("Delete##Design"u8) && _design.HasValue) + _lastError = new DeleteDesign(pluginInterface).Invoke(_design.Value); } private void DrawDesignsPopup() diff --git a/OtterGui b/OtterGui index abfce28..d1ba194 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit abfce28e0bacdea841f029f59bc19971c43814d8 +Subproject commit d1ba1942efaae219b06ebc27d43de6d1889af97d From b1abbb8e77b0987c0432cc6eac9e5490d9c2b666 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 May 2025 00:30:27 +0200 Subject: [PATCH 672/786] Support model id input in weapon combo, support middle-mouse pipette. --- Glamourer/Gui/Equipment/EquipDrawData.cs | 3 + Glamourer/Gui/Equipment/EquipmentDrawer.cs | 33 +++++++-- Glamourer/Gui/Equipment/ItemCopyService.cs | 78 ++++++++++++++++++++++ Glamourer/Gui/Equipment/WeaponCombo.cs | 28 +++++++- Penumbra.GameData | 2 +- 5 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 Glamourer/Gui/Equipment/ItemCopyService.cs diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 77f4533..f32e22b 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -27,6 +27,9 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData) public readonly void SetStains(StainIds stains) => _editor.ChangeStains(_object, Slot, stains, ApplySettings.Manual); + public readonly void SetStain(int which, StainId stain) + => _editor.ChangeStains(_object, Slot, CurrentStains.With(which, stain), ApplySettings.Manual); + public readonly void SetApplyItem(bool value) { var manager = (DesignManager)_editor; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 0d2e8dc..f2ecc08 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -3,16 +3,15 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; using Glamourer.Events; using Glamourer.Gui.Materials; -using Glamourer.Interop.Material; using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; -using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Text.EndObjects; using OtterGui.Widgets; +using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -33,6 +32,7 @@ public class EquipmentDrawer private readonly Configuration _config; private readonly GPoseService _gPose; private readonly AdvancedDyePopup _advancedDyes; + private readonly ItemCopyService _itemCopy; private float _requiredComboWidthUnscaled; private float _requiredComboWidth; @@ -40,13 +40,14 @@ public class EquipmentDrawer private Stain? _draggedStain; public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures, - Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes) + Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy) { _items = items; _textures = textures; _config = config; _gPose = gPose; _advancedDyes = advancedDyes; + _itemCopy = itemCopy; _stainData = items.Stains; _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); @@ -184,6 +185,7 @@ public class EquipmentDrawer return change; } + #region Small private void DrawEquipSmall(in EquipDrawData equipDrawData) @@ -402,6 +404,7 @@ public class EquipmentDrawer ? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss) : _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, width); + _itemCopy.HandleCopyPaste(data, index); if (!change) DrawStainDragDrop(data, index, stain, found); @@ -456,6 +459,7 @@ public class EquipmentDrawer data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); + _itemCopy.HandleCopyPaste(data); if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, ItemManager.NothingItem(data.Slot), out var item)) @@ -473,6 +477,14 @@ public class EquipmentDrawer var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id.BonusItem, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); + if (ImGui.IsItemHovered() && ImGui.GetIO().KeyCtrl) + { + if (ImGui.IsKeyPressed(ImGuiKey.C)) + _itemCopy.Copy(combo.CurrentSelection); + else if (ImGui.IsKeyPressed(ImGuiKey.V)) + _itemCopy.Paste(data.Slot.ToEquipType(), data.SetItem); + } + if (change) data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) @@ -531,8 +543,12 @@ public class EquipmentDrawer if (combo.Draw(mainhand.CurrentItem.Name, mainhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth)) changedItem = combo.CurrentSelection; - else if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem, - default, out var c)) + else if (combo.CustomVariant.Id > 0 && (drawAll || ItemData.ConvertWeaponId(combo.CustomSetId) == mainhand.CurrentItem.Type)) + changedItem = _items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant); + _itemCopy.HandleCopyPaste(mainhand); + + if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem, + default, out var c)) changedItem = c; if (changedItem != null) @@ -549,7 +565,8 @@ public class EquipmentDrawer } if (unknown) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "The weapon type could not be identified, thus changing it to other weapons of that type is not possible."u8); + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, + "The weapon type could not be identified, thus changing it to other weapons of that type is not possible."u8); } private void DrawOffhand(in EquipDrawData mainhand, in EquipDrawData offhand, out string label, bool small, bool clear, bool open) @@ -569,6 +586,9 @@ public class EquipmentDrawer if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth)) offhand.SetItem(combo.CurrentSelection); + else if (combo.CustomVariant.Id > 0 && ItemData.ConvertWeaponId(combo.CustomSetId) == offhand.CurrentItem.Type) + offhand.SetItem(_items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant)); + _itemCopy.HandleCopyPaste(offhand); var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem); if (ResetOrClear(locked, clear, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item)) @@ -623,6 +643,7 @@ public class EquipmentDrawer { ImUtf8.Text(label); } + if (hasAdvancedDyes) ImUtf8.HoverTooltip("This design has advanced dyes setup for this slot."u8); } diff --git a/Glamourer/Gui/Equipment/ItemCopyService.cs b/Glamourer/Gui/Equipment/ItemCopyService.cs new file mode 100644 index 0000000..e72a54b --- /dev/null +++ b/Glamourer/Gui/Equipment/ItemCopyService.cs @@ -0,0 +1,78 @@ +using Dalamud.Game.ClientState.Keys; +using Dalamud.Plugin.Services; +using Glamourer.Services; +using ImGuiNET; +using OtterGui.OtterGuiInternal.Enums; +using OtterGui.Services; +using OtterGuiInternal; +using Penumbra.GameData.Data; +using Penumbra.GameData.DataContainers; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Equipment; + +public class ItemCopyService(ItemManager items, IKeyState keyState, DictStain stainData) : IUiService +{ + public EquipItem? Item { get; private set; } + public Stain? Stain { get; private set; } + + public void Copy(in EquipItem item) + => Item = item; + + public void Copy(in Stain stain) + => Stain = stain; + + public void Paste(int which, Action setter) + { + if (Stain is { } stain) + setter(which, stain.RowIndex); + } + + public void Paste(FullEquipType type, Action setter) + { + if (Item is not { } item) + return; + + if (type != item.Type) + { + if (type.IsBonus()) + item = items.Identify(type.ToBonus(), item.PrimaryId, item.Variant); + else if (type.IsEquipment() || type.IsAccessory()) + item = items.Identify(type.ToSlot(), item.PrimaryId, item.Variant); + else + item = items.Identify(type.ToSlot(), item.PrimaryId, item.SecondaryId, item.Variant); + } + + if (item.Valid && item.Type == type) + setter(item); + } + + public void HandleCopyPaste(in EquipDrawData data) + { + if (ImGui.GetIO().KeyCtrl) + { + if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Middle)) + Paste(data.CurrentItem.Type, data.SetItem); + } + else if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) && ImGui.IsMouseClicked(ImGuiMouseButton.Middle)) + { + Copy(data.CurrentItem); + } + } + + public void HandleCopyPaste(in EquipDrawData data, int which) + { + if (ImGui.GetIO().KeyCtrl) + { + if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Middle)) + Paste(which, data.SetStain); + } + else if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) + && ImGui.IsMouseClicked(ImGuiMouseButton.Middle) + && stainData.TryGetValue(data.CurrentStains[which].Id, out var stain)) + { + Copy(stain); + } + } +} diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index e96f721..6e38e7c 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -1,7 +1,6 @@ using Glamourer.Services; using Glamourer.Unlocks; using ImGuiNET; -using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Log; @@ -20,6 +19,10 @@ public sealed class WeaponCombo : FilterComboCache private ItemId _currentItem; private float _innerWidth; + public PrimaryId CustomSetId { get; private set; } + public SecondaryId CustomWeaponId { get; private set; } + public Variant CustomVariant { get; private set; } + public WeaponCombo(ItemManager items, FullEquipType type, Logger log, FavoriteManager favorites) : base(() => GetWeapons(favorites, items, type), MouseWheelType.Control, log) { @@ -47,8 +50,9 @@ public sealed class WeaponCombo : FilterComboCache public bool Draw(string previewName, ItemId previewIdx, float width, float innerWidth) { - _innerWidth = innerWidth; - _currentItem = previewIdx; + _innerWidth = innerWidth; + _currentItem = previewIdx; + CustomVariant = 0; return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); } @@ -75,6 +79,24 @@ public sealed class WeaponCombo : FilterComboCache return ret; } + protected override void OnClosePopup() + { + // If holding control while the popup closes, try to parse the input as a full tuple of set id, weapon id and variant, and set a custom item for that. + if (!ImGui.GetIO().KeyCtrl) + return; + + var split = Filter.Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (split.Length != 3 + || !ushort.TryParse(split[0], out var setId) + || !ushort.TryParse(split[1], out var weaponId) + || !byte.TryParse(split[2], out var variant)) + return; + + CustomSetId = setId; + CustomWeaponId = weaponId; + CustomVariant = variant; + } + protected override bool IsVisible(int globalIndex, LowerString filter) => base.IsVisible(globalIndex, filter) || Items[globalIndex].ModelString.StartsWith(filter.Lower); diff --git a/Penumbra.GameData b/Penumbra.GameData index 002260d..1019b56 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 002260d9815e571f1496c50374f5b712818e9880 +Subproject commit 1019b56de3b7ab2a6a1aefd699f9a507323e92fc From c1e1476fa647cdf50a36757bd63b4ca5becc29f2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 May 2025 00:30:47 +0200 Subject: [PATCH 673/786] Fix some issues with glamourer not searching mods by name. --- Glamourer/Interop/Penumbra/PenumbraService.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 3f93551..d66ddc4 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -172,14 +172,14 @@ public class PenumbraService : IDisposable if (_queryTemporaryModSettings != null) { - var tempEc = _queryTemporaryModSettings.Invoke(collection, modDirectory, out var tempTuple, out source); + var tempEc = _queryTemporaryModSettings.Invoke(collection, modDirectory, out var tempTuple, out source, 0, modName); if (tempEc is PenumbraApiEc.Success && tempTuple != null) return new ModSettings(tempTuple.Value.Settings, tempTuple.Value.Priority, tempTuple.Value.Enabled, tempTuple.Value.ForceInherit, false); } source = string.Empty; - var (ec2, tuple2) = _getCurrentSettings!.Invoke(collection, modDirectory); + var (ec2, tuple2) = _getCurrentSettings!.Invoke(collection, modDirectory, modName); if (ec2 is not PenumbraApiEc.Success) return ModSettings.Empty; @@ -265,7 +265,7 @@ public class PenumbraService : IDisposable if (!Available) return; - if (_openModPage!.Invoke(TabType.Mods, mod.DirectoryName) == PenumbraApiEc.ModMissing) + if (_openModPage!.Invoke(TabType.Mods, mod.DirectoryName, mod.Name) == PenumbraApiEc.ModMissing) Glamourer.Messager.NotificationMessage($"Could not open the mod {mod.Name}, no fitting mod was found in your Penumbra install.", NotificationType.Info, false); } @@ -349,14 +349,14 @@ public class PenumbraService : IDisposable var ex = settings.Remove ? index.HasValue - ? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, key) - : _removeTemporaryModSettings!.Invoke(collection, mod.DirectoryName, key) + ? _removeTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, key, mod.Name) + : _removeTemporaryModSettings!.Invoke(collection, mod.DirectoryName, key, mod.Name) : index.HasValue ? _setTemporaryModSettingsPlayer!.Invoke(index.Value.Index, mod.DirectoryName, settings.ForceInherit, settings.Enabled, settings.Priority, - settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key) + settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key, mod.Name) : _setTemporaryModSettings!.Invoke(collection, mod.DirectoryName, settings.ForceInherit, settings.Enabled, settings.Priority, - settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key); + settings.Settings.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value), name, key, mod.Name); switch (ex) { case PenumbraApiEc.InvalidArgument: @@ -384,8 +384,8 @@ public class PenumbraService : IDisposable private void SetModPermanent(StringBuilder sb, Mod mod, ModSettings settings, Guid collection) { var ec = settings.ForceInherit - ? _inheritMod!.Invoke(collection, mod.DirectoryName, true) - : _setMod!.Invoke(collection, mod.DirectoryName, settings.Enabled); + ? _inheritMod!.Invoke(collection, mod.DirectoryName, true, mod.Name) + : _setMod!.Invoke(collection, mod.DirectoryName, settings.Enabled, mod.Name); switch (ec) { case PenumbraApiEc.ModMissing: @@ -399,14 +399,14 @@ public class PenumbraService : IDisposable if (settings.ForceInherit || !settings.Enabled) return; - ec = _setModPriority!.Invoke(collection, mod.DirectoryName, settings.Priority); + ec = _setModPriority!.Invoke(collection, mod.DirectoryName, settings.Priority, mod.Name); Debug.Assert(ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged, "Setting Priority should not be able to fail."); foreach (var (setting, list) in settings.Settings) { ec = list.Count == 1 - ? _setModSetting!.Invoke(collection, mod.DirectoryName, setting, list[0]) - : _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list); + ? _setModSetting!.Invoke(collection, mod.DirectoryName, setting, list[0], mod.Name) + : _setModSettings!.Invoke(collection, mod.DirectoryName, setting, list, mod.Name); switch (ec) { case PenumbraApiEc.OptionGroupMissing: sb.AppendLine($"Could not find the option group {setting} in mod {mod.Name}."); break; From 8a9877bb014eb6493b6c3b21157e036cc1fa2c9e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 6 May 2025 00:31:26 +0200 Subject: [PATCH 674/786] Add testing Dynamis IPC for debugging. --- Glamourer/Glamourer.cs | 2 ++ Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 5 +++-- Glamourer/Services/ServiceManager.cs | 3 ++- OtterGui | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index 1e2a62d..f62085a 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -26,6 +26,7 @@ public class Glamourer : IDalamudPlugin public static readonly Logger Log = new(); public static MessageService Messager { get; private set; } = null!; + public static DynamisIpc Dynamis { get; private set; } = null!; private readonly ServiceManager _services; @@ -35,6 +36,7 @@ public class Glamourer : IDalamudPlugin { _services = StaticServiceManager.CreateProvider(pluginInterface, Log, this); Messager = _services.GetService(); + Dynamis = _services.GetService(); _services.EnsureRequiredServices(); _services.GetService(); diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index fc4799f..a0f2491 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -45,9 +45,10 @@ public unsafe class ModelEvaluationPanel( ImGuiUtil.DrawTableColumn("Address"); ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(actor.ToString()); + + Glamourer.Dynamis.DrawPointer(actor); ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(model.ToString()); + Glamourer.Dynamis.DrawPointer(model); ImGui.TableNextColumn(); if (actor.IsCharacter) { diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 78a0bf0..0754313 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -112,7 +112,8 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); private static ServiceManager AddDesigns(this ServiceManager services) => services.AddSingleton() diff --git a/OtterGui b/OtterGui index d1ba194..9235599 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit d1ba1942efaae219b06ebc27d43de6d1889af97d +Subproject commit 9235599dd6efd17067a06ad98066a6c0d5b625e0 From 9abd7f27671b44a039ea34b9f007f28209ad0d9f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 8 May 2025 23:44:16 +0200 Subject: [PATCH 675/786] Make Dynamis IPC working. --- Glamourer/Gui/Equipment/ItemCopyService.cs | 9 ++------- Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DynamisPanel.cs | 16 ++++++++++++++++ Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 6 ++++-- OtterGui | 2 +- 5 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 Glamourer/Gui/Tabs/DebugTab/DynamisPanel.cs diff --git a/Glamourer/Gui/Equipment/ItemCopyService.cs b/Glamourer/Gui/Equipment/ItemCopyService.cs index e72a54b..ea37963 100644 --- a/Glamourer/Gui/Equipment/ItemCopyService.cs +++ b/Glamourer/Gui/Equipment/ItemCopyService.cs @@ -1,18 +1,13 @@ -using Dalamud.Game.ClientState.Keys; -using Dalamud.Plugin.Services; -using Glamourer.Services; +using Glamourer.Services; using ImGuiNET; -using OtterGui.OtterGuiInternal.Enums; using OtterGui.Services; -using OtterGuiInternal; -using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; namespace Glamourer.Gui.Equipment; -public class ItemCopyService(ItemManager items, IKeyState keyState, DictStain stainData) : IUiService +public class ItemCopyService(ItemManager items, DictStain stainData) : IUiService { public EquipItem? Item { get; private set; } public Stain? Stain { get; private set; } diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs index 90282e8..3df425f 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTabHeader.cs @@ -1,5 +1,4 @@ using Glamourer.Gui.Tabs.DebugTab.IpcTester; -using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; @@ -36,6 +35,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees) provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), diff --git a/Glamourer/Gui/Tabs/DebugTab/DynamisPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DynamisPanel.cs new file mode 100644 index 0000000..92cd777 --- /dev/null +++ b/Glamourer/Gui/Tabs/DebugTab/DynamisPanel.cs @@ -0,0 +1,16 @@ +using OtterGui.Services; +using Penumbra.GameData.Gui.Debug; + +namespace Glamourer.Gui.Tabs.DebugTab; + +public class DynamisPanel(DynamisIpc dynamis) : IGameDataDrawer +{ + public string Label + => "Dynamis Interop"; + + public void Draw() + => dynamis.DrawDebugInfo(); + + public bool Disabled + => false; +} diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 3714c82..012e5ba 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -61,7 +61,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); ImGui.InputInt("##CutsceneIndex", ref _gameObjectIndex, 0, 0); ImGuiUtil.DrawTableColumn(_penumbra.Available - ? _penumbra.CutsceneParent((ushort) _gameObjectIndex).ToString() + ? _penumbra.CutsceneParent((ushort)_gameObjectIndex).ToString() : "Penumbra Unavailable"); ImGuiUtil.DrawTableColumn("Redraw Object"); @@ -76,7 +76,9 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem } ImGuiUtil.DrawTableColumn("Last Tooltip Date"); - ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastTooltip > DateTime.MinValue ? $"{_penumbraTooltip.LastTooltip.ToLongTimeString()} ({_penumbraTooltip.LastType} {_penumbraTooltip.LastId})" : "Never"); + ImGuiUtil.DrawTableColumn(_penumbraTooltip.LastTooltip > DateTime.MinValue + ? $"{_penumbraTooltip.LastTooltip.ToLongTimeString()} ({_penumbraTooltip.LastType} {_penumbraTooltip.LastId})" + : "Never"); ImGui.TableNextColumn(); ImGuiUtil.DrawTableColumn("Last Click Date"); diff --git a/OtterGui b/OtterGui index 9235599..ce8f88e 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 9235599dd6efd17067a06ad98066a6c0d5b625e0 +Subproject commit ce8f88ee892536016756769e86bdf48132351d80 From c93370ec920f03d3fb32154cfdcf730ba6129abd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 9 May 2025 00:06:19 +0200 Subject: [PATCH 676/786] Again. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index ce8f88e..77485cd 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit ce8f88ee892536016756769e86bdf48132351d80 +Subproject commit 77485cdd92ffcadb58e183ea9147d4ba37a2b93b From e4b32343aeb4d515ee35b2b492d6e442abf4dc57 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 21 May 2025 17:08:17 +0200 Subject: [PATCH 677/786] Update libraries. --- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OtterGui b/OtterGui index 77485cd..9aeda9a 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 77485cdd92ffcadb58e183ea9147d4ba37a2b93b +Subproject commit 9aeda9a892d9b971e32b10db21a8daf9c0b9ee53 diff --git a/Penumbra.Api b/Penumbra.Api index f578091..574ef3a 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit f578091fa579fb098c21036b492ff6e6088184c9 +Subproject commit 574ef3a8afd42b949e713e247a0b812886f088bb diff --git a/Penumbra.GameData b/Penumbra.GameData index 1019b56..bb3b462 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 1019b56de3b7ab2a6a1aefd699f9a507323e92fc +Subproject commit bb3b462bbc5bc2a598c1ad8c372b0cb255551fe1 From 081ac6bf8b3489aae69a3d80fafe0e0a9db63584 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 21 May 2025 17:09:41 +0200 Subject: [PATCH 678/786] Split ResetAdvanced into two parts. --- Glamourer/Configuration.cs | 4 +- Glamourer/Gui/DesignQuickBar.cs | 61 +++++++++++++++---- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 5 +- Glamourer/Services/ConfigMigrationService.cs | 13 +++- Glamourer/State/StateApplier.cs | 2 +- Glamourer/State/StateManager.cs | 61 ++++++++++++++++--- 6 files changed, 120 insertions(+), 26 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 1d3689d..c9ff0e6 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -81,7 +81,7 @@ public class Configuration : IPluginConfiguration, ISavable public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; public QdbButtons QdbButtons { get; set; } = - QdbButtons.ApplyDesign | QdbButtons.RevertAll | QdbButtons.RevertAutomation | QdbButtons.RevertAdvanced; + QdbButtons.ApplyDesign | QdbButtons.RevertAll | QdbButtons.RevertAutomation | QdbButtons.RevertAdvancedDyes; [JsonConverter(typeof(SortModeConverter))] [JsonProperty(Order = int.MaxValue)] @@ -158,7 +158,7 @@ public class Configuration : IPluginConfiguration, ISavable public static class Constants { - public const int CurrentVersion = 7; + public const int CurrentVersion = 8; public static readonly ISortMode[] ValidSortModes = [ diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 5112d97..b64a5f2 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -19,14 +19,15 @@ namespace Glamourer.Gui; [Flags] public enum QdbButtons { - ApplyDesign = 0x01, - RevertAll = 0x02, - RevertAutomation = 0x04, - RevertAdvanced = 0x08, - RevertEquip = 0x10, - RevertCustomize = 0x20, - ReapplyAutomation = 0x40, - ResetSettings = 0x80, + ApplyDesign = 0x01, + RevertAll = 0x02, + RevertAutomation = 0x04, + RevertAdvancedDyes = 0x08, + RevertEquip = 0x10, + RevertCustomize = 0x20, + ReapplyAutomation = 0x40, + ResetSettings = 0x80, + RevertAdvancedCustomization = 0x100, } public sealed class DesignQuickBar : Window, IDisposable @@ -124,6 +125,7 @@ public sealed class DesignQuickBar : Window, IDisposable DrawRevertEquipButton(buttonSize); DrawRevertCustomizeButton(buttonSize); DrawRevertAdvancedCustomization(buttonSize); + DrawRevertAdvancedDyes(buttonSize); DrawRevertAutomationButton(buttonSize); DrawReapplyAutomationButton(buttonSize); DrawResetSettingsButton(buttonSize); @@ -318,7 +320,7 @@ public sealed class DesignQuickBar : Window, IDisposable private void DrawRevertAdvancedCustomization(Vector2 buttonSize) { - if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) + if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization)) return; var available = 0; @@ -327,7 +329,7 @@ public sealed class DesignQuickBar : Window, IDisposable if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) { available |= 1; - _tooltipBuilder.Append("Left-Click: Revert the advanced customizations and dyes of the player character to their game state."); + _tooltipBuilder.Append("Left-Click: Revert the advanced customizations of the player character to their game state."); } if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) @@ -335,7 +337,40 @@ public sealed class DesignQuickBar : Window, IDisposable if (available != 0) _tooltipBuilder.Append('\n'); available |= 2; - _tooltipBuilder.Append("Right-Click: Revert the advanced customizations and dyes of ") + _tooltipBuilder.Append("Right-Click: Revert the advanced customizations of ") + .Append(_targetIdentifier) + .Append(" to their game state."); + } + + if (available == 0) + _tooltipBuilder.Append("Neither player character nor target are available or their state is locked."); + + var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.PaintBrush, buttonSize, available); + ImGui.SameLine(); + if (clicked) + _stateManager.ResetAdvancedCustomizations(state!, StateSource.Manual); + } + + private void DrawRevertAdvancedDyes(Vector2 buttonSize) + { + if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedDyes)) + return; + + var available = 0; + _tooltipBuilder.Clear(); + + if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid) + { + available |= 1; + _tooltipBuilder.Append("Left-Click: Revert the advanced dyes of the player character to their game state."); + } + + if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid) + { + if (available != 0) + _tooltipBuilder.Append('\n'); + available |= 2; + _tooltipBuilder.Append("Right-Click: Revert the advanced dyes of ") .Append(_targetIdentifier) .Append(" to their game state."); } @@ -346,7 +381,7 @@ public sealed class DesignQuickBar : Window, IDisposable var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, available); ImGui.SameLine(); if (clicked) - _stateManager.ResetAdvancedState(state!, StateSource.Manual); + _stateManager.ResetAdvancedDyes(state!, StateSource.Manual); } private void DrawRevertCustomizeButton(Vector2 buttonSize) @@ -501,7 +536,7 @@ public sealed class DesignQuickBar : Window, IDisposable ++_numButtons; } - if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced)) + if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization)) ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize)) ++_numButtons; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 746bb47..cf57824 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -251,7 +251,7 @@ public class SettingsTab( private void DrawQuickDesignBoxes() { var showAuto = config.EnableAutoDesigns; - var numColumns = 8 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); + var numColumns = 9 - (showAuto ? 0 : 2) - (config.UseTemporarySettings ? 0 : 1); ImGui.NewLine(); ImUtf8.Text("Show the Following Buttons in the Quick Design Bar:"u8); ImGui.Dummy(Vector2.Zero); @@ -268,7 +268,8 @@ public class SettingsTab( ("Reapply Auto", showAuto, QdbButtons.ReapplyAutomation), ("Revert Equip", true, QdbButtons.RevertEquip), ("Revert Customize", true, QdbButtons.RevertCustomize), - ("Revert Advanced", true, QdbButtons.RevertAdvanced), + ("Revert Advanced Customization", true, QdbButtons.RevertAdvancedCustomization), + ("Revert Advanced Dyes", true, QdbButtons.RevertAdvancedDyes), ("Reset Settings", config.UseTemporarySettings, QdbButtons.ResetSettings), ]; diff --git a/Glamourer/Services/ConfigMigrationService.cs b/Glamourer/Services/ConfigMigrationService.cs index 3f997c9..ef39f1a 100644 --- a/Glamourer/Services/ConfigMigrationService.cs +++ b/Glamourer/Services/ConfigMigrationService.cs @@ -24,9 +24,20 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator MigrateV4To5(); MigrateV5To6(); MigrateV6To7(); + MigrateV7To8(); AddColors(config, true); } + private void MigrateV7To8() + { + if (_config.Version > 7) + return; + + if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedDyes)) + _config.QdbButtons |= QdbButtons.RevertAdvancedCustomization; + _config.Version = 8; + } + private void MigrateV6To7() { if (_config.Version > 6) @@ -43,7 +54,7 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator return; if (_data["ShowRevertAdvancedParametersButton"]?.ToObject() ?? true) - _config.QdbButtons |= QdbButtons.RevertAdvanced; + _config.QdbButtons |= QdbButtons.RevertAdvancedCustomization; _config.Version = 6; } diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 698151f..93a3450 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -411,6 +411,6 @@ public class StateApplier( return actors; } - private ActorData GetData(ActorState state) + public ActorData GetData(ActorState state) => _objects.TryGetValue(state.Identifier, out var data) ? data : ActorData.Invalid; } diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 2dda310..98b12aa 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -270,16 +270,63 @@ public sealed class StateManager( state.Materials.Clear(); - var actors = ActorData.Invalid; + var objects = ActorData.Invalid; if (source is not StateSource.Game) - actors = Applier.ApplyAll(state, redraw, true); + objects = Applier.ApplyAll(state, redraw, true); Glamourer.Log.Verbose( - $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); + $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); // only invoke if we define this reset call as the final call in our state update. - if(isFinal) - StateFinalized.Invoke(StateFinalizationType.Revert, actors); + if (isFinal) + StateFinalized.Invoke(StateFinalizationType.Revert, objects); + } + + public void ResetAdvancedDyes(ActorState state, StateSource source, uint key = 0) + { + if (!state.Unlock(key) || !state.ModelData.IsHuman) + return; + + state.ModelData.Parameters = state.BaseData.Parameters; + + foreach (var flag in CustomizeParameterExtensions.AllFlags) + state.Sources[flag] = StateSource.Game; + + var objects = Applier.GetData(state); + if (source is not StateSource.Game) + foreach (var (idx, mat) in state.Materials.Values) + Applier.ChangeMaterialValue(state, objects, MaterialValueIndex.FromKey(idx), mat.Game); + + state.Materials.Clear(); + + Glamourer.Log.Verbose( + $"Reset advanced dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); + // Update that we have completed a full operation. (We can do this directly as nothing else is linked) + StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, objects); + } + + public void ResetAdvancedCustomizations(ActorState state, StateSource source, uint key = 0) + { + if (!state.Unlock(key) || !state.ModelData.IsHuman) + return; + + state.ModelData.Parameters = state.BaseData.Parameters; + + foreach (var flag in CustomizeParameterExtensions.AllFlags) + state.Sources[flag] = StateSource.Game; + + var objects = ActorData.Invalid; + if (source is not StateSource.Game) + objects = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); + + state.Materials.Clear(); + + Glamourer.Log.Verbose( + $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {objects.ToLazyString("nothing")}.]"); + StateChanged.Invoke(StateChangeType.Reset, source, state, objects, null); + // Update that we have completed a full operation. (We can do this directly as nothing else is linked) + StateFinalized.Invoke(StateFinalizationType.RevertAdvanced, objects); } public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0) @@ -468,7 +515,7 @@ public sealed class StateManager( || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); - if(isFinal) + if (isFinal) StateFinalized.Invoke(StateFinalizationType.Reapply, data); } From aa1ac291829f7f9948b8b10bcc8744f8fe4b7a04 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 21 May 2025 17:16:55 +0200 Subject: [PATCH 679/786] Make design selector resizable. --- Glamourer/EphemeralConfig.cs | 4 ++++ .../DesignTab/DesignFileSystemSelector.cs | 24 +++++++++++++++++++ Glamourer/Gui/Tabs/DesignTab/DesignTab.cs | 5 +--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Glamourer/EphemeralConfig.cs b/Glamourer/EphemeralConfig.cs index 3e13dc4..98dabec 100644 --- a/Glamourer/EphemeralConfig.cs +++ b/Glamourer/EphemeralConfig.cs @@ -20,6 +20,10 @@ public class EphemeralConfig : ISavable public Guid SelectedQuickDesign { get; set; } = Guid.Empty; public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion; + public float CurrentDesignSelectorWidth { get; set; } = 200f; + public float DesignSelectorMinimumScale { get; set; } = 0.1f; + public float DesignSelectorMaximumScale { get; set; } = 0.5f; + [JsonIgnore] private readonly SaveService _saveService; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index ea117c5..11e803b 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -12,6 +12,7 @@ using OtterGui.Filesystem; using OtterGui.FileSystem.Selector; using OtterGui.Log; using OtterGui.Raii; +using OtterGui.Text; namespace Glamourer.Gui.Tabs.DesignTab; @@ -45,6 +46,29 @@ public sealed class DesignFileSystemSelector : FileSystemSelector _config.Ephemeral.CurrentDesignSelectorWidth * ImUtf8.GlobalScale; + + protected override float MinimumAbsoluteRemainder + => 470 * ImUtf8.GlobalScale; + + protected override float MinimumScaling + => _config.Ephemeral.DesignSelectorMinimumScale; + + protected override float MaximumScaling + => _config.Ephemeral.DesignSelectorMaximumScale; + + protected override void SetSize(Vector2 size) + { + base.SetSize(size); + var adaptedSize = MathF.Round(size.X / ImUtf8.GlobalScale); + if (adaptedSize == _config.Ephemeral.CurrentDesignSelectorWidth) + return; + + _config.Ephemeral.CurrentDesignSelectorWidth = adaptedSize; + _config.Ephemeral.Save(); + } + public DesignFileSystemSelector(DesignManager designManager, DesignFileSystem fileSystem, IKeyState keyState, DesignChanged @event, Configuration config, DesignConverter converter, TabSelected selectionEvent, Logger log, DesignColors designColors, DesignApplier designApplier) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index 9832451..afb5900 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -16,7 +16,7 @@ public class DesignTab(DesignFileSystemSelector _selector, DesignPanel _panel, I public void DrawContent() { - _selector.Draw(GetDesignSelectorSize()); + _selector.Draw(); if (_importService.CreateCharaTarget(out var designBase, out var name)) { var newDesign = _manager.CreateClone(designBase, name, true); @@ -27,7 +27,4 @@ public class DesignTab(DesignFileSystemSelector _selector, DesignPanel _panel, I _panel.Draw(); _importService.CreateCharaSource(); } - - public float GetDesignSelectorSize() - => 200f * ImGuiHelpers.GlobalScale; } From f192c17c9bcb99b720b413b4aad16a095dafd8de Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 21 May 2025 17:46:56 +0200 Subject: [PATCH 680/786] Allow drag & drop for color buttons. --- .../CustomizationDrawer.Color.cs | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index 4d34a05..4cc6ac3 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -4,13 +4,80 @@ using Glamourer.GameData; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Text; +using OtterGui.Text.EndObjects; using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using System; namespace Glamourer.Gui.Customization; public partial class CustomizationDrawer { - private const string ColorPickerPopupName = "ColorPicker"; + private const string ColorPickerPopupName = "ColorPicker"; + private CustomizeValue _draggedColorValue; + private CustomizeIndex _draggedColorType; + + + private void DrawDragDropSource(CustomizeIndex index, CustomizeData custom) + { + using var dragDropSource = ImUtf8.DragDropSource(); + if (!dragDropSource) + return; + + if (!DragDropSource.SetPayload("##colorDragDrop"u8)) + _draggedColorValue = _customize[index]; + ImUtf8.Text( + $"Dragging {(custom.Color == 0 ? $"{_currentOption} (NPC)" : _currentOption)} #{_draggedColorValue.Value}..."); + _draggedColorType = index; + } + + private void DrawDragDropTarget(CustomizeIndex index) + { + using var dragDropTarget = ImUtf8.DragDropTarget(); + if (!dragDropTarget.Success || !dragDropTarget.IsDropping("##colorDragDrop"u8)) + return; + + var idx = _set.DataByValue(_draggedColorType, _draggedColorValue, out var draggedData, _customize.Face); + var bestMatch = _draggedColorValue; + if (draggedData.HasValue) + { + var draggedColor = draggedData.Value.Color; + var targetData = _set.Data(index, idx); + if (targetData.Color != draggedColor) + { + var bestDiff = Diff(targetData.Color, draggedColor); + var count = _set.Count(index); + for (var i = 0; i < count; ++i) + { + targetData = _set.Data(index, i); + if (targetData.Color == draggedColor) + { + UpdateValue(_draggedColorValue); + return; + } + + var diff = Diff(targetData.Color, draggedColor); + if (diff >= bestDiff) + continue; + + bestDiff = diff; + bestMatch = (CustomizeValue)i; + } + } + } + + UpdateValue(bestMatch); + return; + + static uint Diff(uint color1, uint color2) + { + var r = (color1 & 0xFF) - (color2 & 0xFF); + var g = ((color1 >> 8) & 0xFF) - ((color2 >> 8) & 0xFF); + var b = ((color1 >> 16) & 0xFF) - ((color2 >> 16) & 0xFF); + return 30 * r * r + 59 * g * g + 11 * b * b; + } + } private void DrawColorPicker(CustomizeIndex index) { @@ -21,7 +88,7 @@ public partial class CustomizationDrawer using (_ = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0)) { - if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.None, _framedIconSize)) + if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.NoDragDrop, _framedIconSize)) { ImGui.OpenPopup(ColorPickerPopupName); } @@ -30,6 +97,9 @@ public partial class CustomizationDrawer var data = _set.Data(_currentIndex, current, _customize.Face); UpdateValue(data.Value); } + + DrawDragDropSource(index, custom); + DrawDragDropTarget(index); } var npc = false; From 74674cfa0c52d6e24dd63276a35e274c7ebdf29b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 23 May 2025 10:47:47 +0200 Subject: [PATCH 681/786] Make combos start from preview selection if possible. --- Glamourer/Gui/Customization/CustomizationDrawer.Color.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs | 8 -------- OtterGui | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index 4cc6ac3..8db07ff 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -37,7 +37,7 @@ public partial class CustomizationDrawer using var dragDropTarget = ImUtf8.DragDropTarget(); if (!dragDropTarget.Success || !dragDropTarget.IsDropping("##colorDragDrop"u8)) return; - + var idx = _set.DataByValue(_draggedColorType, _draggedColorValue, out var draggedData, _customize.Face); var bestMatch = _draggedColorValue; if (draggedData.HasValue) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs index 9444ff7..0efa358 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -1,7 +1,6 @@ using Glamourer.Designs; using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Widgets; @@ -13,13 +12,6 @@ public sealed class DesignColorCombo(DesignColors _designColors, bool _skipAutom : _designColors.Keys.OrderBy(k => k).Prepend(DesignColors.AutomaticName), MouseWheelType.Control, Glamourer.Log) { - protected override void OnMouseWheel(string preview, ref int current, int steps) - { - if (CurrentSelectionIdx < 0) - CurrentSelectionIdx = Items.IndexOf(preview); - base.OnMouseWheel(preview, ref current, steps); - } - protected override bool DrawSelectable(int globalIdx, bool selected) { var isAutomatic = !_skipAutomatic && globalIdx == 0; diff --git a/OtterGui b/OtterGui index 9aeda9a..421874a 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 9aeda9a892d9b971e32b10db21a8daf9c0b9ee53 +Subproject commit 421874a12540b7f8c1279dcc6a92e895a94d2fbc From b4485f028d29e0e2a99e20a348f819066224cc83 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 23 May 2025 15:21:14 +0200 Subject: [PATCH 682/786] Batch some multi-design changes to skip unnecessary computations. --- Glamourer/Designs/History/EditorHistory.cs | 4 +- Glamourer/Gui/DesignCombo.cs | 50 ++++++++++++++++++- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 24 +++++++-- Glamourer/Services/ServiceManager.cs | 3 +- OtterGui | 2 +- 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/Glamourer/Designs/History/EditorHistory.cs b/Glamourer/Designs/History/EditorHistory.cs index 58bce3d..caec151 100644 --- a/Glamourer/Designs/History/EditorHistory.cs +++ b/Glamourer/Designs/History/EditorHistory.cs @@ -152,7 +152,7 @@ public class EditorHistory : IDisposable, IService { if (!_stateEntries.TryGetValue(state, out var list)) { - list = new Queue(); + list = []; _stateEntries.Add(state, list); } @@ -163,7 +163,7 @@ public class EditorHistory : IDisposable, IService { if (!_designEntries.TryGetValue(design, out var list)) { - list = new Queue(); + list = []; _designEntries.Add(design, list); } diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index a871cf1..c1e474d 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -10,6 +10,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Log; +using OtterGui.Services; using OtterGui.Widgets; namespace Glamourer.Gui; @@ -21,6 +22,7 @@ public abstract class DesignComboBase : FilterComboCache>> generator, Logger log, DesignChanged designChanged, @@ -32,6 +34,7 @@ public abstract class DesignComboBase : FilterComboCache Combos = services.GetServicesImplementing().ToArray(); + + internal DesignComboListener StopListening() + { + var list = new List(Combos.Count); + foreach (var combo in Combos.Where(c => c.IsListening)) + { + combo.StopListening(); + list.Add(combo); + } + + return new DesignComboListener(list); + } + + internal readonly struct DesignComboListener(List combos) : IDisposable + { + public void Dispose() + { + foreach (var combo in combos) + combo.StartListening(); + } + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 3567fb6..ec8b465 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -3,7 +3,6 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop.Material; using ImGuiNET; -using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; @@ -11,7 +10,12 @@ using static Glamourer.Gui.Tabs.HeaderDrawer; namespace Glamourer.Gui.Tabs.DesignTab; -public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager editor, DesignColors colors, Configuration config) +public class MultiDesignPanel( + DesignFileSystemSelector selector, + DesignManager editor, + DesignColors colors, + Configuration config, + DesignComboWrapper combos) { private readonly Button[] _leftButtons = []; private readonly Button[] _rightButtons = [new IncognitoButton(config)]; @@ -201,16 +205,23 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e ? $"All {_numDesigns} selected designs are already displayed in the quick design bar." : $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs."; if (ImUtf8.ButtonEx("Display Selected Designs in QDB"u8, tt, buttonWidth, diff == 0)) + { + using var disableListener = combos.StopListening(); foreach (var design in selector.SelectedPaths.OfType()) editor.SetQuickDesign(design.Value, true); + } ImGui.SameLine(); tt = _numQuickDesignEnabled == 0 ? $"All {_numDesigns} selected designs are already hidden in the quick design bar." : $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs."; if (ImUtf8.ButtonEx("Hide Selected Designs in QDB"u8, tt, buttonWidth, _numQuickDesignEnabled == 0)) + { + using var disableListener = combos.StopListening(); foreach (var design in selector.SelectedPaths.OfType()) editor.SetQuickDesign(design.Value, false); + } + ImGui.Separator(); } @@ -327,8 +338,11 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e : $"Set the color of {_addDesigns.Count} designs to \"{_colorCombo.CurrentSelection}\"\n\n\t{string.Join("\n\t", _addDesigns.Select(m => m.Name.Text))}"; ImGui.SameLine(); if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) + { + using var disableListener = combos.StopListening(); foreach (var design in _addDesigns) editor.ChangeColor(design, _colorCombo.CurrentSelection!); + } label = _removeDesigns.Count > 0 ? $"Unset {_removeDesigns.Count} Designs" @@ -338,8 +352,11 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e : $"Set {_removeDesigns.Count} designs to use automatic color again:\n\n\t{string.Join("\n\t", _removeDesigns.Select(m => m.Item1.Name.Text))}"; ImGui.SameLine(); if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0)) + { + using var disableListener = combos.StopListening(); foreach (var (design, _) in _removeDesigns) editor.ChangeColor(design, string.Empty); + } ImGui.Separator(); } @@ -455,7 +472,8 @@ public class MultiDesignPanel(DesignFileSystemSelector selector, DesignManager e foreach (var design in selector.SelectedPaths.OfType().Select(l => l.Value)) { - editor.ChangeApplyMulti(design, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, equip); + editor.ChangeApplyMulti(design, equip, customize, equip, customize.HasValue && !customize.Value ? false : null, null, equip, equip, + equip); if (equip.HasValue) { editor.ChangeApplyMeta(design, MetaIndex.HatState, equip.Value); diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 0754313..6c30d68 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -169,5 +169,6 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); } diff --git a/OtterGui b/OtterGui index 421874a..cee50c3 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 421874a12540b7f8c1279dcc6a92e895a94d2fbc +Subproject commit cee50c3fe97a03ca7445c81de651b609620da526 From 5b59e74417e0fc3d90ea4b7a66be4972bc2cadd3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 27 May 2025 12:06:29 +0200 Subject: [PATCH 683/786] Use CS ReadStainingTemplate again. --- Glamourer/Interop/Material/PrepareColorSet.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index dfa6811..cf9be19 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -1,5 +1,4 @@ using Dalamud.Hooking; -using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; @@ -51,10 +50,6 @@ public sealed unsafe class PrepareColorSet private delegate Texture* Delegate(MaterialResourceHandle* material, StainId stainId1, StainId stainId2); - // TODO use CS when stabilized in Dalamud. - [Signature("E8 ?? ?? ?? ?? 48 8B FB EB 07")] - private static delegate* unmanaged _readStainingTemplate = null; - private Texture* Detour(MaterialResourceHandle* material, StainId stainId1, StainId stainId2) { Glamourer.Log.Excessive($"[{Name}] Triggered with 0x{(nint)material:X} {stainId1.Id} {stainId2.Id}."); @@ -84,10 +79,10 @@ public sealed unsafe class PrepareColorSet if (GetDyeTable(material, out var dyeTable)) { if (stainIds.Stain1.Id != 0) - _readStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)(&newTable), 0); + MaterialResourceHandle.MemberFunctionPointers.ReadStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 0); if (stainIds.Stain2.Id != 0) - _readStainingTemplate(material, dyeTable, stainIds.Stain2.Id, (Half*)(&newTable), 1); + MaterialResourceHandle.MemberFunctionPointers.ReadStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 1); } table = newTable; From 07df3186c28e4e549075cb0ba3c73826a4e5e97b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 27 May 2025 14:38:04 +0200 Subject: [PATCH 684/786] Better. --- Glamourer/Interop/Material/PrepareColorSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index cf9be19..342bb74 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -79,10 +79,10 @@ public sealed unsafe class PrepareColorSet if (GetDyeTable(material, out var dyeTable)) { if (stainIds.Stain1.Id != 0) - MaterialResourceHandle.MemberFunctionPointers.ReadStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 0); + material->ReadStainingTemplate(dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 0); if (stainIds.Stain2.Id != 0) - MaterialResourceHandle.MemberFunctionPointers.ReadStainingTemplate(material, dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 1); + material->ReadStainingTemplate(dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 1); } table = newTable; From a0d2c39f45ba17e12c931c64c3d717018177e089 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 May 2025 13:56:06 +0200 Subject: [PATCH 685/786] 1.4.0.0 --- Glamourer.Api | 2 +- Glamourer/Gui/GlamourerChangelog.cs | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Glamourer.Api b/Glamourer.Api index 9c86a9d..64897c0 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 9c86a9d6847f68e75679fe57d34f53f680d949c6 +Subproject commit 64897c069d3b6c3fe027b3ae0e832828728b9108 diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index e12b32e..a62743c 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -42,6 +42,7 @@ public class GlamourerChangelog Add1_3_6_0(Changelog); Add1_3_7_0(Changelog); Add1_3_8_0(Changelog); + Add1_4_0_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -62,6 +63,31 @@ public class GlamourerChangelog } } + private static void Add1_4_0_0(Changelog log) + => log.NextVersion("Version 1.4.0.0") + .RegisterHighlight("The design selector width is now draggable within certain restrictions that depend on the total window width.") + .RegisterEntry("The current behavior may not be final, let me know if you have any comments.", 1) + .RegisterEntry("Regular customization colors can now be dragged & dropped onto other customizations.") + .RegisterEntry( + "If no identical color is available in the target slot, the most similar color available (for certain values of similar) will be chosen instead.", + 1) + .RegisterEntry("Resetting advanced dyes and customizations has been split into two buttons for the quick design bar.") + .RegisterEntry("Weapons now also support custom ID input in the combo search box.") + .RegisterEntry("Added new IPC methods GetExtendedDesignData, AddDesign, DeleteDesign, GetDesignBase64, GetDesignJObject.") + .RegisterEntry("Added the option to prevent immediate repeats for random design selection (Thanks Diorik!).") + .RegisterEntry("Optimized some multi-design changes when selecting many designs and changing them at once.") + .RegisterEntry("Fixed item combos not starting from the currently selected item when scrolling them via mouse wheel.") + .RegisterEntry("Fixed some issue with Glamourer not searching mods by name for mod associations in some cases.") + .RegisterEntry("Fixed the IPC methods SetMetaState and SetMetaStateName not working (Thanks Caraxi!).") + .RegisterEntry("Added new IPC method GetDesignListExtended. (1.3.8.6)") + .RegisterEntry( + "Improved the naming of NPCs for identifiers by using Haselnussbombers new naming functionality (Thanks Hasel!). (1.3.8.6)") + .RegisterEntry( + "Added a modifier key separate from the delete modifier key that is used for less important key-checks, specifically toggling incognito mode. (1.3.8.5)") + .RegisterEntry("Used better Penumbra IPC for some things. (1.3.8.5)") + .RegisterEntry("Fixed an issue with advanced dyes for weapons. (1.3.8.5)") + .RegisterEntry("Fixed an issue with NPC automation due to missing job detection. (1.3.8.1)"); + private static void Add1_3_8_0(Changelog log) => log.NextVersion("Version 1.3.8.0") .RegisterImportant("Updated Glamourer for update 7.20 and Dalamud API 12.") From b8e1e7c3842ec602997b4ef2f724f030093d7858 Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 28 May 2025 11:58:09 +0000 Subject: [PATCH 686/786] [CI] Updating repo.json for 1.4.0.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 3f1f29b..2ac55a2 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.3.8.6", - "TestingAssemblyVersion": "1.3.8.6", + "AssemblyVersion": "1.4.0.0", + "TestingAssemblyVersion": "1.4.0.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.3.8.6/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 56bbf6593af82af67008177bcac08ab96c8721b0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 May 2025 02:55:11 +0200 Subject: [PATCH 687/786] Fix button counting. --- Glamourer/Gui/DesignQuickBar.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index b64a5f2..d83fce9 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -538,6 +538,8 @@ public sealed class DesignQuickBar : Window, IDisposable if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization)) ++_numButtons; + if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedDyes)) + ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize)) ++_numButtons; if (_config.QdbButtons.HasFlag(QdbButtons.RevertEquip)) From 66bed4217f01fb85bbee7024a50afaf0412f4d85 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 May 2025 02:55:20 +0200 Subject: [PATCH 688/786] Fix staining template reading. --- Glamourer/Interop/Material/PrepareColorSet.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 342bb74..f52bb68 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -82,7 +82,7 @@ public sealed unsafe class PrepareColorSet material->ReadStainingTemplate(dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 0); if (stainIds.Stain2.Id != 0) - material->ReadStainingTemplate(dyeTable, stainIds.Stain1.Id, (Half*)&newTable, 1); + material->ReadStainingTemplate(dyeTable, stainIds.Stain2.Id, (Half*)&newTable, 1); } table = newTable; From d7b189b7148ec92052b16119afaadea93cc86acb Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 29 May 2025 00:57:24 +0000 Subject: [PATCH 689/786] [CI] Updating repo.json for 1.4.0.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 2ac55a2..47bc610 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.4.0.0", - "TestingAssemblyVersion": "1.4.0.0", + "AssemblyVersion": "1.4.0.1", + "TestingAssemblyVersion": "1.4.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 282935c6d677417ff371b075e9541bfb4b17b001 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 3 Jun 2025 18:40:41 +0200 Subject: [PATCH 690/786] Allow drag & drop of equipment pieces. --- Glamourer/Gui/Equipment/EquipItemSlotCache.cs | 83 +++++++++++++++++++ Glamourer/Gui/Equipment/EquipmentDrawer.cs | 56 ++++++++++++- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 1 + Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 1 + Glamourer/Services/ItemManager.cs | 30 +++++++ Penumbra.Api | 2 +- 6 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 Glamourer/Gui/Equipment/EquipItemSlotCache.cs diff --git a/Glamourer/Gui/Equipment/EquipItemSlotCache.cs b/Glamourer/Gui/Equipment/EquipItemSlotCache.cs new file mode 100644 index 0000000..20aaf11 --- /dev/null +++ b/Glamourer/Gui/Equipment/EquipItemSlotCache.cs @@ -0,0 +1,83 @@ +using Glamourer.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Gui.Equipment; + +[InlineArray(13)] +public struct EquipItemSlotCache +{ + private EquipItem _element; + + public EquipItem Dragged + { + get => this[^1]; + set => this[^1] = value; + } + + public void Clear() + => ((Span)this).Clear(); + + public EquipItem this[EquipSlot slot] + { + get => this[(int)slot.ToIndex()]; + set => this[(int)slot.ToIndex()] = value; + } + + public void Update(ItemManager items, in EquipItem item, EquipSlot startSlot) + { + if (item.Id == Dragged.Id && item.Type == Dragged.Type) + return; + + switch (startSlot) + { + case EquipSlot.MainHand: + { + Clear(); + this[EquipSlot.MainHand] = item; + if (item.Type is FullEquipType.Sword) + this[EquipSlot.OffHand] = items.FindClosestShield(item.ItemId, out var shield) ? shield : default; + else + this[EquipSlot.OffHand] = items.ItemData.Secondary.GetValueOrDefault(item.ItemId); + break; + } + case EquipSlot.OffHand: + { + Clear(); + if (item.Type is FullEquipType.Shield) + this[EquipSlot.MainHand] = items.FindClosestSword(item.ItemId, out var sword) ? sword : default; + else + this[EquipSlot.MainHand] = items.ItemData.Primary.GetValueOrDefault(item.ItemId); + this[EquipSlot.OffHand] = item; + break; + } + default: + { + this[EquipSlot.MainHand] = default; + this[EquipSlot.OffHand] = default; + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + if (startSlot == slot) + { + this[slot] = item; + continue; + } + + var slotItem = items.Identify(slot, item.PrimaryId, item.Variant); + if (!slotItem.Valid || slotItem.ItemId.Id is not 0 != item.ItemId.Id is not 0) + { + slotItem = items.Identify(EquipSlot.OffHand, item.PrimaryId, item.SecondaryId, 1, item.Type); + if (slotItem.ItemId.Id is not 0 != item.ItemId.Id is not 0) + slotItem = default; + } + + this[slot] = slotItem; + } + + break; + } + } + + Dragged = item; + } +} diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index f2ecc08..43fda84 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -37,7 +37,9 @@ public class EquipmentDrawer private float _requiredComboWidthUnscaled; private float _requiredComboWidth; - private Stain? _draggedStain; + private Stain? _draggedStain; + private EquipItemSlotCache _draggedItem; + private EquipSlot _dragTarget; public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures, Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy) @@ -80,6 +82,7 @@ public class EquipmentDrawer _requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale; _advancedMaterialColor = ColorId.AdvancedDyeActive.Value(); + _dragTarget = EquipSlot.Unknown; } private bool VerifyRestrictedGear(EquipDrawData data) @@ -429,8 +432,8 @@ public class EquipmentDrawer using var dragSource = ImUtf8.DragDropSource(); if (dragSource.Success) { - if (DragDropSource.SetPayload("stainDragDrop"u8)) - _draggedStain = stain; + DragDropSource.SetPayload("stainDragDrop"u8); + _draggedStain = stain; ImUtf8.Text($"Dragging {stain.Name}..."); } } @@ -455,6 +458,7 @@ public class EquipmentDrawer using var disabled = ImRaii.Disabled(data.Locked); var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength, _requiredComboWidth); + DrawGearDragDrop(data); if (change) data.SetItem(combo.CurrentSelection); else if (combo.CustomVariant.Id > 0) @@ -495,6 +499,50 @@ public class EquipmentDrawer data.SetItem(item); } + private void DrawGearDragDrop(in EquipDrawData data) + { + if (data.CurrentItem.Valid) + { + using var dragSource = ImUtf8.DragDropSource(); + if (dragSource.Success) + { + DragDropSource.SetPayload("equipDragDrop"u8); + _draggedItem.Update(_items, data.CurrentItem, data.Slot); + } + } + + using var dragTarget = ImUtf8.DragDropTarget(); + if (!dragTarget) + return; + + var item = _draggedItem[data.Slot]; + if (!item.Valid) + return; + + _dragTarget = data.Slot; + if (!dragTarget.IsDropping("equipDragDrop"u8)) + return; + + data.SetItem(item); + _draggedItem.Clear(); + } + + public unsafe void DrawDragDropTooltip() + { + var payload = ImGui.GetDragDropPayload().NativePtr; + if (payload is null) + return; + + if (!MemoryMarshal.CreateReadOnlySpanFromNullTerminated(payload->DataType).SequenceEqual("equipDragDrop"u8)) + return; + + using var tt = ImUtf8.Tooltip(); + if (_dragTarget is EquipSlot.Unknown) + ImUtf8.Text($"Dragging {_draggedItem.Dragged.Name}..."); + else + ImUtf8.Text($"Converting to {_draggedItem[_dragTarget].Name}..."); + } + private static bool ResetOrClear(bool locked, bool clicked, bool allowRevert, bool allowClear, in T currentItem, in T revertItem, in T clearItem, out T? item) where T : IEquatable { @@ -546,6 +594,7 @@ public class EquipmentDrawer else if (combo.CustomVariant.Id > 0 && (drawAll || ItemData.ConvertWeaponId(combo.CustomSetId) == mainhand.CurrentItem.Type)) changedItem = _items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant); _itemCopy.HandleCopyPaste(mainhand); + DrawGearDragDrop(mainhand); if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem, default, out var c)) @@ -589,6 +638,7 @@ public class EquipmentDrawer else if (combo.CustomVariant.Id > 0 && ItemData.ConvertWeaponId(combo.CustomSetId) == offhand.CurrentItem.Type) offhand.SetItem(_items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant)); _itemCopy.HandleCopyPaste(offhand); + DrawGearDragDrop(offhand); var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem); if (ResetOrClear(locked, clear, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item)) diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 5419f23..98d9157 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -238,6 +238,7 @@ public class ActorPanel ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); DrawEquipmentMetaToggles(); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + _equipmentDrawer.DrawDragDropTooltip(); } private void DrawParameterHeader() diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 65014a4..d2b98b2 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -130,6 +130,7 @@ public class DesignPanel ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); DrawEquipmentMetaToggles(); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); + _equipmentDrawer.DrawDragDropTooltip(); } private void DrawEquipmentMetaToggles() diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 5d6f074..2cc6461 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -174,6 +174,36 @@ public class ItemManager return NothingItem(offhandType); } + public bool FindClosestShield(ItemId id, out EquipItem item) + { + var list = ItemData.ByType[FullEquipType.Shield]; + try + { + item = list.Where(i => i.ItemId.Id > id.Id && i.ItemId.Id - id.Id < 50).MinBy(i => i.ItemId.Id); + return true; + } + catch + { + item = default; + return false; + } + } + + public bool FindClosestSword(ItemId id, out EquipItem item) + { + var list = ItemData.ByType[FullEquipType.Sword]; + try + { + item = list.Where(i => i.ItemId.Id < id.Id && id.Id - i.ItemId.Id < 50).MaxBy(i => i.ItemId.Id); + return true; + } + catch + { + item = default; + return false; + } + } + public EquipItem Identify(EquipSlot slot, PrimaryId id, SecondaryId type, Variant variant, FullEquipType mainhandType = FullEquipType.Unknown) { diff --git a/Penumbra.Api b/Penumbra.Api index 574ef3a..ff7b3b4 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 574ef3a8afd42b949e713e247a0b812886f088bb +Subproject commit ff7b3b4014a97455f823380c78b8a7c5107f8e2f From 75c76a92b9aa08d351f0979a2daa816c37fbe5d5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 13 Jun 2025 17:17:58 +0200 Subject: [PATCH 691/786] Optimize design combos and file system. --- Glamourer/Api/DesignsApi.cs | 6 +- Glamourer/Designs/DesignFileSystem.cs | 13 +- Glamourer/Designs/Special/RandomPredicate.cs | 2 +- Glamourer/Gui/DesignCombo.cs | 153 +++++++----------- .../AutomationTab/RandomRestrictionDrawer.cs | 2 +- .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 28 +++- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 7 +- Glamourer/Services/ServiceManager.cs | 3 +- OtterGui | 2 +- 9 files changed, 92 insertions(+), 124 deletions(-) diff --git a/Glamourer/Api/DesignsApi.cs b/Glamourer/Api/DesignsApi.cs index e21e6cb..9b48ade 100644 --- a/Glamourer/Api/DesignsApi.cs +++ b/Glamourer/Api/DesignsApi.cs @@ -20,12 +20,12 @@ public class DesignsApi( => designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text); public Dictionary GetDesignListExtended() - => designs.Designs.ToDictionary(d => d.Identifier, - d => (d.Name.Text, fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign)); + => fileSystem.ToDictionary(kvp => kvp.Key.Identifier, + kvp => (kvp.Key.Name.Text, kvp.Value.FullName(), color.GetColor(kvp.Key), kvp.Key.QuickDesign)); public (string DisplayName, string FullPath, uint DisplayColor, bool ShowInQdb) GetExtendedDesignData(Guid designId) => designs.Designs.ByIdentifier(designId) is { } d - ? (d.Name.Text, fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign) + ? (d.Name.Text, fileSystem.TryGetValue(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign) : (string.Empty, string.Empty, 0, false); public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags) diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index e985e32..4a1cb3d 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -114,14 +114,14 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable return; case DesignChanged.Type.Deleted: - if (FindLeaf(design, out var leaf1)) + if (TryGetValue(design, out var leaf1)) Delete(leaf1); return; case DesignChanged.Type.ReloadedAll: Reload(); return; case DesignChanged.Type.Renamed when (data as RenameTransaction?)?.Old is { } oldName: - if (!FindLeaf(design, out var leaf2)) + if (!TryGetValue(design, out var leaf2)) return; var old = oldName.FixName(); @@ -150,15 +150,6 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable ? (string.Empty, false) : (DesignToIdentifier(design), true); - // Search the entire filesystem for the leaf corresponding to a design. - public bool FindLeaf(Design design, [NotNullWhen(true)] out Leaf? leaf) - { - leaf = Root.GetAllDescendants(ISortMode.Lexicographical) - .OfType() - .FirstOrDefault(l => l.Value == design); - return leaf != null; - } - internal static void MigrateOldPaths(SaveService saveService, Dictionary oldPaths) { if (oldPaths.Count == 0) diff --git a/Glamourer/Designs/Special/RandomPredicate.cs b/Glamourer/Designs/Special/RandomPredicate.cs index efb3233..ae05f8f 100644 --- a/Glamourer/Designs/Special/RandomPredicate.cs +++ b/Glamourer/Designs/Special/RandomPredicate.cs @@ -22,7 +22,7 @@ public interface IDesignPredicate : designs; private static (Design Design, string LowerName, string Identifier, string LowerPath) Transform(Design d, DesignFileSystem fs) - => (d, d.Name.Lower, d.Identifier.ToString(), fs.FindLeaf(d, out var l) ? l.FullName().ToLowerInvariant() : string.Empty); + => (d, d.Name.Lower, d.Identifier.ToString(), fs.TryGetValue(d, out var l) ? l.FullName().ToLowerInvariant() : string.Empty); } public static class RandomPredicate diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index c1e474d..e5f3785 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -10,7 +10,6 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Log; -using OtterGui.Services; using OtterGui.Widgets; namespace Glamourer.Gui; @@ -22,8 +21,8 @@ public abstract class DesignComboBase : FilterComboCache>> generator, Logger log, DesignChanged designChanged, TabSelected tabSelected, EphemeralConfig config, DesignColors designColors) @@ -34,7 +33,6 @@ public abstract class DesignComboBase : FilterComboCache _currentDesign == p.Item1); - UpdateSelection(CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : null); - return CurrentSelectionIdx; - } - protected bool Draw(IDesignStandIn? currentDesign, string? label, float width) { _currentDesign = currentDesign; - InnerWidth = 400 * ImGuiHelpers.GlobalScale; + UpdateCurrentSelection(); + InnerWidth = 400 * ImGuiHelpers.GlobalScale; var name = label ?? "Select Design Here..."; bool ret; using (_ = currentDesign != null ? ImRaii.PushColor(ImGuiCol.Text, DesignColors.GetColor(currentDesign as Design)) : null) @@ -150,37 +123,52 @@ public abstract class DesignComboBase : FilterComboCache ReferenceEquals(s.Item1, CurrentSelection?.Item1)); + if (CurrentSelectionIdx >= 0) + { + UpdateSelection(Items[CurrentSelectionIdx]); + } + else if (Items.Count > 0) + { + CurrentSelectionIdx = 0; + UpdateSelection(Items[0]); + } + else + { + UpdateSelection(null); + } + + if (!priorState) + Cleanup(); + _isCurrentSelectionDirty = false; + } + + protected override int UpdateCurrentSelected(int currentSelected) + { + CurrentSelectionIdx = Items.IndexOf(p => _currentDesign == p.Item1); + UpdateSelection(CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : null); + return CurrentSelectionIdx; + } + private void OnDesignChanged(DesignChanged.Type type, Design? _1, ITransaction? _2 = null) { - switch (type) + _isCurrentSelectionDirty = type switch { - case DesignChanged.Type.Created: - case DesignChanged.Type.Renamed: - case DesignChanged.Type.ChangedColor: - case DesignChanged.Type.Deleted: - case DesignChanged.Type.QuickDesignBar: - var priorState = IsInitialized; - if (priorState) - Cleanup(); - CurrentSelectionIdx = Items.IndexOf(s => ReferenceEquals(s.Item1, CurrentSelection?.Item1)); - if (CurrentSelectionIdx >= 0) - { - UpdateSelection(Items[CurrentSelectionIdx]); - } - else if (Items.Count > 0) - { - CurrentSelectionIdx = 0; - UpdateSelection(Items[0]); - } - else - { - UpdateSelection(null); - } - - if (!priorState) - Cleanup(); - break; - } + DesignChanged.Type.Created => true, + DesignChanged.Type.Renamed => true, + DesignChanged.Type.ChangedColor => true, + DesignChanged.Type.Deleted => true, + DesignChanged.Type.QuickDesignBar => true, + _ => _isCurrentSelectionDirty, + }; } private void QuickSelectedDesignTooltip(IDesignStandIn? design) @@ -248,8 +236,7 @@ public abstract class DesignCombo : DesignComboBase public sealed class QuickDesignCombo : DesignCombo { - public QuickDesignCombo(DesignManager designs, - DesignFileSystem fileSystem, + public QuickDesignCombo(DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, TabSelected tabSelected, @@ -257,9 +244,9 @@ public sealed class QuickDesignCombo : DesignCombo DesignColors designColors) : base(log, designChanged, tabSelected, config, designColors, () => [ - .. designs.Designs - .Where(d => d.QuickDesign) - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .. fileSystem + .Where(kvp => kvp.Key.QuickDesign) + .Select(kvp => new Tuple(kvp.Key, kvp.Value.FullName())) .OrderBy(d => d.Item2), ]) { @@ -300,7 +287,6 @@ public sealed class QuickDesignCombo : DesignCombo } public sealed class LinkDesignCombo( - DesignManager designs, DesignFileSystem fileSystem, Logger log, DesignChanged designChanged, @@ -309,8 +295,8 @@ public sealed class LinkDesignCombo( DesignColors designColors) : DesignCombo(log, designChanged, tabSelected, config, designColors, () => [ - .. designs.Designs - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .. fileSystem + .Select(kvp => new Tuple(kvp.Key, kvp.Value.FullName())) .OrderBy(d => d.Item2), ]); @@ -324,8 +310,8 @@ public sealed class RandomDesignCombo( DesignColors designColors) : DesignCombo(log, designChanged, tabSelected, config, designColors, () => [ - .. designs.Designs - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + .. fileSystem + .Select(kvp => new Tuple(kvp.Key, kvp.Value.FullName())) .OrderBy(d => d.Item2), ]) { @@ -351,7 +337,6 @@ public sealed class RandomDesignCombo( } public sealed class SpecialDesignCombo( - DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, @@ -361,8 +346,8 @@ public sealed class SpecialDesignCombo( EphemeralConfig config, RandomDesignGenerator rng, QuickSelectedDesign quickSelectedDesign) - : DesignComboBase(() => designs.Designs - .Select(d => new Tuple(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty)) + : DesignComboBase(() => fileSystem + .Select(kvp => new Tuple(kvp.Key, kvp.Value.FullName())) .OrderBy(d => d.Item2) .Prepend(new Tuple(new RandomDesign(rng), string.Empty)) .Prepend(new Tuple(quickSelectedDesign, string.Empty)) @@ -380,29 +365,3 @@ public sealed class SpecialDesignCombo( autoDesignManager.AddDesign(set, CurrentSelection!.Item1); } } - -public class DesignComboWrapper(ServiceManager services) -{ - public readonly IReadOnlyList Combos = services.GetServicesImplementing().ToArray(); - - internal DesignComboListener StopListening() - { - var list = new List(Combos.Count); - foreach (var combo in Combos.Where(c => c.IsListening)) - { - combo.StopListening(); - list.Add(combo); - } - - return new DesignComboListener(list); - } - - internal readonly struct DesignComboListener(List combos) : IDisposable - { - public void Dispose() - { - foreach (var combo in combos) - combo.StartListening(); - } - } -} diff --git a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs index e7efc09..ae3be7e 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs @@ -278,7 +278,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable private void LookupTooltip(IEnumerable designs) { using var _ = ImRaii.Tooltip(); - var tt = string.Join('\n', designs.Select(d => _designFileSystem.FindLeaf(d, out var l) ? l.FullName() : d.Name.Text).OrderBy(t => t)); + var tt = string.Join('\n', designs.Select(d => _designFileSystem.TryGetValue(d, out var l) ? l.FullName() : d.Name.Text).OrderBy(t => t)); ImGui.TextUnformatted(tt.Length == 0 ? "Matches no currently existing designs." : "Matches the following designs:"); diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index ede3e9e..342bc41 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -3,7 +3,9 @@ using Glamourer.Designs; using ImGuiNET; using OtterGui; using OtterGui.Extensions; +using OtterGui.Filesystem; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; @@ -19,6 +21,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ public void Draw() { + DrawButtons(); foreach (var (design, idx) in _designManager.Designs.WithIndex()) { using var t = ImRaii.TreeNode($"{design.Name}##{idx}"); @@ -26,7 +29,8 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ continue; DrawDesign(design, _designFileSystem); - var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize, design.Application.Meta, + var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize, + design.Application.Meta, design.WriteProtected()); using var font = ImRaii.PushFont(UiBuilder.MonoFont); ImGuiUtil.TextWrapped(base64); @@ -35,6 +39,26 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ } } + private void DrawButtons() + { + if (ImUtf8.Button("Generate 500 Test Designs"u8)) + for (var i = 0; i < 500; ++i) + { + var design = _designManager.CreateEmpty($"Test Designs/Test Design {i}", true); + _designManager.AddTag(design, "_DebugTest"); + } + + ImUtf8.SameLineInner(); + if (ImUtf8.Button("Remove All Test Designs"u8)) + { + var designs = _designManager.Designs.Where(d => d.Tags.Contains("_DebugTest")).ToArray(); + foreach (var design in designs) + _designManager.Delete(design); + if (_designFileSystem.Find("Test Designs", out var path) && path is DesignFileSystem.Folder { TotalChildren: 0 }) + _designFileSystem.Delete(path); + } + } + public static void DrawDesign(DesignBase design, DesignFileSystem? fileSystem) { using var table = ImRaii.Table("##equip", 8, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); @@ -53,7 +77,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ ImGui.TableNextRow(); ImGuiUtil.DrawTableColumn("Design File System Path"); if (fileSystem != null) - ImGuiUtil.DrawTableColumn(fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : "No Path Known"); + ImGuiUtil.DrawTableColumn(fileSystem.TryGetValue(d, out var leaf) ? leaf.FullName() : "No Path Known"); ImGui.TableNextRow(); ImGuiUtil.DrawTableColumn("Creation"); diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index ec8b465..1ecda0b 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -14,8 +14,7 @@ public class MultiDesignPanel( DesignFileSystemSelector selector, DesignManager editor, DesignColors colors, - Configuration config, - DesignComboWrapper combos) + Configuration config) { private readonly Button[] _leftButtons = []; private readonly Button[] _rightButtons = [new IncognitoButton(config)]; @@ -206,7 +205,6 @@ public class MultiDesignPanel( : $"Display all {_numDesigns} selected designs in the quick design bar. Changes {diff} designs."; if (ImUtf8.ButtonEx("Display Selected Designs in QDB"u8, tt, buttonWidth, diff == 0)) { - using var disableListener = combos.StopListening(); foreach (var design in selector.SelectedPaths.OfType()) editor.SetQuickDesign(design.Value, true); } @@ -217,7 +215,6 @@ public class MultiDesignPanel( : $"Hide all {_numDesigns} selected designs in the quick design bar. Changes {_numQuickDesignEnabled} designs."; if (ImUtf8.ButtonEx("Hide Selected Designs in QDB"u8, tt, buttonWidth, _numQuickDesignEnabled == 0)) { - using var disableListener = combos.StopListening(); foreach (var design in selector.SelectedPaths.OfType()) editor.SetQuickDesign(design.Value, false); } @@ -339,7 +336,6 @@ public class MultiDesignPanel( ImGui.SameLine(); if (ImUtf8.ButtonEx(label, tooltip, width, _addDesigns.Count == 0)) { - using var disableListener = combos.StopListening(); foreach (var design in _addDesigns) editor.ChangeColor(design, _colorCombo.CurrentSelection!); } @@ -353,7 +349,6 @@ public class MultiDesignPanel( ImGui.SameLine(); if (ImUtf8.ButtonEx(label, tooltip, width, _removeDesigns.Count == 0)) { - using var disableListener = combos.StopListening(); foreach (var (design, _) in _removeDesigns) editor.ChangeColor(design, string.Empty); } diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 6c30d68..0754313 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -169,6 +169,5 @@ public static class StaticServiceManager .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() - .AddSingleton(); + .AddSingleton(); } diff --git a/OtterGui b/OtterGui index cee50c3..78528f9 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit cee50c3fe97a03ca7445c81de651b609620da526 +Subproject commit 78528f93ac253db0061d9a8244cfa0cee5c2f873 From 2e9a7004c696940085bf65bac506348927d0391b Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 13 Jun 2025 15:21:04 +0000 Subject: [PATCH 692/786] [CI] Updating repo.json for 1.4.0.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 47bc610..8b0be29 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.4.0.1", - "TestingAssemblyVersion": "1.4.0.1", + "AssemblyVersion": "1.4.0.2", + "TestingAssemblyVersion": "1.4.0.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c0a278ca2cc0d53cca1349b289bc94112a8b782f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 15 Jun 2025 23:33:54 +0200 Subject: [PATCH 693/786] Make designs update on mousewheel. --- Glamourer/Gui/DesignCombo.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index e5f3785..e35ad5e 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -123,6 +123,14 @@ public abstract class DesignComboBase : FilterComboCache Date: Mon, 14 Jul 2025 17:09:42 +0200 Subject: [PATCH 694/786] Fix issue with invalid bonus items. --- Glamourer/Services/ItemManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Glamourer/Services/ItemManager.cs b/Glamourer/Services/ItemManager.cs index 2cc6461..a885b54 100644 --- a/Glamourer/Services/ItemManager.cs +++ b/Glamourer/Services/ItemManager.cs @@ -145,8 +145,10 @@ public class ItemManager // Only from early designs as migration. if (!id.IsBonusItem || id.Id == 0) { - IsBonusItemValid(slot, (BonusItemId)id.Id, out var item); - return item; + if (IsBonusItemValid(slot, (BonusItemId)id.Id, out var item)) + return item; + + return EquipItem.BonusItemNothing(slot); } if (!id.IsCustom) From 8a1f03c27254f7f9f01c7ae237b39bce79931d6e Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 14 Jul 2025 15:12:49 +0000 Subject: [PATCH 695/786] [CI] Updating repo.json for 1.4.0.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 8b0be29..0c6e22f 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.4.0.2", - "TestingAssemblyVersion": "1.4.0.2", + "AssemblyVersion": "1.4.0.3", + "TestingAssemblyVersion": "1.4.0.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 40b4a8fd7a87bb7c80654f0ed75085da027d8fa1 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Mon, 28 Jul 2025 08:37:57 -0700 Subject: [PATCH 696/786] Ensure Revert via Command invokes a StateFinalization type for Reset --- Glamourer/Services/CommandService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 3d40fa9..1b88a00 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -407,7 +407,7 @@ public class CommandService : IDisposable, IApiService foreach (var identifier in identifiers) { if (_stateManager.TryGetValue(identifier, out var state)) - _stateManager.ResetState(state, StateSource.Manual); + _stateManager.ResetState(state, StateSource.Manual, isFinal: true); } From 4ef4e65d46bb15581a25b4983988534e353ec660 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Mon, 28 Jul 2025 08:43:13 -0700 Subject: [PATCH 697/786] Ensure that reverts called by API invoke their StateFinalizationType upon completing a reset state. --- Glamourer/Api/StateApi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 43dc453..68c593b 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -272,7 +272,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable { case ApplyFlag.Equipment: _stateManager.ResetEquip(state, source, key); break; case ApplyFlag.Customization: _stateManager.ResetCustomize(state, source, key); break; - case ApplyFlag.Equipment | ApplyFlag.Customization: _stateManager.ResetState(state, source, key); break; + case ApplyFlag.Equipment | ApplyFlag.Customization: _stateManager.ResetState(state, source, key, true); break; } ApiHelpers.Lock(state, key, flags); From d6df9885dcc15940e1263d50533b1afd3d6c05a8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 2 Aug 2025 00:07:27 +0200 Subject: [PATCH 698/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index bb3b462..82b4467 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit bb3b462bbc5bc2a598c1ad8c372b0cb255551fe1 +Subproject commit 82b446721a9b9c99d2470c54ad49fe19ff4987e3 From 2c3bed6ba5964456e51b75f82bfe6f552517e833 Mon Sep 17 00:00:00 2001 From: Karou Date: Thu, 7 Aug 2025 21:50:23 -0400 Subject: [PATCH 699/786] Api 13 grunt work --- Glamourer.Api | 2 +- Glamourer/DesignPanelFlag.cs | 2 +- Glamourer/Designs/ApplicationCollection.cs | 2 +- Glamourer/Designs/ApplicationRules.cs | 2 +- Glamourer/Designs/DesignColors.cs | 2 +- Glamourer/Glamourer.csproj | 3 ++- Glamourer/Gui/Colors.cs | 2 +- Glamourer/Gui/Customization/CustomizationDrawer.Color.cs | 2 +- .../Gui/Customization/CustomizationDrawer.GenderRace.cs | 2 +- Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs | 8 ++++---- Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs | 2 +- Glamourer/Gui/Customization/CustomizationDrawer.cs | 2 +- Glamourer/Gui/Customization/CustomizeParameterDrawer.cs | 2 +- Glamourer/Gui/DesignCombo.cs | 2 +- Glamourer/Gui/DesignQuickBar.cs | 2 +- Glamourer/Gui/Equipment/BonusItemCombo.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 2 +- Glamourer/Gui/Equipment/GlamourerColorCombo.cs | 2 +- Glamourer/Gui/Equipment/ItemCombo.cs | 2 +- Glamourer/Gui/Equipment/ItemCopyService.cs | 2 +- Glamourer/Gui/Equipment/WeaponCombo.cs | 2 +- Glamourer/Gui/GenericPopupWindow.cs | 2 +- Glamourer/Gui/MainWindow.cs | 2 +- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 2 +- Glamourer/Gui/Materials/MaterialDrawer.cs | 2 +- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorTab.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs | 2 +- .../Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs | 2 +- .../Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DebugTab.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/FunPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 2 +- Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignTab.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/ModCombo.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs | 2 +- Glamourer/Gui/Tabs/HeaderDrawer.cs | 2 +- Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs | 2 +- Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs | 2 +- Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs | 2 +- Glamourer/Gui/Tabs/NpcTab/NpcTab.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs | 2 +- .../Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs | 2 +- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 2 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs | 2 +- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 2 +- Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs | 2 +- Glamourer/Gui/UiHelpers.cs | 2 +- Glamourer/Interop/CrestService.cs | 4 ++-- Glamourer/Interop/ImportService.cs | 2 +- Glamourer/Interop/Material/LiveColorTablePreviewer.cs | 5 +++-- Glamourer/Services/CommandService.cs | 2 +- Glamourer/Services/DesignResolver.cs | 2 +- Glamourer/State/FunModule.cs | 2 +- Glamourer/packages.lock.json | 8 ++++---- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 92 files changed, 102 insertions(+), 100 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 64897c0..6589ecd 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 64897c069d3b6c3fe027b3ae0e832828728b9108 +Subproject commit 6589ecdde5dac55730797fcc594be301e820bfcc diff --git a/Glamourer/DesignPanelFlag.cs b/Glamourer/DesignPanelFlag.cs index db84173..f9465d9 100644 --- a/Glamourer/DesignPanelFlag.cs +++ b/Glamourer/DesignPanelFlag.cs @@ -1,5 +1,5 @@ using Glamourer.Designs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Text; using OtterGui.Text.EndObjects; diff --git a/Glamourer/Designs/ApplicationCollection.cs b/Glamourer/Designs/ApplicationCollection.cs index 13813a3..8beeb78 100644 --- a/Glamourer/Designs/ApplicationCollection.cs +++ b/Glamourer/Designs/ApplicationCollection.cs @@ -1,6 +1,6 @@ using Glamourer.Api.Enums; using Glamourer.GameData; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Penumbra.GameData.Enums; namespace Glamourer.Designs; diff --git a/Glamourer/Designs/ApplicationRules.cs b/Glamourer/Designs/ApplicationRules.cs index 0df4feb..281a940 100644 --- a/Glamourer/Designs/ApplicationRules.cs +++ b/Glamourer/Designs/ApplicationRules.cs @@ -1,7 +1,7 @@ using Glamourer.Api.Enums; using Glamourer.GameData; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Penumbra.GameData.Enums; namespace Glamourer.Designs; diff --git a/Glamourer/Designs/DesignColors.cs b/Glamourer/Designs/DesignColors.cs index 172e10f..a8f3178 100644 --- a/Glamourer/Designs/DesignColors.cs +++ b/Glamourer/Designs/DesignColors.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility.Raii; using Glamourer.Gui; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 9dabbb2..40b1218 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,4 +1,4 @@ - + Glamourer Glamourer @@ -29,6 +29,7 @@ + diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index 98deace..b2713eb 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -1,4 +1,4 @@ -using ImGuiNET; +using Dalamud.Bindings.ImGui; namespace Glamourer.Gui; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs index 8db07ff..4f463d6 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Color.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.GameData; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs index 2f67012..26e9002 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.GenderRace.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs index eabb6f0..8599f8c 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs @@ -1,7 +1,7 @@ using Dalamud.Interface.Textures.TextureWraps; using Glamourer.GameData; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; @@ -35,7 +35,7 @@ public partial class CustomizationDrawer var hasIcon = icon.TryGetWrap(out var wrap, out _); using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw)) { - if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize)) + if (ImGui.ImageButton(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, _iconSize)) { ImGui.OpenPopup(IconSelectorPopup); } @@ -89,7 +89,7 @@ public partial class CustomizationDrawer : ImRaii.PushColor(ImGuiCol.Button, ColorId.FavoriteStarOn.Value(), isFavorite); var hasIcon = icon.TryGetWrap(out var wrap, out var _); - if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize)) + if (ImGui.ImageButton(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, _iconSize)) { UpdateValue(custom.Value); ImGui.CloseCurrentPopup(); @@ -215,7 +215,7 @@ public partial class CustomizationDrawer hasIcon = icon.TryGetWrap(out wrap, out _); } - if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, + if (ImGui.ImageButton(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, Vector4.Zero, enabled ? Vector4.One : _redTint)) { _customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max); diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs index 2c797b8..ec5523f 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs @@ -1,4 +1,4 @@ -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGuiInternal; diff --git a/Glamourer/Gui/Customization/CustomizationDrawer.cs b/Glamourer/Gui/Customization/CustomizationDrawer.cs index 4ec6146..349891c 100644 --- a/Glamourer/Gui/Customization/CustomizationDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizationDrawer.cs @@ -4,7 +4,7 @@ using Dalamud.Plugin.Services; using Glamourer.GameData; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 9bfb2f8..18a9d1a 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -3,7 +3,7 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Interop.PalettePlus; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index e35ad5e..6dfffef 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -5,7 +5,7 @@ using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Designs.Special; using Glamourer.Events; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index d83fce9..2dee0e4 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -8,7 +8,7 @@ using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Classes; using OtterGui.Text; using Penumbra.GameData.Actors; diff --git a/Glamourer/Gui/Equipment/BonusItemCombo.cs b/Glamourer/Gui/Equipment/BonusItemCombo.cs index 892d9f1..aa43da7 100644 --- a/Glamourer/Gui/Equipment/BonusItemCombo.cs +++ b/Glamourer/Gui/Equipment/BonusItemCombo.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin.Services; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Lumina.Excel.Sheets; using OtterGui; using OtterGui.Classes; diff --git a/Glamourer/Gui/Equipment/EquipmentDrawer.cs b/Glamourer/Gui/Equipment/EquipmentDrawer.cs index 43fda84..91187a8 100644 --- a/Glamourer/Gui/Equipment/EquipmentDrawer.cs +++ b/Glamourer/Gui/Equipment/EquipmentDrawer.cs @@ -5,7 +5,7 @@ using Glamourer.Events; using Glamourer.Gui.Materials; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs index 527dbb5..3149e67 100644 --- a/Glamourer/Gui/Equipment/GlamourerColorCombo.cs +++ b/Glamourer/Gui/Equipment/GlamourerColorCombo.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Widgets; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Structs; diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 14f2e8a..7c0c3bc 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin.Services; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Lumina.Excel.Sheets; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/Gui/Equipment/ItemCopyService.cs b/Glamourer/Gui/Equipment/ItemCopyService.cs index ea37963..6912f1f 100644 --- a/Glamourer/Gui/Equipment/ItemCopyService.cs +++ b/Glamourer/Gui/Equipment/ItemCopyService.cs @@ -1,5 +1,5 @@ using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Services; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Equipment/WeaponCombo.cs b/Glamourer/Gui/Equipment/WeaponCombo.cs index 6e38e7c..3029db7 100644 --- a/Glamourer/Gui/Equipment/WeaponCombo.cs +++ b/Glamourer/Gui/Equipment/WeaponCombo.cs @@ -1,6 +1,6 @@ using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Log; diff --git a/Glamourer/Gui/GenericPopupWindow.cs b/Glamourer/Gui/GenericPopupWindow.cs index 502af14..5061862 100644 --- a/Glamourer/Gui/GenericPopupWindow.cs +++ b/Glamourer/Gui/GenericPopupWindow.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; using Glamourer.Gui.Materials; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index f21a2b7..a39db2e 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -12,7 +12,7 @@ using Glamourer.Gui.Tabs.NpcTab; using Glamourer.Gui.Tabs.SettingsTab; using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Interop.Penumbra; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Custom; diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index ec25378..8cf81ff 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -8,7 +8,7 @@ using FFXIVClientStructs.Interop; using Glamourer.Designs; using Glamourer.Interop.Material; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 0c4433d..ce50ff2 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Glamourer.Designs; using Glamourer.Interop.Material; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Services; using OtterGui.Text; diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index 1723333..ed6018e 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -3,7 +3,7 @@ using Glamourer.Events; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using Penumbra.Api.Enums; using Penumbra.GameData.Data; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 98d9157..dcd3a12 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -11,7 +11,7 @@ using Glamourer.Gui.Equipment; using Glamourer.Gui.Materials; using Glamourer.Interop; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs index e46d651..7d132a1 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorSelector.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs index 4e5e15c..9751a71 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.ActorTab; diff --git a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs index 831ee7c..da3b636 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/AutomationTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.AutomationTab; diff --git a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs index 8a08437..ce843c4 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs @@ -1,6 +1,6 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Custom; using OtterGui.Extensions; diff --git a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs index b197a1a..ba2e424 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/IdentifierDrawer.cs @@ -1,5 +1,5 @@ using Dalamud.Game.ClientState.Objects.Enums; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Penumbra.GameData.Actors; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Gui; diff --git a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs index ae3be7e..8eba59b 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/RandomRestrictionDrawer.cs @@ -4,7 +4,7 @@ using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.Events; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 1600837..4b05e35 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -6,7 +6,7 @@ using Glamourer.Designs.Special; using Glamourer.Interop; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Log; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index 96730e8..dca0ce5 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.Utility; using Glamourer.Automation; using Glamourer.Events; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index b4bdc2a..e0ec4bd 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -2,7 +2,7 @@ using Glamourer.GameData; using Glamourer.Designs; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs index 5a02621..2202ceb 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AdvancedCustomizationDrawer.cs @@ -1,5 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs index df39f45..aee59b6 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs @@ -1,5 +1,5 @@ using Glamourer.Automation; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs index 565b6ba..6c0995c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationServicePanel.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Glamourer.GameData; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs index a53a677..4bf7d7b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/CustomizationUnlockPanel.cs @@ -1,5 +1,5 @@ using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs index 11f27fd..7c61392 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DatFilePanel.cs @@ -1,5 +1,5 @@ using Glamourer.Interop; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using Penumbra.GameData.Files; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs index cd89aec..b760221 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DebugTab.cs @@ -1,4 +1,4 @@ -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs index 2345abc..287d373 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignConverterPanel.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Glamourer.Designs; using Glamourer.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index 342bc41..38f0077 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Glamourer.Designs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Filesystem; diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs index 26be8ce..cf45077 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignTesterPanel.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Glamourer.Designs; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs index c517070..370c4e5 100644 --- a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs @@ -1,5 +1,5 @@ using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index aafc21a..f480f6d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -5,7 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Designs; using Glamourer.Services; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs index f57a57e..ca9ff7b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs @@ -1,5 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.Game; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs index 9e11b99..8cbf57a 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/DesignIpcTester.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.Api.Enums; using Glamourer.Api.IpcSubscribers; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs index 500fddd..dbcb30c 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs @@ -1,6 +1,6 @@ using Glamourer.Api.Enums; using Glamourer.Designs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using static Penumbra.GameData.Files.ShpkFile; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs index 1a78b24..f4e6925 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin; using Dalamud.Plugin.Services; using Glamourer.Api.IpcSubscribers; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs index 1499fcb..ea95a9d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/ItemsIpcTester.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin; using Glamourer.Api.Enums; using Glamourer.Api.IpcSubscribers; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index 638bffc..e97d337 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -5,7 +5,7 @@ using Glamourer.Api.Enums; using Glamourer.Api.Helpers; using Glamourer.Api.IpcSubscribers; using Glamourer.Designs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs index afbb86b..f82bfb3 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs @@ -1,7 +1,7 @@ using Dalamud.Interface.Utility; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index a0f2491..d1b42e8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -2,7 +2,7 @@ using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Structs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 966bc6f..0d93bb8 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -4,7 +4,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; using Glamourer.GameData; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs index a4d1224..97847ae 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -1,4 +1,4 @@ -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Text; using Penumbra.GameData.Actors; diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 012e5ba..9992bc3 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -1,6 +1,6 @@ using Dalamud.Interface.Utility; using Glamourer.Interop.Penumbra; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.Api.Enums; diff --git a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs index 99c79ed..b22008d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/UnlockableItemsPanel.cs @@ -1,7 +1,7 @@ using Dalamud.Interface.Utility; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs index 0efa358..e9fe775 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignColorCombo.cs @@ -1,5 +1,5 @@ using Glamourer.Designs; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index cbf7acd..042f2a2 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 11e803b..7341d31 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -5,7 +5,7 @@ using Glamourer.Designs; using Glamourer.Designs.History; using Glamourer.Events; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Filesystem; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index b81ffdf..7e5807e 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.Utility; using Glamourer.Automation; using Glamourer.Designs; using Glamourer.Designs.Links; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index d2b98b2..5bb3d77 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -12,7 +12,7 @@ using Glamourer.Gui.Equipment; using Glamourer.Gui.Materials; using Glamourer.Interop; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs index afb5900..1b92291 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignTab.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Classes; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index bab25f0..02f00db 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -5,7 +5,7 @@ using Dalamud.Utility; using Glamourer.Designs; using Glamourer.Interop.Penumbra; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs index 6ec9a1c..76579c2 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModCombo.cs @@ -1,6 +1,6 @@ using Dalamud.Interface.Utility; using Glamourer.Interop.Penumbra; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Classes; using OtterGui.Log; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 1ecda0b..1cdb171 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -2,7 +2,7 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop.Material; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; diff --git a/Glamourer/Gui/Tabs/HeaderDrawer.cs b/Glamourer/Gui/Tabs/HeaderDrawer.cs index d6baeb9..cb169ba 100644 --- a/Glamourer/Gui/Tabs/HeaderDrawer.cs +++ b/Glamourer/Gui/Tabs/HeaderDrawer.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs index 7941c13..1b70e27 100644 --- a/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs +++ b/Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs @@ -2,7 +2,7 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index c6166a3..29fe7ef 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -6,7 +6,7 @@ using Glamourer.Gui.Customization; using Glamourer.Gui.Equipment; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs index 847b65e..8497ab4 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs @@ -1,5 +1,5 @@ using Glamourer.GameData; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Extensions; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs index 4efa4c3..318e017 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcTab.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.Utility; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Widgets; namespace Glamourer.Gui.Tabs.NpcTab; diff --git a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs index b4d3740..1dc9331 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CodeDrawer.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Glamourer.Services; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Filesystem; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs index 98ba870..7080b4d 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionCombo.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Glamourer.Interop.Penumbra; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Log; using OtterGui.Raii; diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs index 87490db..76435ec 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Glamourer.Interop.Penumbra; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index cf57824..311cf85 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -8,7 +8,7 @@ using Glamourer.Designs; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.PalettePlus; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Widgets; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 903257b..947f0c6 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -5,7 +5,7 @@ using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index 6d9ce51..d75f2dc 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -6,7 +6,7 @@ using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Raii; using OtterGui.Table; diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs index c2e06e5..661b2a4 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.Windowing; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Raii; using OtterGui; using OtterGui.Widgets; diff --git a/Glamourer/Gui/UiHelpers.cs b/Glamourer/Gui/UiHelpers.cs index 1ec79d7..94ddb06 100644 --- a/Glamourer/Gui/UiHelpers.cs +++ b/Glamourer/Gui/UiHelpers.cs @@ -2,7 +2,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Glamourer.Services; using Glamourer.Unlocks; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Lumina.Misc; using OtterGui; using OtterGui.Raii; diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 95b3587..74b7800 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -122,7 +122,7 @@ public sealed unsafe class CrestService : EventWrapperRef3IsFreeCompanyCrestVisibleOnSlot(index) != 0; + return model.AsHuman->IsFreeCompanyCrestVisibleOnSlot(index); } case CrestType.Offhand: { @@ -130,7 +130,7 @@ public sealed unsafe class CrestService : EventWrapperRef3IsFreeCompanyCrestVisibleOnSlot(index) != 0; + return model.AsWeapon->IsFreeCompanyCrestVisibleOnSlot(index); } } diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index b9dfbe1..c6e90fd 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.ImGuiNotification; using Glamourer.Designs; using Glamourer.Interop.CharaFile; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index 94cb9ca..4190746 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -1,5 +1,5 @@ using Dalamud.Plugin.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Services; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Structs; @@ -114,7 +114,8 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable var frame = DateTimeOffset.UtcNow.UtcTicks; var hueByte = frame % (steps * frameLength) / frameLength; var hue = (float)hueByte / steps; - ImGui.ColorConvertHSVtoRGB(hue, 1, 1, out var r, out var g, out var b); + float r, g, b = 0; + ImGui.ColorConvertHSVtoRGB(hue, 1, 1, &r, &g, &b); return new Vector3(r, g, b); } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index 1b88a00..d47f26f 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -9,7 +9,7 @@ using Glamourer.Gui; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop.Penumbra; using Glamourer.State; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/Services/DesignResolver.cs b/Glamourer/Services/DesignResolver.cs index 68b54bb..8bb5cd2 100644 --- a/Glamourer/Services/DesignResolver.cs +++ b/Glamourer/Services/DesignResolver.cs @@ -4,7 +4,7 @@ using Glamourer.Designs; using Glamourer.Designs.Special; using Glamourer.Gui; using Glamourer.Gui.Tabs.DesignTab; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui.Services; using OtterGui.Classes; diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index ae7160c..6abb03a 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -4,7 +4,7 @@ using Glamourer.Designs; using Glamourer.GameData; using Glamourer.Gui; using Glamourer.Services; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json index e6f2fe5..f66e6e4 100644 --- a/Glamourer/packages.lock.json +++ b/Glamourer/packages.lock.json @@ -4,9 +4,9 @@ "net9.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[12.0.0, )", - "resolved": "12.0.0", - "contentHash": "J5TJLV3f16T/E2H2P17ClWjtfEBPpq3yxvqW46eN36JCm6wR+EaoaYkqG9Rm5sHqs3/nK/vKjWWyvEs/jhKoXw==" + "requested": "[13.0.0, )", + "resolved": "13.0.0", + "contentHash": "Mb3cUDSK/vDPQ8gQIeuCw03EMYrej1B4J44a1AvIJ9C759p9XeqdU9Hg4WgOmlnlPe0G7ILTD32PKSUpkQNa8w==" }, "DotNet.ReproducibleBuilds": { "type": "Direct", @@ -96,7 +96,7 @@ "type": "Project", "dependencies": { "OtterGui": "[1.0.0, )", - "Penumbra.Api": "[5.6.1, )", + "Penumbra.Api": "[5.10.0, )", "Penumbra.String": "[1.0.6, )" } }, diff --git a/OtterGui b/OtterGui index 78528f9..9523b7a 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 78528f93ac253db0061d9a8244cfa0cee5c2f873 +Subproject commit 9523b7ac725656b21fa98faef96962652e86e64f diff --git a/Penumbra.Api b/Penumbra.Api index ff7b3b4..c27a060 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit ff7b3b4014a97455f823380c78b8a7c5107f8e2f +Subproject commit c27a06004138f2ec80ccdb494bb6ddf6d39d2165 diff --git a/Penumbra.GameData b/Penumbra.GameData index 82b4467..65c5bf3 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 82b446721a9b9c99d2470c54ad49fe19ff4987e3 +Subproject commit 65c5bf3f46569a54b0057c9015ab839b4e2a4350 diff --git a/Penumbra.String b/Penumbra.String index 2896c05..878acce 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 2896c0561f60827f97408650d52a15c38f4d9d10 +Subproject commit 878acce46e286867d6ef1f8ecedb390f7bac34fd From 72e05e23bc77c877f29c039e6901117e3a973ed5 Mon Sep 17 00:00:00 2001 From: Karou Date: Thu, 7 Aug 2025 21:53:01 -0400 Subject: [PATCH 700/786] stupid detached head.... --- Glamourer.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer.Api b/Glamourer.Api index 6589ecd..1c51730 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 6589ecdde5dac55730797fcc594be301e820bfcc +Subproject commit 1c517301c9fd0818e2c02e72304d6de121b9d703 From c25f0f72db033fcc264ef3ee2c9f24c735bd288f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:36:14 +0200 Subject: [PATCH 701/786] Update for ottergui. --- Glamourer/Designs/DesignFileSystem.cs | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Glamourer/Designs/DesignFileSystem.cs b/Glamourer/Designs/DesignFileSystem.cs index 4a1cb3d..fd47793 100644 --- a/Glamourer/Designs/DesignFileSystem.cs +++ b/Glamourer/Designs/DesignFileSystem.cs @@ -41,11 +41,11 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable public struct CreationDate : ISortMode { - public string Name - => "Creation Date (Older First)"; + public ReadOnlySpan Name + => "Creation Date (Older First)"u8; - public string Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."; + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."u8; public IEnumerable GetChildren(Folder f) => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderBy(l => l.Value.CreationDate)); @@ -53,11 +53,11 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable public struct UpdateDate : ISortMode { - public string Name - => "Update Date (Older First)"; + public ReadOnlySpan Name + => "Update Date (Older First)"u8; - public string Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."; + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."u8; public IEnumerable GetChildren(Folder f) => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderBy(l => l.Value.LastEdit)); @@ -65,11 +65,11 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable public struct InverseCreationDate : ISortMode { - public string Name - => "Creation Date (Newer First)"; + public ReadOnlySpan Name + => "Creation Date (Newer First)"u8; - public string Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."; + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."u8; public IEnumerable GetChildren(Folder f) => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderByDescending(l => l.Value.CreationDate)); @@ -77,11 +77,11 @@ public sealed class DesignFileSystem : FileSystem, IDisposable, ISavable public struct InverseUpdateDate : ISortMode { - public string Name - => "Update Date (Newer First)"; + public ReadOnlySpan Name + => "Update Date (Newer First)"u8; - public string Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."; + public ReadOnlySpan Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."u8; public IEnumerable GetChildren(Folder f) => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderByDescending(l => l.Value.LastEdit)); From 0f98fac157bdf693d186984e915cca9c36a98356 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:36:47 +0200 Subject: [PATCH 702/786] Add auto-locking to design defaults. --- Glamourer/Configuration.cs | 1 + Glamourer/Designs/DesignManager.cs | 3 +++ Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index c9ff0e6..f128bdd 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -32,6 +32,7 @@ public class DefaultDesignSettings public bool ResetAdvancedDyes = false; public bool ShowQuickDesignBar = true; public bool ResetTemporarySettings = false; + public bool Locked = false; } public class Configuration : IPluginConfiguration, ISavable diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index fff9565..3ddfefd 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -110,6 +110,7 @@ public sealed class DesignManager : DesignEditor QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar, ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings, }; + design.SetWriteProtected(Config.DefaultDesignSettings.Locked); Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier}."); SaveService.ImmediateSave(design); @@ -134,6 +135,7 @@ public sealed class DesignManager : DesignEditor ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings, }; + design.SetWriteProtected(Config.DefaultDesignSettings.Locked); Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design."); SaveService.ImmediateSave(design); @@ -153,6 +155,7 @@ public sealed class DesignManager : DesignEditor Name = actualName, Index = Designs.Count, }; + design.SetWriteProtected(Config.DefaultDesignSettings.Locked); Designs.Add(design); Glamourer.Log.Debug( $"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}."); diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 311cf85..1ccb079 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -113,6 +113,8 @@ public class SettingsTab( if (!ImUtf8.CollapsingHeader("Design Defaults")) return; + Checkbox("Locked Designs"u8, "Newly created designs will be locked to prevent unintended changes."u8, + config.DefaultDesignSettings.Locked, v => config.DefaultDesignSettings.Locked = v); Checkbox("Show in Quick Design Bar"u8, "Newly created designs will be shown in the quick design bar by default."u8, config.DefaultDesignSettings.ShowQuickDesignBar, v => config.DefaultDesignSettings.ShowQuickDesignBar = v); Checkbox("Reset Advanced Dyes"u8, "Newly created designs will be configured to reset advanced dyes on application by default."u8, From 00d550f4feb344342bb5f3962d205c23feedc821 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:38:51 +0200 Subject: [PATCH 703/786] Add viera ear flags --- Glamourer.Api | 2 +- Glamourer/Automation/ApplicationType.cs | 2 +- Glamourer/Designs/ApplicationCollection.cs | 6 +- Glamourer/Designs/DesignBase.cs | 14 +++- Glamourer/Designs/DesignData.cs | 15 ++++ Glamourer/Designs/MetaIndex.cs | 13 ++- Glamourer/Events/PenumbraReloaded.cs | 3 + Glamourer/Events/VieraEarStateChanged.cs | 22 +++++ Glamourer/Events/VisorStateChanged.cs | 2 +- Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs | 6 ++ .../Gui/Tabs/DebugTab/ActiveStatePanel.cs | 3 + .../Gui/Tabs/DebugTab/DesignManagerPanel.cs | 1 + .../Gui/Tabs/DebugTab/ModelEvaluationPanel.cs | 22 +++++ Glamourer/Interop/VieraEarService.cs | 82 +++++++++++++++++++ Glamourer/Interop/VisorService.cs | 6 +- Glamourer/Services/ServiceManager.cs | 2 + Glamourer/State/StateApplier.cs | 9 ++ Glamourer/State/StateIndex.cs | 4 +- Glamourer/State/StateListener.cs | 47 ++++++++++- Glamourer/State/StateManager.cs | 3 +- Penumbra.GameData | 2 +- 21 files changed, 245 insertions(+), 21 deletions(-) create mode 100644 Glamourer/Events/VieraEarStateChanged.cs create mode 100644 Glamourer/Interop/VieraEarService.cs diff --git a/Glamourer.Api b/Glamourer.Api index 1c51730..54c1944 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 1c517301c9fd0818e2c02e72304d6de121b9d703 +Subproject commit 54c1944dc7db704733b4788520e494761bb0b58e diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs index 58f296e..f72c93f 100644 --- a/Glamourer/Automation/ApplicationType.cs +++ b/Glamourer/Automation/ApplicationType.cs @@ -38,7 +38,7 @@ public static class ApplicationTypeExtensions var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0; var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0; var crestFlags = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0; - var metaFlags = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0) + var metaFlags = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.EarState : 0) | (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0) | (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0); var bonusFlags = type.HasFlag(ApplicationType.Armor) ? BonusExtensions.All : 0; diff --git a/Glamourer/Designs/ApplicationCollection.cs b/Glamourer/Designs/ApplicationCollection.cs index 8beeb78..c03d4b4 100644 --- a/Glamourer/Designs/ApplicationCollection.cs +++ b/Glamourer/Designs/ApplicationCollection.cs @@ -19,13 +19,13 @@ public record struct ApplicationCollection( public static readonly ApplicationCollection None = new(0, 0, CustomizeFlag.BodyType, 0, 0, 0); public static readonly ApplicationCollection Equipment = new(EquipFlagExtensions.All, BonusExtensions.All, - CustomizeFlag.BodyType, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState); + CustomizeFlag.BodyType, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState | MetaFlag.EarState); public static readonly ApplicationCollection Customizations = new(0, 0, CustomizeFlagExtensions.AllRelevant, 0, CustomizeParameterExtensions.All, MetaFlag.Wetness); public static readonly ApplicationCollection Default = new(EquipFlagExtensions.All, BonusExtensions.All, - CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState); + CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState | MetaFlag.EarState); public static ApplicationCollection FromKeys() => (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch @@ -47,7 +47,7 @@ public record struct ApplicationCollection( Equip = 0; BonusItem = 0; Crest = 0; - Meta &= ~(MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState); + Meta &= ~(MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState | MetaFlag.EarState); } public void RemoveCustomize() diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index b21c433..f87d75a 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -40,7 +40,8 @@ public class DesignBase } /// Used when importing .cma or .chara files. - internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags, BonusItemFlag bonusFlags) + internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags, + BonusItemFlag bonusFlags) { _designData = designData; ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; @@ -254,9 +255,10 @@ public class DesignBase ret[slot.ToString()] = Serialize(item.Id, stains, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot)); } - ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply"); - ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyMeta(MetaIndex.VisorState)).ToJObject("IsToggled", "Apply"); - ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyMeta(MetaIndex.WeaponState)).ToJObject("Show", "Apply"); + ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply"); + ret["VieraEars"] = new QuadBool(_designData.AreEarsVisible(), DoApplyMeta(MetaIndex.EarState)).ToJObject("Show", "Apply"); + ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyMeta(MetaIndex.VisorState)).ToJObject("IsToggled", "Apply"); + ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyMeta(MetaIndex.WeaponState)).ToJObject("Show", "Apply"); } else { @@ -603,6 +605,10 @@ public class DesignBase metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse); design.SetApplyMeta(MetaIndex.VisorState, metaValue.Enabled); design._designData.SetVisor(metaValue.ForcedValue); + + metaValue = QuadBool.FromJObject(equip["VieraEars"], "Show", "Apply", QuadBool.NullTrue); + design.SetApplyMeta(MetaIndex.EarState, metaValue.Enabled); + design._designData.SetEarsVisible(metaValue.ForcedValue); return; void PrintWarning(string msg) diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index bba0ccb..c7ca8e5 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -287,6 +287,7 @@ public unsafe struct DesignData MetaIndex.HatState => IsHatVisible(), MetaIndex.VisorState => IsVisorToggled(), MetaIndex.WeaponState => IsWeaponVisible(), + MetaIndex.EarState => AreEarsVisible(), _ => false, }; @@ -297,6 +298,7 @@ public unsafe struct DesignData MetaIndex.HatState => SetHatVisible(value), MetaIndex.VisorState => SetVisor(value), MetaIndex.WeaponState => SetWeaponVisible(value), + MetaIndex.EarState => SetEarsVisible(value), _ => false, }; @@ -340,6 +342,9 @@ public unsafe struct DesignData public readonly bool IsWeaponVisible() => (_states & 0x08) == 0x08; + public readonly bool AreEarsVisible() + => (_states & 0x10) == 0x00; + public bool SetWeaponVisible(bool value) { if (value == IsWeaponVisible()) @@ -349,6 +354,15 @@ public unsafe struct DesignData return true; } + public bool SetEarsVisible(bool value) + { + if (value == AreEarsVisible()) + return false; + + _states = (byte)(value ? _states & ~0x10 : _states | 0x10); + return true; + } + public void SetDefaultEquipment(ItemManager items) { foreach (var slot in EquipSlotExtensions.EqdpSlots) @@ -386,6 +400,7 @@ public unsafe struct DesignData SetHatVisible(true); SetWeaponVisible(true); + SetEarsVisible(true); SetVisor(false); fixed (uint* ptr = _itemIds) { diff --git a/Glamourer/Designs/MetaIndex.cs b/Glamourer/Designs/MetaIndex.cs index 3fc4655..1842ae3 100644 --- a/Glamourer/Designs/MetaIndex.cs +++ b/Glamourer/Designs/MetaIndex.cs @@ -10,14 +10,15 @@ public enum MetaIndex VisorState = StateIndex.MetaVisorState, WeaponState = StateIndex.MetaWeaponState, ModelId = StateIndex.MetaModelId, + EarState = StateIndex.MetaEarState, } public static class MetaExtensions { public static readonly IReadOnlyList AllRelevant = - [MetaIndex.Wetness, MetaIndex.HatState, MetaIndex.VisorState, MetaIndex.WeaponState]; + [MetaIndex.Wetness, MetaIndex.HatState, MetaIndex.VisorState, MetaIndex.WeaponState, MetaIndex.EarState]; - public const MetaFlag All = MetaFlag.Wetness | MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState; + public const MetaFlag All = MetaFlag.Wetness | MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState | MetaFlag.EarState; public static MetaFlag ToFlag(this MetaIndex index) => index switch @@ -26,6 +27,7 @@ public static class MetaExtensions MetaIndex.HatState => MetaFlag.HatState, MetaIndex.VisorState => MetaFlag.VisorState, MetaIndex.WeaponState => MetaFlag.WeaponState, + MetaIndex.EarState => MetaFlag.EarState, _ => (MetaFlag)byte.MaxValue, }; @@ -36,7 +38,8 @@ public static class MetaExtensions MetaFlag.HatState => MetaIndex.HatState, MetaFlag.VisorState => MetaIndex.VisorState, MetaFlag.WeaponState => MetaIndex.WeaponState, - _ => (MetaIndex)byte.MaxValue, + MetaFlag.EarState => MetaIndex.EarState, + _ => (MetaIndex)byte.MaxValue, }; public static IEnumerable ToIndices(this MetaFlag index) @@ -49,6 +52,8 @@ public static class MetaExtensions yield return MetaIndex.VisorState; if (index.HasFlag(MetaFlag.WeaponState)) yield return MetaIndex.WeaponState; + if (index.HasFlag(MetaFlag.EarState)) + yield return MetaIndex.EarState; } public static string ToName(this MetaIndex index) @@ -58,6 +63,7 @@ public static class MetaExtensions MetaIndex.VisorState => "Visor Toggled", MetaIndex.WeaponState => "Weapon Visible", MetaIndex.Wetness => "Force Wetness", + MetaIndex.EarState => "Ears Visible", _ => "Unknown Meta", }; @@ -68,6 +74,7 @@ public static class MetaExtensions MetaIndex.VisorState => "Toggle the visor state of the characters head gear.", MetaIndex.WeaponState => "Hide or show the characters weapons when not drawn.", MetaIndex.Wetness => "Force the character to be wet or not.", + MetaIndex.EarState => "Hide or show the characters ears through the head gear. (Viera only)", _ => string.Empty, }; } diff --git a/Glamourer/Events/PenumbraReloaded.cs b/Glamourer/Events/PenumbraReloaded.cs index 20b58f9..0975670 100644 --- a/Glamourer/Events/PenumbraReloaded.cs +++ b/Glamourer/Events/PenumbraReloaded.cs @@ -15,5 +15,8 @@ public sealed class PenumbraReloaded() /// VisorService = 0, + + /// + VieraEarService = 0, } } diff --git a/Glamourer/Events/VieraEarStateChanged.cs b/Glamourer/Events/VieraEarStateChanged.cs new file mode 100644 index 0000000..65730b8 --- /dev/null +++ b/Glamourer/Events/VieraEarStateChanged.cs @@ -0,0 +1,22 @@ +using OtterGui.Classes; +using Penumbra.GameData.Interop; + +namespace Glamourer.Events; + +/// +/// Triggered when the state of viera ear visibility for any draw object is changed. +/// +/// Parameter is the model with a changed viera ear visibility state. +/// Parameter is the new state. +/// Parameter is whether to call the original function. +/// +/// +public sealed class VieraEarStateChanged() + : EventWrapperRef2(nameof(VieraEarStateChanged)) +{ + public enum Priority + { + /// + StateListener = 0, + } +} diff --git a/Glamourer/Events/VisorStateChanged.cs b/Glamourer/Events/VisorStateChanged.cs index d2d3a6c..03b7336 100644 --- a/Glamourer/Events/VisorStateChanged.cs +++ b/Glamourer/Events/VisorStateChanged.cs @@ -19,4 +19,4 @@ public sealed class VisorStateChanged() /// StateListener = 0, } -} +} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index dcd3a12..224154b 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -305,6 +305,12 @@ public class ActorPanel EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.WeaponState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!)); } + + ImGui.SameLine(); + using (_ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.EarState, _stateManager, _state!)); + } } private void DrawMonsterPanel() diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index e0ec4bd..35642a7 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -87,6 +87,9 @@ public class ActiveStatePanel(StateManager _stateManager, ActorObjectManager _ob PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(), state.Sources[MetaIndex.VisorState]); ImGui.TableNextRow(); + PrintRow("Viera Ears Visible", state.BaseData.AreEarsVisible(), state.ModelData.AreEarsVisible(), + state.Sources[MetaIndex.EarState]); + ImGui.TableNextRow(); PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(), state.Sources[MetaIndex.WeaponState]); ImGui.TableNextRow(); diff --git a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs index 38f0077..7c60dda 100644 --- a/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/DesignManagerPanel.cs @@ -114,6 +114,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _ ImGuiUtil.DrawTableColumn(index.ToName()); ImGuiUtil.DrawTableColumn(design.DesignData.GetMeta(index).ToString()); ImGuiUtil.DrawTableColumn(design.DoApplyMeta(index) ? "Apply" : "Keep"); + ImGui.TableNextRow(); } ImGuiUtil.DrawTableColumn("Model ID"); diff --git a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs index d1b42e8..185e19b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ModelEvaluationPanel.cs @@ -17,6 +17,7 @@ namespace Glamourer.Gui.Tabs.DebugTab; public unsafe class ModelEvaluationPanel( ActorObjectManager _objectManager, VisorService _visorService, + VieraEarService _vieraEarService, UpdateSlotService _updateSlotService, ChangeCustomizeService _changeCustomizeService, CrestService _crestService, @@ -84,6 +85,7 @@ public unsafe class ModelEvaluationPanel( ImGuiUtil.CopyOnClickSelectable(offhand.ToString()); DrawVisor(actor, model); + DrawVieraEars(actor, model); DrawHatState(actor, model); DrawWeaponState(actor, model); DrawWetness(actor, model); @@ -135,6 +137,26 @@ public unsafe class ModelEvaluationPanel( _visorService.SetVisorState(model, !VisorService.GetVisorState(model)); } + private void DrawVieraEars(Actor actor, Model model) + { + using var id = ImRaii.PushId("Viera Ears"); + ImGuiUtil.DrawTableColumn("Viera Ears"); + ImGuiUtil.DrawTableColumn(actor.IsCharacter ? actor.ShowVieraEars.ToString() : "No Character"); + ImGuiUtil.DrawTableColumn(model.IsHuman ? model.VieraEarsVisible.ToString() : "No Human"); + ImGui.TableNextColumn(); + if (!model.IsHuman) + return; + + if (ImGui.SmallButton("Set True")) + _vieraEarService.SetVieraEarState(model, true); + ImGui.SameLine(); + if (ImGui.SmallButton("Set False")) + _vieraEarService.SetVieraEarState(model, false); + ImGui.SameLine(); + if (ImGui.SmallButton("Toggle")) + _vieraEarService.SetVieraEarState(model, !model.VieraEarsVisible); + } + private void DrawHatState(Actor actor, Model model) { using var id = ImRaii.PushId("HatState"); diff --git a/Glamourer/Interop/VieraEarService.cs b/Glamourer/Interop/VieraEarService.cs new file mode 100644 index 0000000..1e5c4eb --- /dev/null +++ b/Glamourer/Interop/VieraEarService.cs @@ -0,0 +1,82 @@ +using Dalamud.Hooking; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using Glamourer.Events; +using Penumbra.GameData.Interop; + +namespace Glamourer.Interop; + +public unsafe class VieraEarService : IDisposable +{ + private readonly PenumbraReloaded _penumbra; + private readonly IGameInteropProvider _interop; + public readonly VieraEarStateChanged Event; + + public VieraEarService(VieraEarStateChanged visorStateChanged, IGameInteropProvider interop, PenumbraReloaded penumbra) + { + _interop = interop; + _penumbra = penumbra; + Event = visorStateChanged; + _setupVieraEarHook = Create(); + _penumbra.Subscribe(Restore, PenumbraReloaded.Priority.VieraEarService); + } + + public void Dispose() + { + _setupVieraEarHook.Dispose(); + _penumbra.Unsubscribe(Restore); + } + + /// Obtain the current state of viera ears for the given draw object (true: toggled). + public static unsafe bool GetVieraEarState(Model characterBase) + => characterBase is { IsCharacterBase: true, VieraEarsVisible: true }; + + /// Manually set the state of the Visor for the given draw object. + /// The draw object. + /// The desired state (true: toggled). + /// Whether the state was changed. + public bool SetVieraEarState(Model human, bool on) + { + if (!human.IsHuman) + return false; + + var oldState = GetVieraEarState(human); + Glamourer.Log.Verbose($"[SetVieraEarState] Invoked manually on 0x{human.Address:X} switching from {oldState} to {on}."); + if (oldState == on) + return false; + + human.VieraEarsVisible = on; + return true; + } + + private delegate void UpdateVieraEarDelegateInternal(DrawDataContainer* drawData, byte on); + + private Hook _setupVieraEarHook; + + private void SetupVieraEarDetour(DrawDataContainer* drawData, byte value) + { + Actor actor = drawData->OwnerObject; + var originalOn = value != 0; + var on = originalOn; + // Invoke an event that can change the requested value + Event.Invoke(actor, ref on); + + Glamourer.Log.Verbose( + $"[SetVieraEarState] Invoked from game on 0x{actor.Address:X} switching to {on} (original {originalOn} from {value})."); + + _setupVieraEarHook.Original(drawData, on ? (byte)1 : (byte)0); + } + + private unsafe Hook Create() + { + var hook = _interop.HookFromSignature("E8 ?? ?? ?? ?? 48 8D 8F ?? ?? ?? ?? 4C 8D 4C 24", SetupVieraEarDetour); + hook.Enable(); + return hook; + } + + private void Restore() + { + _setupVieraEarHook.Dispose(); + _setupVieraEarHook = Create(); + } +} diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 9763682..25823b6 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -9,9 +9,9 @@ namespace Glamourer.Interop; public class VisorService : IDisposable { - private readonly PenumbraReloaded _penumbra; - private readonly IGameInteropProvider _interop; - public readonly VisorStateChanged Event; + private readonly PenumbraReloaded _penumbra; + private readonly IGameInteropProvider _interop; + public readonly VisorStateChanged Event; public VisorService(VisorStateChanged visorStateChanged, IGameInteropProvider interop, PenumbraReloaded penumbra) { diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 0754313..6cfb4b6 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -71,6 +71,7 @@ public static class StaticServiceManager private static ServiceManager AddEvents(this ServiceManager services) => services.AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() @@ -96,6 +97,7 @@ public static class StaticServiceManager private static ServiceManager AddInterop(this ServiceManager services) => services.AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 93a3450..c346ab1 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -262,6 +262,14 @@ public class StateApplier( _visor.SetVisorState(actor.Model, value); return; } + case MetaIndex.EarState: + foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) + { + var model = actor.Model; + model.VieraEarsVisible = value; + } + + return; } } @@ -402,6 +410,7 @@ public class StateApplier( ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible()); ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled()); + ChangeMetaState(actors, MetaIndex.EarState, state.ModelData.AreEarsVisible()); ChangeCrests(actors, state.ModelData.CrestVisibility); ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters); ChangeMaterialValues(actors, state.Materials); diff --git a/Glamourer/State/StateIndex.cs b/Glamourer/State/StateIndex.cs index e3f1863..dff05a3 100644 --- a/Glamourer/State/StateIndex.cs +++ b/Glamourer/State/StateIndex.cs @@ -189,8 +189,9 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators MetaFlag.HatState, MetaVisorState => MetaFlag.VisorState, MetaWeaponState => MetaFlag.WeaponState, + MetaEarState => MetaFlag.EarState, MetaModelId => true, CrestHead => CrestFlag.Head, diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 90520b2..4b70718 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -9,6 +9,7 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.GameData; using Penumbra.GameData.DataContainers; @@ -39,6 +40,7 @@ public class StateListener : IDisposable private readonly WeaponLoading _weaponLoading; private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly VisorStateChanged _visorState; + private readonly VieraEarStateChanged _vieraEarState; private readonly WeaponVisibilityChanged _weaponVisibility; private readonly StateFinalized _stateFinalized; private readonly AutoDesignApplier _autoDesignApplier; @@ -62,7 +64,7 @@ public class StateListener : IDisposable WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ActorObjectManager objects, GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, - CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateFinalized stateFinalized) + CrestService crestService, BonusSlotUpdating bonusSlotUpdating, StateFinalized stateFinalized, VieraEarStateChanged vieraEarState) { _manager = manager; _items = items; @@ -88,6 +90,7 @@ public class StateListener : IDisposable _crestService = crestService; _bonusSlotUpdating = bonusSlotUpdating; _stateFinalized = stateFinalized; + _vieraEarState = vieraEarState; Subscribe(); } @@ -266,7 +269,7 @@ public class StateListener : IDisposable private void OnGearsetDataLoaded(Actor actor, Model model) { - if (!actor.Valid || (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)) + if (!actor.Valid || _condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) return; // ensure actor and state are valid. @@ -710,6 +713,44 @@ public class StateListener : IDisposable } } + /// Handle visor state changes made by the game. + private void OnVieraEarChange(Actor actor, ref bool value) + { + // Value is inverted compared to our own handling. + + // Skip updates when in customize update. + if (ChangeCustomizeService.InUpdate.InMethod) + return; + + if (!actor.IsCharacter) + return; + + if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart) + return; + + if (!actor.Identifier(_actors, out var identifier)) + return; + + if (!_manager.TryGetValue(identifier, out var state)) + return; + + // Update visor base state. + if (state.BaseData.SetEarsVisible(!value)) + { + // if base state changed, either overwrite the actual value if we have fixed values, + // or overwrite the stored model state with the new one. + if (state.Sources[MetaIndex.EarState].IsFixed()) + value = !state.ModelData.AreEarsVisible(); + else + _manager.ChangeMetaState(state, MetaIndex.EarState, !value, ApplySettings.Game); + } + else + { + // if base state did not change, overwrite the value with the model state one. + value = !state.ModelData.AreEarsVisible(); + } + } + /// Handle Hat Visibility changes. These act on the game object. private void OnHeadGearVisibilityChange(Actor actor, ref bool value) { @@ -802,6 +843,7 @@ public class StateListener : IDisposable _movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener); _weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener); _visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener); + _vieraEarState.Subscribe(OnVieraEarChange, VieraEarStateChanged.Priority.StateListener); _headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener); _weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener); _changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener); @@ -820,6 +862,7 @@ public class StateListener : IDisposable _movedEquipment.Unsubscribe(OnMovedEquipment); _weaponLoading.Unsubscribe(OnWeaponLoading); _visorState.Unsubscribe(OnVisorChange); + _vieraEarState.Unsubscribe(OnVieraEarChange); _headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange); _weaponVisibility.Unsubscribe(OnWeaponVisibilityChange); _changeCustomizeService.Unsubscribe(OnCustomizeChange); diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 98b12aa..e8926d6 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -158,6 +158,7 @@ public sealed class StateManager( // Visor state is a flag on the game object, but we can see the actual state on the draw object. ret.SetVisor(VisorService.GetVisorState(model)); + ret.SetEarsVisible(model.VieraEarsVisible); foreach (var slot in CrestExtensions.AllRelevantSet) ret.SetCrest(slot, CrestService.GetModelCrest(actor, slot)); @@ -186,7 +187,7 @@ public sealed class StateManager( off = actor.GetOffhand(); FistWeaponHack(ref ret, ref main, ref off); ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled); - + ret.SetEarsVisible(actor.ShowVieraEars); foreach (var slot in CrestExtensions.AllRelevantSet) ret.SetCrest(slot, actor.GetCrest(slot)); diff --git a/Penumbra.GameData b/Penumbra.GameData index 65c5bf3..17f2f49 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 65c5bf3f46569a54b0057c9015ab839b4e2a4350 +Subproject commit 17f2f496664b0d69ebd7fcdabe7bc8e3e20b6463 From ac6a726f57a2eefda348425ed6da5aec25bc58cd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:39:05 +0200 Subject: [PATCH 704/786] Remaining API13 updates. --- Glamourer/Glamourer.csproj | 1 - Glamourer/Glamourer.json | 2 +- .../Customization/CustomizeParameterDrawer.cs | 4 ++-- Glamourer/Gui/DesignCombo.cs | 2 +- Glamourer/Gui/Equipment/EquipmentDrawer.cs | 4 ++-- Glamourer/Gui/Materials/AdvancedDyePopup.cs | 2 +- Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs | 2 +- .../Gui/Tabs/AutomationTab/SetSelector.cs | 4 ++-- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 2 +- .../Gui/Tabs/DesignTab/DesignLinkDrawer.cs | 2 +- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 5 +++++ .../SettingsTab/CollectionOverrideDrawer.cs | 2 +- .../Gui/Tabs/UnlocksTab/UnlockOverview.cs | 8 ++++---- Glamourer/Interop/CrestService.cs | 2 +- .../Material/LiveColorTablePreviewer.cs | 6 +++--- Glamourer/Services/TextureService.cs | 19 ++++++++++--------- 16 files changed, 36 insertions(+), 31 deletions(-) diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 40b1218..86ae713 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -29,7 +29,6 @@ - diff --git a/Glamourer/Glamourer.json b/Glamourer/Glamourer.json index 3127d7d..2daff91 100644 --- a/Glamourer/Glamourer.json +++ b/Glamourer/Glamourer.json @@ -8,7 +8,7 @@ "AssemblyVersion": "9.0.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 12, + "DalamudApiLevel": 13, "ImageUrls": null, "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png" } \ No newline at end of file diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs index 18a9d1a..4db6b14 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawer.cs @@ -287,13 +287,13 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import } private ImGuiColorEditFlags GetFlags() - => Format | Display | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions; + => Format | Display | ImGuiColorEditFlags.Hdr | ImGuiColorEditFlags.NoOptions; private ImGuiColorEditFlags Format => config.UseFloatForColors ? ImGuiColorEditFlags.Float : ImGuiColorEditFlags.Uint8; private ImGuiColorEditFlags Display - => config.UseRgbForColors ? ImGuiColorEditFlags.DisplayRGB : ImGuiColorEditFlags.DisplayHSV; + => config.UseRgbForColors ? ImGuiColorEditFlags.DisplayRgb : ImGuiColorEditFlags.DisplayHsv; private ImRaii.IEndObject EnsureSize() { diff --git a/Glamourer/Gui/DesignCombo.cs b/Glamourer/Gui/DesignCombo.cs index 6dfffef..2d8880e 100644 --- a/Glamourer/Gui/DesignCombo.cs +++ b/Glamourer/Gui/DesignCombo.cs @@ -194,7 +194,7 @@ public abstract class DesignComboBase : FilterComboCacheDataType).SequenceEqual("equipDragDrop"u8)) + if (!MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)Unsafe.AsPointer(ref payload->DataType_0)).SequenceEqual("equipDragDrop"u8)) return; using var tt = ImUtf8.Tooltip(); diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index 8cf81ff..4499107 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -91,7 +91,7 @@ public sealed unsafe class AdvancedDyePopup( var modelHandle = model == null ? null : model->ModelResourceHandle; var path = materialHandle == null ? string.Empty - : ByteString.FromSpanUnsafe(materialHandle->ResourceHandle.FileName.AsSpan(), true).ToString(); + : ByteString.FromSpanUnsafe(materialHandle->FileName.AsSpan(), true).ToString(); var gamePath = modelHandle == null ? string.Empty : modelHandle->GetMaterialFileNameBySlot(index.MaterialIndex).ToString(); diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 4b05e35..8a85a45 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -432,7 +432,7 @@ public class SetPanel( if (source) { ImUtf8.Text($"Moving design #{index + 1:D2}..."); - if (ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0)) + if (ImGui.SetDragDropPayload(dragDropLabel, null, 0)) { _dragIndex = index; _selector.DragDesignIndex = index; diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs index dca0ce5..8a235ae 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetSelector.cs @@ -144,7 +144,7 @@ public class SetSelector : IDisposable ImGui.SameLine(); var f = _enabledFilter; - if (ImGui.CheckboxFlags("##enabledFilter", ref f, 3)) + if (ImGui.CheckboxFlags("##enabledFilter", ref f, 3u)) { _enabledFilter = _enabledFilter switch { @@ -347,7 +347,7 @@ public class SetSelector : IDisposable if (source) { ImGui.TextUnformatted($"Moving design set {GetSetName(set, index)} from position {index + 1}..."); - if (ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0)) + if (ImGui.SetDragDropPayload(dragDropLabel, null, 0)) _dragIndex = index; } } diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index 9992bc3..aa94a23 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -49,7 +49,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem ImGui.TableNextColumn(); var address = _drawObject.Address; ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); - if (ImGui.InputScalar("##drawObjectPtr", ImGuiDataType.U64, (nint)(&address), nint.Zero, nint.Zero, "%llx", + if (ImGui.InputScalar("##drawObjectPtr", ImGuiDataType.U64, ref address, nint.Zero, nint.Zero, "%llx", ImGuiInputTextFlags.CharsHexadecimal)) _drawObject = address; ImGuiUtil.DrawTableColumn(_penumbra.Available diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs index 7e5807e..d9517c8 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -195,7 +195,7 @@ public class DesignLinkDrawer( { if (source) { - ImGui.SetDragDropPayload("DraggingLink", IntPtr.Zero, 0); + ImGui.SetDragDropPayload("DraggingLink", null, 0); ImGui.TextUnformatted($"Reordering {design.Name}..."); _dragDropIndex = index; _dragDropOrder = order; diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 5bb3d77..84aaac0 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -154,6 +154,11 @@ public class DesignPanel EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!)); } + ImGui.SameLine(); + using (var _ = ImRaii.Group()) + { + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.EarState, _manager, _selector.Selected!)); + } } private void DrawCustomize() diff --git a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs index 76435ec..5c4fec3 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/CollectionOverrideDrawer.cs @@ -123,7 +123,7 @@ public class CollectionOverrideDrawer( { if (source) { - ImGui.SetDragDropPayload("DraggingOverride", nint.Zero, 0); + ImGui.SetDragDropPayload("DraggingOverride", null, 0); ImGui.TextUnformatted($"Reordering Override #{idx + 1}..."); _dragDropIndex = idx; } diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs index 947f0c6..8644aeb 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockOverview.cs @@ -123,7 +123,7 @@ public class UnlockOverview( var unlocked = customizeUnlocks.IsUnlocked(customize, out var time); var icon = customizations.Manager.GetIcon(customize.IconId); var hasIcon = icon.TryGetWrap(out var wrap, out _); - ImGui.Image(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, + ImGui.Image(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, iconSize, Vector2.Zero, Vector2.One, unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); if (favorites.Contains(_selected3, _selected2, customize.Index, customize.Value)) @@ -135,7 +135,7 @@ public class UnlockOverview( using var tt = ImRaii.Tooltip(); var size = new Vector2(wrap!.Width, wrap.Height); if (size.X >= iconSize.X && size.Y >= iconSize.Y) - ImGui.Image(wrap.ImGuiHandle, size); + ImGui.Image(wrap.Handle, size); ImGui.TextUnformatted(unlockData.Name); ImGui.TextUnformatted($"{customize.Index.ToDefaultName()} {customize.Value.Value}"); ImGui.TextUnformatted(unlocked ? $"Unlocked on {time:g}" : "Not unlocked."); @@ -194,7 +194,7 @@ public class UnlockOverview( if (!textures.TryLoadIcon(item.IconId.Id, out var iconHandle)) return; - var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); + var (icon, size) = (iconHandle.Handle, new Vector2(iconHandle.Width, iconHandle.Height)); ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); @@ -265,7 +265,7 @@ public class UnlockOverview( if (!textures.TryLoadIcon(item.IconId.Id, out var iconHandle)) return; - var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); + var (icon, size) = (iconHandle.Handle, new Vector2(iconHandle.Width, iconHandle.Height)); ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint); diff --git a/Glamourer/Interop/CrestService.cs b/Glamourer/Interop/CrestService.cs index 74b7800..2b55f94 100644 --- a/Glamourer/Interop/CrestService.cs +++ b/Glamourer/Interop/CrestService.cs @@ -67,7 +67,7 @@ public sealed unsafe class CrestService : EventWrapperRef3 _crestChangeHook = null!; private void CrestChangeDetour(DrawDataContainer* container, byte crestFlags) diff --git a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs index 4190746..3b9edb7 100644 --- a/Glamourer/Interop/Material/LiveColorTablePreviewer.cs +++ b/Glamourer/Interop/Material/LiveColorTablePreviewer.cs @@ -114,9 +114,9 @@ public sealed unsafe class LiveColorTablePreviewer : IService, IDisposable var frame = DateTimeOffset.UtcNow.UtcTicks; var hueByte = frame % (steps * frameLength) / frameLength; var hue = (float)hueByte / steps; - float r, g, b = 0; - ImGui.ColorConvertHSVtoRGB(hue, 1, 1, &r, &g, &b); - return new Vector3(r, g, b); + Vector3 ret; + ImGui.ColorConvertHSVtoRGB(hue, 1, 1, &ret.X, &ret.Y, &ret.Z); + return ret; } public void Dispose() diff --git a/Glamourer/Services/TextureService.cs b/Glamourer/Services/TextureService.cs index 29c2343..a0ec443 100644 --- a/Glamourer/Services/TextureService.cs +++ b/Glamourer/Services/TextureService.cs @@ -1,3 +1,4 @@ +using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; @@ -12,30 +13,30 @@ public sealed class TextureService(IUiBuilder uiBuilder, IDataManager dataManage { private readonly IDalamudTextureWrap?[] _slotIcons = CreateSlotIcons(uiBuilder); - public (nint, Vector2, bool) GetIcon(EquipItem item, EquipSlot slot) + public (ImTextureID, Vector2, bool) GetIcon(EquipItem item, EquipSlot slot) { if (item.IconId.Id != 0 && TryLoadIcon(item.IconId.Id, out var ret)) - return (ret.ImGuiHandle, new Vector2(ret.Width, ret.Height), false); + return (ret.Handle, new Vector2(ret.Width, ret.Height), false); var idx = slot.ToIndex(); return idx < 12 && _slotIcons[idx] != null - ? (_slotIcons[idx]!.ImGuiHandle, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) - : (nint.Zero, Vector2.Zero, true); + ? (_slotIcons[idx]!.Handle, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) + : (default, Vector2.Zero, true); } - public (nint, Vector2, bool) GetIcon(EquipItem item, BonusItemFlag slot) + public (ImTextureID, Vector2, bool) GetIcon(EquipItem item, BonusItemFlag slot) { if (item.IconId.Id != 0 && TryLoadIcon(item.IconId.Id, out var ret)) - return (ret.ImGuiHandle, new Vector2(ret.Width, ret.Height), false); + return (ret.Handle, new Vector2(ret.Width, ret.Height), false); var idx = slot.ToIndex(); if (idx == uint.MaxValue) - return (nint.Zero, Vector2.Zero, true); + return (default, Vector2.Zero, true); idx += 12; return idx < 13 && _slotIcons[idx] != null - ? (_slotIcons[idx]!.ImGuiHandle, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) - : (nint.Zero, Vector2.Zero, true); + ? (_slotIcons[idx]!.Handle, new Vector2(_slotIcons[idx]!.Width, _slotIcons[idx]!.Height), true) + : (default, Vector2.Zero, true); } public void Dispose() From 44729205368908f17e3d0660fe7779741122e0a4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:46:24 +0200 Subject: [PATCH 705/786] Move Viera Ears sig to gamedata. --- Glamourer/Interop/VieraEarService.cs | 3 ++- Penumbra.GameData | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Glamourer/Interop/VieraEarService.cs b/Glamourer/Interop/VieraEarService.cs index 1e5c4eb..a6afd1d 100644 --- a/Glamourer/Interop/VieraEarService.cs +++ b/Glamourer/Interop/VieraEarService.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using Glamourer.Events; +using Penumbra.GameData; using Penumbra.GameData.Interop; namespace Glamourer.Interop; @@ -69,7 +70,7 @@ public unsafe class VieraEarService : IDisposable private unsafe Hook Create() { - var hook = _interop.HookFromSignature("E8 ?? ?? ?? ?? 48 8D 8F ?? ?? ?? ?? 4C 8D 4C 24", SetupVieraEarDetour); + var hook = _interop.HookFromSignature(Sigs.SetupVieraEars, SetupVieraEarDetour); hook.Enable(); return hook; } diff --git a/Penumbra.GameData b/Penumbra.GameData index 17f2f49..7981d20 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 17f2f496664b0d69ebd7fcdabe7bc8e3e20b6463 +Subproject commit 7981d2041bc49096101ab128682155dbe1fbc468 From 97e32a3cb7a953eba65bcbca0ee4040d76a0f1da Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:48:19 +0200 Subject: [PATCH 706/786] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 7981d20..ea49bc0 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 7981d2041bc49096101ab128682155dbe1fbc468 +Subproject commit ea49bc099e783ecafdf78f0bd0bc41fb8c60ad19 From be78f1447bd20c1c273a55ce4823780437f7e748 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 15:56:08 +0200 Subject: [PATCH 707/786] 1.5.0.0 --- Glamourer/Gui/GlamourerChangelog.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index a62743c..31d1ce3 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -43,6 +43,7 @@ public class GlamourerChangelog Add1_3_7_0(Changelog); Add1_3_8_0(Changelog); Add1_4_0_0(Changelog); + Add1_5_0_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -63,6 +64,19 @@ public class GlamourerChangelog } } + private static void Add1_5_0_0(Changelog log) + => log.NextVersion("Version 1.5.0.0") + .RegisterImportant("Updated for game version 7.30 and Dalamud API13, which uses a new GUI backend. Some things may not work as expected. Please let me know any issues you encounter.") + .RegisterHighlight("Added the new Viera Ears state to designs. Old designs will not apply the state.") + .RegisterHighlight("Added the option to make newly created designs write-protected by default to the design defaults.") + .RegisterEntry("Fixed issues with reverting state and IPC.") + .RegisterEntry("Fixed an issue when using the mousewheel to scroll through designs (1.4.0.3).") + .RegisterEntry("Fixed an issue with invalid bonus items (1.4.0.3).") + .RegisterHighlight("Added drag & drop of equipment pieces which will try to match the corresponding model IDs in other slots if possible (1.4.0.2).") + .RegisterEntry("Heavily optimized some issues when having many designs and creating new ones or updating them (1.4.0.2)") + .RegisterEntry("Fixed an issue with staining templates (1.4.0.1).") + .RegisterEntry("Fixed an issue with the QDB buttons not counting correctly (1.4.0.1)."); + private static void Add1_4_0_0(Changelog log) => log.NextVersion("Version 1.4.0.0") .RegisterHighlight("The design selector width is now draggable within certain restrictions that depend on the total window width.") From b66df624f77b7a765f6c4739627d1645422e74a5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 23:01:54 +0200 Subject: [PATCH 708/786] Update Gamedata. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index ea49bc0..fd875c4 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit ea49bc099e783ecafdf78f0bd0bc41fb8c60ad19 +Subproject commit fd875c43ee910350107b2609809335285bd4ac0f From 56753ae7bab53ce93ea9e150d569f7b46e8c1e99 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 23:03:58 +0200 Subject: [PATCH 709/786] Use staging for release. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18435ae..327b75b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | From 557cbf23ce7cf431f87643738092bb1b9d334b98 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 8 Aug 2025 21:06:10 +0000 Subject: [PATCH 710/786] [CI] Updating repo.json for 1.5.0.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 0c6e22f..3f9f19c 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.4.0.3", - "TestingAssemblyVersion": "1.4.0.3", + "AssemblyVersion": "1.5.0.0", + "TestingAssemblyVersion": "1.5.0.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 12, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.4.0.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 98574558e5b3fdbbebc9368c6f2033543956294a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 8 Aug 2025 23:07:08 +0200 Subject: [PATCH 711/786] Set Repo API level to 13 and remove stg from future releases. --- .github/workflows/release.yml | 2 +- repo.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 327b75b..18435ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | diff --git a/repo.json b/repo.json index 3f9f19c..40cbed2 100644 --- a/repo.json +++ b/repo.json @@ -21,8 +21,8 @@ "TestingAssemblyVersion": "1.5.0.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 12, - "TestingDalamudApiLevel": 12, + "DalamudApiLevel": 13, + "TestingDalamudApiLevel": 13, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1, From a8b79993df3200c196e53ea92177558b1e9ef56f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 11:47:11 +0200 Subject: [PATCH 712/786] Make QDB ignore close hotkey. --- Glamourer/Gui/DesignQuickBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 2dee0e4..6425c5c 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -52,7 +52,6 @@ public sealed class DesignQuickBar : Window, IDisposable public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) - : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking) { _config = config; _designCombo = designCombo; @@ -64,6 +63,7 @@ public sealed class DesignQuickBar : Window, IDisposable IsOpen = _config.Ephemeral.ShowDesignQuickBar; DisableWindowSounds = true; Size = Vector2.Zero; + RespectCloseHotkey = false; } public void Dispose() From 52fd29c4787255067aa118fa9fbe1a8080e8ab82 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 11:52:17 +0200 Subject: [PATCH 713/786] Woops --- Glamourer/Gui/DesignQuickBar.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 6425c5c..e8c0ce3 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -52,6 +52,7 @@ public sealed class DesignQuickBar : Window, IDisposable public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState, ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra) + : base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking) { _config = config; _designCombo = designCombo; From e83f328cdcc306d2b2ef0af7332629bc75c33a39 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 11:58:52 +0200 Subject: [PATCH 714/786] Fix resizable child. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 9523b7a..539ce9e 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 9523b7ac725656b21fa98faef96962652e86e64f +Subproject commit 539ce9e504fdc8bb0c2ca229905f4d236c376f6a From 0d94aae7322b5baf2a0f50b03cc561b52f60e4ac Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 12:11:42 +0200 Subject: [PATCH 715/786] Fix popups not working early. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 539ce9e..5224ac5 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 539ce9e504fdc8bb0c2ca229905f4d236c376f6a +Subproject commit 5224ac538b1a7c0e86e7d2ceaf652d8d807888ae From 4761b8f5847ddd246703437f821886007cae476e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 13:01:48 +0200 Subject: [PATCH 716/786] Need staging again... --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18435ae..327b75b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | From 34bf95dddb739d10741122bbe76d01e178bcc957 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 9 Aug 2025 11:03:48 +0000 Subject: [PATCH 717/786] [CI] Updating repo.json for 1.5.0.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 40cbed2..ffa61be 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.0", - "TestingAssemblyVersion": "1.5.0.0", + "AssemblyVersion": "1.5.0.1", + "TestingAssemblyVersion": "1.5.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a9caddafd50c0e223fd191f4decdc6352aa8aad1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 18:39:07 +0200 Subject: [PATCH 718/786] Maybe fix design crashes. --- Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs index 7341d31..e0e4543 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignFileSystemSelector.cs @@ -175,7 +175,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector Date: Sat, 9 Aug 2025 16:41:32 +0000 Subject: [PATCH 719/786] [CI] Updating repo.json for 1.5.0.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index ffa61be..56d0c96 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.1", - "TestingAssemblyVersion": "1.5.0.1", + "AssemblyVersion": "1.5.0.2", + "TestingAssemblyVersion": "1.5.0.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 8f34f197d0aa70693c8ccf98911778b397cc2729 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 18:53:22 +0200 Subject: [PATCH 720/786] Another try. --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 5224ac5..5e32bb2 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 5224ac538b1a7c0e86e7d2ceaf652d8d807888ae +Subproject commit 5e32bb225e5fbb60ed8426ed887fd7e8a831ebae From 304b362002cafbaf97689d51105644ced66157a8 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 9 Aug 2025 16:55:27 +0000 Subject: [PATCH 721/786] [CI] Updating repo.json for 1.5.0.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 56d0c96..e117eee 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.2", - "TestingAssemblyVersion": "1.5.0.2", + "AssemblyVersion": "1.5.0.3", + "TestingAssemblyVersion": "1.5.0.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 612cd31c3e86eafa8d81077cb20882ce5e709523 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 19:02:26 +0200 Subject: [PATCH 722/786] Fix some caravans. --- Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs index 042f2a2..8a3dd06 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignDetailTab.cs @@ -189,10 +189,7 @@ public class DesignDetailTab else if (_selector.Selected!.Color.Length != 0) { ImGui.SameLine(); - var size = new Vector2(ImGui.GetFrameHeight()); - using var font = ImRaii.PushFont(UiBuilder.IconFont); - ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor); - ImUtf8.HoverTooltip("The color associated with this design does not exist."u8); + ImUtf8.Icon(FontAwesomeIcon.ExclamationCircle, "The color associated with this design does not exist."u8, _colors.MissingColor); } ImUtf8.DrawFrameColumn("Creation Date"u8); From 240c889fff459afd0769a92123e93c815f386df2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 9 Aug 2025 20:48:46 +0200 Subject: [PATCH 723/786] Fix changed equipment access to use new size. --- Glamourer/Interop/Material/MaterialManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 9eccb29..1ffd820 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -197,7 +197,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable if (human->ChangedEquipData == null) return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0); - return ((CharacterArmor*)human->ChangedEquipData + slotId * 3)->ToWeapon(0); + return ((CharacterArmor*)human->ChangedEquipData + slotId * 4)->ToWeapon(0); } /// From e4374337f2ea3099da8fc06d84b0f02a5ad524ff Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 9 Aug 2025 18:50:50 +0000 Subject: [PATCH 724/786] [CI] Updating repo.json for 1.5.0.4 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index e117eee..447bda2 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.3", - "TestingAssemblyVersion": "1.5.0.3", + "AssemblyVersion": "1.5.0.4", + "TestingAssemblyVersion": "1.5.0.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.3/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 26862ba78f46e69d7d3e1ab48c795e60c1ef7b0b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 11 Aug 2025 19:58:36 +0200 Subject: [PATCH 725/786] Update ChangedEquipData. --- Glamourer/Interop/Material/MaterialManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 1ffd820..5b731b0 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -197,7 +197,8 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable if (human->ChangedEquipData == null) return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0); - return ((CharacterArmor*)human->ChangedEquipData + slotId * 4)->ToWeapon(0); + var item = (ChangedEquipData*)human->ChangedEquipData + slotId; + return ((CharacterArmor*)item)->ToWeapon(0); } /// From dc431c10a55f32894af23aff9650ae94d0c81631 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 11 Aug 2025 19:59:20 +0200 Subject: [PATCH 726/786] Add chat command to toggle automation. --- Glamourer/Services/CommandService.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index d47f26f..d2feac0 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -96,6 +96,12 @@ public class CommandService : IDisposable, IApiService _config.Ephemeral.LockMainWindow = !_config.Ephemeral.LockMainWindow; _config.Ephemeral.Save(); return; + case "automation": + var newValue = !_config.EnableAutoDesigns; + _config.EnableAutoDesigns = newValue; + _autoDesignApplier.OnEnableAutoDesignsChanged(newValue); + _config.Save(); + return; default: _chat.Print("Use without argument to toggle the main window."); _chat.Print(new SeStringBuilder().AddText("Use ").AddPurple("/glamour").AddText(" instead of ").AddRed("/glamourer") From 4cc191cb25308629225847a3d27732e69eb34999 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 11 Aug 2025 20:53:44 +0200 Subject: [PATCH 727/786] Add viera ear visibility to application rules. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 84aaac0..381d342 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -270,11 +270,9 @@ public class DesignPanel DrawCrestApplication(); ImUtf8.IconDummy(); DrawMetaApplication(); - ImUtf8.IconDummy(); - DrawBonusSlotApplication(); } - ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); + ImGui.SameLine(210 * ImUtf8.GlobalScale + ImGui.GetStyle().ItemSpacing.X); using (var _ = ImRaii.Group()) { void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable slots) @@ -316,6 +314,9 @@ public class DesignPanel ImUtf8.IconDummy(); DrawParameterApplication(); + + ImUtf8.IconDummy(); + DrawBonusSlotApplication(); } } @@ -324,7 +325,7 @@ public class DesignPanel var enabled = _config.DeleteDesignModifier.IsActive(); bool? equip = null; bool? customize = null; - var size = new Vector2(200 * ImUtf8.GlobalScale, 0); + var size = new Vector2(210 * ImUtf8.GlobalScale, 0); if (ImUtf8.ButtonEx("Disable Everything"u8, "Disable application of everything, including any existing advanced dyes, advanced customizations, crests and wetness."u8, size, !enabled)) @@ -414,6 +415,7 @@ public class DesignPanel "Apply Hat Visibility", "Apply Visor State", "Apply Weapon Visibility", + "Apply Viera Ear Visibility", ]; private void DrawMetaApplication() From abf998a7273c7bcc9ff2ec262b6bc5954f660f50 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 12 Aug 2025 12:29:55 +0200 Subject: [PATCH 728/786] Update GameData --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index fd875c4..2f5e901 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit fd875c43ee910350107b2609809335285bd4ac0f +Subproject commit 2f5e901314444238ab3aa6c5043368622bca815a From 65f789880d06d94879a78d33ab39241362b8ed95 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 12 Aug 2025 10:32:03 +0000 Subject: [PATCH 729/786] [CI] Updating repo.json for 1.5.0.5 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 447bda2..513dd43 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.4", - "TestingAssemblyVersion": "1.5.0.4", + "AssemblyVersion": "1.5.0.5", + "TestingAssemblyVersion": "1.5.0.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.4/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c9b291c2f313a199766a410ee4711cd893907ffe Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 12 Aug 2025 14:46:56 +0200 Subject: [PATCH 730/786] Add new parameter to LoadWeapon hook. --- Glamourer/Interop/WeaponService.cs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Glamourer/Interop/WeaponService.cs b/Glamourer/Interop/WeaponService.cs index b0bdd19..54f318b 100644 --- a/Glamourer/Interop/WeaponService.cs +++ b/Glamourer/Interop/WeaponService.cs @@ -13,7 +13,7 @@ public unsafe class WeaponService : IDisposable private readonly WeaponLoading _event; private readonly ThreadLocal _inUpdate = new(() => false); - private readonly delegate* unmanaged[Stdcall] + private readonly delegate* unmanaged[Stdcall] _original; public WeaponService(WeaponLoading @event, IGameInteropProvider interop) @@ -22,7 +22,7 @@ public unsafe class WeaponService : IDisposable _loadWeaponHook = interop.HookFromAddress((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour); _original = - (delegate* unmanaged[Stdcall] < DrawDataContainer*, uint, ulong, byte, byte, byte, byte, void >) + (delegate* unmanaged[Stdcall] < DrawDataContainer*, uint, ulong, byte, byte, byte, byte, int, void >) DrawDataContainer.MemberFunctionPointers.LoadWeapon; _loadWeaponHook.Enable(); } @@ -36,13 +36,14 @@ public unsafe class WeaponService : IDisposable // redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one. // skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change) // unk4 seemed to be the same as unk1. + // unk5 is new in 7.30 and is checked at the beginning of the function to call some timeline related function. private delegate void LoadWeaponDelegate(DrawDataContainer* drawData, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, - byte skipGameObject, byte unk4); + byte skipGameObject, byte unk4, byte unk5); private readonly Hook _loadWeaponHook; private void LoadWeaponDetour(DrawDataContainer* drawData, uint slot, ulong weaponValue, byte redrawOnEquality, byte unk2, - byte skipGameObject, byte unk4) + byte skipGameObject, byte unk4, byte unk5) { if (!_inUpdate.Value) { @@ -64,21 +65,21 @@ public unsafe class WeaponService : IDisposable else if (weaponValue == actor.GetMainhand().Value && weaponValue != 0) _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); - _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); + _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4, unk5); if (tmpWeapon.Value != weapon.Value) { if (tmpWeapon.Skeleton.Id == 0) tmpWeapon.Stains = StainIds.None; - _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4); + _loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4, unk5); } Glamourer.Log.Excessive( - $"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}"); + $"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}, {unk5}"); } else { - _loadWeaponHook.Original(drawData, slot, weaponValue, redrawOnEquality, unk2, skipGameObject, unk4); + _loadWeaponHook.Original(drawData, slot, weaponValue, redrawOnEquality, unk2, skipGameObject, unk4, unk5); } } @@ -89,18 +90,18 @@ public unsafe class WeaponService : IDisposable { case EquipSlot.MainHand: _inUpdate.Value = true; - _original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); + _original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0, 0); _inUpdate.Value = false; return; case EquipSlot.OffHand: _inUpdate.Value = true; - _original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0); + _original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0, 0); _inUpdate.Value = false; return; case EquipSlot.BothHand: _inUpdate.Value = true; - _original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); - _original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0); + _original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0, 0); + _original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0, 0); _inUpdate.Value = false; return; } From 49d24df2e7e8812ce1a3b960ce355f06260b56a3 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 12 Aug 2025 12:53:44 +0000 Subject: [PATCH 731/786] [CI] Updating repo.json for 1.5.0.6 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 513dd43..eec3de8 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.5", - "TestingAssemblyVersion": "1.5.0.5", + "AssemblyVersion": "1.5.0.6", + "TestingAssemblyVersion": "1.5.0.6", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.5/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From e854386b2384c2f20cba1d1c149f3a70a81779f3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 16 Aug 2025 11:58:04 +0200 Subject: [PATCH 732/786] Update OtterGui --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index 5e32bb2..4a9b71a 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 5e32bb225e5fbb60ed8426ed887fd7e8a831ebae +Subproject commit 4a9b71a93e76aa5eed818542288329e34ec0dd89 From bb2ba0cf113fa6b0b60ef2571829dccfc9c21cfc Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 16 Aug 2025 11:58:41 +0200 Subject: [PATCH 733/786] Add glasses to advanced dye slot combo. --- Glamourer/Gui/Materials/MaterialDrawer.cs | 41 +++++++++++++++---- .../Gui/Tabs/AutomationTab/HumanNpcCombo.cs | 2 +- .../Interop/Material/MaterialValueIndex.cs | 19 +++++++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index ce50ff2..7c16372 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -18,7 +18,6 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) public const float GlossWidth = 100; public const float SpecularStrengthWidth = 125; - private EquipSlot _newSlot = EquipSlot.Head; private int _newMaterialIdx; private int _newRowIdx; private MaterialValueIndex _newKey = MaterialValueIndex.FromSlot(EquipSlot.Head); @@ -178,14 +177,42 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config) public sealed class MaterialSlotCombo; + private void DrawSlotCombo() + { + var width = ImUtf8.CalcTextSize(EquipSlot.OffHand.ToName()).X + ImGui.GetFrameHeightWithSpacing(); + ImGui.SetNextItemWidth(width); + using var combo = ImUtf8.Combo("##slot"u8, _newKey.SlotName()); + if (combo) + { + var currentSlot = _newKey.ToEquipSlot(); + foreach (var tmpSlot in EquipSlotExtensions.FullSlots) + { + if (ImUtf8.Selectable(tmpSlot.ToName(), tmpSlot == currentSlot) && currentSlot != tmpSlot) + _newKey = MaterialValueIndex.FromSlot(tmpSlot) with + { + MaterialIndex = (byte)_newMaterialIdx, + RowIndex = (byte)_newRowIdx, + }; + } + + var currentBonus = _newKey.ToBonusSlot(); + foreach (var bonusSlot in BonusExtensions.AllFlags) + { + if (ImUtf8.Selectable(bonusSlot.ToName(), bonusSlot == currentBonus) && bonusSlot != currentBonus) + _newKey = MaterialValueIndex.FromSlot(bonusSlot) with + { + MaterialIndex = (byte)_newMaterialIdx, + RowIndex = (byte)_newRowIdx, + }; + } + } + + ImUtf8.HoverTooltip("Choose a slot for an advanced dye row."u8); + } + public void DrawNew(Design design) { - if (EquipSlotCombo.Draw("##slot", "Choose a slot for an advanced dye row.", ref _newSlot)) - _newKey = MaterialValueIndex.FromSlot(_newSlot) with - { - MaterialIndex = (byte)_newMaterialIdx, - RowIndex = (byte)_newRowIdx, - }; + DrawSlotCombo(); ImUtf8.SameLineInner(); DrawMaterialIdxDrag(); ImUtf8.SameLineInner(); diff --git a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs index ce843c4..1d3e711 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/HumanNpcCombo.cs @@ -2,11 +2,11 @@ using Dalamud.Utility; using Dalamud.Bindings.ImGui; using OtterGui; -using OtterGui.Custom; using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Widgets; using Penumbra.GameData.DataContainers; +using OtterGui.Custom; namespace Glamourer.Gui.Tabs.AutomationTab; diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index 712b0d5..eb3f71f 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -50,6 +50,18 @@ public readonly record struct MaterialValueIndex( return idx > 2 ? Invalid : new MaterialValueIndex(DrawObjectType.Human, (byte)(idx + 16), 0, 0); } + public string SlotName() + { + var slot = ToEquipSlot(); + if (slot is not EquipSlot.Unknown) + return slot.ToName(); + + if (DrawObject is DrawObjectType.Human && SlotIndex is 16) + return BonusItemFlag.Glasses.ToString(); + + return EquipSlot.Unknown.ToName(); + } + public EquipSlot ToEquipSlot() => DrawObject switch { @@ -59,6 +71,13 @@ public readonly record struct MaterialValueIndex( _ => EquipSlot.Unknown, }; + public BonusItemFlag ToBonusSlot() + => DrawObject switch + { + DrawObjectType.Human when SlotIndex > 15 => ((uint)SlotIndex - 16).ToBonusSlot(), + _ => BonusItemFlag.Unknown, + }; + public unsafe bool TryGetModel(Actor actor, out Model model) { if (!actor.Valid) From 22e6c0655bcb67e0ee8a6c1a6b03fbdef5729815 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 16 Aug 2025 11:59:03 +0200 Subject: [PATCH 734/786] Add ear state when toggling meta application via button. --- Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 381d342..e3c965c 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -154,6 +154,7 @@ public class DesignPanel EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!)); } + ImGui.SameLine(); using (var _ = ImRaii.Group()) { @@ -403,6 +404,7 @@ public class DesignPanel _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, equip.Value); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.VisorState, equip.Value); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.WeaponState, equip.Value); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.EarState, equip.Value); } if (customize.HasValue) From b2b8f2b6ebc5fa54b4051c204ae11a05aa6e716a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 17 Aug 2025 10:43:26 +0200 Subject: [PATCH 735/786] Make glamourers visor toggle trigger static visors. (?!?) --- Glamourer/Interop/VisorService.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Glamourer/Interop/VisorService.cs b/Glamourer/Interop/VisorService.cs index 25823b6..83262e4 100644 --- a/Glamourer/Interop/VisorService.cs +++ b/Glamourer/Interop/VisorService.cs @@ -9,9 +9,9 @@ namespace Glamourer.Interop; public class VisorService : IDisposable { - private readonly PenumbraReloaded _penumbra; - private readonly IGameInteropProvider _interop; - public readonly VisorStateChanged Event; + private readonly PenumbraReloaded _penumbra; + private readonly IGameInteropProvider _interop; + public readonly VisorStateChanged Event; public VisorService(VisorStateChanged visorStateChanged, IGameInteropProvider interop, PenumbraReloaded penumbra) { @@ -36,7 +36,7 @@ public class VisorService : IDisposable /// The draw object. /// The desired state (true: toggled). /// Whether the state was changed. - public bool SetVisorState(Model human, bool on) + public unsafe bool SetVisorState(Model human, bool on) { if (!human.IsHuman) return false; @@ -46,6 +46,8 @@ public class VisorService : IDisposable if (oldState == on) return false; + // No clue what this flag does, but it's necessary for toggling static visors on or off, e.g. Alternate Cap (6229-1). + human.AsHuman->StateFlags |= (CharacterBase.StateFlag)0x40000000; SetupVisorDetour(human, human.GetArmor(EquipSlot.Head).Set.Id, on); return true; } From 3704051b0fc9f367c750ee9b636e937ddeaa3c9e Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 17 Aug 2025 08:45:55 +0000 Subject: [PATCH 736/786] [CI] Updating repo.json for 1.5.0.7 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index eec3de8..849d380 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.6", - "TestingAssemblyVersion": "1.5.0.6", + "AssemblyVersion": "1.5.0.7", + "TestingAssemblyVersion": "1.5.0.7", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.6/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 2c34154915a242694ffca33f7684c1e349044f71 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Aug 2025 18:08:53 +0200 Subject: [PATCH 737/786] Update API. --- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Penumbra.Api b/Penumbra.Api index c27a060..0a97029 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit c27a06004138f2ec80ccdb494bb6ddf6d39d2165 +Subproject commit 0a970295b2398683b1e49c46fd613541e2486210 diff --git a/Penumbra.GameData b/Penumbra.GameData index 2f5e901..15e7c8e 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 2f5e901314444238ab3aa6c5043368622bca815a +Subproject commit 15e7c8eb41867e6bbd3fe6a8885404df087bc7e7 From fb065549e9ea65ca817b41e7cfc0e6df0aec2180 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 22 Aug 2025 20:32:27 +0200 Subject: [PATCH 738/786] Add PCP Service. --- Glamourer/Configuration.cs | 59 ++++----- Glamourer/Glamourer.cs | 1 + Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 40 +++++- Glamourer/Interop/Penumbra/PenumbraService.cs | 63 +++++++--- Glamourer/Services/PcpService.cs | 119 ++++++++++++++++++ Glamourer/packages.lock.json | 20 +++ 6 files changed, 251 insertions(+), 51 deletions(-) create mode 100644 Glamourer/Services/PcpService.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index f128bdd..d266a55 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -40,34 +40,37 @@ public class Configuration : IPluginConfiguration, ISavable [JsonIgnore] public readonly EphemeralConfig Ephemeral; - public bool UseRestrictedGearProtection { get; set; } = false; - public bool OpenFoldersByDefault { get; set; } = false; - public bool AutoRedrawEquipOnChanges { get; set; } = false; - public bool EnableAutoDesigns { get; set; } = true; - public bool HideApplyCheckmarks { get; set; } = false; - public bool SmallEquip { get; set; } = false; - public bool UnlockedItemMode { get; set; } = false; - public byte DisableFestivals { get; set; } = 1; - public bool EnableGameContextMenu { get; set; } = true; - public bool HideWindowInCutscene { get; set; } = false; - public bool ShowAutomationSetEditing { get; set; } = true; - public bool ShowAllAutomatedApplicationRules { get; set; } = true; - public bool ShowUnlockedItemWarnings { get; set; } = true; - public bool RevertManualChangesOnZoneChange { get; set; } = false; - public bool ShowQuickBarInTabs { get; set; } = true; - public bool OpenWindowAtStart { get; set; } = false; - public bool ShowWindowWhenUiHidden { get; set; } = false; - public bool KeepAdvancedDyesAttached { get; set; } = true; - public bool ShowPalettePlusImport { get; set; } = true; - public bool UseFloatForColors { get; set; } = true; - public bool UseRgbForColors { get; set; } = true; - public bool ShowColorConfig { get; set; } = true; - public bool ChangeEntireItem { get; set; } = false; - public bool AlwaysApplyAssociatedMods { get; set; } = false; - public bool UseTemporarySettings { get; set; } = true; - public bool AllowDoubleClickToApply { get; set; } = false; - public bool RespectManualOnAutomationUpdate { get; set; } = false; - public bool PreventRandomRepeats { get; set; } = false; + public bool AttachToPcp { get; set; } = true; + public bool UseRestrictedGearProtection { get; set; } = false; + public bool OpenFoldersByDefault { get; set; } = false; + public bool AutoRedrawEquipOnChanges { get; set; } = false; + public bool EnableAutoDesigns { get; set; } = true; + public bool HideApplyCheckmarks { get; set; } = false; + public bool SmallEquip { get; set; } = false; + public bool UnlockedItemMode { get; set; } = false; + public byte DisableFestivals { get; set; } = 1; + public bool EnableGameContextMenu { get; set; } = true; + public bool HideWindowInCutscene { get; set; } = false; + public bool ShowAutomationSetEditing { get; set; } = true; + public bool ShowAllAutomatedApplicationRules { get; set; } = true; + public bool ShowUnlockedItemWarnings { get; set; } = true; + public bool RevertManualChangesOnZoneChange { get; set; } = false; + public bool ShowQuickBarInTabs { get; set; } = true; + public bool OpenWindowAtStart { get; set; } = false; + public bool ShowWindowWhenUiHidden { get; set; } = false; + public bool KeepAdvancedDyesAttached { get; set; } = true; + public bool ShowPalettePlusImport { get; set; } = true; + public bool UseFloatForColors { get; set; } = true; + public bool UseRgbForColors { get; set; } = true; + public bool ShowColorConfig { get; set; } = true; + public bool ChangeEntireItem { get; set; } = false; + public bool AlwaysApplyAssociatedMods { get; set; } = true; + public bool UseTemporarySettings { get; set; } = true; + public bool AllowDoubleClickToApply { get; set; } = false; + public bool RespectManualOnAutomationUpdate { get; set; } = false; + public bool PreventRandomRepeats { get; set; } = false; + public string PcpFolder { get; set; } = "PCP"; + public string PcpColor { get; set; } = ""; public DesignPanelFlag HideDesignPanel { get; set; } = 0; public DesignPanelFlag AutoExpandDesignPanel { get; set; } = 0; diff --git a/Glamourer/Glamourer.cs b/Glamourer/Glamourer.cs index f62085a..33c67d5 100644 --- a/Glamourer/Glamourer.cs +++ b/Glamourer/Glamourer.cs @@ -71,6 +71,7 @@ public class Glamourer : IDalamudPlugin sb.Append($"> **`Festival Easter-Eggs: `** {config.DisableFestivals}\n"); sb.Append($"> **`Apply Entire Weapon: `** {config.ChangeEntireItem}\n"); sb.Append($"> **`Apply Associated Mods:`** {config.AlwaysApplyAssociatedMods}\n"); + sb.Append($"> **`Attach to PCP: `** {config.AttachToPcp}\n"); sb.Append($"> **`Hidden Panels: `** {config.HideDesignPanel}\n"); sb.Append($"> **`Show QDB: `** {config.Ephemeral.ShowDesignQuickBar}\n"); sb.Append($"> **`QDB Hotkey: `** {config.ToggleQuickDesignBar}\n"); diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 1ccb079..0a84adc 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -1,4 +1,5 @@ -using Dalamud.Game.ClientState.Keys; +using Dalamud.Bindings.ImGui; +using Dalamud.Game.ClientState.Keys; using Dalamud.Interface; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; @@ -8,7 +9,8 @@ using Glamourer.Designs; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.PalettePlus; -using Dalamud.Bindings.ImGui; +using Glamourer.Services; +using OtterGui; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Widgets; @@ -27,7 +29,8 @@ public class SettingsTab( CollectionOverrideDrawer overrides, CodeDrawer codeDrawer, Glamourer glamourer, - AutoDesignApplier autoDesignApplier) + AutoDesignApplier autoDesignApplier, + PcpService pcpService) : ITab { private readonly VirtualKey[] _validKeys = keys.GetValidVirtualKeys().Prepend(VirtualKey.NO_KEY).ToArray(); @@ -89,6 +92,15 @@ public class SettingsTab( Checkbox("Auto-Reload Gear"u8, "Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection."u8, config.AutoRedrawEquipOnChanges, v => config.AutoRedrawEquipOnChanges = v); + Checkbox("Attach to PCP-Handling"u8, + "Add the actor's glamourer state when a PCP is created by Penumbra, and create a design and apply it if possible when a PCP is installed by Penumbra."u8, + config.AttachToPcp, pcpService.Set); + var active = config.DeleteDesignModifier.IsActive(); + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Delete all PCP Designs"u8, "Deletes all designs tagged with 'PCP' from the design list."u8, disabled: !active)) + pcpService.CleanPcpDesigns(); + if (!active) + ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"\nHold {config.DeleteDesignModifier} while clicking."); Checkbox("Revert Manual Changes on Zone Change"u8, "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone."u8, config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); @@ -124,6 +136,28 @@ public class SettingsTab( Checkbox("Reset Temporary Settings"u8, "Newly created designs will be configured to clear all advanced settings applied by Glamourer to the collection by default."u8, config.DefaultDesignSettings.ResetTemporarySettings, v => config.DefaultDesignSettings.ResetTemporarySettings = v); + + var tmp = config.PcpFolder; + ImGui.SetNextItemWidth(0.4f * ImGui.GetContentRegionAvail().X); + if (ImUtf8.InputText("##pcpFolder"u8, ref tmp)) + config.PcpFolder = tmp; + + if (ImGui.IsItemDeactivatedAfterEdit()) + config.Save(); + + ImGuiUtil.LabeledHelpMarker("Default PCP Organizational Folder", + "The folder any designs created due to penumbra character packs are moved to on creation.\nLeave blank to import into Root."); + + tmp = config.PcpColor; + ImGui.SetNextItemWidth(0.4f * ImGui.GetContentRegionAvail().X); + if (ImUtf8.InputText("##pcpColor"u8, ref tmp)) + config.PcpColor = tmp; + + if (ImGui.IsItemDeactivatedAfterEdit()) + config.Save(); + + ImGuiUtil.LabeledHelpMarker("Default PCP Design Color", + "The name of the color group any designs created due to penumbra character packs are assigned.\nLeave blank for no specific color assignment."); } private void DrawInterfaceSettings() diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index d66ddc4..123e989 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin; using Glamourer.Events; using Glamourer.State; +using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; @@ -49,6 +50,8 @@ public class PenumbraService : IDisposable private readonly EventSubscriber _creatingCharacterBase; private readonly EventSubscriber _createdCharacterBase; private readonly EventSubscriber _modSettingChanged; + private readonly EventSubscriber _pcpParsed; + private readonly EventSubscriber _pcpCreated; private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; @@ -101,6 +104,8 @@ public class PenumbraService : IDisposable _createdCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatedCharacterBase.Subscriber(pi); _creatingCharacterBase = global::Penumbra.Api.IpcSubscribers.CreatingCharacterBase.Subscriber(pi); _modSettingChanged = global::Penumbra.Api.IpcSubscribers.ModSettingChanged.Subscriber(pi); + _pcpCreated = global::Penumbra.Api.IpcSubscribers.CreatingPcp.Subscriber(pi); + _pcpParsed = global::Penumbra.Api.IpcSubscribers.ParsingPcp.Subscriber(pi); Reattach(); } @@ -135,6 +140,18 @@ public class PenumbraService : IDisposable remove => _modSettingChanged.Event -= value; } + public event Action PcpCreated + { + add => _pcpCreated.Event += value; + remove => _pcpCreated.Event -= value; + } + + public event Action PcpParsed + { + add => _pcpParsed.Event += value; + remove => _pcpParsed.Event -= value; + } + public Dictionary GetCollections() => Available ? _collections!.Invoke() : []; @@ -514,28 +531,30 @@ public class PenumbraService : IDisposable _clickSubscriber.Enable(); _creatingCharacterBase.Enable(); _createdCharacterBase.Enable(); + _pcpCreated.Enable(); + _pcpParsed.Enable(); _modSettingChanged.Enable(); - _collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface); - _collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface); - _redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface); - _checkCutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndexFunc(_pluginInterface).Invoke(); - _getGameObject = new global::Penumbra.Api.IpcSubscribers.GetGameObjectFromDrawObjectFunc(_pluginInterface).Invoke(); - _objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface); - _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); - _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface); - _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface); - _inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface); - _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface); - _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface); - _setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface); - _setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface); - _openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface); - _getChangedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItems(_pluginInterface); - _setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface); - _setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface); - _removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface); + _collectionByIdentifier = new global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier(_pluginInterface); + _collections = new global::Penumbra.Api.IpcSubscribers.GetCollections(_pluginInterface); + _redraw = new global::Penumbra.Api.IpcSubscribers.RedrawObject(_pluginInterface); + _checkCutsceneParent = new global::Penumbra.Api.IpcSubscribers.GetCutsceneParentIndexFunc(_pluginInterface).Invoke(); + _getGameObject = new global::Penumbra.Api.IpcSubscribers.GetGameObjectFromDrawObjectFunc(_pluginInterface).Invoke(); + _objectCollection = new global::Penumbra.Api.IpcSubscribers.GetCollectionForObject(_pluginInterface); + _getMods = new global::Penumbra.Api.IpcSubscribers.GetModList(_pluginInterface); + _currentCollection = new global::Penumbra.Api.IpcSubscribers.GetCollection(_pluginInterface); + _getCurrentSettings = new global::Penumbra.Api.IpcSubscribers.GetCurrentModSettings(_pluginInterface); + _inheritMod = new global::Penumbra.Api.IpcSubscribers.TryInheritMod(_pluginInterface); + _setMod = new global::Penumbra.Api.IpcSubscribers.TrySetMod(_pluginInterface); + _setModPriority = new global::Penumbra.Api.IpcSubscribers.TrySetModPriority(_pluginInterface); + _setModSetting = new global::Penumbra.Api.IpcSubscribers.TrySetModSetting(_pluginInterface); + _setModSettings = new global::Penumbra.Api.IpcSubscribers.TrySetModSettings(_pluginInterface); + _openModPage = new global::Penumbra.Api.IpcSubscribers.OpenMainWindow(_pluginInterface); + _getChangedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItems(_pluginInterface); + _setTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettings(_pluginInterface); + _setTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.SetTemporaryModSettingsPlayer(_pluginInterface); + _removeTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettings(_pluginInterface); _removeTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveTemporaryModSettingsPlayer(_pluginInterface); - _removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface); + _removeAllTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettings(_pluginInterface); _removeAllTemporaryModSettingsPlayer = new global::Penumbra.Api.IpcSubscribers.RemoveAllTemporaryModSettingsPlayer(_pluginInterface); _queryTemporaryModSettings = new global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettings(_pluginInterface); @@ -566,6 +585,8 @@ public class PenumbraService : IDisposable _creatingCharacterBase.Disable(); _createdCharacterBase.Disable(); _modSettingChanged.Disable(); + _pcpCreated.Disable(); + _pcpParsed.Disable(); if (Available) { _collectionByIdentifier = null; @@ -612,5 +633,7 @@ public class PenumbraService : IDisposable _initializedEvent.Dispose(); _disposedEvent.Dispose(); _modSettingChanged.Dispose(); + _pcpCreated.Dispose(); + _pcpParsed.Dispose(); } } diff --git a/Glamourer/Services/PcpService.cs b/Glamourer/Services/PcpService.cs new file mode 100644 index 0000000..3894981 --- /dev/null +++ b/Glamourer/Services/PcpService.cs @@ -0,0 +1,119 @@ +using Glamourer.Designs; +using Glamourer.Interop.Penumbra; +using Glamourer.State; +using Newtonsoft.Json.Linq; +using OtterGui.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Interop; + +namespace Glamourer.Services; + +public class PcpService : IRequiredService +{ + private readonly Configuration _config; + private readonly PenumbraService _penumbra; + private readonly ActorObjectManager _objects; + private readonly StateManager _state; + private readonly DesignConverter _designConverter; + private readonly DesignManager _designManager; + + public PcpService(Configuration config, PenumbraService penumbra, ActorObjectManager objects, StateManager state, + DesignConverter designConverter, DesignManager designManager) + { + _config = config; + _penumbra = penumbra; + _objects = objects; + _state = state; + _designConverter = designConverter; + _designManager = designManager; + + _config.AttachToPcp = !_config.AttachToPcp; + Set(!_config.AttachToPcp); + } + + public void CleanPcpDesigns() + { + var designs = _designManager.Designs.Where(d => d.Tags.Contains("PCP")).ToList(); + Glamourer.Log.Information($"[PCPService] Deleting {designs.Count} designs containing the tag PCP."); + foreach (var design in designs) + _designManager.Delete(design); + } + + public void Set(bool value) + { + if (value == _config.AttachToPcp) + return; + + _config.AttachToPcp = value; + _config.Save(); + if (value) + { + Glamourer.Log.Information("[PCPService] Attached to PCP handling."); + _penumbra.PcpCreated += OnPcpCreation; + _penumbra.PcpParsed += OnPcpParse; + } + else + { + Glamourer.Log.Information("[PCPService] Detached from PCP handling."); + _penumbra.PcpCreated -= OnPcpCreation; + _penumbra.PcpParsed -= OnPcpParse; + } + } + + private void OnPcpParse(JObject jObj, string modDirectory, Guid collection) + { + Glamourer.Log.Debug("[PCPService] Parsing PCP file."); + if (jObj["Glamourer"] is not JObject glamourer) + return; + + if (glamourer["Version"]!.ToObject() is not 1) + return; + + if (_designConverter.FromJObject(glamourer["Design"] as JObject, true, true) is not { } designBase) + return; + + var actorIdentifier = _objects.Actors.FromJson(jObj["Actor"] as JObject); + if (!actorIdentifier.IsValid) + return; + + var time = new DateTimeOffset(jObj["Time"]?.ToObject() ?? DateTime.UtcNow); + var design = _designManager.CreateClone(designBase, + $"{_config.PcpFolder}/{actorIdentifier} - {jObj["Note"]?.ToObject() ?? string.Empty}", true); + _designManager.AddTag(design, "PCP"); + _designManager.SetWriteProtection(design, true); + _designManager.AddMod(design, new Mod(modDirectory, modDirectory), new ModSettings([], 0, true, false, false)); + _designManager.ChangeDescription(design, $"PCP design created for {actorIdentifier} on {time}."); + _designManager.ChangeResetAdvancedDyes(design, true); + _designManager.SetQuickDesign(design, false); + _designManager.ChangeColor(design, _config.PcpColor); + + Glamourer.Log.Debug("[PCPService] Created PCP design."); + if (_state.GetOrCreate(actorIdentifier, _objects.TryGetValue(actorIdentifier, out var data) ? data.Objects[0] : Actor.Null, + out var state)) + { + _state.ApplyDesign(state!, design, ApplySettings.Manual); + Glamourer.Log.Debug($"[PCPService] Applied PCP design to {actorIdentifier.Incognito(null)}"); + } + } + + private void OnPcpCreation(JObject jObj, ushort index) + { + Glamourer.Log.Debug("[PCPService] Adding Glamourer data to PCP file."); + var actorIdentifier = _objects.Actors.FromJson(jObj["Actor"] as JObject); + if (!actorIdentifier.IsValid) + return; + + if (!_state.GetOrCreate(actorIdentifier, _objects.Objects[(int)index], out var state)) + { + Glamourer.Log.Debug($"[PCPService] Could not get or create state for actor {index}."); + return; + } + + var design = _designConverter.Convert(state, ApplicationRules.All); + jObj["Glamourer"] = new JObject() + { + ["Version"] = 1, + ["Design"] = design.JsonSerialize(), + }; + } +} diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json index f66e6e4..9e36276 100644 --- a/Glamourer/packages.lock.json +++ b/Glamourer/packages.lock.json @@ -24,6 +24,19 @@ "Vortice.DXGI": "3.4.2-beta" } }, + "FlatSharp.Compiler": { + "type": "Transitive", + "resolved": "7.9.0", + "contentHash": "MU6808xvdbWJ3Ev+5PKalqQuzvVbn1DzzQH8txRDHGFUNDvHjd+ejqpvnYc9BSJ8Qp8VjkkpJD8OzRYilbPp3A==" + }, + "FlatSharp.Runtime": { + "type": "Transitive", + "resolved": "7.9.0", + "contentHash": "Bm8+WqzEsWNpxqrD5x4x+zQ8dyINlToCreM5FI2oNSfUVc9U9ZB+qztX/jd8rlJb3r0vBSlPwVLpw0xBtPa3Vw==", + "dependencies": { + "System.Memory": "4.5.5" + } + }, "JetBrains.Annotations": { "type": "Transitive", "resolved": "2024.3.0", @@ -55,6 +68,11 @@ "SharpGen.Runtime": "2.1.2-beta" } }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" + }, "Vortice.DirectX": { "type": "Transitive", "resolved": "3.4.2-beta", @@ -95,6 +113,8 @@ "penumbra.gamedata": { "type": "Project", "dependencies": { + "FlatSharp.Compiler": "[7.9.0, )", + "FlatSharp.Runtime": "[7.9.0, )", "OtterGui": "[1.0.0, )", "Penumbra.Api": "[5.10.0, )", "Penumbra.String": "[1.0.6, )" From 4d4e4669dd30ca0e5029972bb70f7da6a13d4f6a Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 22 Aug 2025 18:34:49 +0000 Subject: [PATCH 739/786] [CI] Updating repo.json for testing_1.5.0.8 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 849d380..a40f1ff 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.5.0.7", - "TestingAssemblyVersion": "1.5.0.7", + "TestingAssemblyVersion": "1.5.0.8", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.0.8/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 487d3b9399f3065239bf89ffaaca396c418d87d2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 24 Aug 2025 15:49:24 +0200 Subject: [PATCH 740/786] Update PCP Service. --- Glamourer/Interop/Penumbra/PenumbraService.cs | 4 ++-- Glamourer/Services/PcpService.cs | 4 ++-- Penumbra.Api | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 123e989..4d70a3f 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -51,7 +51,7 @@ public class PenumbraService : IDisposable private readonly EventSubscriber _createdCharacterBase; private readonly EventSubscriber _modSettingChanged; private readonly EventSubscriber _pcpParsed; - private readonly EventSubscriber _pcpCreated; + private readonly EventSubscriber _pcpCreated; private global::Penumbra.Api.IpcSubscribers.GetCollectionsByIdentifier? _collectionByIdentifier; private global::Penumbra.Api.IpcSubscribers.GetCollections? _collections; @@ -140,7 +140,7 @@ public class PenumbraService : IDisposable remove => _modSettingChanged.Event -= value; } - public event Action PcpCreated + public event Action PcpCreated { add => _pcpCreated.Event += value; remove => _pcpCreated.Event -= value; diff --git a/Glamourer/Services/PcpService.cs b/Glamourer/Services/PcpService.cs index 3894981..3363172 100644 --- a/Glamourer/Services/PcpService.cs +++ b/Glamourer/Services/PcpService.cs @@ -96,7 +96,7 @@ public class PcpService : IRequiredService } } - private void OnPcpCreation(JObject jObj, ushort index) + private void OnPcpCreation(JObject jObj, ushort index, string path) { Glamourer.Log.Debug("[PCPService] Adding Glamourer data to PCP file."); var actorIdentifier = _objects.Actors.FromJson(jObj["Actor"] as JObject); @@ -110,7 +110,7 @@ public class PcpService : IRequiredService } var design = _designConverter.Convert(state, ApplicationRules.All); - jObj["Glamourer"] = new JObject() + jObj["Glamourer"] = new JObject { ["Version"] = 1, ["Design"] = design.JsonSerialize(), diff --git a/Penumbra.Api b/Penumbra.Api index 0a97029..297941b 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 0a970295b2398683b1e49c46fd613541e2486210 +Subproject commit 297941bc22300f4a8368f4d0177f62943eb69727 From 3eabe591dfb3e46b02e699f6e6381936961f3fb3 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 24 Aug 2025 13:59:02 +0000 Subject: [PATCH 741/786] [CI] Updating repo.json for testing_1.5.0.9 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index a40f1ff..02c85f5 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.5.0.7", - "TestingAssemblyVersion": "1.5.0.8", + "TestingAssemblyVersion": "1.5.0.9", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.0.8/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.0.9/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 389a8781d6007865891c548db2184aa157ee2b18 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 25 Aug 2025 10:39:32 +0200 Subject: [PATCH 742/786] Update library. --- Penumbra.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.Api b/Penumbra.Api index 297941b..af41b17 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 297941bc22300f4a8368f4d0177f62943eb69727 +Subproject commit af41b1787acef9df7dc83619fe81e63a36443ee5 From 835ba23935e9a785076bc98a8514776cc8eada5a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 25 Aug 2025 10:43:14 +0200 Subject: [PATCH 743/786] 1.5.1.0 --- Glamourer/Gui/GlamourerChangelog.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 31d1ce3..686d4a1 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -44,6 +44,7 @@ public class GlamourerChangelog Add1_3_8_0(Changelog); Add1_4_0_0(Changelog); Add1_5_0_0(Changelog); + Add1_5_1_0(Changelog); } private (int, ChangeLogDisplayType) ConfigData() @@ -64,6 +65,16 @@ public class GlamourerChangelog } } + private static void Add1_5_1_0(Changelog log) + => log.NextVersion("Version 1.5.1.0") + .RegisterHighlight("Added support for Penumbras PCP functionality to add the current state of the character as a design.") + .RegisterEntry("On import, a design for the PCP is created and, if possible, applied to the character.", 1) + .RegisterEntry("No automation is assigned.", 1) + .RegisterEntry("Finer control about this can be found in the settings.", 1) + .RegisterEntry("Fixed an issue with static visors not toggling through Glamourer (1.5.0.7).") + .RegisterEntry("The advanced dye slot combo now contains glasses (1.5.0.7).") + .RegisterEntry("Several fixes for patch-related issues (1.5.0.1 - 1.5.0.6"); + private static void Add1_5_0_0(Changelog log) => log.NextVersion("Version 1.5.0.0") .RegisterImportant("Updated for game version 7.30 and Dalamud API13, which uses a new GUI backend. Some things may not work as expected. Please let me know any issues you encounter.") From 654787fa0d536ec3d69114bd697cb6fe496ce060 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 25 Aug 2025 08:45:28 +0000 Subject: [PATCH 744/786] [CI] Updating repo.json for 1.5.1.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 02c85f5..ab6a415 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.0.7", - "TestingAssemblyVersion": "1.5.0.9", + "AssemblyVersion": "1.5.1.0", + "TestingAssemblyVersion": "1.5.1.0", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.0.7/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.0.9/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 6e62905fa70ef5851fb01859b887310c318ef269 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 26 Aug 2025 11:54:00 +0200 Subject: [PATCH 745/786] Fix staging incompatibility with CS. --- Glamourer/Interop/Material/MaterialService.cs | 4 ++-- Glamourer/Interop/Material/PrepareColorSet.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index a5f2b36..e232798 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -69,9 +69,9 @@ public static unsafe class MaterialService return null; var material = (MaterialResourceHandle*) model.AsCharacterBase->MaterialsSpan[index].Value; - if (material == null || material->ColorTable == null) + if (material == null || material->DataSet == null) return null; - return (ColorTable.Table*)material->ColorTable; + return (ColorTable.Table*)material->DataSet; } } diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index f52bb68..21a731b 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -69,13 +69,13 @@ public sealed unsafe class PrepareColorSet public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds, out ColorTable.Table table) { - if (material->ColorTable == null) + if (material->DataSet == null) { table = default; return false; } - var newTable = *(ColorTable.Table*)material->ColorTable; + var newTable = *(ColorTable.Table*)material->DataSet; if (GetDyeTable(material, out var dyeTable)) { if (stainIds.Stain1.Id != 0) From 889f01a7243b0c6af7b3483947ea2cea8b01add4 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 26 Aug 2025 09:58:08 +0000 Subject: [PATCH 746/786] [CI] Updating repo.json for 1.5.1.1 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index ab6a415..78132bf 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.1.0", - "TestingAssemblyVersion": "1.5.1.0", + "AssemblyVersion": "1.5.1.1", + "TestingAssemblyVersion": "1.5.1.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.0/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 8e1745d67aa87a80a5491be8477a3ea26308d985 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 28 Aug 2025 18:47:51 +0200 Subject: [PATCH 747/786] Once more with feeling --- Glamourer/Interop/Material/MaterialService.cs | 3 +-- Glamourer/Interop/Material/PrepareColorSet.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Glamourer/Interop/Material/MaterialService.cs b/Glamourer/Interop/Material/MaterialService.cs index e232798..4893e14 100644 --- a/Glamourer/Interop/Material/MaterialService.cs +++ b/Glamourer/Interop/Material/MaterialService.cs @@ -1,6 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; -using Lumina.Data.Files; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Interop; using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture; @@ -69,7 +68,7 @@ public static unsafe class MaterialService return null; var material = (MaterialResourceHandle*) model.AsCharacterBase->MaterialsSpan[index].Value; - if (material == null || material->DataSet == null) + if (material == null || material->DataSet == null || material->DataSetSize < sizeof(ColorTable.Table) || !material->HasColorTable) return null; return (ColorTable.Table*)material->DataSet; diff --git a/Glamourer/Interop/Material/PrepareColorSet.cs b/Glamourer/Interop/Material/PrepareColorSet.cs index 21a731b..821a152 100644 --- a/Glamourer/Interop/Material/PrepareColorSet.cs +++ b/Glamourer/Interop/Material/PrepareColorSet.cs @@ -69,7 +69,7 @@ public sealed unsafe class PrepareColorSet public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds, out ColorTable.Table table) { - if (material->DataSet == null) + if (material->DataSet == null || material->DataSetSize < sizeof(ColorTable.Table) || !material->HasColorTable) { table = default; return false; From 414bd8bee7d1d738290014d726b730baae0f8385 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 28 Aug 2025 16:52:43 +0000 Subject: [PATCH 748/786] [CI] Updating repo.json for 1.5.1.2 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 78132bf..9f7e922 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.1.1", - "TestingAssemblyVersion": "1.5.1.1", + "AssemblyVersion": "1.5.1.2", + "TestingAssemblyVersion": "1.5.1.2", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.1/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From c420b1f18062ce5d0c41c06126b107a576dc94ab Mon Sep 17 00:00:00 2001 From: Bracket <31086695+Bracket416@users.noreply.github.com> Date: Sun, 31 Aug 2025 23:04:41 +0100 Subject: [PATCH 749/786] Update .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 137c8ba..26b4730 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,5 +16,5 @@ branch = main [submodule "Glamourer.Api"] path = Glamourer.Api - url = https://github.com/Ottermandias/Glamourer.Api.git + url = https://github.com/Bracket416/Glamourer.Api branch = main From da1db70635787c00818c3468094cb02ed9f52a82 Mon Sep 17 00:00:00 2001 From: Bracket <31086695+Bracket416@users.noreply.github.com> Date: Sun, 31 Aug 2025 23:06:23 +0100 Subject: [PATCH 750/786] Update IpcProviders.cs --- Glamourer/Api/IpcProviders.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 2701f18..1dc1cc8 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -53,6 +53,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.RevertState.Provider(pi, api.State), IpcSubscribers.RevertStateName.Provider(pi, api.State), IpcSubscribers.UnlockState.Provider(pi, api.State), + IpcSubscribers.IsUnlocked.Provider(pi, api.State), IpcSubscribers.UnlockStateName.Provider(pi, api.State), IpcSubscribers.UnlockAll.Provider(pi, api.State), IpcSubscribers.RevertToAutomation.Provider(pi, api.State), @@ -75,3 +76,4 @@ public sealed class IpcProviders : IDisposable, IApiService _disposedProvider.Dispose(); } } + From c31f6c19a604e40f178ba20985333982f3637faa Mon Sep 17 00:00:00 2001 From: Bracket <31086695+Bracket416@users.noreply.github.com> Date: Sun, 31 Aug 2025 23:06:54 +0100 Subject: [PATCH 751/786] Update StateApi.cs --- Glamourer/Api/StateApi.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 68c593b..f7e2de1 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -180,6 +180,16 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } + public (GlamourerApiEc, bool?) IsUnlocked(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), null); + if (state == null) + return (ApiHelpers.Return(GlamourerApiEc.InvalidState, args), null); // Possibly, the error type could be changed. I just looked at what was available. + return (ApiHelpers.Return(GlamourerApiEc.Success, args), state.CanUnlock(key)); + } + public GlamourerApiEc UnlockStateName(string playerName, uint key) { var args = ApiHelpers.Args("Name", playerName, "Key", key); From 6a34d41f6ad37a33c10340be6ca92d5ed894d4fb Mon Sep 17 00:00:00 2001 From: Bracket <31086695+Bracket416@users.noreply.github.com> Date: Mon, 1 Sep 2025 11:27:11 +0100 Subject: [PATCH 752/786] Update .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 26b4730..49b25bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,5 +16,5 @@ branch = main [submodule "Glamourer.Api"] path = Glamourer.Api - url = https://github.com/Bracket416/Glamourer.Api + url = https://github.com/Ottermandias/Glamourer.Api branch = main From c62c3c4eea6d1ddee5f908ef0444ceffddfbafaf Mon Sep 17 00:00:00 2001 From: Bracket <31086695+Bracket416@users.noreply.github.com> Date: Mon, 1 Sep 2025 11:27:35 +0100 Subject: [PATCH 753/786] Update .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 49b25bb..137c8ba 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,5 +16,5 @@ branch = main [submodule "Glamourer.Api"] path = Glamourer.Api - url = https://github.com/Ottermandias/Glamourer.Api + url = https://github.com/Ottermandias/Glamourer.Api.git branch = main From 0442fb7b607287abc39fbf5510dcead05e85aa29 Mon Sep 17 00:00:00 2001 From: Bracket <31086695+Bracket416@users.noreply.github.com> Date: Tue, 2 Sep 2025 02:02:08 +0100 Subject: [PATCH 754/786] Update IpcProviders.cs --- Glamourer/Api/IpcProviders.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 1dc1cc8..9be723c 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -53,7 +53,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.RevertState.Provider(pi, api.State), IpcSubscribers.RevertStateName.Provider(pi, api.State), IpcSubscribers.UnlockState.Provider(pi, api.State), - IpcSubscribers.IsUnlocked.Provider(pi, api.State), + IpcSubscribers.CanUnlock.Provider(pi, api.State), IpcSubscribers.UnlockStateName.Provider(pi, api.State), IpcSubscribers.UnlockAll.Provider(pi, api.State), IpcSubscribers.RevertToAutomation.Provider(pi, api.State), @@ -77,3 +77,4 @@ public sealed class IpcProviders : IDisposable, IApiService } } + From 8f362c5121f645cd5120e8e3c5a40787b99730ed Mon Sep 17 00:00:00 2001 From: Bracket <31086695+Bracket416@users.noreply.github.com> Date: Tue, 2 Sep 2025 02:02:52 +0100 Subject: [PATCH 755/786] Update StateApi.cs --- Glamourer/Api/StateApi.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index f7e2de1..97d7fcc 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -180,14 +180,18 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } - public (GlamourerApiEc, bool?) IsUnlocked(int objectIndex, uint key) + public GlamourerApiEc CanUnlock(int objectIndex, uint key, out bool isLocked, out bool canUnlock) { var args = ApiHelpers.Args("Index", objectIndex, "Key", key); + isLocked = false; // These seem like reasonable defaults. + canUnlock = false; if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success) - return (ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args), null); + return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); if (state == null) - return (ApiHelpers.Return(GlamourerApiEc.InvalidState, args), null); // Possibly, the error type could be changed. I just looked at what was available. - return (ApiHelpers.Return(GlamourerApiEc.Success, args), state.CanUnlock(key)); + return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); // Possibly, the error type could be changed. I just looked at what was available. + isLocked = state.IsLocked; + canUnlock = state.CanUnlock(key); + return ApiHelpers.Return(GlamourerApiEc.Success, args); } public GlamourerApiEc UnlockStateName(string playerName, uint key) From 0a9693daea99f79c44b2a69e1bfb006573a721a0 Mon Sep 17 00:00:00 2001 From: Ottermandias <70807659+Ottermandias@users.noreply.github.com> Date: Mon, 15 Sep 2025 20:29:13 +0200 Subject: [PATCH 756/786] Update CodeService.cs --- Glamourer/Services/CodeService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Glamourer/Services/CodeService.cs b/Glamourer/Services/CodeService.cs index af2e88b..4a82f0e 100644 --- a/Glamourer/Services/CodeService.cs +++ b/Glamourer/Services/CodeService.cs @@ -50,7 +50,8 @@ public class CodeService | CodeFlag.OopsMiqote | CodeFlag.OopsRoegadyn | CodeFlag.OopsAuRa - | CodeFlag.OopsHrothgar; + | CodeFlag.OopsHrothgar + | CodeFlag.OopsViera; public const CodeFlag FullCodes = CodeFlag.Face | CodeFlag.Manderville | CodeFlag.Smiles; @@ -250,3 +251,4 @@ public class CodeService _ => (false, 0, string.Empty, string.Empty, string.Empty), }; } + From 20914bc064ba9f1e90ccbec2c1525c6672af6c15 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 26 Sep 2025 19:11:07 -0700 Subject: [PATCH 757/786] Add ReapplyState & ReapplyStateName with Helpers. --- Glamourer/Api/StateApi.cs | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 68c593b..73e9540 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -123,6 +123,48 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } + public GlamourerApiEc ReapplyState(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); + + Reapply(_objects.Objects[objectIndex], state, key, flags); + return ApiHelpers.Return(GlamourerApiEc.Success, args); + } + + public GlamourerApiEc ReapplyStateName(string playerName, uint key, ApplyFlag flags) + { + var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags); + var states = _helpers.FindExistingStates(playerName); + + var any = false; + var anyReapplied = false; + foreach (var state in states) + { + any = true; + if (!state.CanUnlock(key)) + continue; + + anyReapplied = true; + anyReapplied |= Reapply(state, key, flags) is GlamourerApiEc.Success; + } + + if (any) + ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + if (!anyReapplied) + return ApiHelpers.Return(GlamourerApiEc.InvalidKey, 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); @@ -265,6 +307,24 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable ApiHelpers.Lock(state, key, flags); } + private GlamourerApiEc Reapply(ActorState state, uint key, ApplyFlag flags) + { + if (!_objects.TryGetValue(state.Identifier, out var actors) || !actors.Valid) + return GlamourerApiEc.ActorNotFound; + + foreach (var actor in actors.Objects) + Reapply(actor, state, key, flags); + + return GlamourerApiEc.Success; + } + + private void Reapply(Actor actor, ActorState state, uint key, ApplyFlag flags) + { + var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; + _stateManager.ReapplyState(actor, state, false, source, true); + ApiHelpers.Lock(state, key, flags); + } + private void Revert(ActorState state, uint key, ApplyFlag flags) { var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; From 44345b942910b54c62df0a6702a703400c2060bf Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 26 Sep 2025 19:11:39 -0700 Subject: [PATCH 758/786] Init providers from API --- Glamourer/Api/IpcProviders.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 2701f18..6d4b5eb 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -50,6 +50,8 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.GetStateBase64Name.Provider(pi, api.State), IpcSubscribers.ApplyState.Provider(pi, api.State), IpcSubscribers.ApplyStateName.Provider(pi, api.State), + IpcSubscribers.ReapplyState.Provider(pi, api.State), + IpcSubscribers.ReapplyStateName.Provider(pi, api.State), IpcSubscribers.RevertState.Provider(pi, api.State), IpcSubscribers.RevertStateName.Provider(pi, api.State), IpcSubscribers.UnlockState.Provider(pi, api.State), From d6c36ca4f7ada7b801100f1477115f555f46cb3a Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Fri, 26 Sep 2025 19:12:02 -0700 Subject: [PATCH 759/786] Add IPC Calls to IPC Tester. --- Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index e97d337..232c48e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -138,6 +138,14 @@ public class StateIpcTester : IUiService, IDisposable if (ImUtf8.Button("Apply Base64##Name"u8)) _lastError = new ApplyStateName(_pluginInterface).Invoke(_base64State, _gameObjectName, _key, _flags); + IpcTesterHelpers.DrawIntro(ReapplyState.Label); + if (ImUtf8.Button("Reapply##Idx"u8)) + _lastError = new ReapplyState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); + + IpcTesterHelpers.DrawIntro(ReapplyStateName.Label); + if (ImUtf8.Button("Reapply##Name"u8)) + _lastError = new ReapplyStateName(_pluginInterface).Invoke(_gameObjectName, _key, _flags); + IpcTesterHelpers.DrawIntro(RevertState.Label); if (ImUtf8.Button("Revert##Idx"u8)) _lastError = new RevertState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags); From c3469a1687285f5278182600877201b76b9b3d97 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 28 Sep 2025 23:55:44 +0200 Subject: [PATCH 760/786] Fix facewear advanced dyes, fix backup service not running in task, update libraries. --- Glamourer.Api | 2 +- Glamourer/Designs/DesignManager.cs | 1 - Glamourer/Glamourer.csproj | 2 +- .../Gui/Tabs/DesignTab/MultiDesignPanel.cs | 1 - Glamourer/Interop/Material/MaterialManager.cs | 23 ++++++++++++++----- Glamourer/Services/BackupService.cs | 10 ++++++-- Glamourer/packages.lock.json | 6 ++--- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 11 files changed, 34 insertions(+), 19 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 54c1944..7e8505c 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 54c1944dc7db704733b4788520e494761bb0b58e +Subproject commit 7e8505cd6f8dbc5bcf41b72e16785d62b4d218f3 diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index 3ddfefd..e3648a4 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -8,7 +8,6 @@ using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Extensions; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 86ae713..d7e62a9 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,4 +1,4 @@ - + Glamourer Glamourer diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 1cdb171..4c7c103 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -3,7 +3,6 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop.Material; using Dalamud.Bindings.ImGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using static Glamourer.Gui.Tabs.HeaderDrawer; diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 5b731b0..43e500b 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -62,7 +62,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable var drawData = type switch { - MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, slotId), + MaterialValueIndex.DrawObjectType.Human => GetTempSlot((Human*)characterBase, (HumanSlot)slotId), _ => GetTempSlot((Weapon*)characterBase), }; var mode = PrepareColorSet.GetMode(material); @@ -192,13 +192,24 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable } /// We need to get the temporary set, variant and stain that is currently being set if it is available. - private static CharacterWeapon GetTempSlot(Human* human, byte slotId) + private static CharacterWeapon GetTempSlot(Human* human, HumanSlot slotId) { - if (human->ChangedEquipData == null) - return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0); + if (human->ChangedEquipData is null) + return slotId.ToSpecificEnum() switch + { + EquipSlot slot => ((Model)human).GetArmor(slot).ToWeapon(0), + BonusItemFlag bonus => ((Model)human).GetBonus(bonus).ToWeapon(0), + _ => default, + }; - var item = (ChangedEquipData*)human->ChangedEquipData + slotId; - return ((CharacterArmor*)item)->ToWeapon(0); + if (!slotId.ToSlotIndex(out var index)) + return default; + + var item = (ChangedEquipData*)human->ChangedEquipData + index; + if (index < 10) + return ((CharacterArmor*)item)->ToWeapon(0); + + return new CharacterWeapon(item->BonusModel, 0, item->BonusVariant, StainIds.None); } /// diff --git a/Glamourer/Services/BackupService.cs b/Glamourer/Services/BackupService.cs index 3abf13a..511cca6 100644 --- a/Glamourer/Services/BackupService.cs +++ b/Glamourer/Services/BackupService.cs @@ -1,9 +1,10 @@ using OtterGui.Classes; using OtterGui.Log; +using OtterGui.Services; namespace Glamourer.Services; -public class BackupService +public class BackupService : IAsyncService { private readonly Logger _logger; private readonly DirectoryInfo _configDirectory; @@ -14,7 +15,7 @@ public class BackupService _logger = logger; _fileNames = GlamourerFiles(fileNames); _configDirectory = new DirectoryInfo(fileNames.ConfigDirectory); - Backup.CreateAutomaticBackup(logger, _configDirectory, _fileNames); + Awaiter = Task.Run(() => Backup.CreateAutomaticBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), _fileNames)); } /// Create a permanent backup with a given name for migrations. @@ -40,4 +41,9 @@ public class BackupService return list; } + + public Task Awaiter { get; } + + public bool Finished + => Awaiter.IsCompletedSuccessfully; } diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json index 9e36276..8ac1fe4 100644 --- a/Glamourer/packages.lock.json +++ b/Glamourer/packages.lock.json @@ -4,9 +4,9 @@ "net9.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[13.0.0, )", - "resolved": "13.0.0", - "contentHash": "Mb3cUDSK/vDPQ8gQIeuCw03EMYrej1B4J44a1AvIJ9C759p9XeqdU9Hg4WgOmlnlPe0G7ILTD32PKSUpkQNa8w==" + "requested": "[13.1.0, )", + "resolved": "13.1.0", + "contentHash": "XdoNhJGyFby5M/sdcRhnc5xTop9PHy+H50PTWpzLhJugjB19EDBiHD/AsiDF66RETM+0qKUdJBZrNuebn7qswQ==" }, "DotNet.ReproducibleBuilds": { "type": "Direct", diff --git a/OtterGui b/OtterGui index 4a9b71a..f354444 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 4a9b71a93e76aa5eed818542288329e34ec0dd89 +Subproject commit f354444776591ae423e2d8374aae346308d81424 diff --git a/Penumbra.Api b/Penumbra.Api index af41b17..648b6fc 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit af41b1787acef9df7dc83619fe81e63a36443ee5 +Subproject commit 648b6fc2ce600a95ab2b2ced27e1639af2b04502 diff --git a/Penumbra.GameData b/Penumbra.GameData index 15e7c8e..a34f314 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 15e7c8eb41867e6bbd3fe6a8885404df087bc7e7 +Subproject commit a34f314cbc1053a09923a0d64aa3173044d32101 diff --git a/Penumbra.String b/Penumbra.String index 878acce..c8611a0 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 878acce46e286867d6ef1f8ecedb390f7bac34fd +Subproject commit c8611a0c546b6b2ec29214ab319fc2c38fe74793 From 48bef12555b5d1d0ca6d6013f9192de963d565f0 Mon Sep 17 00:00:00 2001 From: Cordelia Mist Date: Sun, 5 Oct 2025 10:31:41 -0700 Subject: [PATCH 761/786] Optional Addition: Include IPC to get the current state of Auto-Reload Gear, and an IPC Event call for when the option changes. - Should help clear up ambiguity with any external plugins intending to call ReapplyState on a mod-change to themselves, to know if Glamourer has it handled for them. --- Glamourer/Api/GlamourerApi.cs | 5 +++- Glamourer/Api/IpcProviders.cs | 2 ++ Glamourer/Api/StateApi.cs | 13 +++++++-- Glamourer/Events/AutoRedrawChanged.cs | 16 +++++++++++ .../DebugTab/IpcTester/IpcTesterHelpers.cs | 3 --- .../Tabs/DebugTab/IpcTester/IpcTesterPanel.cs | 7 +++++ .../Tabs/DebugTab/IpcTester/StateIpcTester.cs | 27 ++++++++++++++----- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 8 +++++- 8 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 Glamourer/Events/AutoRedrawChanged.cs diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 14c0512..1942d65 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -3,7 +3,7 @@ using OtterGui.Services; namespace Glamourer.Api; -public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService +public class GlamourerApi(Configuration config, DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; public const int CurrentApiVersionMinor = 6; @@ -11,6 +11,9 @@ public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); + public bool AutoReloadGearEnabled + => config.AutoRedrawEquipOnChanges; + public IGlamourerApiDesigns Designs => designs; diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 6d4b5eb..3b7fb08 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -22,6 +22,7 @@ public sealed class IpcProviders : IDisposable, IApiService 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.AutoReloadGearEnabled.Provider(pi, api), IpcSubscribers.GetDesignList.Provider(pi, api.Designs), IpcSubscribers.GetDesignListExtended.Provider(pi, api.Designs), @@ -59,6 +60,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.UnlockAll.Provider(pi, api.State), IpcSubscribers.RevertToAutomation.Provider(pi, api.State), IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), + IpcSubscribers.AutoReloadGearChanged.Provider(pi, api.State), IpcSubscribers.StateChanged.Provider(pi, api.State), IpcSubscribers.StateChangedWithType.Provider(pi, api.State), IpcSubscribers.StateFinalized.Provider(pi, api.State), diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 73e9540..4ed2d57 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -20,6 +20,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private readonly Configuration _config; private readonly AutoDesignApplier _autoDesigns; private readonly ActorObjectManager _objects; + private readonly AutoRedrawChanged _autoRedraw; private readonly StateChanged _stateChanged; private readonly StateFinalized _stateFinalized; private readonly GPoseService _gPose; @@ -30,6 +31,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable Configuration config, AutoDesignApplier autoDesigns, ActorObjectManager objects, + AutoRedrawChanged autoRedraw, StateChanged stateChanged, StateFinalized stateFinalized, GPoseService gPose) @@ -40,9 +42,11 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable _config = config; _autoDesigns = autoDesigns; _objects = objects; + _autoRedraw = autoRedraw; _stateChanged = stateChanged; _stateFinalized = stateFinalized; _gPose = gPose; + _autoRedraw.Subscribe(OnAutoRedrawChange, AutoRedrawChanged.Priority.StateApi); _stateChanged.Subscribe(OnStateChanged, Events.StateChanged.Priority.GlamourerIpc); _stateFinalized.Subscribe(OnStateFinalized, Events.StateFinalized.Priority.StateApi); _gPose.Subscribe(OnGPoseChange, GPoseService.Priority.StateApi); @@ -50,6 +54,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public void Dispose() { + _autoRedraw.Unsubscribe(OnAutoRedrawChange); _stateChanged.Unsubscribe(OnStateChanged); _stateFinalized.Unsubscribe(OnStateFinalized); _gPose.Unsubscribe(OnGPoseChange); @@ -293,6 +298,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } + public event Action? AutoReloadGearChanged; public event Action? StateChanged; public event Action? StateChangedWithType; public event Action? StateFinalized; @@ -385,8 +391,8 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable }; } - private void OnGPoseChange(bool gPose) - => GPoseChanged?.Invoke(gPose); + private void OnAutoRedrawChange(bool autoReload) + => AutoReloadGearChanged?.Invoke(autoReload); private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5) { @@ -407,4 +413,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable foreach (var actor in actors.Objects) StateFinalized.Invoke(actor.Address, type); } + + private void OnGPoseChange(bool gPose) + => GPoseChanged?.Invoke(gPose); } diff --git a/Glamourer/Events/AutoRedrawChanged.cs b/Glamourer/Events/AutoRedrawChanged.cs new file mode 100644 index 0000000..a8dd03a --- /dev/null +++ b/Glamourer/Events/AutoRedrawChanged.cs @@ -0,0 +1,16 @@ +using OtterGui.Classes; + +namespace Glamourer.Events; + +/// +/// Triggered when the auto-reload gear setting is changed in glamourer configuration. +/// +public sealed class AutoRedrawChanged() + : EventWrapper(nameof(AutoRedrawChanged)) +{ + public enum Priority + { + /// + StateApi = int.MinValue, + } +} \ No newline at end of file diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs index dbcb30c..61dad53 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterHelpers.cs @@ -1,8 +1,5 @@ using Glamourer.Api.Enums; -using Glamourer.Designs; using Dalamud.Bindings.ImGui; -using OtterGui; -using static Penumbra.GameData.Files.ShpkFile; namespace Glamourer.Gui.Tabs.DebugTab.IpcTester; diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs index f4e6925..22c7597 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/IpcTesterPanel.cs @@ -33,6 +33,11 @@ public class IpcTesterPanel( ImGui.SameLine(); ImGui.TextUnformatted($"({major}.{minor:D4})"); + ImGui.TextUnformatted(AutoReloadGearEnabled.Label); + var autoRedraw = new AutoReloadGearEnabled(pluginInterface).Invoke(); + ImGui.SameLine(); + ImGui.TextUnformatted(autoRedraw ? "Enabled" : "Disabled"); + designs.Draw(); items.Draw(); state.Draw(); @@ -49,6 +54,7 @@ public class IpcTesterPanel( return; Glamourer.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester."); + state.AutoRedrawChanged.Enable(); state.GPoseChanged.Enable(); state.StateChanged.Enable(); state.StateFinalized.Enable(); @@ -72,6 +78,7 @@ public class IpcTesterPanel( Glamourer.Log.Debug("[IPCTester] Unsubscribed from IPC events for IPC tester."); _subscribed = false; + state.AutoRedrawChanged.Disable(); state.GPoseChanged.Disable(); state.StateChanged.Disable(); state.StateFinalized.Disable(); diff --git a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs index 232c48e..6fb9d68 100644 --- a/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs +++ b/Glamourer/Gui/Tabs/DebugTab/IpcTester/StateIpcTester.cs @@ -1,11 +1,11 @@ -using Dalamud.Interface; +using Dalamud.Bindings.ImGui; +using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Plugin; using Glamourer.Api.Enums; using Glamourer.Api.Helpers; using Glamourer.Api.IpcSubscribers; using Glamourer.Designs; -using Dalamud.Bindings.ImGui; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; @@ -31,6 +31,10 @@ public class StateIpcTester : IUiService, IDisposable private string _base64State = string.Empty; private string? _getStateString; + public readonly EventSubscriber AutoRedrawChanged; + private bool _lastAutoRedrawChangeValue; + private DateTime _lastAutoRedrawChangeTime; + public readonly EventSubscriber StateChanged; private nint _lastStateChangeActor; private ByteString _lastStateChangeName = ByteString.Empty; @@ -51,10 +55,12 @@ public class StateIpcTester : IUiService, IDisposable public StateIpcTester(IDalamudPluginInterface pluginInterface) { - _pluginInterface = pluginInterface; - StateChanged = StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); - StateFinalized = Api.IpcSubscribers.StateFinalized.Subscriber(_pluginInterface, OnStateFinalized); - GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange); + _pluginInterface = pluginInterface; + AutoRedrawChanged = AutoReloadGearChanged.Subscriber(_pluginInterface, OnAutoRedrawChanged); + StateChanged = StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged); + StateFinalized = Api.IpcSubscribers.StateFinalized.Subscriber(_pluginInterface, OnStateFinalized); + GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange); + AutoRedrawChanged.Disable(); StateChanged.Disable(); StateFinalized.Disable(); GPoseChanged.Disable(); @@ -62,6 +68,7 @@ public class StateIpcTester : IUiService, IDisposable public void Dispose() { + AutoRedrawChanged.Dispose(); StateChanged.Dispose(); StateFinalized.Dispose(); GPoseChanged.Dispose(); @@ -83,6 +90,8 @@ public class StateIpcTester : IUiService, IDisposable IpcTesterHelpers.DrawIntro("Last Error"); ImGui.TextUnformatted(_lastError.ToString()); + IpcTesterHelpers.DrawIntro("Last Auto Redraw Change"); + ImGui.TextUnformatted($"{_lastAutoRedrawChangeValue} at {_lastAutoRedrawChangeTime.ToLocalTime().TimeOfDay}"); IpcTesterHelpers.DrawIntro("Last State Change"); PrintChangeName(); IpcTesterHelpers.DrawIntro("Last State Finalization"); @@ -233,6 +242,12 @@ public class StateIpcTester : IUiService, IDisposable ImUtf8.Text($"at {_lastStateFinalizeTime.ToLocalTime().TimeOfDay}"); } + private void OnAutoRedrawChanged(bool value) + { + _lastAutoRedrawChangeValue = value; + _lastAutoRedrawChangeTime = DateTime.UtcNow; + } + private void OnStateChanged(nint actor, StateChangeType type) { _lastStateChangeActor = actor; diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 0a84adc..82bdd54 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -6,6 +6,7 @@ using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; using Glamourer.Automation; using Glamourer.Designs; +using Glamourer.Events; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.PalettePlus; @@ -30,6 +31,7 @@ public class SettingsTab( CodeDrawer codeDrawer, Glamourer glamourer, AutoDesignApplier autoDesignApplier, + AutoRedrawChanged autoRedraw, PcpService pcpService) : ITab { @@ -91,7 +93,11 @@ public class SettingsTab( config.DisableFestivals == 0, v => config.DisableFestivals = v ? (byte)0 : (byte)2); Checkbox("Auto-Reload Gear"u8, "Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection."u8, - config.AutoRedrawEquipOnChanges, v => config.AutoRedrawEquipOnChanges = v); + config.AutoRedrawEquipOnChanges, v => + { + config.AutoRedrawEquipOnChanges = v; + autoRedraw.Invoke(v); + }); Checkbox("Attach to PCP-Handling"u8, "Add the actor's glamourer state when a PCP is created by Penumbra, and create a design and apply it if possible when a PCP is installed by Penumbra."u8, config.AttachToPcp, pcpService.Set); From 76ed347cbfbe9db2a314e9d42a24f9a3917a613a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 7 Oct 2025 12:28:18 +0200 Subject: [PATCH 762/786] Update signatures. --- Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs | 2 ++ Penumbra.GameData | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs index 02f00db..587fe65 100644 --- a/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs +++ b/Glamourer/Gui/Tabs/DesignTab/ModAssociationsTab.cs @@ -71,6 +71,8 @@ public class ModAssociationsTab(PenumbraService penumbra, DesignFileSystemSelect private void DrawApplyAllButton() { var (id, name) = penumbra.CurrentCollection; + if (config.Ephemeral.IncognitoMode) + name = id.ShortGuid(); if (ImGuiUtil.DrawDisabledButton($"Try Applying All Associated Mods to {name}##applyAll", new Vector2(ImGui.GetContentRegionAvail().X, 0), string.Empty, id == Guid.Empty)) ApplyAll(); diff --git a/Penumbra.GameData b/Penumbra.GameData index a34f314..7e7d510 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit a34f314cbc1053a09923a0d64aa3173044d32101 +Subproject commit 7e7d510a2ce78e2af78312a8b2215c23bf43a56f From ace3a8f755f61c71c67df5f3718c6d39d2d8bb22 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 7 Oct 2025 12:43:40 +0200 Subject: [PATCH 763/786] Again. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 7e7d510..3baace7 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 7e7d510a2ce78e2af78312a8b2215c23bf43a56f +Subproject commit 3baace73c828271dcb71a8156e3e7b91e1dd12ae From e644b8da289002dd26b51bd638c70f856e7b031b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 7 Oct 2025 12:53:55 +0200 Subject: [PATCH 764/786] Fix span issue. --- Glamourer/Designs/DesignManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index e3648a4..e568218 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -6,11 +6,13 @@ using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; using Glamourer.Services; +using OtterGui.Extensions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; + namespace Glamourer.Designs; public sealed class DesignManager : DesignEditor @@ -227,7 +229,7 @@ public sealed class DesignManager : DesignEditor design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray(); design.LastEdit = DateTimeOffset.UtcNow; - var idx = design.Tags.IndexOf(tag); + var idx = design.Tags.AsEnumerable().IndexOf(tag); SaveService.QueueSave(design); Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}."); DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx)); @@ -260,7 +262,7 @@ public sealed class DesignManager : DesignEditor SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags."); DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, - new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.IndexOf(newTag))); + new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.AsEnumerable().IndexOf(newTag))); } /// Add an associated mod to a design. From 4228fc1b896a5bd4421f7327047bdb65365ce703 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 7 Oct 2025 12:59:39 +0200 Subject: [PATCH 765/786] fu --- Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs index 4c7c103..a68c191 100644 --- a/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/MultiDesignPanel.cs @@ -3,6 +3,7 @@ using Dalamud.Interface.Utility; using Glamourer.Designs; using Glamourer.Interop.Material; using Dalamud.Bindings.ImGui; +using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using static Glamourer.Gui.Tabs.HeaderDrawer; @@ -489,7 +490,7 @@ public class MultiDesignPanel( foreach (var leaf in selector.SelectedPaths.OfType()) { - var index = leaf.Value.Tags.IndexOf(_tag); + var index = leaf.Value.Tags.AsEnumerable().IndexOf(_tag); if (index >= 0) _removeDesigns.Add((leaf.Value, index)); else From a56852f918b718a1b1643dc130ae6ac94e111b87 Mon Sep 17 00:00:00 2001 From: Actions User Date: Tue, 7 Oct 2025 11:02:02 +0000 Subject: [PATCH 766/786] [CI] Updating repo.json for 1.5.1.3 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 9f7e922..55e372c 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.1.2", - "TestingAssemblyVersion": "1.5.1.2", + "AssemblyVersion": "1.5.1.3", + "TestingAssemblyVersion": "1.5.1.3", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.2/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From a0d912a395f31eab5923815bd7bcca5e0774d8dd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 23 Oct 2025 17:23:43 +0200 Subject: [PATCH 767/786] Fix issue with reverting state of unavailable actors. --- Glamourer/State/StateApplier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index c346ab1..9800445 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -385,7 +385,7 @@ public class StateApplier( var actors = ChangeMetaState(state, MetaIndex.Wetness, true); if (redraw) { - if (withLock) + if (withLock && actors.Valid) state.TempLock(); ForceRedraw(actors); } From c604d5dbe50174da8a22d5c660c777c645b0efe0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 23 Oct 2025 17:25:54 +0200 Subject: [PATCH 768/786] Add DeletePlayerState. --- Glamourer.Api | 2 +- Glamourer/Api/ApiHelpers.cs | 19 ++++++++++++++----- Glamourer/Api/GlamourerApi.cs | 2 +- Glamourer/Api/IpcProviders.cs | 1 + Glamourer/Api/StateApi.cs | 25 ++++++++++++++++++++++--- 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 7e8505c..59a7ab5 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 7e8505cd6f8dbc5bcf41b72e16785d62b4d218f3 +Subproject commit 59a7ab5fa9941eb754757b62e4cb189e455e9514 diff --git a/Glamourer/Api/ApiHelpers.cs b/Glamourer/Api/ApiHelpers.cs index 45d84b9..14cff3b 100644 --- a/Glamourer/Api/ApiHelpers.cs +++ b/Glamourer/Api/ApiHelpers.cs @@ -1,13 +1,13 @@ using Glamourer.Api.Enums; using Glamourer.Designs; using Glamourer.State; -using OtterGui; using OtterGui.Extensions; using OtterGui.Log; using OtterGui.Services; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; +using Penumbra.GameData.Structs; using Penumbra.String; namespace Glamourer.Api; @@ -15,14 +15,23 @@ namespace Glamourer.Api; public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - internal IEnumerable FindExistingStates(string actorName) + internal IEnumerable FindExistingStates(string actorName, ushort worldId = ushort.MaxValue) { if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString)) yield break; - foreach (var state in stateManager.Values.Where(state - => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString)) - yield return state; + if (worldId == WorldId.AnyWorld.Id) + { + foreach (var state in stateManager.Values.Where(state + => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString)) + yield return state; + } + else + { + var identifier = actors.CreatePlayer(byteString, worldId); + if (stateManager.TryGetValue(identifier, out var state)) + yield return state; + } } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] diff --git a/Glamourer/Api/GlamourerApi.cs b/Glamourer/Api/GlamourerApi.cs index 14c0512..4bad983 100644 --- a/Glamourer/Api/GlamourerApi.cs +++ b/Glamourer/Api/GlamourerApi.cs @@ -6,7 +6,7 @@ namespace Glamourer.Api; public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService { public const int CurrentApiVersionMajor = 1; - public const int CurrentApiVersionMinor = 6; + public const int CurrentApiVersionMinor = 7; public (int Major, int Minor) ApiVersion => (CurrentApiVersionMajor, CurrentApiVersionMinor); diff --git a/Glamourer/Api/IpcProviders.cs b/Glamourer/Api/IpcProviders.cs index 2701f18..6019e68 100644 --- a/Glamourer/Api/IpcProviders.cs +++ b/Glamourer/Api/IpcProviders.cs @@ -54,6 +54,7 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.RevertStateName.Provider(pi, api.State), IpcSubscribers.UnlockState.Provider(pi, api.State), IpcSubscribers.UnlockStateName.Provider(pi, api.State), + IpcSubscribers.DeletePlayerState.Provider(pi, api.State), IpcSubscribers.UnlockAll.Provider(pi, api.State), IpcSubscribers.RevertToAutomation.Provider(pi, api.State), IpcSubscribers.RevertToAutomationName.Provider(pi, api.State), diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 68c593b..3b0c2c5 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -8,6 +8,7 @@ using Glamourer.State; using Newtonsoft.Json.Linq; using OtterGui.Services; using Penumbra.GameData.Interop; +using Penumbra.GameData.Structs; using StateChanged = Glamourer.Events.StateChanged; namespace Glamourer.Api; @@ -17,7 +18,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private readonly ApiHelpers _helpers; private readonly StateManager _stateManager; private readonly DesignConverter _converter; - private readonly Configuration _config; private readonly AutoDesignApplier _autoDesigns; private readonly ActorObjectManager _objects; private readonly StateChanged _stateChanged; @@ -27,7 +27,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public StateApi(ApiHelpers helpers, StateManager stateManager, DesignConverter converter, - Configuration config, AutoDesignApplier autoDesigns, ActorObjectManager objects, StateChanged stateChanged, @@ -37,7 +36,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable _helpers = helpers; _stateManager = stateManager; _converter = converter; - _config = config; _autoDesigns = autoDesigns; _objects = objects; _stateChanged = stateChanged; @@ -202,6 +200,27 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable return ApiHelpers.Return(GlamourerApiEc.Success, args); } + public GlamourerApiEc DeletePlayerState(string playerName, ushort worldId, uint key) + { + var args = ApiHelpers.Args("Name", playerName, "World", worldId, "Key", key); + var states = _helpers.FindExistingStates(playerName).ToList(); + if (states.Count is 0) + return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); + + var anyLocked = false; + foreach (var state in states) + { + if (state.CanUnlock(key)) + _stateManager.DeleteState(state.Identifier); + else + anyLocked = true; + } + + return ApiHelpers.Return(anyLocked + ? GlamourerApiEc.InvalidKey + : GlamourerApiEc.Success, args); + } + public int UnlockAll(uint key) => _stateManager.Values.Count(state => state.Unlock(key)); From bef1e39ac34c5b08bb17c0b2075234d3ee282f55 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 23 Oct 2025 17:36:03 +0200 Subject: [PATCH 769/786] Update Libraries. --- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OtterGui b/OtterGui index f354444..a63f673 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit f354444776591ae423e2d8374aae346308d81424 +Subproject commit a63f6735cf4bed4f7502a022a10378607082b770 diff --git a/Penumbra.Api b/Penumbra.Api index 648b6fc..c23ee05 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 648b6fc2ce600a95ab2b2ced27e1639af2b04502 +Subproject commit c23ee05c1e9fa103eaa52e6aa7e855ef568ee669 diff --git a/Penumbra.GameData b/Penumbra.GameData index 3baace7..d889f9e 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 3baace73c828271dcb71a8156e3e7b91e1dd12ae +Subproject commit d889f9ef918514a46049725052d378b441915b00 From 88fe25f69e86906bd18ccd4ee4e552bdda1c4428 Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 23 Oct 2025 15:40:25 +0000 Subject: [PATCH 770/786] [CI] Updating repo.json for testing_1.5.1.4 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 55e372c..ab87553 100644 --- a/repo.json +++ b/repo.json @@ -18,7 +18,7 @@ ], "InternalName": "Glamourer", "AssemblyVersion": "1.5.1.3", - "TestingAssemblyVersion": "1.5.1.3", + "TestingAssemblyVersion": "1.5.1.4", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -29,7 +29,7 @@ "LastUpdate": 1618608322, "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.1.4/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 434a5a809e6ea4efbec7482dec17e6fbda500f11 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 7 Nov 2025 23:29:41 +0100 Subject: [PATCH 771/786] Make old backup files overwrite instead of throwing. --- Glamourer/Designs/DesignManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index e568218..92f8398 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -557,7 +557,7 @@ public sealed class DesignManager : DesignEditor try { File.Move(SaveService.FileNames.MigrationDesignFile, - Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak")); + Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak"), true); Glamourer.Log.Information($"Moved migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file."); } catch (Exception ex) From 76b214c643a9bf4b3a4d1e0f2bb50c81a461123a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 7 Nov 2025 23:29:59 +0100 Subject: [PATCH 772/786] Fix try-on interaction with Penumbra for facewear. --- Glamourer/Gui/PenumbraChangedItemTooltip.cs | 62 ++++++++++++++++++-- Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs | 8 ++- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index ed6018e..dff9a6e 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -22,11 +22,12 @@ public sealed class PenumbraChangedItemTooltip : IDisposable private readonly CustomizeService _customize; private readonly GPoseService _gpose; - private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2]; + private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2 + BonusExtensions.AllFlags.Count]; - public IEnumerable> LastItems - => EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand).Zip(_lastItems) - .Select(p => new KeyValuePair(p.First, p.Second)); + public IEnumerable> LastItems + => EquipSlotExtensions.EqdpSlots.Cast().Append(EquipSlot.MainHand).Append(EquipSlot.OffHand) + .Concat(BonusExtensions.AllFlags.Cast()).Zip(_lastItems) + .Select(p => new KeyValuePair(p.First, p.Second)); public ChangedItemType LastType { get; private set; } = ChangedItemType.None; public uint LastId { get; private set; } @@ -72,6 +73,21 @@ public sealed class PenumbraChangedItemTooltip : IDisposable if (!Player()) return; + var bonusSlot = item.Type.ToBonus(); + if (bonusSlot is not BonusItemFlag.Unknown) + { + // + 2 due to weapons. + var glasses = _lastItems[bonusSlot.ToSlot() + 2]; + using (_ = !openTooltip ? null : ImRaii.Tooltip()) + { + ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor."); + if (glasses.Valid) + ImGui.TextUnformatted($"{prefix}Control + Right-Click to re-apply {glasses.Name} to current actor."); + } + + return; + } + var slot = item.Type.ToSlot(); var last = _lastItems[slot.ToIndex()]; switch (slot) @@ -109,6 +125,27 @@ public sealed class PenumbraChangedItemTooltip : IDisposable public void ApplyItem(ActorState state, EquipItem item) { + var bonusSlot = item.Type.ToBonus(); + if (bonusSlot is not BonusItemFlag.Unknown) + { + // + 2 due to weapons. + var glasses = _lastItems[bonusSlot.ToSlot() + 2]; + if (ImGui.GetIO().KeyCtrl && glasses.Valid) + { + Glamourer.Log.Debug($"Re-Applying {glasses.Name} to {bonusSlot.ToName()}."); + SetLastItem(bonusSlot, default, state); + _stateManager.ChangeBonusItem(state, bonusSlot, glasses, ApplySettings.Manual); + } + else + { + Glamourer.Log.Debug($"Applying {item.Name} to {bonusSlot.ToName()}."); + SetLastItem(bonusSlot, item, state); + _stateManager.ChangeBonusItem(state, bonusSlot, item, ApplySettings.Manual); + } + + return; + } + var slot = item.Type.ToSlot(); var last = _lastItems[slot.ToIndex()]; switch (slot) @@ -265,7 +302,22 @@ public sealed class PenumbraChangedItemTooltip : IDisposable { var oldItem = state.ModelData.Item(slot); if (oldItem.Id != item.Id) - _lastItems[slot.ToIndex()] = oldItem; + last = oldItem; + } + } + + private void SetLastItem(BonusItemFlag slot, EquipItem item, ActorState state) + { + ref var last = ref _lastItems[slot.ToSlot() + 2]; + if (!item.Valid) + { + last = default; + } + else + { + var oldItem = state.ModelData.BonusItem(slot); + if (oldItem.Id != item.Id) + last = oldItem; } } diff --git a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs index aa94a23..833ebe4 100644 --- a/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/PenumbraPanel.cs @@ -89,7 +89,13 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem ImGui.Separator(); foreach (var (slot, item) in _penumbraTooltip.LastItems) { - ImGuiUtil.DrawTableColumn($"{slot.ToName()} Revert-Item"); + switch (slot) + { + case EquipSlot e: ImGuiUtil.DrawTableColumn($"{e.ToName()} Revert-Item"); break; + case BonusItemFlag f: ImGuiUtil.DrawTableColumn($"{f.ToName()} Revert-Item"); break; + default: ImGuiUtil.DrawTableColumn("Unk Revert-Item"); break; + } + ImGuiUtil.DrawTableColumn(item.Valid ? item.Name : "None"); ImGui.TableNextColumn(); } From bf4673a1d950a78bc40928388402b6a3e658c594 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 7 Nov 2025 23:30:11 +0100 Subject: [PATCH 773/786] Save Remove and Inherit for mod associations. --- Glamourer/Designs/Design.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 35ee3aa..848e7d6 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -100,7 +100,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn public new JObject JsonSerialize() { - var ret = new JObject() + var ret = new JObject { ["FileVersion"] = FileVersion, ["Identifier"] = Identifier, @@ -131,12 +131,17 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn var ret = new JArray(); foreach (var (mod, settings) in AssociatedMods) { - var obj = new JObject() + var obj = new JObject { ["Name"] = mod.Name, ["Directory"] = mod.DirectoryName, - ["Enabled"] = settings.Enabled, }; + if (settings.Remove) + obj["Remove"] = true; + else if (settings.ForceInherit) + obj["Inherit"] = true; + else + obj["Enabled"] = settings.Enabled; if (settings.Enabled) { obj["Priority"] = settings.Priority; From 04fb37d6618c27fae86299fa1fc69efe8a269dca Mon Sep 17 00:00:00 2001 From: Exter-N Date: Thu, 13 Nov 2025 20:09:26 +0100 Subject: [PATCH 774/786] Display relevant settings in Penumbra --- Glamourer/Gui/MainWindow.cs | 7 +++- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 33 ++++++++++++++----- Glamourer/Interop/Penumbra/PenumbraService.cs | 25 +++++++++++++- Penumbra.Api | 2 +- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index a39db2e..abde603 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -102,6 +102,8 @@ public class MainWindow : Window, IDisposable SelectTab = _config.Ephemeral.SelectedTab; _event.Subscribe(OnTabSelected, TabSelected.Priority.MainWindow); IsOpen = _config.OpenWindowAtStart; + + _penumbra.DrawSettingsSection += Settings.DrawPenumbraIntegrationSettings; } public void OpenSettings() @@ -120,7 +122,10 @@ public class MainWindow : Window, IDisposable } public void Dispose() - => _event.Unsubscribe(OnTabSelected); + { + _event.Unsubscribe(OnTabSelected); + _penumbra.DrawSettingsSection -= Settings.DrawPenumbraIntegrationSettings; + } public override void Draw() { diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 0a84adc..fda9a7c 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -9,6 +9,7 @@ using Glamourer.Designs; using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Interop; using Glamourer.Interop.PalettePlus; +using Glamourer.Interop.Penumbra; using Glamourer.Services; using OtterGui; using OtterGui.Raii; @@ -69,6 +70,12 @@ public class SettingsTab( MainWindow.DrawSupportButtons(glamourer, changelog.Changelog); } + public void DrawPenumbraIntegrationSettings() + { + DrawPenumbraIntegrationSettings1(); + DrawPenumbraIntegrationSettings2(); + } + private void DrawBehaviorSettings() { if (!ImUtf8.CollapsingHeader("Glamourer Behavior"u8)) @@ -89,6 +96,20 @@ public class SettingsTab( Checkbox("Enable Festival Easter-Eggs"u8, "Glamourer may do some fun things on specific dates. Disable this if you do not want your experience disrupted by this."u8, config.DisableFestivals == 0, v => config.DisableFestivals = v ? (byte)0 : (byte)2); + DrawPenumbraIntegrationSettings1(); + Checkbox("Revert Manual Changes on Zone Change"u8, + "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone."u8, + config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); + PaletteImportButton(); + DrawPenumbraIntegrationSettings2(); + Checkbox("Prevent Random Design Repeats"u8, + "When using random designs, prevent the same design from being chosen twice in a row."u8, + config.PreventRandomRepeats, v => config.PreventRandomRepeats = v); + ImGui.NewLine(); + } + + private void DrawPenumbraIntegrationSettings1() + { Checkbox("Auto-Reload Gear"u8, "Automatically reload equipment pieces on your own character when changing any mod options in Penumbra in their associated collection."u8, config.AutoRedrawEquipOnChanges, v => config.AutoRedrawEquipOnChanges = v); @@ -101,10 +122,10 @@ public class SettingsTab( pcpService.CleanPcpDesigns(); if (!active) ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"\nHold {config.DeleteDesignModifier} while clicking."); - Checkbox("Revert Manual Changes on Zone Change"u8, - "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone."u8, - config.RevertManualChangesOnZoneChange, v => config.RevertManualChangesOnZoneChange = v); - PaletteImportButton(); + } + + private void DrawPenumbraIntegrationSettings2() + { Checkbox("Always Apply Associated Mods"u8, "Whenever a design is applied to a character (including via automation), Glamourer will try to apply its associated mod settings to the collection currently associated with that character, if it is available.\n\n"u8 + "Glamourer will NOT revert these applied settings automatically. This may mess up your collection and configuration.\n\n"u8 @@ -114,10 +135,6 @@ public class SettingsTab( "Apply all settings as temporary settings so they will be reset when Glamourer or the game shut down."u8, config.UseTemporarySettings, v => config.UseTemporarySettings = v); - Checkbox("Prevent Random Design Repeats"u8, - "When using random designs, prevent the same design from being chosen twice in a row."u8, - config.PreventRandomRepeats, v => config.PreventRandomRepeats = v); - ImGui.NewLine(); } private void DrawDesignDefaultSettings() diff --git a/Glamourer/Interop/Penumbra/PenumbraService.cs b/Glamourer/Interop/Penumbra/PenumbraService.cs index 4d70a3f..b2813cd 100644 --- a/Glamourer/Interop/Penumbra/PenumbraService.cs +++ b/Glamourer/Interop/Penumbra/PenumbraService.cs @@ -1,5 +1,6 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin; +using Dalamud.Plugin.Ipc.Exceptions; using Glamourer.Events; using Glamourer.State; using Newtonsoft.Json.Linq; @@ -36,7 +37,7 @@ public readonly record struct ModSettings(Dictionary> Setti public class PenumbraService : IDisposable { public const int RequiredPenumbraBreakingVersion = 5; - public const int RequiredPenumbraFeatureVersion = 8; + public const int RequiredPenumbraFeatureVersion = 13; private const int KeyFixed = -1610; private const string NameFixed = "Glamourer (Automation)"; @@ -77,6 +78,8 @@ public class PenumbraService : IDisposable private global::Penumbra.Api.IpcSubscribers.QueryTemporaryModSettingsPlayer? _queryTemporaryModSettingsPlayer; private global::Penumbra.Api.IpcSubscribers.OpenMainWindow? _openModPage; private global::Penumbra.Api.IpcSubscribers.GetChangedItems? _getChangedItems; + private global::Penumbra.Api.IpcSubscribers.RegisterSettingsSection? _registerSettingsSection; + private global::Penumbra.Api.IpcSubscribers.UnregisterSettingsSection? _unregisterSettingsSection; private IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)>? _changedItems; private Func? _checkCurrentChangedItems; private Func? _checkCutsceneParent; @@ -152,6 +155,11 @@ public class PenumbraService : IDisposable remove => _pcpParsed.Event -= value; } + public event Action? DrawSettingsSection; + + private void InvokeDrawSettingsSection() + => DrawSettingsSection?.Invoke(); + public Dictionary GetCollections() => Available ? _collections!.Invoke() : []; @@ -565,6 +573,10 @@ public class PenumbraService : IDisposable _changedItems = new global::Penumbra.Api.IpcSubscribers.GetChangedItemAdapterList(_pluginInterface).Invoke(); _checkCurrentChangedItems = new global::Penumbra.Api.IpcSubscribers.CheckCurrentChangedItemFunc(_pluginInterface).Invoke(); + _registerSettingsSection = new global::Penumbra.Api.IpcSubscribers.RegisterSettingsSection(_pluginInterface); + _unregisterSettingsSection = new global::Penumbra.Api.IpcSubscribers.UnregisterSettingsSection(_pluginInterface); + + _registerSettingsSection.Invoke(InvokeDrawSettingsSection); Available = true; _penumbraReloaded.Invoke(); @@ -587,6 +599,15 @@ public class PenumbraService : IDisposable _modSettingChanged.Disable(); _pcpCreated.Disable(); _pcpParsed.Disable(); + try + { + _unregisterSettingsSection?.Invoke(InvokeDrawSettingsSection); + } + catch (IpcNotReadyError) + { + // Ignore. + } + if (Available) { _collectionByIdentifier = null; @@ -617,6 +638,8 @@ public class PenumbraService : IDisposable _getChangedItems = null; _changedItems = null; _checkCurrentChangedItems = null; + _registerSettingsSection = null; + _unregisterSettingsSection = null; Available = false; Glamourer.Log.Debug("Glamourer detached from Penumbra."); } diff --git a/Penumbra.Api b/Penumbra.Api index c23ee05..b97784b 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit c23ee05c1e9fa103eaa52e6aa7e855ef568ee669 +Subproject commit b97784bd7cd911bd0a323cd8e717714de1875469 From aadcf771e71115cf9d93da6b275c5da341d52dcf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 28 Nov 2025 23:06:53 +0100 Subject: [PATCH 775/786] 1.5.1.5 --- OtterGui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OtterGui b/OtterGui index a63f673..1459e2b 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit a63f6735cf4bed4f7502a022a10378607082b770 +Subproject commit 1459e2b8f5e1687f659836709e23571235d4206c From 5b6517aae8259031c8bc9f600f5bdd431a1af3ae Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 28 Nov 2025 22:09:08 +0000 Subject: [PATCH 776/786] [CI] Updating repo.json for 1.5.1.5 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index ab87553..fd4c07f 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.1.3", - "TestingAssemblyVersion": "1.5.1.4", + "AssemblyVersion": "1.5.1.5", + "TestingAssemblyVersion": "1.5.1.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.3/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/testing_1.5.1.4/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 507e1268acb34141a8874e82830fe9b396cd67cf Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 17 Dec 2025 18:39:30 +0100 Subject: [PATCH 777/786] Update SDK. --- Glamourer.Api | 2 +- Glamourer/Glamourer.csproj | 2 +- Glamourer/packages.lock.json | 26 +++++++++----------------- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- 7 files changed, 15 insertions(+), 23 deletions(-) diff --git a/Glamourer.Api b/Glamourer.Api index 59a7ab5..3bfd1db 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 59a7ab5fa9941eb754757b62e4cb189e455e9514 +Subproject commit 3bfd1db3a471f6e808c4d981485a08f58a4bf6cd diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index d7e62a9..061e920 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,4 +1,4 @@ - + Glamourer Glamourer diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json index 8ac1fe4..30a6d00 100644 --- a/Glamourer/packages.lock.json +++ b/Glamourer/packages.lock.json @@ -1,18 +1,18 @@ { "version": 1, "dependencies": { - "net9.0-windows7.0": { + "net10.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[13.1.0, )", - "resolved": "13.1.0", - "contentHash": "XdoNhJGyFby5M/sdcRhnc5xTop9PHy+H50PTWpzLhJugjB19EDBiHD/AsiDF66RETM+0qKUdJBZrNuebn7qswQ==" + "requested": "[14.0.0, )", + "resolved": "14.0.0", + "contentHash": "9c1q/eAeAs82mkQWBOaCvbt3GIQxAIadz5b/7pCXDIy9nHPtnRc+tDXEvKR+M36Wvi7n+qBTevRupkLUQp6DFA==" }, "DotNet.ReproducibleBuilds": { "type": "Direct", - "requested": "[1.2.25, )", - "resolved": "1.2.25", - "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg==" + "requested": "[1.2.39, )", + "resolved": "1.2.39", + "contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg==" }, "Vortice.Direct3D11": { "type": "Direct", @@ -32,10 +32,7 @@ "FlatSharp.Runtime": { "type": "Transitive", "resolved": "7.9.0", - "contentHash": "Bm8+WqzEsWNpxqrD5x4x+zQ8dyINlToCreM5FI2oNSfUVc9U9ZB+qztX/jd8rlJb3r0vBSlPwVLpw0xBtPa3Vw==", - "dependencies": { - "System.Memory": "4.5.5" - } + "contentHash": "Bm8+WqzEsWNpxqrD5x4x+zQ8dyINlToCreM5FI2oNSfUVc9U9ZB+qztX/jd8rlJb3r0vBSlPwVLpw0xBtPa3Vw==" }, "JetBrains.Annotations": { "type": "Transitive", @@ -68,11 +65,6 @@ "SharpGen.Runtime": "2.1.2-beta" } }, - "System.Memory": { - "type": "Transitive", - "resolved": "4.5.5", - "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" - }, "Vortice.DirectX": { "type": "Transitive", "resolved": "3.4.2-beta", @@ -116,7 +108,7 @@ "FlatSharp.Compiler": "[7.9.0, )", "FlatSharp.Runtime": "[7.9.0, )", "OtterGui": "[1.0.0, )", - "Penumbra.Api": "[5.10.0, )", + "Penumbra.Api": "[5.13.0, )", "Penumbra.String": "[1.0.6, )" } }, diff --git a/OtterGui b/OtterGui index 1459e2b..6f32364 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 1459e2b8f5e1687f659836709e23571235d4206c +Subproject commit 6f3236453b1edfaa25c8edcc8b39a9d9b2fc18ac diff --git a/Penumbra.Api b/Penumbra.Api index c23ee05..e4934cc 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit c23ee05c1e9fa103eaa52e6aa7e855ef568ee669 +Subproject commit e4934ccca0379f22dadf989ab2d34f30b3c5c7ea diff --git a/Penumbra.GameData b/Penumbra.GameData index d889f9e..2ff50e6 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d889f9ef918514a46049725052d378b441915b00 +Subproject commit 2ff50e68f7c951f0f8b25957a400a2e32ed9d6dc diff --git a/Penumbra.String b/Penumbra.String index c8611a0..0315144 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit c8611a0c546b6b2ec29214ab319fc2c38fe74793 +Subproject commit 0315144ab5614c11911e2a4dddf436fb18c5d7e3 From cf87184c923e78b317683d6dada3831b48df49b2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 19 Dec 2025 01:26:19 +0100 Subject: [PATCH 778/786] SDK update. --- .github/workflows/release.yml | 8 +++++--- .github/workflows/test_release.yml | 8 +++++--- Glamourer.Api | 2 +- Glamourer/Glamourer.csproj | 2 +- Glamourer/Glamourer.json | 2 +- Glamourer/Services/DalamudServices.cs | 2 +- Glamourer/packages.lock.json | 8 ++++---- OtterGui | 2 +- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra.String | 2 +- repo.json | 4 ++-- 12 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 327b75b..feecf19 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,13 +9,15 @@ jobs: build: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 with: submodules: recursive - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '9.x.x' + dotnet-version: | + 10.x.x + 9.x.x - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/.github/workflows/test_release.yml b/.github/workflows/test_release.yml index 6316776..5639c7b 100644 --- a/.github/workflows/test_release.yml +++ b/.github/workflows/test_release.yml @@ -9,13 +9,15 @@ jobs: build: runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 with: submodules: recursive - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '9.x.x' + dotnet-version: | + 10.x.x + 9.x.x - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/Glamourer.Api b/Glamourer.Api index 3bfd1db..5b6730d 160000 --- a/Glamourer.Api +++ b/Glamourer.Api @@ -1 +1 @@ -Subproject commit 3bfd1db3a471f6e808c4d981485a08f58a4bf6cd +Subproject commit 5b6730d46f17bdd02a441e23e2141576cf7acf53 diff --git a/Glamourer/Glamourer.csproj b/Glamourer/Glamourer.csproj index 061e920..560621d 100644 --- a/Glamourer/Glamourer.csproj +++ b/Glamourer/Glamourer.csproj @@ -1,4 +1,4 @@ - + Glamourer Glamourer diff --git a/Glamourer/Glamourer.json b/Glamourer/Glamourer.json index 2daff91..e2dbf8b 100644 --- a/Glamourer/Glamourer.json +++ b/Glamourer/Glamourer.json @@ -8,7 +8,7 @@ "AssemblyVersion": "9.0.0.1", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 13, + "DalamudApiLevel": 14, "ImageUrls": null, "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png" } \ No newline at end of file diff --git a/Glamourer/Services/DalamudServices.cs b/Glamourer/Services/DalamudServices.cs index 85783b9..e8a9f55 100644 --- a/Glamourer/Services/DalamudServices.cs +++ b/Glamourer/Services/DalamudServices.cs @@ -1,4 +1,3 @@ -using Dalamud.Game.ClientState.Objects; using Dalamud.Interface.DragDrop; using Dalamud.Plugin; using Dalamud.Plugin.Services; @@ -17,6 +16,7 @@ public class DalamudServices services.AddDalamudService(pi); services.AddDalamudService(pi); services.AddDalamudService(pi); + services.AddDalamudService(pi); services.AddDalamudService(pi); services.AddDalamudService(pi); services.AddDalamudService(pi); diff --git a/Glamourer/packages.lock.json b/Glamourer/packages.lock.json index 30a6d00..b15c917 100644 --- a/Glamourer/packages.lock.json +++ b/Glamourer/packages.lock.json @@ -4,9 +4,9 @@ "net10.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[14.0.0, )", - "resolved": "14.0.0", - "contentHash": "9c1q/eAeAs82mkQWBOaCvbt3GIQxAIadz5b/7pCXDIy9nHPtnRc+tDXEvKR+M36Wvi7n+qBTevRupkLUQp6DFA==" + "requested": "[14.0.1, )", + "resolved": "14.0.1", + "contentHash": "y0WWyUE6dhpGdolK3iKgwys05/nZaVf4ZPtIjpLhJBZvHxkkiE23zYRo7K7uqAgoK/QvK5cqF6l3VG5AbgC6KA==" }, "DotNet.ReproducibleBuilds": { "type": "Direct", @@ -109,7 +109,7 @@ "FlatSharp.Runtime": "[7.9.0, )", "OtterGui": "[1.0.0, )", "Penumbra.Api": "[5.13.0, )", - "Penumbra.String": "[1.0.6, )" + "Penumbra.String": "[1.0.7, )" } }, "penumbra.string": { diff --git a/OtterGui b/OtterGui index 6f32364..ff1e654 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 6f3236453b1edfaa25c8edcc8b39a9d9b2fc18ac +Subproject commit ff1e6543845e3b8c53a5f8b240bc38faffb1b3bf diff --git a/Penumbra.Api b/Penumbra.Api index e4934cc..1750c41 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit e4934ccca0379f22dadf989ab2d34f30b3c5c7ea +Subproject commit 1750c41b53e1000c99a7fb9d8a0f082aef639a41 diff --git a/Penumbra.GameData b/Penumbra.GameData index 2ff50e6..0e973ed 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 2ff50e68f7c951f0f8b25957a400a2e32ed9d6dc +Subproject commit 0e973ed6eace6afd31cd298f8c58f76fa8d5ef60 diff --git a/Penumbra.String b/Penumbra.String index 0315144..9bd016f 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit 0315144ab5614c11911e2a4dddf436fb18c5d7e3 +Subproject commit 9bd016fbef5fb2de467dd42165267fdd93cd9592 diff --git a/repo.json b/repo.json index fd4c07f..d2c030b 100644 --- a/repo.json +++ b/repo.json @@ -21,8 +21,8 @@ "TestingAssemblyVersion": "1.5.1.5", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", - "DalamudApiLevel": 13, - "TestingDalamudApiLevel": 13, + "DalamudApiLevel": 14, + "TestingDalamudApiLevel": 14, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 1, From 643c83a6f3ceeab7416928b9e78ca6c71b8c07e4 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 19 Dec 2025 13:56:36 +0100 Subject: [PATCH 779/786] Touch up CanUnlock. --- Glamourer/Api/StateApi.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index 8914dee..ffd541e 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -228,12 +228,12 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public GlamourerApiEc CanUnlock(int objectIndex, uint key, out bool isLocked, out bool canUnlock) { var args = ApiHelpers.Args("Index", objectIndex, "Key", key); - isLocked = false; // These seem like reasonable defaults. - canUnlock = false; - if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success) + isLocked = false; + canUnlock = true; + if (_helpers.FindExistingState(objectIndex, out var state) is not GlamourerApiEc.Success) return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); - if (state == null) - return ApiHelpers.Return(GlamourerApiEc.InvalidState, args); // Possibly, the error type could be changed. I just looked at what was available. + if (state is null) + return ApiHelpers.Return(GlamourerApiEc.Success, args); isLocked = state.IsLocked; canUnlock = state.CanUnlock(key); return ApiHelpers.Return(GlamourerApiEc.Success, args); From 77f3912bf2a5aee0b375e7ee1a4d6afdb38afbf5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 19 Dec 2025 14:01:51 +0100 Subject: [PATCH 780/786] Minor touch up ReapplyState. --- Glamourer/Api/StateApi.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Glamourer/Api/StateApi.cs b/Glamourer/Api/StateApi.cs index ffd541e..4ce9c01 100644 --- a/Glamourer/Api/StateApi.cs +++ b/Glamourer/Api/StateApi.cs @@ -129,10 +129,10 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable public GlamourerApiEc ReapplyState(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) + if (_helpers.FindExistingState(objectIndex, out var state) is not GlamourerApiEc.Success) return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args); - if (state == null) + if (state is null) return ApiHelpers.Return(GlamourerApiEc.NothingDone, args); if (!state.CanUnlock(key)) @@ -359,14 +359,14 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable private void Reapply(Actor actor, ActorState state, uint key, ApplyFlag flags) { - var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; + var source = flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcFixed : StateSource.IpcManual; _stateManager.ReapplyState(actor, state, false, source, true); ApiHelpers.Lock(state, key, flags); } private void Revert(ActorState state, uint key, ApplyFlag flags) { - var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed; + var source = flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcFixed : StateSource.IpcManual; switch (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) { case ApplyFlag.Equipment: _stateManager.ResetEquip(state, source, key); break; From 3c68124b29e96d846a5b274344403eeaa47a6424 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 19 Dec 2025 14:15:14 +0100 Subject: [PATCH 781/786] Ny --- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index 6432811..e559841 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -119,7 +119,7 @@ public class SettingsTab( config.AutoRedrawEquipOnChanges = v; autoRedraw.Invoke(v); }); - Checkbox("Attach to PCP-Handling"u8, + Checkbox("Attach to PCP Handling"u8, "Add the actor's glamourer state when a PCP is created by Penumbra, and create a design and apply it if possible when a PCP is installed by Penumbra."u8, config.AttachToPcp, pcpService.Set); var active = config.DeleteDesignModifier.IsActive(); From 98b702d6e663f4affe8df479683ed80f0bd65ab1 Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 19 Dec 2025 13:16:42 +0000 Subject: [PATCH 782/786] [CI] Updating repo.json for 1.5.1.6 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index d2c030b..73f3e21 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.1.5", - "TestingAssemblyVersion": "1.5.1.5", + "AssemblyVersion": "1.5.1.6", + "TestingAssemblyVersion": "1.5.1.6", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 14, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.5/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.6/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.6/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.6/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 96f825e29851acab5374bf15c4ee877803111b93 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 20 Dec 2025 16:01:00 +0100 Subject: [PATCH 783/786] Update Penumbra API. --- Penumbra.Api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.Api b/Penumbra.Api index 1750c41..52a3216 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 1750c41b53e1000c99a7fb9d8a0f082aef639a41 +Subproject commit 52a3216a525592205198303df2844435e382cf87 From 0d9a0d49aba6f04f3eb20f56037c46327614d57d Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 20 Dec 2025 15:03:30 +0000 Subject: [PATCH 784/786] [CI] Updating repo.json for 1.5.1.7 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 73f3e21..31b4c6e 100644 --- a/repo.json +++ b/repo.json @@ -17,8 +17,8 @@ "Character" ], "InternalName": "Glamourer", - "AssemblyVersion": "1.5.1.6", - "TestingAssemblyVersion": "1.5.1.6", + "AssemblyVersion": "1.5.1.7", + "TestingAssemblyVersion": "1.5.1.7", "RepoUrl": "https://github.com/Ottermandias/Glamourer", "ApplicableVersion": "any", "DalamudApiLevel": 14, @@ -27,9 +27,9 @@ "IsTestingExclusive": "False", "DownloadCount": 1, "LastUpdate": 1618608322, - "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.6/Glamourer.zip", - "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.6/Glamourer.zip", - "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.6/Glamourer.zip", + "DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.7/Glamourer.zip", + "DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.7/Glamourer.zip", + "DownloadLinkTesting": "https://github.com/Ottermandias/Glamourer/releases/download/1.5.1.7/Glamourer.zip", "IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/main/images/icon.png" } ] From 59c9601a9bc8743eee7dd6ea0ad6080b611cc394 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 26 Dec 2025 18:20:44 +0100 Subject: [PATCH 785/786] Fix issue with material value in history. --- Glamourer/Designs/History/DesignTransaction.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Glamourer/Designs/History/DesignTransaction.cs b/Glamourer/Designs/History/DesignTransaction.cs index 98e3eec..65086db 100644 --- a/Glamourer/Designs/History/DesignTransaction.cs +++ b/Glamourer/Designs/History/DesignTransaction.cs @@ -1,6 +1,7 @@ using Glamourer.GameData; using Glamourer.Interop.Material; using Glamourer.Interop.Penumbra; +using Glamourer.State; using Penumbra.GameData.Enums; namespace Glamourer.Designs.History; @@ -125,7 +126,10 @@ public readonly record struct MaterialTransaction(MaterialValueIndex Index, Colo => older is MaterialTransaction other && Index == other.Index ? new MaterialTransaction(Index, other.Old, New) : null; public void Revert(IDesignEditor editor, object data) - => ((DesignManager)editor).ChangeMaterialValue((Design)data, Index, Old); + { + if (editor is DesignManager e) + e.ChangeMaterialValue((Design)data, Index, Old); + } } /// Only Designs. From f8ca572d380da3e71b761e6b955e106898109801 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 26 Dec 2025 18:21:47 +0100 Subject: [PATCH 786/786] Fix issue in unlocks tab. --- Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs index d75f2dc..2323ca2 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlockTable.cs @@ -194,7 +194,7 @@ public class UnlockTable : Table, IDisposable ImGui.Dummy(new Vector2(ImGui.GetFrameHeight())); ImGui.SameLine(); ImGui.AlignTextToFramePadding(); - if (ImGui.Selectable(item.Name)) + if (ImGui.Selectable(item.Name) && !item.Id.IsBonusItem) Glamourer.Messager.Chat.Print(new SeStringBuilder().AddItemLink(item.ItemId.Id, false).BuiltString); if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _tooltip.Player(out var state))