diff --git a/Glamourer/GameData/NpcCustomizeSet.cs b/Glamourer/GameData/NpcCustomizeSet.cs index 24f1077..a8f3e1b 100644 --- a/Glamourer/GameData/NpcCustomizeSet.cs +++ b/Glamourer/GameData/NpcCustomizeSet.cs @@ -100,7 +100,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList // Event NPCs have a reference to NpcEquip but also contain the appearance in their own row. // Prefer the NpcEquip reference if it is set and the own does not appear to be set, otherwise use the own. - if (row.NpcEquip.RowId != 0 && row.NpcEquip.Value is { } equip && row is { ModelBody: 0, ModelLegs: 0 }) + if (row.NpcEquip.RowId is not 0 && row.NpcEquip.Value is { } equip && row is { ModelBody: 0, ModelLegs: 0 }) ApplyNpcEquip(ref ret, equip); else ApplyNpcEquip(ref ret, row); @@ -121,12 +121,12 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList foreach (var baseRow in bnpcSheet) { // Only accept humans. - if (baseRow.ModelChara.Value.Type != 1) + if (baseRow.ModelChara.Value.Type is not 1) continue; var bnpcNameIds = bNpcNames[baseRow.RowId]; // Only accept battle NPCs with known associated names. - if (bnpcNameIds.Count == 0) + if (bnpcNameIds.Count is 0) continue; // Check if the customization is a valid human. @@ -186,7 +186,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList } // If there is only a single entry, add that. - if (duplicates.Count == 1) + if (duplicates.Count is 1) { _data.Add(duplicates[0]); Memory += 96; @@ -200,7 +200,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList // Sort non-alphanumeric entries at the end instead of the beginning. var lastWeird = _data.FindIndex(d => char.IsAsciiLetterOrDigit(d.Name[0])); - if (lastWeird != -1) + if (lastWeird is not -1) { _data.AddRange(_data.Take(lastWeird)); _data.RemoveRange(0, lastWeird); diff --git a/Glamourer/GameData/NpcData.cs b/Glamourer/GameData/NpcData.cs index 0076bb6..96f6210 100644 --- a/Glamourer/GameData/NpcData.cs +++ b/Glamourer/GameData/NpcData.cs @@ -4,16 +4,13 @@ using Penumbra.GameData.Structs; namespace Glamourer.GameData; /// A struct containing everything to replicate the appearance of a human NPC. -public unsafe struct NpcData +public struct NpcData { /// The name of the NPC. public string Name; - /// The customizations of the NPC. - public CustomizeArray Customize; - /// The equipment appearance of the NPC, 10 * CharacterArmor. - private fixed byte _equip[CharacterArmor.Size * 10]; + private EquipArray _equip; /// The mainhand weapon appearance of the NPC. public CharacterWeapon Mainhand; @@ -21,6 +18,9 @@ public unsafe struct NpcData /// The offhand weapon appearance of the NPC. public CharacterWeapon Offhand; + /// The customizations of the NPC. + public CustomizeArray Customize; + /// The data ID of the NPC, either event NPC or battle NPC name. public NpcId Id; @@ -33,57 +33,50 @@ public unsafe struct NpcData /// Whether the NPC is an event NPC or a battle NPC. public ObjectKind Kind; + /// Obtain an equipment piece. + public readonly CharacterArmor Item(int i) + => _equip[i]; + /// Obtain the equipment as CharacterArmors. - public ReadOnlySpan Equip - { - get - { - fixed (byte* ptr = _equip) - { - return new ReadOnlySpan((CharacterArmor*)ptr, 10); - } - } - } + public readonly CharacterArmor[] Equip() + => ((ReadOnlySpan)_equip).ToArray(); /// Write all the gear appearance to a single string. public string WriteGear() { - var sb = new StringBuilder(128); - var span = Equip; + var sb = new StringBuilder(256); + for (var i = 0; i < 10; ++i) { - sb.Append(span[i].Set.Id.ToString("D4")) + sb.Append($"{_equip[i].Set.Id:D4}") .Append('-') - .Append(span[i].Variant.Id.ToString("D3")); - foreach (var stain in span[i].Stains) - sb.Append('-').Append(stain.Id.ToString("D3")); + .Append($"{_equip[i].Variant.Id:D3}"); + foreach (var stain in _equip[i].Stains) + sb.Append('-').Append($"{stain.Id:D3}"); } - sb.Append(Mainhand.Skeleton.Id.ToString("D4")) + sb.Append($"{Mainhand.Skeleton.Id:D4}") .Append('-') - .Append(Mainhand.Weapon.Id.ToString("D4")) + .Append($"{Mainhand.Weapon.Id:D4}") .Append('-') - .Append(Mainhand.Variant.Id.ToString("D3")); + .Append($"{Mainhand.Variant.Id:D3}"); foreach (var stain in Mainhand.Stains) - sb.Append('-').Append(stain.Id.ToString("D3")); + sb.Append('-').Append($"{stain.Id:D3}"); sb.Append(", ") - .Append(Offhand.Skeleton.Id.ToString("D4")) + .Append($"{Offhand.Skeleton.Id:D4}") .Append('-') - .Append(Offhand.Weapon.Id.ToString("D4")) + .Append($"{Offhand.Weapon.Id:D4}") .Append('-') - .Append(Offhand.Variant.Id.ToString("D3")); + .Append($"{Offhand.Variant.Id:D3}"); foreach (var stain in Mainhand.Stains) - sb.Append('-').Append(stain.Id.ToString("D3")); + sb.Append('-').Append($"{stain.Id:D3}"); return sb.ToString(); } /// Set an equipment piece to a given value. internal void Set(int idx, ulong value) { - fixed (byte* ptr = _equip) - { - ((ulong*)ptr)[idx] = value; - } + _equip[idx] = Unsafe.As(ref value); } /// Check if the appearance data, excluding ID and Name, of two NpcData is equal. @@ -104,9 +97,12 @@ public unsafe struct NpcData if (!Offhand.Equals(other.Offhand)) return false; - fixed (byte* ptr1 = _equip, ptr2 = other._equip) - { - return new ReadOnlySpan(ptr1, 40).SequenceEqual(new ReadOnlySpan(ptr2, 40)); - } + return ((ReadOnlySpan)_equip).SequenceEqual(other._equip); + } + + [InlineArray(10)] + private struct EquipArray + { + private CharacterArmor _element; } } diff --git a/Glamourer/Gui/Equipment/BonusItemCombo.cs b/Glamourer/Gui/Equipment/BonusItemCombo.cs index 6b1445e..5d486e9 100644 --- a/Glamourer/Gui/Equipment/BonusItemCombo.cs +++ b/Glamourer/Gui/Equipment/BonusItemCombo.cs @@ -1,21 +1,80 @@ -using Dalamud.Plugin.Services; +using Dalamud.Bindings.ImGui; +using Dalamud.Plugin.Services; using Glamourer.Services; using Glamourer.Unlocks; -using Dalamud.Bindings.ImGui; using ImSharp; -using Lumina.Excel.Sheets; using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Log; -using OtterGui.Raii; using OtterGui.Widgets; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; +using Addon = Lumina.Excel.Sheets.Addon; using MouseWheelType = OtterGui.Widgets.MouseWheelType; namespace Glamourer.Gui.Equipment; +public sealed class BonusItemCombo2(IDataManager gameData, ItemManager items, FavoriteManager favorites, BonusItemFlag slot) + : ImSharp.FilterComboBase(new ItemFilter()) +{ + public readonly StringU8 Label = GetLabel(gameData, slot); + public readonly BonusItemFlag Slot = slot; + + public readonly struct CacheItem(EquipItem item) : IDisposable + { + public readonly EquipItem Item = item; + public readonly StringPair Name = new(item.Name); + public readonly SizedString Model = new($"({item.PrimaryId.Id}-{item.Variant.Id})"); + + public void Dispose() + => Model.Dispose(); + } + + private sealed class ItemFilter : PartwiseFilterBase + { + protected override string ToFilterString(in CacheItem item, int globalIndex) + => item.Name.Utf16; + } + + protected override IEnumerable GetItems() + { + var nothing = EquipItem.BonusItemNothing(Slot); + return items.ItemData.ByType[Slot.ToEquipType()].OrderByDescending(favorites.Contains).ThenBy(i => i.Id.Id).Prepend(nothing) + .Select(i => new CacheItem(i)); + } + + protected override float ItemHeight + => Im.Style.TextHeightWithSpacing; + + protected override bool DrawItem(in CacheItem item, int globalIndex, bool selected) + { + UiHelpers.DrawFavoriteStar(favorites, item.Item); + Im.Line.Same(); + var ret = Im.Selectable(item.Name.Utf8, selected); + Im.Line.Same(); + using var color = ImGuiColor.Text.Push(Rgba32.Gray); + ImEx.TextRightAligned(item.Model); + return ret; + } + + protected override bool IsSelected(CacheItem item, int globalIndex) + => throw new NotImplementedException(); + + private static StringU8 GetLabel(IDataManager gameData, BonusItemFlag slot) + { + var sheet = gameData.GetExcelSheet()!; + + return slot switch + { + BonusItemFlag.Glasses => sheet.TryGetRow(16050, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Facewear"u8), + BonusItemFlag.UnkSlot => sheet.TryGetRow(16051, out var text) ? new StringU8(text.Text.Data, false) : new StringU8("Facewear"u8), + + _ => StringU8.Empty, + }; + } +} + public sealed class BonusItemCombo : FilterComboCache { private readonly FavoriteManager _favorites; diff --git a/Glamourer/Gui/Equipment/ItemCombo.cs b/Glamourer/Gui/Equipment/ItemCombo.cs index 8f6898c..25cd74a 100644 --- a/Glamourer/Gui/Equipment/ItemCombo.cs +++ b/Glamourer/Gui/Equipment/ItemCombo.cs @@ -7,7 +7,6 @@ using Lumina.Excel.Sheets; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Log; -using OtterGui.Raii; using OtterGui.Text; using OtterGui.Widgets; using Penumbra.GameData.Enums; diff --git a/Glamourer/Gui/MainWindow.cs b/Glamourer/Gui/MainWindow.cs index 0e6eb2e..e55d990 100644 --- a/Glamourer/Gui/MainWindow.cs +++ b/Glamourer/Gui/MainWindow.cs @@ -22,7 +22,7 @@ public sealed class MainWindow : Window, IDisposable SizeConstraints = new WindowSizeConstraints { MinimumSize = new Vector2(700, 675), - MaximumSize = Im.Io.DisplaySize, + MaximumSize = new Vector2(3840, 2160), }; _mainTabBar = mainTabBar; _quickBar = quickBar; diff --git a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs index db4b266..0bf64bb 100644 --- a/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/FunPanel.cs @@ -1,5 +1,5 @@ using Glamourer.State; -using Dalamud.Bindings.ImGui; +using ImSharp; using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; @@ -14,20 +14,20 @@ public sealed class FunPanel(FunModule funModule, Configuration config) : IGameD 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")) + Im.Text($"Current Festival: {funModule.CurrentFestival}"); + Im.Text($"Festivals Enabled: {config.DisableFestivals switch { 1 => "Undecided"u8, 0 => "Enabled"u8, _ => "Disabled"u8 }}"); + Im.Text($"Popup Open: {Im.Popup.IsOpen("FestivalPopup"u8, PopupQueryFlags.AnyPopup)}"); + if (Im.Button("Force Christmas"u8)) funModule.ForceFestival(FunModule.FestivalType.Christmas); - if (ImGui.Button("Force Halloween")) + if (Im.Button("Force Halloween"u8)) funModule.ForceFestival(FunModule.FestivalType.Halloween); - if (ImGui.Button("Force April First")) + if (Im.Button("Force April First"u8)) funModule.ForceFestival(FunModule.FestivalType.AprilFirst); - if (ImGui.Button("Force None")) + if (Im.Button("Force None"u8)) funModule.ForceFestival(FunModule.FestivalType.None); - if (ImGui.Button("Revert")) + if (Im.Button("Revert"u8)) funModule.ResetFestival(); - if (ImGui.Button("Reset Popup")) + if (Im.Button("Reset Popup"u8)) { config.DisableFestivals = 1; config.Save(); diff --git a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs index 7044bca..1cf2958 100644 --- a/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/GlamourPlatePanel.cs @@ -1,14 +1,10 @@ -using Dalamud.Interface.Utility.Raii; -using Dalamud.Plugin.Services; +using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game; using Glamourer.Designs; using Glamourer.Services; using Glamourer.State; -using Dalamud.Bindings.ImGui; using ImSharp; -using OtterGui.Extensions; -using OtterGui.Text; using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; @@ -43,29 +39,29 @@ public sealed unsafe class GlamourPlatePanel : IGameDataDrawer public void Draw() { var manager = MirageManager.Instance(); - using (ImRaii.Group()) + using (Im.Group()) { - ImUtf8.Text("Address:"u8); - ImUtf8.Text("Number of Glamour Plates:"u8); - ImUtf8.Text("Glamour Plates Requested:"u8); - ImUtf8.Text("Glamour Plates Loaded:"u8); - ImUtf8.Text("Is Applying Glamour Plates:"u8); + Im.Text("Address:"u8); + Im.Text("Number of Glamour Plates:"u8); + Im.Text("Glamour Plates Requested:"u8); + Im.Text("Glamour Plates Loaded:"u8); + Im.Text("Is Applying Glamour Plates:"u8); } Im.Line.Same(); - using (ImRaii.Group()) + using (Im.Group()) { - ImUtf8.CopyOnClickSelectable($"0x{(ulong)manager:X}"); - ImUtf8.Text(manager == null ? "-" : manager->GlamourPlates.Length.ToString()); - ImUtf8.Text(manager == null ? "-" : manager->GlamourPlatesRequested.ToString()); + Glamourer.Dynamis.DrawPointer(manager); + Im.Text(manager is null ? "-"u8 : $"{manager->GlamourPlates.Length}"); + Im.Text(manager is null ? "-"u8 : $"{manager->GlamourPlatesRequested}"); Im.Line.Same(); if (Im.SmallButton("Request Update"u8)) RequestGlamour(); - ImUtf8.Text(manager == null ? "-" : manager->GlamourPlatesLoaded.ToString()); - ImUtf8.Text(manager == null ? "-" : manager->IsApplyingGlamourPlate.ToString()); + Im.Text(manager is null ? "-"u8 : $"{manager->GlamourPlatesLoaded}"); + Im.Text(manager is null ? "-"u8 : $"{manager->IsApplyingGlamourPlate}"); } - if (manager == null) + if (manager is null) return; ActorState? state = null; @@ -74,28 +70,28 @@ public sealed unsafe class GlamourPlatePanel : IGameDataDrawer for (var i = 0; i < manager->GlamourPlates.Length; ++i) { - using var tree = ImUtf8.TreeNode($"Plate #{i + 1:D2}"); + using var tree = Im.Tree.Node($"Plate #{i + 1:D2}"); if (!tree) continue; ref var plate = ref manager->GlamourPlates[i]; - if (ImUtf8.ButtonEx("Apply to Player"u8, ""u8, Vector2.Zero, !enabled)) + if (ImEx.Button("Apply to Player"u8, Vector2.Zero, StringU8.Empty, !enabled)) { var design = CreateDesign(plate); _state.ApplyDesign(state!, design, ApplySettings.Manual with { IsFinal = true }); } - using (ImRaii.Group()) + using (Im.Group()) { foreach (var slot in EquipSlotExtensions.FullSlots) - ImUtf8.Text(slot.ToName()); + Im.Text(slot.ToNameU8()); } Im.Line.Same(); - using (ImRaii.Group()) + using (Im.Group()) { - foreach (var (_, index) in EquipSlotExtensions.FullSlots.WithIndex()) - ImUtf8.Text($"{plate.ItemIds[index]:D6}, {StainIds.FromGlamourPlate(plate, index)}"); + foreach (var (index, _) in EquipSlotExtensions.FullSlots.Index()) + Im.Text($"{plate.ItemIds[index]:D6}, {StainIds.FromGlamourPlate(plate, index)}"); } } } @@ -106,7 +102,7 @@ public sealed unsafe class GlamourPlatePanel : IGameDataDrawer public void RequestGlamour() { var manager = MirageManager.Instance(); - if (manager == null) + if (manager is null) return; _requestUpdate(manager); @@ -116,10 +112,10 @@ public sealed unsafe class GlamourPlatePanel : IGameDataDrawer { var design = _design.CreateTemporary(); design.Application = ApplicationCollection.None; - foreach (var (slot, index) in EquipSlotExtensions.FullSlots.WithIndex()) + foreach (var (index, slot) in EquipSlotExtensions.FullSlots.Index()) { var itemId = plate.ItemIds[index]; - if (itemId == 0) + if (itemId is 0) continue; var item = _items.Resolve(slot, itemId); diff --git a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs index ab1a37e..cf4a85d 100644 --- a/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/InventoryPanel.cs @@ -1,8 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.Game; -using Dalamud.Bindings.ImGui; using ImSharp; -using OtterGui; -using OtterGui.Raii; using Penumbra.GameData.Gui.Debug; namespace Glamourer.Gui.Tabs.DebugTab; @@ -18,16 +15,16 @@ public sealed unsafe class InventoryPanel : IGameDataDrawer public void Draw() { var inventory = InventoryManager.Instance(); - if (inventory == null) + if (inventory is null) return; - ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)inventory:X}"); + Glamourer.Dynamis.DrawPointer(inventory); var equip = inventory->GetInventoryContainer(InventoryType.EquippedItems); - if (equip == null || equip->IsLoaded) + if (equip is null || equip->IsLoaded) return; - ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)equip:X}"); + Glamourer.Dynamis.DrawPointer(equip); using var table = Im.Table.Begin("items"u8, 4, TableFlags.RowBackground | TableFlags.SizingFixedFit); if (!table) @@ -35,19 +32,19 @@ public sealed unsafe class InventoryPanel : IGameDataDrawer for (var i = 0; i < equip->Size; ++i) { - ImGuiUtil.DrawTableColumn(i.ToString()); + table.DrawColumn($"{i}"); var item = equip->GetInventorySlot(i); - if (item == null) + if (item is null) { - ImGuiUtil.DrawTableColumn("NULL"); - ImGui.TableNextRow(); + table.DrawColumn("NULL"u8); + table.NextRow(); } else { - ImGuiUtil.DrawTableColumn(item->ItemId.ToString()); - ImGuiUtil.DrawTableColumn(item->GlamourId.ToString()); - ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)item:X}"); + table.DrawColumn($"{item->ItemId}"); + table.DrawColumn($"{item->GlamourId}"); + table.NextColumn(); + Glamourer.Dynamis.DrawPointer(item); } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs index 64903e6..e9b07ef 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ItemUnlockPanel.cs @@ -1,12 +1,8 @@ -using Dalamud.Interface.Utility; -using Glamourer.Services; +using Glamourer.Services; using Glamourer.Unlocks; -using Dalamud.Bindings.ImGui; using ImSharp; -using OtterGui; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; -using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; @@ -32,31 +28,28 @@ public sealed class ItemUnlockPanel(ItemUnlockManager itemUnlocks, ItemManager i table.SetupColumn("Model"u8, TableColumnFlags.WidthFixed, 80 * Im.Style.GlobalScale); table.SetupColumn("Unlock"u8, TableColumnFlags.WidthFixed, 120 * Im.Style.GlobalScale); - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(Im.Style.TextHeightWithSpacing); - ImGui.TableNextRow(); - var remainder = ImGuiClip.ClippedDraw(itemUnlocks, skips, t => + using var clipper = new Im.ListClipper(itemUnlocks.Count, Im.Style.TextHeightWithSpacing); + foreach (var (id, _) in clipper.Iterate(itemUnlocks)) { - ImGuiUtil.DrawTableColumn(t.Key.ToString()); - if (items.ItemData.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) + table.DrawColumn($"{id}"); + if (items.ItemData.TryGetValue(id, EquipSlot.MainHand, out var equip)) { - ImGuiUtil.DrawTableColumn(equip.Name); - ImGuiUtil.DrawTableColumn(equip.Type.ToName()); - ImGuiUtil.DrawTableColumn(equip.Weapon().ToString()); + table.DrawColumn(equip.Name); + table.DrawColumn(equip.Type.ToName()); + table.DrawColumn($"{equip.Weapon()}"); } else { - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); + table.NextColumn(); + table.NextColumn(); + table.NextColumn(); } - ImGuiUtil.DrawTableColumn(itemUnlocks.IsUnlocked(t.Key, out var time) + table.DrawColumn(itemUnlocks.IsUnlocked(id, out var time) ? time == DateTimeOffset.MinValue - ? "Always" - : time.LocalDateTime.ToString("g") - : "Never"); - }, itemUnlocks.Count); - ImGuiClip.DrawEndDummy(remainder, Im.Style.TextHeight); + ? "Always"u8 + : $"{time.LocalDateTime:g}" + : "Never"u8); + } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index a9393e2..1cb920b 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -1,21 +1,21 @@ using Dalamud.Interface; -using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Object; using Glamourer.Designs; using Glamourer.GameData; using Glamourer.State; -using Dalamud.Bindings.ImGui; using ImSharp; -using OtterGui.Raii; -using OtterGui.Text; +using Luna; using Penumbra.GameData.Enums; using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Interop; -using ImGuiClip = OtterGui.ImGuiClip; namespace Glamourer.Gui.Tabs.DebugTab; -public sealed class NpcAppearancePanel(NpcCombo npcCombo, StateManager stateManager, ActorObjectManager objectManager, DesignConverter designConverter) +public sealed class NpcAppearancePanel( + NpcCustomizeSet npcData, + StateManager stateManager, + ActorObjectManager objectManager, + DesignConverter designConverter) : IGameDataDrawer { public ReadOnlySpan Label @@ -24,23 +24,45 @@ public sealed class NpcAppearancePanel(NpcCombo npcCombo, StateManager stateMana public bool Disabled => false; - private string _npcFilter = string.Empty; - private bool _customizeOrGear; + private readonly NpcDataFilter _filter = new(); + private bool _customizeOrGear; + + private sealed class NpcDataFilter : TextFilterBase + { + protected override string ToFilterString(in CacheItem item, int globalIndex) + => item.Name.Utf16; + } + + private readonly struct CacheItem(NpcData data) + { + public readonly NpcData Data = data; + public readonly StringPair Name = new(data.Name); + public readonly StringU8 DataId = new($"{data.Id.Id}"); + public readonly StringU8 ModelId = new($"{data.ModelId}"); + public readonly AwesomeIcon Visor = data.VisorToggled ? LunaStyle.TrueIcon : LunaStyle.FalseIcon; + public readonly StringU8 CustomizeData = new($"{data.Customize}"); + public readonly StringU8 GearData = StringU8.Empty; //new(data.WriteGear()); + } + + private sealed class Cache(NpcCustomizeSet npcData, NpcDataFilter filter) : BasicFilterCache(filter) + { + protected override IEnumerable GetItems() + => npcData.Select(i => new CacheItem(i)); + } public void Draw() { - ImUtf8.Checkbox("Compare Customize (or Gear)"u8, ref _customizeOrGear); - ImGui.SetNextItemWidth(Im.ContentRegion.Available.X); - var resetScroll = ImUtf8.InputText("##npcFilter"u8, ref _npcFilter, "Filter..."u8); + Im.Checkbox("Compare Customize (or Gear)"u8, ref _customizeOrGear); + var resetScroll = _filter.DrawFilter("Filter..."u8, Im.ContentRegion.Available); using var table = Im.Table.Begin("npcs"u8, 7, TableFlags.RowBackground | TableFlags.ScrollY | TableFlags.SizingFixedFit, - new Vector2(-1, 400 * Im.Style.GlobalScale)); + Im.ContentRegion.Available with { Y = 400 * Im.Style.GlobalScale }); if (!table) return; - + if (resetScroll) - ImGui.SetScrollY(0); - + Im.Scroll.Y = 0; + table.SetupColumn("Button"u8, TableColumnFlags.WidthFixed); table.SetupColumn("Name"u8, TableColumnFlags.WidthFixed, Im.Style.GlobalScale * 300); table.SetupColumn("Kind"u8, TableColumnFlags.WidthFixed); @@ -48,45 +70,30 @@ public sealed class NpcAppearancePanel(NpcCombo npcCombo, StateManager stateMana table.SetupColumn("Model"u8, TableColumnFlags.WidthFixed); table.SetupColumn("Visor"u8, TableColumnFlags.WidthFixed); table.SetupColumn("Compare"u8, TableColumnFlags.WidthStretch); - - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(Im.Style.FrameHeightWithSpacing); - ImGui.TableNextRow(); - var idx = 0; - var remainder = ImGuiClip.FilteredClippedDraw(npcCombo.Items, skips, - d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), DrawData); - ImGui.TableNextColumn(); - ImGuiClip.DrawEndDummy(remainder, Im.Style.FrameHeightWithSpacing); - return; - - void DrawData(NpcData data) + + var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new Cache(npcData, _filter)); + using var clipper = new Im.ListClipper(cache.Count, Im.Style.FrameHeightWithSpacing); + foreach (var (idx, data) in clipper.Iterate(cache).Index()) { - using var id = Im.Id.Push(idx++); + using var id = Im.Id.Push(idx); var disabled = !stateManager.GetOrCreate(objectManager.Player, out var state); - ImGui.TableNextColumn(); - if (ImUtf8.ButtonEx("Apply"u8, ""u8, Vector2.Zero, disabled)) + table.NextColumn(); + if (ImEx.Button("Apply"u8, Vector2.Zero, StringU8.Empty, disabled)) { - foreach (var (slot, item, stain) in designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true)) + foreach (var (slot, item, stain) in designConverter.FromDrawData(data.Data.Equip(), data.Data.Mainhand, data.Data.Offhand, true)) stateManager.ChangeEquip(state!, slot, item, stain, ApplySettings.Manual); - stateManager.ChangeMetaState(state!, MetaIndex.VisorState, data.VisorToggled, ApplySettings.Manual); - stateManager.ChangeEntireCustomize(state!, data.Customize, CustomizeFlagExtensions.All, ApplySettings.Manual); - } - - ImUtf8.DrawFrameColumn(data.Name); - - ImUtf8.DrawFrameColumn(data.Kind is ObjectKind.BattleNpc ? "B" : "E"); - - ImUtf8.DrawFrameColumn(data.Id.Id.ToString()); - - ImUtf8.DrawFrameColumn(data.ModelId.ToString()); - - using (_ = ImRaii.PushFont(UiBuilder.IconFont)) - { - ImUtf8.DrawFrameColumn(data.VisorToggled ? FontAwesomeIcon.Check.ToIconString() : FontAwesomeIcon.Times.ToIconString()); + stateManager.ChangeMetaState(state!, MetaIndex.VisorState, data.Data.VisorToggled, ApplySettings.Manual); + stateManager.ChangeEntireCustomize(state!, data.Data.Customize, CustomizeFlagExtensions.All, ApplySettings.Manual); } + table.DrawFrameColumn(data.Name.Utf8); + table.DrawFrameColumn(data.Data.Kind is ObjectKind.BattleNpc ? "B"u8 : "E"u8); + table.DrawFrameColumn(data.DataId); + table.DrawFrameColumn(data.ModelId); + table.NextColumn(); + ImEx.Icon.DrawAligned(data.Visor); using var mono = Im.Font.PushMono(); - ImUtf8.DrawFrameColumn(_customizeOrGear ? data.Customize.ToString() : data.WriteGear()); + table.DrawFrameColumn(_customizeOrGear ? data.CustomizeData : data.GearData); } } } diff --git a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs index 3d944aa..d75fa99 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ObjectManagerPanel.cs @@ -1,7 +1,4 @@ -using Dalamud.Bindings.ImGui; -using ImSharp; -using OtterGui; -using OtterGui.Text; +using ImSharp; using Penumbra.GameData.Actors; using Penumbra.GameData.Gui.Debug; using Penumbra.GameData.Interop; @@ -16,7 +13,44 @@ public sealed class ObjectManagerPanel(ActorObjectManager objectManager, ActorMa public bool Disabled => false; - private string _objectFilter = string.Empty; + private sealed class Filter : TextFilterBase + { + protected override string ToFilterString(in CacheItem item, int globalIndex) + => item.Label.Utf16; + } + + private readonly struct CacheItem(ActorIdentifier identifier, ActorData data) + { + public readonly StringPair Label = new(data.Label); + public readonly StringU8 Name = new($"{identifier}"); + public readonly StringU8 Objects = StringU8.Join(", "u8, data.Objects.OrderBy(a => a.Index).Select(a => a.Index)); + } + + private sealed class Cache : BasicFilterCache + { + private readonly ActorObjectManager _objectManager; + + public Cache(ActorObjectManager objectManager, Filter filter) + : base(filter) + { + _objectManager = objectManager; + _objectManager.Objects.OnUpdate += OnUpdate; + } + + private void OnUpdate() + => Dirty |= IManagedCache.DirtyFlags.Custom; + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _objectManager.Objects.OnUpdate -= OnUpdate; + } + + protected override IEnumerable GetItems() + => _objectManager.Select(o => new CacheItem(o.Key, o.Value)); + } + + private readonly Filter _filter = new(); public void Draw() { @@ -27,58 +61,52 @@ public sealed class ObjectManagerPanel(ActorObjectManager objectManager, ActorMa if (!table) return; - ImUtf8.DrawTableColumn("World"u8); - ImUtf8.DrawTableColumn(actors.Finished ? actors.Data.ToWorldName(objectManager.World) : "Service Missing"); - ImUtf8.DrawTableColumn(objectManager.World.ToString()); + table.DrawColumn("World"u8); + table.DrawColumn(actors.Finished ? actors.Data.ToWorldName(objectManager.World) : "Service Missing"u8); + table.DrawColumn($"{objectManager.World}"); - ImUtf8.DrawTableColumn("Player Character"u8); - ImUtf8.DrawTableColumn($"{objectManager.Player.Utf8Name} ({objectManager.Player.Index})"); - ImGui.TableNextColumn(); - ImUtf8.CopyOnClickSelectable(objectManager.Player.ToString()); + table.DrawColumn("Player Character"u8); + table.DrawColumn($"{objectManager.Player.Utf8Name} ({objectManager.Player.Index})"); + table.NextColumn(); + Glamourer.Dynamis.DrawPointer(objectManager.Player.Address); - ImUtf8.DrawTableColumn("In GPose"u8); - ImUtf8.DrawTableColumn(objectManager.IsInGPose.ToString()); - ImGui.TableNextColumn(); + table.DrawColumn("In GPose"u8); + table.DrawColumn($"{objectManager.IsInGPose}"); + table.NextColumn(); - ImUtf8.DrawTableColumn("In Lobby"u8); - ImUtf8.DrawTableColumn(objectManager.IsInLobby.ToString()); - ImGui.TableNextColumn(); + table.DrawColumn("In Lobby"u8); + table.DrawColumn($"{objectManager.IsInLobby}"); + table.NextColumn(); if (objectManager.IsInGPose) { - ImUtf8.DrawTableColumn("GPose Player"u8); - ImUtf8.DrawTableColumn($"{objectManager.GPosePlayer.Utf8Name} ({objectManager.GPosePlayer.Index})"); - ImGui.TableNextColumn(); - ImUtf8.CopyOnClickSelectable(objectManager.GPosePlayer.ToString()); + table.DrawColumn("GPose Player"u8); + table.DrawColumn($"{objectManager.GPosePlayer.Utf8Name} ({objectManager.GPosePlayer.Index})"); + table.NextColumn(); + Glamourer.Dynamis.DrawPointer(objectManager.GPosePlayer.Address); } - ImUtf8.DrawTableColumn("Number of Players"u8); - ImUtf8.DrawTableColumn(objectManager.Count.ToString()); - ImGui.TableNextColumn(); + table.DrawColumn("Number of Players"u8); + table.DrawColumn($"{objectManager.Count}"); + table.NextColumn(); } - var filterChanged = ImUtf8.InputText("##Filter"u8, ref _objectFilter, "Filter..."u8); - using var table2 = Im.Table.Begin("##data2"u8, 3, - TableFlags.RowBackground | TableFlags.BordersOuter | TableFlags.ScrollY, - new Vector2(-1, 20 * Im.Style.TextHeightWithSpacing)); + var filterChanged = _filter.DrawFilter("Filter..."u8, Im.ContentRegion.Available); + using var table2 = Im.Table.Begin("##data2"u8, 3, TableFlags.RowBackground | TableFlags.BordersOuter | TableFlags.ScrollY, + Im.ContentRegion.Available with { Y = 20 * Im.Style.TextHeightWithSpacing }); if (!table2) return; if (filterChanged) - ImGui.SetScrollY(0); + Im.Scroll.Y = 0; - ImGui.TableNextColumn(); - var skips = ImGuiClip.GetNecessarySkips(Im.Style.TextHeightWithSpacing); - ImGui.TableNextRow(); - - var remainder = ImGuiClip.FilteredClippedDraw(objectManager, skips, - p => p.Value.Label.Contains(_objectFilter, StringComparison.OrdinalIgnoreCase), p - => - { - ImUtf8.DrawTableColumn(p.Key.ToString()); - ImUtf8.DrawTableColumn(p.Value.Label); - ImUtf8.DrawTableColumn(string.Join(", ", p.Value.Objects.OrderBy(a => a.Index).Select(a => a.Index.ToString()))); - }); - ImGuiClip.DrawEndDummy(remainder, Im.Style.TextHeightWithSpacing); + var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new Cache(objectManager, _filter)); + using var clip = new Im.ListClipper(cache.Count, Im.Style.TextHeightWithSpacing); + foreach (var item in clip.Iterate(cache)) + { + table2.DrawColumn(item.Name); + table2.DrawColumn(item.Label.Utf8); + table2.DrawColumn(item.Objects); + } } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index b88b441..d549c43 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -185,7 +185,7 @@ public class NpcPanel private DesignData ToDesignData() { var selection = _selector.Selection; - var items = _converter.FromDrawData(selection.Equip.ToArray(), selection.Mainhand, selection.Offhand, true).ToArray(); + var items = _converter.FromDrawData(selection.Equip(), selection.Mainhand, selection.Offhand, true).ToArray(); var designData = new DesignData { Customize = selection.Customize }; foreach (var (slot, item, stain) in items) { diff --git a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs index 8f8a3c6..f257b7c 100644 --- a/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs +++ b/Glamourer/Gui/Tabs/UnlocksTab/UnlocksTab.cs @@ -21,8 +21,8 @@ public sealed class UnlocksTab : Window, ITab IsOpen = false; SizeConstraints = new WindowSizeConstraints { - MinimumSize = new Vector2(700, 675), - MaximumSize = Im.Io.DisplaySize, + MinimumSize = new Vector2(700, 675), + MaximumSize = new Vector2(3840, 2160), }; } diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 483355c..fbb6a55 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -250,7 +250,7 @@ public unsafe class FunModule : IDisposable customize = npc.Customize; var idx = 0; foreach (ref var a in armor) - a = npc.Equip[idx++]; + a = npc.Item(idx++); return true; } diff --git a/Penumbra.GameData b/Penumbra.GameData index b5213e2..478febd 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit b5213e2dee715bb6c53dc15dee61392b183a34a1 +Subproject commit 478febd4ed9af42055ce7396f69cec0334bc4140