mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
We have config at home.
This commit is contained in:
parent
d10cb3137f
commit
80ab57e96d
13 changed files with 487 additions and 62 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<T>(ref T old, T value) where T : IEquatable<T>
|
||||
{
|
||||
|
|
@ -176,4 +228,4 @@ public unsafe struct DesignData
|
|||
old = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<byte>.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<byte> ToLabel(TabType type)
|
||||
=> type switch
|
||||
{
|
||||
TabType.Settings => Settings.Label,
|
||||
TabType.Debug => Debug.Label,
|
||||
_ => ReadOnlySpan<byte>.Empty,
|
||||
};
|
||||
|
||||
private TabType FromLabel(ReadOnlySpan<byte> 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";
|
||||
|
||||
|
||||
/// <summary> Draw the support button group on the right-hand side of the window. </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<byte> 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<CustomizeIndex>())
|
||||
{
|
||||
var value = data.Customize[index];
|
||||
ImGuiUtil.DrawTableColumn(index.ToDefaultName());
|
||||
ImGuiUtil.DrawTableColumn(value.Value.ToString());
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
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
|
||||
}
|
||||
|
|
|
|||
68
Glamourer/Gui/Tabs/SettingsTab.cs
Normal file
68
Glamourer/Gui/Tabs/SettingsTab.cs
Normal file
|
|
@ -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<byte> 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();
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Draw the entire Color subsection. </summary>
|
||||
private void DrawColorSettings()
|
||||
{
|
||||
if (!ImGui.CollapsingHeader("Colors"))
|
||||
return;
|
||||
|
||||
foreach (var color in Enum.GetValues<ColorId>())
|
||||
{
|
||||
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<bool> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Actor>
|
|||
public CharacterWeapon GetOffhand()
|
||||
=> *(CharacterWeapon*)&AsCharacter->DrawData.OffHandModel;
|
||||
|
||||
public Customize GetCustomize()
|
||||
=> *(Customize*)&AsCharacter->DrawData.CustomizeData;
|
||||
|
||||
public override string ToString()
|
||||
=> $"0x{Address:X}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Model>
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -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<DesignManager>()
|
||||
.AddSingleton<DesignFileSystem>();
|
||||
|
||||
private static IServiceCollection AddState(this IServiceCollection services)
|
||||
=> services.AddSingleton<StateManager>();
|
||||
|
||||
private static IServiceCollection AddUi(this IServiceCollection services)
|
||||
=> services.AddSingleton<DebugTab>()
|
||||
.AddSingleton<SettingsTab>()
|
||||
.AddSingleton<MainWindow>()
|
||||
.AddSingleton<GlamourerWindowSystem>();
|
||||
|
||||
|
|
|
|||
13
Glamourer/State/ActorState.cs
Normal file
13
Glamourer/State/ActorState.cs
Normal file
|
|
@ -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;
|
||||
}
|
||||
146
Glamourer/State/StateManager.cs
Normal file
146
Glamourer/State/StateManager.cs
Normal file
|
|
@ -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<ActorIdentifier, ActorState>
|
||||
{
|
||||
private readonly ActorService _actors;
|
||||
private readonly ItemManager _items;
|
||||
private readonly CustomizationService _customizations;
|
||||
private readonly VisorService _visor;
|
||||
|
||||
private readonly Dictionary<ActorIdentifier, ActorState> _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<KeyValuePair<ActorIdentifier, ActorState>> 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<ActorIdentifier> Keys
|
||||
=> _states.Keys;
|
||||
|
||||
public IEnumerable<ActorState> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue