From 80ab57e96d93ed4538105361c2887bdef76ab12d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 18 Jun 2023 13:38:45 +0200 Subject: [PATCH] We have config at home. --- Glamourer/Configuration.cs | 18 ++- Glamourer/Designs/Design.cs | 18 +-- Glamourer/Designs/DesignData.cs | 56 ++++++- Glamourer/Gui/Colors.cs | 5 + Glamourer/Gui/MainWindow.cs | 65 +++++++- Glamourer/Gui/Tabs/DebugTab.cs | 140 +++++++++++++---- Glamourer/Gui/Tabs/SettingsTab.cs | 68 ++++++++ Glamourer/Interop/Structs/Actor.cs | 4 + Glamourer/Interop/Structs/Model.cs | 4 + Glamourer/Services/ServiceManager.cs | 6 + Glamourer/State/ActorState.cs | 13 ++ Glamourer/State/StateManager.cs | 146 ++++++++++++++++++ .../Gui/Customization/CustomizationDrawer.cs | 6 +- 13 files changed, 487 insertions(+), 62 deletions(-) create mode 100644 Glamourer/Gui/Tabs/SettingsTab.cs create mode 100644 Glamourer/State/ActorState.cs create mode 100644 Glamourer/State/StateManager.cs diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 485e8aa..a3f9e55 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -13,7 +13,15 @@ namespace Glamourer; public class Configuration : IPluginConfiguration, ISavable { - public bool UseRestrictedGearProtection = true; + public bool UseRestrictedGearProtection { get; set; } = true; + public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings; + + +#if DEBUG + public bool DebugMode { get; set; } = true; +#else + public bool DebugMode { get; set; } = false; +#endif public int Version { get; set; } = Constants.CurrentVersion; @@ -30,7 +38,7 @@ public class Configuration : IPluginConfiguration, ISavable } public void Save() - => _saveService.QueueSave(this); + => _saveService.DelaySave(this); public void Load(ConfigMigrationService migrator) { @@ -68,8 +76,8 @@ 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) { Formatting = Formatting.Indented }; + var serializer = new JsonSerializer { Formatting = Formatting.Indented }; serializer.Serialize(jWriter, this); } @@ -77,4 +85,4 @@ public class Configuration : IPluginConfiguration, ISavable { public const int CurrentVersion = 2; } -} \ No newline at end of file +} diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index c3564dd..bba2ab6 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -19,7 +19,7 @@ public class Design : ISavable internal Design(ItemManager items) { - SetDefaultEquipment(items); + DesignData.SetDefaultEquipment(items); } // Metadata @@ -35,20 +35,6 @@ public class Design : ISavable internal DesignData DesignData; - public void SetDefaultEquipment(ItemManager items) - { - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - DesignData.SetItem(slot, ItemManager.NothingItem(slot)); - DesignData.SetStain(slot, 0); - } - - DesignData.SetItem(EquipSlot.MainHand, items.DefaultSword); - DesignData.SetStain(EquipSlot.MainHand, 0); - DesignData.SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); - DesignData.SetStain(EquipSlot.OffHand, 0); - } - #endregion #region Application Data @@ -272,7 +258,7 @@ public class Design : ISavable { if (equip == null) { - design.SetDefaultEquipment(items); + design.DesignData.SetDefaultEquipment(items); Glamourer.Chat.NotificationMessage("The loaded design does not contain any equipment data, reset to default.", "Warning", NotificationType.Warning); return; diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 8bdbbb2..0d92c8e 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -1,8 +1,13 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using FFXIVClientStructs.FFXIV.Client.Game.Gauge; using Glamourer.Customization; +using Glamourer.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Penumbra.String.Functions; +using CustomizeData = Penumbra.GameData.Structs.CustomizeData; namespace Glamourer.Designs; @@ -32,7 +37,7 @@ public unsafe struct DesignData private byte _states; public DesignData() - {} + { } public readonly StainId Stain(EquipSlot slot) { @@ -167,6 +172,53 @@ public unsafe struct DesignData return true; } + public void SetDefaultEquipment(ItemManager items) + { + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + SetItem(slot, ItemManager.NothingItem(slot)); + SetStain(slot, 0); + } + + SetItem(EquipSlot.MainHand, items.DefaultSword); + SetStain(EquipSlot.MainHand, 0); + SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); + SetStain(EquipSlot.OffHand, 0); + } + + public void LoadNonHuman(uint modelId, Customize customize, byte* equipData) + { + ModelId = modelId; + Customize.Load(customize); + fixed (byte* ptr = _equipmentBytes) + { + MemoryUtility.MemCpyUnchecked(ptr, equipData, 40); + } + } + + public readonly byte[] GetCustomizeBytes() + { + var ret = new byte[CustomizeData.Size]; + fixed (byte* retPtr = ret, inPtr = Customize.Data.Data) + { + MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length); + } + + return ret; + } + + public readonly byte[] GetEquipmentBytes() + { + var ret = new byte[40]; + fixed (byte* retPtr = ret, inPtr = _equipmentBytes) + { + MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length); + } + + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private static bool SetIfDifferent(ref T old, T value) where T : IEquatable { @@ -176,4 +228,4 @@ public unsafe struct DesignData old = value; return true; } -} \ No newline at end of file +} diff --git a/Glamourer/Gui/Colors.cs b/Glamourer/Gui/Colors.cs index a5ce52f..ae5b2da 100644 --- a/Glamourer/Gui/Colors.cs +++ b/Glamourer/Gui/Colors.cs @@ -11,6 +11,11 @@ public enum ColorId public static class Colors { + public const uint DiscordColor = 0xFFDA8972; + public const uint ReniColorButton = 0xFFCC648D; + public const uint ReniColorHovered = 0xFFB070B0; + public const uint ReniColorActive = 0xFF9070E0; + public static (uint DefaultColor, string Name, string Description) Data(this ColorId color) => color switch { diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 0eace1b..f773394 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -4,15 +4,29 @@ using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Glamourer.Gui.Tabs; using ImGuiNET; +using OtterGui.Custom; using OtterGui.Widgets; namespace Glamourer.Gui; public class MainWindow : Window { - private readonly ITab[] _tabs; + public enum TabType + { + None = -1, + Settings = 0, + Debug = 1, + } - public MainWindow(DalamudPluginInterface pi, DebugTab debugTab) + private readonly Configuration _config; + private readonly ITab[] _tabs; + + public readonly SettingsTab Settings; + public readonly DebugTab Debug; + + public TabType SelectTab = TabType.None; + + public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, DebugTab debugTab) : base(GetLabel()) { pi.UiBuilder.DisableGposeUiHide = true; @@ -21,19 +35,64 @@ public class MainWindow : Window MinimumSize = new Vector2(675, 675), MaximumSize = ImGui.GetIO().DisplaySize, }; + Settings = settings; + Debug = debugTab; + _config = config; _tabs = new ITab[] { + settings, debugTab, }; + + IsOpen = _config.DebugMode; } public override void Draw() { - TabBar.Draw("##tabs", ImGuiTabBarFlags.None, ReadOnlySpan.Empty, out var currentTab, () => { }, _tabs); + if (!TabBar.Draw("##tabs", ImGuiTabBarFlags.None, ToLabel(SelectTab), out var currentTab, () => { }, _tabs)) + return; + + SelectTab = TabType.None; + _config.SelectedTab = FromLabel(currentTab); + _config.Save(); + } + + private ReadOnlySpan ToLabel(TabType type) + => type switch + { + TabType.Settings => Settings.Label, + TabType.Debug => Debug.Label, + _ => ReadOnlySpan.Empty, + }; + + private TabType FromLabel(ReadOnlySpan label) + { + // @formatter:off + if (label == Settings.Label) return TabType.Settings; + if (label == Debug.Label) return TabType.Debug; + // @formatter:on + return TabType.None; } private static string GetLabel() => Glamourer.Version.Length == 0 ? "Glamourer###GlamourerMainWindow" : $"Glamourer v{Glamourer.Version}###GlamourerMainWindow"; + + + /// Draw the support button group on the right-hand side of the window. + public static void DrawSupportButtons() + { + 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.Chat, width); + + ImGui.SetCursorPos(new Vector2(xPos, ImGui.GetFrameHeightWithSpacing())); + CustomGui.DrawGuideButton(Glamourer.Chat, width); + } } diff --git a/Glamourer/Gui/Tabs/DebugTab.cs b/Glamourer/Gui/Tabs/DebugTab.cs index 8a98345..bb089e9 100644 --- a/Glamourer/Gui/Tabs/DebugTab.cs +++ b/Glamourer/Gui/Tabs/DebugTab.cs @@ -12,6 +12,7 @@ using Glamourer.Interop; using Glamourer.Interop.Penumbra; using Glamourer.Interop.Structs; using Glamourer.Services; +using Glamourer.State; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -24,6 +25,7 @@ namespace Glamourer.Gui.Tabs; public unsafe class DebugTab : ITab { + private readonly Configuration _config; private readonly VisorService _visorService; private readonly ChangeCustomizeService _changeCustomizeService; private readonly UpdateSlotService _updateSlotService; @@ -39,12 +41,17 @@ public unsafe class DebugTab : ITab private readonly DesignManager _designManager; private readonly DesignFileSystem _designFileSystem; + private readonly StateManager _state; + private int _gameObjectIndex; + public bool IsVisible + => _config.DebugMode; + public DebugTab(ChangeCustomizeService changeCustomizeService, VisorService visorService, ObjectTable objects, UpdateSlotService updateSlotService, WeaponService weaponService, PenumbraService penumbra, ActorService actors, ItemManager items, CustomizationService customization, ObjectManager objectManager, - DesignFileSystem designFileSystem, DesignManager designManager) + DesignFileSystem designFileSystem, DesignManager designManager, StateManager state, Configuration config) { _changeCustomizeService = changeCustomizeService; _visorService = visorService; @@ -58,6 +65,8 @@ public unsafe class DebugTab : ITab _objectManager = objectManager; _designFileSystem = designFileSystem; _designManager = designManager; + _state = state; + _config = config; } public ReadOnlySpan Label @@ -69,6 +78,7 @@ public unsafe class DebugTab : ITab DrawGameDataHeader(); DrawPenumbraHeader(); DrawDesigns(); + DrawState(); } #region Interop @@ -724,7 +734,8 @@ public unsafe class DebugTab : ITab continue; DrawDesign(design); - var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomize, design.DoApplyHatVisible(), + var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomize, + design.DoApplyHatVisible(), design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected()); using var font = ImRaii.PushFont(UiBuilder.MonoFont); ImGuiUtil.TextWrapped(base64); @@ -773,7 +784,7 @@ public unsafe class DebugTab : ITab } else if (_restore.Length > 0) { - DrawDesignData(_parse64, true); + 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 })) @@ -818,44 +829,60 @@ public unsafe class DebugTab : ITab } } - private static void DrawDesignData(in DesignData data, bool createTable) + private static void DrawDesignData(in DesignData data) { - using var table = createTable ? ImRaii.Table("##equip", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit) : null; - foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) + if (data.ModelId == 0) { - var item = data.Item(slot); - var stain = data.Stain(slot); - ImGuiUtil.DrawTableColumn(slot.ToName()); - ImGuiUtil.DrawTableColumn(item.Name); - ImGuiUtil.DrawTableColumn(item.Id.ToString()); - ImGuiUtil.DrawTableColumn(stain.ToString()); - } + using var table = ImRaii.Table("##equip", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); + 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.Id.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("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(); + 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()); + 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")))); - ImGuiUtil.DrawTableColumn("Is Wet"); - ImGuiUtil.DrawTableColumn(data.IsWet().ToString()); - ImGui.TableNextRow(); + ImGui.TextUnformatted("Equipment Array"); + ImGui.Separator(); + ImGuiUtil.TextWrapped(string.Join(" ", data.GetEquipmentBytes().Select(b => b.ToString("X2")))); + } } private void DrawDesign(Design design) @@ -933,4 +960,51 @@ public unsafe class DebugTab : ITab } #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) + { + using var t = ImRaii.TreeNode(actors.Label); + if (!t) + continue; + + if (_state.GetOrCreate(identifier, actors.Objects[0], out var state)) + DrawDesignData(state.Data); + 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) + DrawDesignData(state.Data); + } + } + + #endregion } diff --git a/Glamourer/Gui/Tabs/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab.cs new file mode 100644 index 0000000..0cdf6a1 --- /dev/null +++ b/Glamourer/Gui/Tabs/SettingsTab.cs @@ -0,0 +1,68 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Widgets; + +namespace Glamourer.Gui.Tabs; + +public class SettingsTab : ITab +{ + private readonly Configuration _config; + + public SettingsTab(Configuration config) + => _config = config; + + public ReadOnlySpan Label + => "Settings"u8; + + public void DrawContent() + { + using var child = ImRaii.Child("##SettingsTab", -Vector2.One, false); + if (!child) + return; + + Checkbox("Restricted Gear Protection", + "Use gender- and race-appropriate models when detecting certain items not available for a characters current gender and race.", + _config.UseRestrictedGearProtection, v => _config.UseRestrictedGearProtection = v); + Checkbox("Debug Mode", "Show the debug tab. Only useful for debugging or advanced use.", _config.DebugMode, v => _config.DebugMode = v); + DrawColorSettings(); + + MainWindow.DrawSupportButtons(); + } + + + /// Draw the entire Color subsection. + private void DrawColorSettings() + { + if (!ImGui.CollapsingHeader("Colors")) + return; + + 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(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void Checkbox(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.Save(); + } + + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker(label, tooltip); + } +} diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 2f28367..d5ee2e5 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 FFXIVClientStructs.FFXIV.Client.System.String; +using Glamourer.Customization; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; @@ -103,6 +104,9 @@ public readonly unsafe struct Actor : IEquatable public CharacterWeapon GetOffhand() => *(CharacterWeapon*)&AsCharacter->DrawData.OffHandModel; + public Customize GetCustomize() + => *(Customize*)&AsCharacter->DrawData.CustomizeData; + public override string ToString() => $"0x{Address:X}"; } diff --git a/Glamourer/Interop/Structs/Model.cs b/Glamourer/Interop/Structs/Model.cs index 4c892ca..0ebda54 100644 --- a/Glamourer/Interop/Structs/Model.cs +++ b/Glamourer/Interop/Structs/Model.cs @@ -1,5 +1,6 @@ using System; 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; @@ -89,6 +90,9 @@ public readonly unsafe struct Model : IEquatable public CharacterArmor GetArmor(EquipSlot slot) => ((CharacterArmor*)AsHuman->EquipSlotData)[slot.ToIndex()]; + public Customize GetCustomize() + => *(Customize*)&AsHuman->Customize; + public (Model Address, CharacterWeapon Data) GetMainhand() { Model weapon = AsDrawObject->Object.ChildObject; diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 742c2e8..2c82354 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -5,6 +5,7 @@ using Glamourer.Gui; using Glamourer.Gui.Tabs; using Glamourer.Interop; using Glamourer.Interop.Penumbra; +using Glamourer.State; using Microsoft.Extensions.DependencyInjection; using OtterGui.Classes; using OtterGui.Log; @@ -23,6 +24,7 @@ public static class ServiceManager .AddEvents() .AddData() .AddDesigns() + .AddState() .AddUi() .AddApi(); @@ -68,8 +70,12 @@ public static class ServiceManager => services.AddSingleton() .AddSingleton(); + private static IServiceCollection AddState(this IServiceCollection services) + => services.AddSingleton(); + private static IServiceCollection AddUi(this IServiceCollection services) => services.AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs new file mode 100644 index 0000000..9adebe8 --- /dev/null +++ b/Glamourer/State/ActorState.cs @@ -0,0 +1,13 @@ +using Glamourer.Designs; +using Penumbra.GameData.Actors; + +namespace Glamourer.State; + +public class ActorState +{ + public ActorIdentifier Identifier { get; internal init; } + public DesignData Data { get; internal set; } + + internal ActorState(ActorIdentifier identifier) + => Identifier = identifier; +} diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs new file mode 100644 index 0000000..56b2122 --- /dev/null +++ b/Glamourer/State/StateManager.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Glamourer.Customization; +using Glamourer.Designs; +using Glamourer.Interop; +using Glamourer.Interop.Structs; +using Glamourer.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.State; + +public class StateManager : IReadOnlyDictionary +{ + private readonly ActorService _actors; + private readonly ItemManager _items; + private readonly CustomizationService _customizations; + private readonly VisorService _visor; + + private readonly Dictionary _states = new(); + + public StateManager(ActorService actors, ItemManager items, CustomizationService customizations, VisorService visor) + { + _actors = actors; + _items = items; + _customizations = customizations; + _visor = visor; + } + + public bool GetOrCreate(Actor actor, [NotNullWhen(true)] out ActorState? state) + => GetOrCreate(actor.GetIdentifier(_actors.AwaitedService), actor, out state); + + public bool GetOrCreate(ActorIdentifier identifier, Actor actor, [NotNullWhen(true)] out ActorState? state) + { + if (TryGetValue(identifier, out state)) + return true; + + try + { + var designData = FromActor(actor); + _states.Add(identifier, new ActorState(identifier) { Data = designData }); + return true; + } + catch (Exception ex) + { + Glamourer.Log.Error($"Could not create new actor data for {identifier}:\n{ex}"); + return false; + } + } + + public IEnumerator> GetEnumerator() + => _states.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public int Count + => _states.Count; + + public bool ContainsKey(ActorIdentifier key) + => _states.ContainsKey(key); + + public bool TryGetValue(ActorIdentifier key, out ActorState value) + => _states.TryGetValue(key, out value!); + + public ActorState this[ActorIdentifier key] + => _states[key]; + + public IEnumerable Keys + => _states.Keys; + + public IEnumerable Values + => _states.Values; + + public unsafe DesignData FromActor(Actor actor) + { + var ret = new DesignData(); + if (!actor.IsCharacter) + { + ret.SetDefaultEquipment(_items); + return ret; + } + + if (actor.AsCharacter->ModelCharaId != 0) + { + ret.LoadNonHuman((uint)actor.AsCharacter->ModelCharaId, *(Customize*)&actor.AsCharacter->DrawData.CustomizeData, + (byte*)&actor.AsCharacter->DrawData.Head); + return ret; + } + + var model = actor.Model; + CharacterWeapon main; + CharacterWeapon off; + + ret.SetHatVisible(!actor.AsCharacter->DrawData.IsHatHidden); + if (model.IsHuman) + { + var head = ret.IsHatVisible() ? 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); + + foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1)) + { + var armor = model.GetArmor(slot); + var item = _items.Identify(slot, armor.Set, armor.Variant); + ret.SetItem(slot, item); + ret.SetStain(slot, armor.Stain); + } + + ret.Customize = model.GetCustomize(); + (_, _, main, off) = model.GetWeapons(actor); + ret.SetVisor(_visor.GetVisorState(model)); + } + else + { + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var armor = actor.GetArmor(slot); + var item = _items.Identify(slot, armor.Set, armor.Variant); + ret.SetItem(slot, item); + ret.SetStain(slot, armor.Stain); + } + + ret.Customize = actor.GetCustomize(); + main = actor.GetMainhand(); + off = actor.GetOffhand(); + ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled); + } + + var mainItem = _items.Identify(EquipSlot.MainHand, main.Set, main.Type, (byte)main.Variant); + var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, (byte)off.Variant, mainItem.Type); + ret.SetItem(EquipSlot.MainHand, mainItem); + ret.SetStain(EquipSlot.MainHand, main.Stain); + ret.SetItem(EquipSlot.OffHand, offItem); + ret.SetStain(EquipSlot.OffHand, off.Stain); + + ret.SetIsWet(actor.AsCharacter->IsGPoseWet); + ret.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden); + return ret; + } +} diff --git a/GlamourerOld/Gui/Customization/CustomizationDrawer.cs b/GlamourerOld/Gui/Customization/CustomizationDrawer.cs index f3c111a..70ec9ac 100644 --- a/GlamourerOld/Gui/Customization/CustomizationDrawer.cs +++ b/GlamourerOld/Gui/Customization/CustomizationDrawer.cs @@ -117,16 +117,16 @@ public partial class CustomizationDrawer : IDisposable foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage]) PercentageSelector(id); - Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.IconSelector], DrawIconSelector, ImGui.SameLine); + CustomGui.IteratePairwise(_set.Order[CharaMakeParams.MenuType.IconSelector], DrawIconSelector, ImGui.SameLine); DrawMultiIconSelector(); foreach (var id in _set.Order[CharaMakeParams.MenuType.ListSelector]) DrawListSelector(id); - Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine); + CustomGui.IteratePairwise(_set.Order[CharaMakeParams.MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine); - Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.Checkmark], DrawCheckbox, + CustomGui.IteratePairwise(_set.Order[CharaMakeParams.MenuType.Checkmark], DrawCheckbox, () => ImGui.SameLine(_inputIntSize + _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X)); return Changed != 0; }