From 4b92eae7232f2893d3fa7e6f6554c85493de0946 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 13 Dec 2023 16:46:14 +0100 Subject: [PATCH] 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()