diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 2c95c8b..a2411a5 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -106,7 +106,7 @@ public class AutoDesignApplier : IDisposable return; } - if (!_state.GetOrCreate(id, actor, out var state)) + if (!_state.TryGetValue(id, out var state)) return; if (oldJob.Id == newJob.Id && state.LastJob == newJob.Id) @@ -128,6 +128,27 @@ public class AutoDesignApplier : IDisposable Reduce(actor, state, set, false); } + public bool Reduce(Actor actor, ActorIdentifier identifier, [NotNullWhen(true)] out ActorState? state) + { + AutoDesignSet set; + if (!_state.TryGetValue(identifier, out state)) + { + if (!_config.EnableAutoDesigns) + return false; + + if (!GetPlayerSet(identifier, out set!)) + return false; + + if (!_state.GetOrCreate(identifier, actor, out state)) + return false; + } + else if (!GetPlayerSet(identifier, out set!)) + return true; + + Reduce(actor, state, set, true); + return true; + } + public void Reduce(Actor actor, ActorIdentifier identifier, ActorState state) { if (!_config.EnableAutoDesigns) diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 218e6a7..4757428 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -39,6 +39,9 @@ public class DesignConverter return ShareJObject(design); } + public string ShareBase64(Design design) + => ShareBase64(ShareJObject(design)); + public string ShareBase64(DesignBase design) => ShareBase64(ShareJObject(design)); diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 100183b..b15d14c 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -17,7 +17,6 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Actors; -using Penumbra.GameData.Data; using Penumbra.GameData.Enums; namespace Glamourer.Gui.Tabs.ActorTab; @@ -33,6 +32,7 @@ public class ActorPanel private readonly Configuration _config; private readonly DesignConverter _converter; private readonly ObjectManager _objects; + private readonly DesignManager _designManager; private ActorIdentifier _identifier; private string _actorName = string.Empty; @@ -42,7 +42,7 @@ public class ActorPanel public ActorPanel(ActorSelector selector, StateManager stateManager, CustomizationDrawer customizationDrawer, EquipmentDrawer equipmentDrawer, IdentifierService identification, AutoDesignApplier autoDesignApplier, - Configuration config, DesignConverter converter, ObjectManager objects) + Configuration config, DesignConverter converter, ObjectManager objects, DesignManager designManager) { _selector = selector; _stateManager = stateManager; @@ -53,6 +53,7 @@ public class ActorPanel _config = config; _converter = converter; _objects = objects; + _designManager = designManager; } public void Draw() @@ -106,9 +107,11 @@ public class ActorPanel if (!child || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state)) return; - ApplyClipboardButton(); + SetFromClipboard(); ImGui.SameLine(); - CopyToClipboardButton(); + ExportToClipboardButton(); + ImGui.SameLine(); + SaveDesignButton(); ImGui.SameLine(); DrawApplyToSelf(); ImGui.SameLine(); @@ -222,7 +225,7 @@ public class ActorPanel _stateManager.TurnHuman(_state, StateChanged.Source.Manual); } - private void ApplyClipboardButton() + private void SetFromClipboard() { if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Try to apply a design from your clipboard.", false, true)) @@ -241,7 +244,7 @@ public class ActorPanel } } - private void CopyToClipboardButton() + private void ExportToClipboardButton() { if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Copy.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Copy the current design to your clipboard.", false, true)) @@ -303,4 +306,26 @@ public class ActorPanel _stateManager.ApplyDesign(_converter.Convert(_state!, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant), state, StateChanged.Source.Manual); } + + private string _newName = string.Empty; + private DesignBase? _newDesign = null; + + private void SaveDesignButton() + { + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Save.ToIconString(), new Vector2(ImGui.GetFrameHeight()), + "Save the current state as a design.", !_state!.ModelData.IsHuman, true)) + { + ImGui.OpenPopup("Save as Design"); + _newName = _state.Identifier.ToName(); + _newDesign = _converter.Convert(_state, EquipFlagExtensions.All, CustomizeFlagExtensions.All); + } + + if (ImGuiUtil.OpenNameField("Save as Design", ref _newName)) + { + if (_newDesign != null && _newName.Length > 0) + _designManager.CreateClone(_newDesign, _newName); + _newDesign = null; + _newName = string.Empty; + } + } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index 0fe5c19..8cc6d49 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -268,6 +268,8 @@ public class SetPanel _manager.ChangeApplicationType(set, autoDesignIndex, newType); } + + private static readonly IReadOnlyList<(AutoDesign.Type, string)> Types = new[] { (AutoDesign.Type.Customizations, diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 53866ed..4b2a4ce 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -289,16 +289,16 @@ public class DesignPanel private void DrawButtonRow() { - DrawSetFromClipboard(); + SetFromClipboardButton(); ImGui.SameLine(); - DrawExportToClipboard(); + ExportToClipboardButton(); ImGui.SameLine(); DrawApplyToSelf(); ImGui.SameLine(); DrawApplyToTarget(); } - private void DrawSetFromClipboard() + private void SetFromClipboardButton() { if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Try to apply a design from your clipboard.", _selector.Selected!.WriteProtected(), true)) @@ -317,7 +317,7 @@ public class DesignPanel } } - private void DrawExportToClipboard() + private void ExportToClipboardButton() { if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Copy.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Copy the current design to your clipboard.", false, true)) diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 6ca6ceb..5694f90 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -2,7 +2,6 @@ using System; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; -using FFXIVClientStructs.FFXIV.Client.System.String; using Glamourer.Customization; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 9353849..7b81365 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -98,7 +98,7 @@ public class StateListener : IDisposable /// Invoked when a new draw object is created from a game object. /// We need to update all state: Model ID, Customize and Equipment. /// Weapons and meta flags are updated independently. - /// We also need to apply fixed designs here (TODO). + /// We also need to apply fixed designs here. /// private unsafe void OnCreatingCharacterBase(nint actorPtr, string _, nint modelPtr, nint customizePtr, nint equipDataPtr) { @@ -107,9 +107,8 @@ public class StateListener : IDisposable ref var modelId = ref *(uint*)modelPtr; ref var customize = ref *(Customize*)customizePtr; - if (_manager.TryGetValue(identifier, out var state)) + if (_autoDesignApplier.Reduce(actor, identifier, out var state)) { - _autoDesignApplier.Reduce(actor, identifier, state); switch (UpdateBaseData(actor, state, modelId, customizePtr, equipDataPtr)) { // TODO handle right diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index d354062..eea06e6 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -286,7 +286,7 @@ public class StateManager : IReadOnlyDictionary 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} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); + $"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)); } @@ -298,7 +298,7 @@ public class StateManager : IReadOnlyDictionary var actors = _applier.ChangeWeaponState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); Glamourer.Log.Verbose( - $"Set Weapon Visibility in state {state.Identifier} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); + $"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)); } @@ -310,7 +310,7 @@ public class StateManager : IReadOnlyDictionary var actors = _applier.ChangeVisor(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); Glamourer.Log.Verbose( - $"Set Visor State in state {state.Identifier} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); + $"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)); } @@ -322,7 +322,7 @@ public class StateManager : IReadOnlyDictionary var actors = _applier.ChangeVisor(state, true); Glamourer.Log.Verbose( - $"Set Wetness in state {state.Identifier} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); + $"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)); }