Rework debug tab.

This commit is contained in:
Ottermandias 2023-12-13 16:46:14 +01:00
parent a04b7cd1db
commit 4b92eae723
31 changed files with 2450 additions and 1790 deletions

View file

@ -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<CharacterArmor> Equip
{
get
{
fixed (byte* ptr = _equip)
{
return new ReadOnlySpan<CharacterArmor>((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<byte>(ptr1, 40).SequenceEqual(new ReadOnlySpan<byte>(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<NpcData> CreateNpcData(IReadOnlyDictionary<uint, string> eNpcs,
IReadOnlyDictionary<uint, string> bnpcNames, IObjectIdentifier identifier, IDataManager data)
{
var enpcSheet = data.GetExcelSheet<ENpcBase>()!;
var bnpcSheet = data.GetExcelSheet<BNpcBase>()!;
var list = new List<NpcData>(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<BNpcCustomize> bNpc, ExcelSheet<ENpcBase> eNpc)
{

View file

@ -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<MessageService>();
_services.GetRequiredService<VisorService>();
_services.GetRequiredService<WeaponService>();
_services.GetRequiredService<ScalingService>();
_services.GetRequiredService<StateListener>(); // Initialize State Listener.
_services.GetRequiredService<GlamourerWindowSystem>(); // initialize ui.
_services.GetRequiredService<CommandService>(); // initialize commands.
_services.GetRequiredService<VisorService>();
_services.GetRequiredService<ScalingService>();
_services.GetRequiredService<GlamourerIpc>(); // initialize IPC.
Log.Information($"Glamourer v{Version} loaded successfully.");
}
catch

File diff suppressed because it is too large Load diff

View file

@ -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<T>(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<CustomizeIndex>())
{
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"))));
}
}
}

View file

@ -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);
}
}

View file

@ -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}");
}
}
}
}

View file

@ -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<CustomizeIndex>())
{
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());
}
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}
}

View file

@ -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<Configuration>();
public bool IsVisible
=> _config.DebugMode;
public ReadOnlySpan<byte> 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);
}
}

View file

@ -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<IDebugTabTree> 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<ModelEvaluationPanel>(),
provider.GetRequiredService<ObjectManagerPanel>(),
provider.GetRequiredService<PenumbraPanel>(),
provider.GetRequiredService<IpcTesterPanel>(),
provider.GetRequiredService<DatFilePanel>()
);
public static DebugTabHeader CreateGameData(IServiceProvider provider)
=> new
(
"Game Data",
provider.GetRequiredService<IdentifierPanel>(),
provider.GetRequiredService<RestrictedGearPanel>(),
provider.GetRequiredService<ActorServicePanel>(),
provider.GetRequiredService<ItemManagerPanel>(),
provider.GetRequiredService<StainPanel>(),
provider.GetRequiredService<CustomizationServicePanel>(),
provider.GetRequiredService<JobPanel>(),
provider.GetRequiredService<NpcAppearancePanel>()
);
public static DebugTabHeader CreateDesigns(IServiceProvider provider)
=> new
(
"Designs",
provider.GetRequiredService<DesignManagerPanel>(),
provider.GetRequiredService<DesignConverterPanel>(),
provider.GetRequiredService<DesignTesterPanel>(),
provider.GetRequiredService<AutoDesignPanel>()
);
public static DebugTabHeader CreateState(IServiceProvider provider)
=> new
(
"State",
provider.GetRequiredService<ActiveStatePanel>(),
provider.GetRequiredService<RetainedStatePanel>(),
provider.GetRequiredService<FunPanel>()
);
public static DebugTabHeader CreateUnlocks(IServiceProvider provider)
=> new
(
"Unlocks",
provider.GetRequiredService<CustomizationUnlockPanel>(),
provider.GetRequiredService<ItemUnlockPanel>(),
provider.GetRequiredService<UnlockableItemsPanel>(),
provider.GetRequiredService<InventoryPanel>()
);
}

View file

@ -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());
}
}
}

View file

@ -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<CustomizeIndex>())
{
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();
}
}

View file

@ -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<byte>();
_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<CustomizeIndex>())
{
var value = data.Customize[index];
ImGuiUtil.DrawTableColumn(index.ToDefaultName());
ImGuiUtil.DrawTableColumn(value.Value.ToString());
ImGui.TableNextRow();
}
ImGuiUtil.DrawTableColumn("Is Wet");
ImGuiUtil.DrawTableColumn(data.IsWet().ToString());
ImGui.TableNextRow();
}
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"))));
}
}

View file

@ -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();
}
}
}

View file

@ -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)));
}
}

View file

@ -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}");
}
}
}
}

View file

@ -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);
}
}

View file

@ -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<FullEquipType>().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())})")));
}
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}
}

View file

@ -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<CustomizeIndex>())
{
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);
}
}

View file

@ -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());
}
}
}

View file

@ -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());
}
}

View file

@ -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();
}
}
}

View file

@ -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<Race>().Skip(1))
{
ReadOnlySpan<Gender> 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}.");
}
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

@ -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<CustomizationNpcOptions.NpcData>(new LazyList(actorManager, identifier, data), false, Glamourer.Log)
{
private class LazyList(ActorService actorManager, IdentifierService identifier, IDataManager data)
: IReadOnlyList<CustomizationNpcOptions.NpcData>
{
private readonly Task<IReadOnlyList<CustomizationNpcOptions.NpcData>> _task
= Task.Run(() => CustomizationNpcOptions.CreateNpcData(actorManager.AwaitedService.Data.ENpcs, actorManager.AwaitedService.Data.BNpcs, identifier.AwaitedService, data));
public IEnumerator<CustomizationNpcOptions.NpcData> 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;
}

View file

@ -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<IdentifierDrawer>()
.AddSingleton<GlamourerChangelog>()
.AddSingleton<DesignQuickBar>()
.AddSingleton<DesignColorUi>();
.AddSingleton<DesignColorUi>()
.AddSingleton<NpcCombo>();
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<CommandService>()