Current state.

This commit is contained in:
Ottermandias 2026-02-02 16:16:54 +01:00
parent 2850067f43
commit e8c6204c25
15 changed files with 294 additions and 219 deletions

View file

@ -100,7 +100,7 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
// 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<NpcData>
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<NpcData>
}
// 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<NpcData>
// 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);

View file

@ -4,16 +4,13 @@ using Penumbra.GameData.Structs;
namespace Glamourer.GameData;
/// <summary> A struct containing everything to replicate the appearance of a human NPC. </summary>
public unsafe struct NpcData
public struct NpcData
{
/// <summary> The name of the NPC. </summary>
public string Name;
/// <summary> The customizations of the NPC. </summary>
public CustomizeArray Customize;
/// <summary> The equipment appearance of the NPC, 10 * CharacterArmor. </summary>
private fixed byte _equip[CharacterArmor.Size * 10];
private EquipArray _equip;
/// <summary> The mainhand weapon appearance of the NPC. </summary>
public CharacterWeapon Mainhand;
@ -21,6 +18,9 @@ public unsafe struct NpcData
/// <summary> The offhand weapon appearance of the NPC. </summary>
public CharacterWeapon Offhand;
/// <summary> The customizations of the NPC. </summary>
public CustomizeArray Customize;
/// <summary> The data ID of the NPC, either event NPC or battle NPC name. </summary>
public NpcId Id;
@ -33,57 +33,50 @@ public unsafe struct NpcData
/// <summary> Whether the NPC is an event NPC or a battle NPC. </summary>
public ObjectKind Kind;
/// <summary> Obtain an equipment piece. </summary>
public readonly CharacterArmor Item(int i)
=> _equip[i];
/// <summary> Obtain the equipment as CharacterArmors. </summary>
public ReadOnlySpan<CharacterArmor> Equip
{
get
{
fixed (byte* ptr = _equip)
{
return new ReadOnlySpan<CharacterArmor>((CharacterArmor*)ptr, 10);
}
}
}
public readonly CharacterArmor[] Equip()
=> ((ReadOnlySpan<CharacterArmor>)_equip).ToArray();
/// <summary> Write all the gear appearance to a single string. </summary>
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();
}
/// <summary> Set an equipment piece to a given value. </summary>
internal void Set(int idx, ulong value)
{
fixed (byte* ptr = _equip)
{
((ulong*)ptr)[idx] = value;
}
_equip[idx] = Unsafe.As<ulong, CharacterArmor>(ref value);
}
/// <summary> Check if the appearance data, excluding ID and Name, of two NpcData is equal. </summary>
@ -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<byte>(ptr1, 40).SequenceEqual(new ReadOnlySpan<byte>(ptr2, 40));
}
return ((ReadOnlySpan<CharacterArmor>)_equip).SequenceEqual(other._equip);
}
[InlineArray(10)]
private struct EquipArray
{
private CharacterArmor _element;
}
}

View file

@ -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<BonusItemCombo2.CacheItem>(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<CacheItem>
{
protected override string ToFilterString(in CacheItem item, int globalIndex)
=> item.Name.Utf16;
}
protected override IEnumerable<CacheItem> 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<Addon>()!;
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<EquipItem>
{
private readonly FavoriteManager _favorites;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<byte> 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<CacheItem>
{
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<CacheItem>(filter)
{
protected override IEnumerable<CacheItem> 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);
}
}
}

View file

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

View file

@ -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)
{

View file

@ -21,8 +21,8 @@ public sealed class UnlocksTab : Window, ITab<MainTabType>
IsOpen = false;
SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(700, 675),
MaximumSize = Im.Io.DisplaySize,
MinimumSize = new Vector2(700, 675),
MaximumSize = new Vector2(3840, 2160),
};
}

View file

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

@ -1 +1 @@
Subproject commit b5213e2dee715bb6c53dc15dee61392b183a34a1
Subproject commit 478febd4ed9af42055ce7396f69cec0334bc4140