From 75f278c945e9747cb3e79ccf67f5f2defc769a90 Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Sun, 28 Dec 2025 09:14:20 +0100 Subject: [PATCH 1/3] Fix KTK, set up Lifecycles --- .../AddonLifecycles/InventoryLifecycles.cs | 25 ++ AetherBags/Hooks/InventoryHook.cs | 2 + AetherBags/Nodes/DragDropNode.cs | 398 +++++++++--------- AetherBags/Plugin.cs | 4 + KamiToolKit | 2 +- 5 files changed, 231 insertions(+), 200 deletions(-) create mode 100644 AetherBags/AddonLifecycles/InventoryLifecycles.cs diff --git a/AetherBags/AddonLifecycles/InventoryLifecycles.cs b/AetherBags/AddonLifecycles/InventoryLifecycles.cs new file mode 100644 index 0000000..996d94c --- /dev/null +++ b/AetherBags/AddonLifecycles/InventoryLifecycles.cs @@ -0,0 +1,25 @@ +using System; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; + +namespace AetherBags.AddonLifecycles; + +public class InventoryLifecycles : IDisposable +{ + + public InventoryLifecycles() + { + Services.AddonLifecycle.RegisterListener(AddonEvent.PreOpen, ["Inventory", "InventoryLarge", "InventoryExpansion"], HandleInventorySetup); + Services.Logger.Verbose("InventoryLifecycles initialized"); + } + + private void HandleInventorySetup(AddonEvent type, AddonArgs args) + { + Services.Logger.Debug("HandleInventorySetup called"); + } + + public void Dispose() + { + Services.AddonLifecycle.UnregisterListener(AddonEvent.PreOpen, ["Inventory", "InventoryLarge", "InventoryExpansion"]); + } +} \ No newline at end of file diff --git a/AetherBags/Hooks/InventoryHook.cs b/AetherBags/Hooks/InventoryHook.cs index dd2fa48..de6a792 100644 --- a/AetherBags/Hooks/InventoryHook.cs +++ b/AetherBags/Hooks/InventoryHook.cs @@ -1,6 +1,8 @@ using System; using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; namespace AetherBags.Hooks; diff --git a/AetherBags/Nodes/DragDropNode.cs b/AetherBags/Nodes/DragDropNode.cs index b7c6bba..4c0ec05 100644 --- a/AetherBags/Nodes/DragDropNode.cs +++ b/AetherBags/Nodes/DragDropNode.cs @@ -1,244 +1,244 @@ - using System; - using System.Numerics; - using AetherBags.Extensions; - using AetherBags.Interop; - using FFXIVClientStructs.FFXIV.Client.Enums; - using FFXIVClientStructs.FFXIV.Client.UI; - using FFXIVClientStructs.FFXIV.Client.UI.Agent; - using FFXIVClientStructs.FFXIV.Component.GUI; - using KamiToolKit.Classes; - using KamiToolKit.Classes.Timelines; - using KamiToolKit.Nodes; - using Lumina.Text.ReadOnly; +using System; +using System.Numerics; +using AetherBags.Extensions; +using AetherBags.Interop; +using FFXIVClientStructs.FFXIV.Client.Enums; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Component.GUI; +using KamiToolKit.Classes; +using KamiToolKit.Classes.Timelines; +using KamiToolKit.Nodes; +using Lumina.Text.ReadOnly; - namespace AetherBags.Nodes; +namespace AetherBags.Nodes; - public unsafe class DragDropNode : ComponentNode { +public unsafe class DragDropNode : ComponentNode { - // FIX: Manually expose the pointers that are 'internal' in KamiToolKit - // We access the raw AtkComponentNode* via 'this.ResNode' and cast from there. - private new AtkComponentDragDrop* Component => (AtkComponentDragDrop*)this.InternalComponentNode->Component; - private new AtkUldComponentDataDragDrop* Data => (AtkUldComponentDataDragDrop*)Component->UldManager.ComponentData; + // FIX: Manually expose the pointers that are 'internal' in KamiToolKit + // We access the raw AtkComponentNode* via 'this.ResNode' and cast from there. + private new AtkComponentDragDrop* Component => (AtkComponentDragDrop*)Node->Component; + private new AtkUldComponentDataDragDrop* Data => (AtkUldComponentDataDragDrop*)Component->UldManager.ComponentData; - public readonly ImageNode DragDropBackgroundNode; - public readonly IconNode IconNode; + public readonly ImageNode DragDropBackgroundNode; + public readonly IconNode IconNode; - public DragDropNode() { - SetInternalComponentType(ComponentType.DragDrop); + public DragDropNode() { + SetInternalComponentType(ComponentType.DragDrop); - DragDropBackgroundNode = new SimpleImageNode { - NodeId = 3, - Size = new Vector2(44.0f, 44.0f), - TexturePath = "ui/uld/DragTargetA.tex", - TextureCoordinates = new Vector2(0.0f, 0.0f), - TextureSize = new Vector2(44.0f, 44.0f), - WrapMode = WrapMode.Tile, - NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents, - }; - DragDropBackgroundNode.AttachNode(this); + DragDropBackgroundNode = new SimpleImageNode { + NodeId = 3, + Size = new Vector2(44.0f, 44.0f), + TexturePath = "ui/uld/DragTargetA.tex", + TextureCoordinates = new Vector2(0.0f, 0.0f), + TextureSize = new Vector2(44.0f, 44.0f), + WrapMode = WrapMode.Tile, + NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents, + }; + DragDropBackgroundNode.AttachNode(this); - IconNode = new IconNode { - NodeId = 2, - Size = new Vector2(44.0f, 48.0f), - NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents, - }; - IconNode.AttachNode(this); + IconNode = new IconNode { + NodeId = 2, + Size = new Vector2(44.0f, 48.0f), + NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.EmitsEvents, + }; + IconNode.AttachNode(this); - LoadTimelines(); + LoadTimelines(); - Data->Nodes[0] = IconNode.NodeId; + Data->Nodes[0] = IconNode.NodeId; - AcceptedType = DragDropType.Everything; - Payload = new DragDropPayload(); + AcceptedType = DragDropType.Everything; + Payload = new DragDropPayload(); - // Use the fixed shadow struct for writing initial values if needed, - // though direct field access on the struct usually works for simple fields. - // However, to be safe with the VTable fix, we just set fields directly here - // as they are standard offsets, or use the pointer. - Component->AtkDragDropInterface.DragDropType = DragDropType.Everything; - Component->AtkDragDropInterface.DragDropReferenceIndex = 0; + // Use the fixed shadow struct for writing initial values if needed, + // though direct field access on the struct usually works for simple fields. + // However, to be safe with the VTable fix, we just set fields directly here + // as they are standard offsets, or use the pointer. + Component->AtkDragDropInterface.DragDropType = DragDropType.Everything; + Component->AtkDragDropInterface.DragDropReferenceIndex = 0; - InitializeComponentEvents(); + InitializeComponentEvents(); - AddEvent(AtkEventType.DragDropBegin, DragDropBeginHandler); - AddEvent(AtkEventType.DragDropInsert, DragDropInsertHandler); - AddEvent(AtkEventType.DragDropDiscard, DragDropDiscardHandler); - AddEvent(AtkEventType.DragDropClick, DragDropClickHandler); - AddEvent(AtkEventType.DragDropRollOver, DragDropRollOverHandler); - AddEvent(AtkEventType.DragDropRollOut, DragDropRollOutHandler); + AddEvent(AtkEventType.DragDropBegin, DragDropBeginHandler); + AddEvent(AtkEventType.DragDropInsert, DragDropInsertHandler); + AddEvent(AtkEventType.DragDropDiscard, DragDropDiscardHandler); + AddEvent(AtkEventType.DragDropClick, DragDropClickHandler); + AddEvent(AtkEventType.DragDropRollOver, DragDropRollOverHandler); + AddEvent(AtkEventType.DragDropRollOut, DragDropRollOutHandler); + } + + private bool IsDragDropEndRegistered { get; set; } + + public Action? OnBegin { get; set; } + public Action? OnEnd { get; set; } + public Action? OnPayloadAccepted { get; set; } + public Action? OnDiscard { get; set; } + public Action? OnClicked { get; set; } + public Action? OnRollOver { get; set; } + public Action? OnRollOut { get; set; } + + public DragDropPayload Payload { get; set; } + + public uint IconId { + get => IconNode.IconId; + set { + IconNode.IconId = value; + IconNode.IsVisible = value != 0; + } + } + + public bool IsIconDisabled { + get => IconNode.IsIconDisabled; + set => IconNode.IsIconDisabled = value; + } + + public int Quantity { + get => int.Parse(Component->GetQuantityText().ToString()); + set => Component->SetQuantity(value); + } + + public string QuantityString { + get => Component->GetQuantityText().ToString(); + set => Component->SetQuantityText(value); + } + + public DragDropType AcceptedType { + get => Component->AcceptedType; + set => Component->AcceptedType = value; + } + + public AtkDragDropInterface.SoundEffectSuppression SoundEffectSuppression { + get => Component->AtkDragDropInterface.DragDropSoundEffectSuppression; + set => Component->AtkDragDropInterface.DragDropSoundEffectSuppression = value; + } + + public bool IsDraggable { + get => !Component->Flags.HasFlag(DragDropFlag.Locked); + set { + if (value) { + Component->Flags &= ~DragDropFlag.Locked; } - - private bool IsDragDropEndRegistered { get; set; } - - public Action? OnBegin { get; set; } - public Action? OnEnd { get; set; } - public Action? OnPayloadAccepted { get; set; } - public Action? OnDiscard { get; set; } - public Action? OnClicked { get; set; } - public Action? OnRollOver { get; set; } - public Action? OnRollOut { get; set; } - - public DragDropPayload Payload { get; set; } - - public uint IconId { - get => IconNode.IconId; - set { - IconNode.IconId = value; - IconNode.IsVisible = value != 0; - } + else { + Component->Flags |= DragDropFlag.Locked; } + } + } - public bool IsIconDisabled { - get => IconNode.IsIconDisabled; - set => IconNode.IsIconDisabled = value; + public bool IsClickable { + get => Component->Flags.HasFlag(DragDropFlag.Clickable); + set { + if (value) { + Component->Flags |= DragDropFlag.Clickable; } - - public int Quantity { - get => int.Parse(Component->GetQuantityText().ToString()); - set => Component->SetQuantity(value); + else { + Component->Flags &= ~DragDropFlag.Clickable; } + } + } - public string QuantityString { - get => Component->GetQuantityText().ToString(); - set => Component->SetQuantityText(value); - } + private void DragDropBeginHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { + atkEvent->SetEventIsHandled(); - public DragDropType AcceptedType { - get => Component->AcceptedType; - set => Component->AcceptedType = value; - } + // FIX: Use extension method to write payload using fixed VTable + Payload.ToFixedInterface(atkEventData->DragDropData.DragDropInterface); - public AtkDragDropInterface.SoundEffectSuppression SoundEffectSuppression { - get => Component->AtkDragDropInterface.DragDropSoundEffectSuppression; - set => Component->AtkDragDropInterface.DragDropSoundEffectSuppression = value; - } + OnBegin?.Invoke(this); - public bool IsDraggable { - get => !Component->Flags.HasFlag(DragDropFlag.Locked); - set { - if (value) { - Component->Flags &= ~DragDropFlag.Locked; - } - else { - Component->Flags |= DragDropFlag.Locked; - } - } - } + if (!IsDragDropEndRegistered) { + AddEvent(AtkEventType.DragDropEnd, DragDropEndHandler); + IsDragDropEndRegistered = true; + } + } - public bool IsClickable { - get => Component->Flags.HasFlag(DragDropFlag.Clickable); - set { - if (value) { - Component->Flags |= DragDropFlag.Clickable; - } - else { - Component->Flags &= ~DragDropFlag.Clickable; - } - } - } + private void DragDropInsertHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { + atkEvent->SetEventIsHandled(); - private void DragDropBeginHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { - atkEvent->SetEventIsHandled(); + atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags; + atkEvent->State.ReturnFlags = 1; - // FIX: Use extension method to write payload using fixed VTable - Payload.ToFixedInterface(atkEventData->DragDropData.DragDropInterface); + // FIX: Use extension method to read payload using fixed VTable + var payload = DragDropPayloadExtensions.FromFixedInterface(atkEventData->DragDropData.DragDropInterface); - OnBegin?.Invoke(this); + Payload.Clear(); + IconId = 0; - if (!IsDragDropEndRegistered) { - AddEvent(AtkEventType.DragDropEnd, DragDropEndHandler); - IsDragDropEndRegistered = true; - } - } + OnPayloadAccepted?.Invoke(this, payload); + } - private void DragDropInsertHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { - atkEvent->SetEventIsHandled(); + private void DragDropDiscardHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { + atkEvent->SetEventIsHandled(); - atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags; - atkEvent->State.ReturnFlags = 1; + atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags; + atkEvent->State.ReturnFlags = 1; - // FIX: Use extension method to read payload using fixed VTable - var payload = DragDropPayloadExtensions.FromFixedInterface(atkEventData->DragDropData.DragDropInterface); + OnDiscard?.Invoke(this); + } - Payload.Clear(); - IconId = 0; + private void DragDropEndHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { + atkEvent->SetEventIsHandled(); - OnPayloadAccepted?.Invoke(this, payload); - } + // FIX: Cast to shadow struct to call the correct GetPayloadContainer (Index 12) + var fixedInterface = (AtkDragDropInterfaceFixed*)atkEventData->DragDropData.DragDropInterface; + fixedInterface->GetPayloadContainer()->Clear(); - private void DragDropDiscardHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { - atkEvent->SetEventIsHandled(); + OnEnd?.Invoke(this); - atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags; - atkEvent->State.ReturnFlags = 1; + if (IsDragDropEndRegistered) { + RemoveEvent(AtkEventType.DragDropEnd, DragDropEndHandler); + IsDragDropEndRegistered = false; + } + } - OnDiscard?.Invoke(this); - } + private void DragDropClickHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { + atkEvent->SetEventIsHandled(); - private void DragDropEndHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { - atkEvent->SetEventIsHandled(); + atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags; + atkEvent->State.ReturnFlags = 1; - // FIX: Cast to shadow struct to call the correct GetPayloadContainer (Index 12) - var fixedInterface = (AtkDragDropInterfaceFixed*)atkEventData->DragDropData.DragDropInterface; - fixedInterface->GetPayloadContainer()->Clear(); + OnClicked?.Invoke(this); + } - OnEnd?.Invoke(this); + private void DragDropRollOverHandler() + => OnRollOver?.Invoke(this); - if (IsDragDropEndRegistered) { - RemoveEvent(AtkEventType.DragDropEnd, DragDropEndHandler); - IsDragDropEndRegistered = false; - } - } + private void DragDropRollOutHandler() + => OnRollOut?.Invoke(this); - private void DragDropClickHandler(AtkEventListener* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData) { - atkEvent->SetEventIsHandled(); + public void Clear() { + Payload.Clear(); + IconId = 0; + } - atkEvent->State.StateFlags |= AtkEventStateFlags.HasReturnFlags; - atkEvent->State.ReturnFlags = 1; + public void ShowTooltip(AtkTooltipManager.AtkTooltipType type, ActionKind actionKind) { + if (AtkStage.Instance()->DragDropManager.IsDragging) return; - OnClicked?.Invoke(this); - } + // FIX: Explicitly use 'this.ResNode' and cast to (AtkResNode*) to avoid ambiguity with the class name + var addon = RaptureAtkUnitManager.Instance()->GetAddonByNode((AtkResNode*)this); + if (addon is null) return; - private void DragDropRollOverHandler() - => OnRollOver?.Invoke(this); + var tooltipArgs = new AtkTooltipManager.AtkTooltipArgs(); + tooltipArgs.Ctor(); + tooltipArgs.ActionArgs.Id = Payload.Int2; + tooltipArgs.ActionArgs.Kind = (DetailKind)actionKind; - private void DragDropRollOutHandler() - => OnRollOut?.Invoke(this); + AtkStage.Instance()->TooltipManager.ShowTooltip( + AtkTooltipManager.AtkTooltipType.Action, + addon->Id, + (AtkResNode*)this, // FIX: Explicit cast here as well + &tooltipArgs); + } - public void Clear() { - Payload.Clear(); - IconId = 0; - } - - public void ShowTooltip(AtkTooltipManager.AtkTooltipType type, ActionKind actionKind) { - if (AtkStage.Instance()->DragDropManager.IsDragging) return; - - // FIX: Explicitly use 'this.ResNode' and cast to (AtkResNode*) to avoid ambiguity with the class name - var addon = RaptureAtkUnitManager.Instance()->GetAddonByNode((AtkResNode*)this); - if (addon is null) return; - - var tooltipArgs = new AtkTooltipManager.AtkTooltipArgs(); - tooltipArgs.Ctor(); - tooltipArgs.ActionArgs.Id = Payload.Int2; - tooltipArgs.ActionArgs.Kind = (DetailKind)actionKind; - - AtkStage.Instance()->TooltipManager.ShowTooltip( - AtkTooltipManager.AtkTooltipType.Action, - addon->Id, - (AtkResNode*)this, // FIX: Explicit cast here as well - &tooltipArgs); - } - - private void LoadTimelines() { - AddTimeline(new TimelineBuilder() - .BeginFrameSet(1, 59) - .AddLabelPair(1, 10, 1) - .AddLabelPair(11, 19, 2) - .AddLabelPair(20, 29, 3) - .AddLabelPair(30, 39, 7) - .AddLabelPair(40, 49, 6) - .AddLabelPair(50, 59, 4) - .EndFrameSet() - .Build()); - } - } \ No newline at end of file + private void LoadTimelines() { + AddTimeline(new TimelineBuilder() + .BeginFrameSet(1, 59) + .AddLabelPair(1, 10, 1) + .AddLabelPair(11, 19, 2) + .AddLabelPair(20, 29, 3) + .AddLabelPair(30, 39, 7) + .AddLabelPair(40, 49, 6) + .AddLabelPair(50, 59, 4) + .EndFrameSet() + .Build()); + } +} \ No newline at end of file diff --git a/AetherBags/Plugin.cs b/AetherBags/Plugin.cs index 858226a..b22e51a 100644 --- a/AetherBags/Plugin.cs +++ b/AetherBags/Plugin.cs @@ -1,5 +1,6 @@ using System; using System.Numerics; +using AetherBags.AddonLifecycles; using AetherBags.Addons; using AetherBags.Helpers; using AetherBags.Hooks; @@ -16,6 +17,7 @@ public unsafe class Plugin : IDalamudPlugin private static string HelpDescription => "Opens your inventory."; private readonly InventoryHooks _inventoryHooks; + private readonly InventoryLifecycles _inventoryLifecycles; public Plugin(IDalamudPluginInterface pluginInterface) { @@ -64,6 +66,7 @@ public unsafe class Plugin : IDalamudPlugin } _inventoryHooks = new InventoryHooks(); + _inventoryLifecycles = new InventoryLifecycles(); } public void Dispose() @@ -82,6 +85,7 @@ public unsafe class Plugin : IDalamudPlugin KamiToolKitLibrary.Dispose(); _inventoryHooks.Dispose(); + _inventoryLifecycles.Dispose(); } private void OnCommand(string command, string args) diff --git a/KamiToolKit b/KamiToolKit index 9519b07..2122482 160000 --- a/KamiToolKit +++ b/KamiToolKit @@ -1 +1 @@ -Subproject commit 9519b07c8db287ef75b7153a5e97c24574e800f2 +Subproject commit 2122482f0dd453a74227965b4f0a6868866e21c1 From 98742482e38bfdfe8fd63b35582ec7dcb3fa135f Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Sun, 28 Dec 2025 13:36:41 +0100 Subject: [PATCH 2/3] Open Inventory, different InventoryNotificationType --- .../AddonLifecycles/InventoryLifecycles.cs | 47 ++++++++++- AetherBags/Addons/AddonInventoryWindow.cs | 11 ++- AetherBags/Configuration/GeneralSettings.cs | 2 + AetherBags/Hooks/InventoryHook.cs | 80 +++++++++++++++++++ .../Inventory/InventoryNotificationState.cs | 2 +- AetherBags/Nodes/Color/ColorInputRow.cs | 10 +-- .../Category/StringListEditorNode.cs | 2 +- .../General/FunctionalConfigurationNode.cs | 77 ++++++++++++++++++ .../General/GeneralScrollingAreaNode.cs | 20 +---- AetherBags/Nodes/DragDropNode.cs | 4 +- .../Inventory/InventoryNotificationNode.cs | 33 +++----- 11 files changed, 230 insertions(+), 58 deletions(-) create mode 100644 AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs diff --git a/AetherBags/AddonLifecycles/InventoryLifecycles.cs b/AetherBags/AddonLifecycles/InventoryLifecycles.cs index 996d94c..7c0abdc 100644 --- a/AetherBags/AddonLifecycles/InventoryLifecycles.cs +++ b/AetherBags/AddonLifecycles/InventoryLifecycles.cs @@ -1,6 +1,13 @@ using System; +using System.Linq; +using AetherBags.Configuration; +using AetherBags.Inventory; using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using Dalamud.Game.NativeWrapper; +using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Component.GUI; +using Lumina.Text.ReadOnly; namespace AetherBags.AddonLifecycles; @@ -9,17 +16,49 @@ public class InventoryLifecycles : IDisposable public InventoryLifecycles() { - Services.AddonLifecycle.RegisterListener(AddonEvent.PreOpen, ["Inventory", "InventoryLarge", "InventoryExpansion"], HandleInventorySetup); + Services.AddonLifecycle.RegisterListener(AddonEvent.PreRefresh, ["Inventory", "InventoryLarge", "InventoryExpansion"], PreRefreshHandler); Services.Logger.Verbose("InventoryLifecycles initialized"); } - private void HandleInventorySetup(AddonEvent type, AddonArgs args) + private unsafe void PreRefreshHandler(AddonEvent type, AddonArgs args) { - Services.Logger.Debug("HandleInventorySetup called"); + if (args is not AddonRefreshArgs refreshArgs) + return; + + GeneralSettings config = System.Config.General; + + Services.Logger.Debug("PreRefresh event for Inventory detected"); + + AtkValuePtr[] atkValues = refreshArgs.AtkValueEnumerable.ToArray(); + + if (atkValues.Length < 7) return; + + AtkValue* value1 = (AtkValue*)atkValues[1].Address; + AtkValue* value5 = (AtkValue*)atkValues[5].Address; + AtkValue* value6 = (AtkValue*)atkValues[6].Address; + + int openTitleId = value1->Int; + ReadOnlySeString title = value5->String.AsReadOnlySeString(); + ReadOnlySeString upperTitle = value6->String.AsReadOnlySeString(); + + System.AddonInventoryWindow.SetNotification(new InventoryNotificationInfo(title, upperTitle)); + + if (config.HideGameInventory) refreshArgs.AtkValueCount = 0; + if (config.OpenWithGameInventory) + { + if (openTitleId == 0) + { + System.AddonInventoryWindow.Toggle(); + } + else + { + System.AddonInventoryWindow.Open(); + } + } } public void Dispose() { - Services.AddonLifecycle.UnregisterListener(AddonEvent.PreOpen, ["Inventory", "InventoryLarge", "InventoryExpansion"]); + Services.AddonLifecycle.UnregisterListener(AddonEvent.PreRefresh, ["Inventory", "InventoryLarge", "InventoryExpansion"]); } } \ No newline at end of file diff --git a/AetherBags/Addons/AddonInventoryWindow.cs b/AetherBags/Addons/AddonInventoryWindow.cs index 6ab3fd7..b56d771 100644 --- a/AetherBags/Addons/AddonInventoryWindow.cs +++ b/AetherBags/Addons/AddonInventoryWindow.cs @@ -125,9 +125,6 @@ public class AddonInventoryWindow : NativeAddon RefreshCategoriesCore(doAutosize); } - InventoryNotificationType currentNotificationType = (InventoryNotificationType) AgentInventory.Instance()->OpenTitleId; - if(currentNotificationType != _notificationNode.NotificationType) _notificationNode.NotificationType = currentNotificationType; - base.OnUpdate(addon); } @@ -292,6 +289,14 @@ public class AddonInventoryWindow : NativeAddon private void ResizeWindow(float width, float height) => ResizeWindow(width, height, recalcLayout: true); + public void SetNotification(InventoryNotificationInfo info) + { + Services.Framework.RunOnTick(() => + { + if(IsOpen) _notificationNode.NotificationInfo = info; + }, delayTicks: 1); + } + protected override unsafe void OnFinalize(AtkUnitBase* addon) { Services.AddonLifecycle.UnregisterListener(OnInventoryUpdate); diff --git a/AetherBags/Configuration/GeneralSettings.cs b/AetherBags/Configuration/GeneralSettings.cs index 1acfa4f..00f6fb9 100644 --- a/AetherBags/Configuration/GeneralSettings.cs +++ b/AetherBags/Configuration/GeneralSettings.cs @@ -11,6 +11,8 @@ public class GeneralSettings public int CompactLookahead { get; set; } = 24; public bool CompactPreferLargestFit { get; set; } = true; public bool CompactStableInsert { get; set; } = true; + public bool OpenWithGameInventory { get; set; } = true; + public bool HideGameInventory { get; set; } = false; } public enum InventoryStackMode : byte diff --git a/AetherBags/Hooks/InventoryHook.cs b/AetherBags/Hooks/InventoryHook.cs index de6a792..1d163ac 100644 --- a/AetherBags/Hooks/InventoryHook.cs +++ b/AetherBags/Hooks/InventoryHook.cs @@ -3,6 +3,7 @@ using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Component.GUI; namespace AetherBags.Hooks; @@ -19,7 +20,14 @@ public sealed unsafe class InventoryHooks : IDisposable ushort dstSlot, bool unk); + private delegate void HandleInventoryEventDelegate(AgentInterface* eventInterface, AtkValue* atkValue, int valueCount); + private readonly Hook? _moveItemSlotHook; + /* + private readonly Hook? _openInventoryHook; + private readonly Hook? _handleInventoryEventHook; + private readonly Hook? _openAddonHook; + */ public InventoryHooks() { @@ -36,6 +44,47 @@ public sealed unsafe class InventoryHooks : IDisposable { Services.Logger.Error(e, "Failed to hook MoveItemSlot"); } + /* + try + { + _openInventoryHook = Services.GameInteropProvider.HookFromAddress( + UIModule.Instance()->VirtualTable->OpenInventory, + OpenInventoryDetour); + _openInventoryHook.Enable(); + + Services.Logger.Debug("OpenInventory hooked successfully."); + } + catch (Exception e) + { + Services.Logger.Error(e, "Failed to hook OpenInventory"); + } + try + { + _handleInventoryEventHook = Services.GameInteropProvider.HookFromSignature( + "E8 ?? ?? ?? ?? 48 8B 74 24 ?? 33 C0 ?? ?? 89 43", + HandleInventoryEventDetour); + _handleInventoryEventHook.Enable(); + + Services.Logger.Debug("HandleInventoryEvent hooked successfully."); + } + catch (Exception e) + { + Services.Logger.Error(e, "Failed to hook HandleInventoryEvent"); + } + try + { + _openAddonHook = Services.GameInteropProvider.HookFromAddress( + RaptureAtkModule.MemberFunctionPointers.OpenAddon, + OpenAddonDetour); + _openAddonHook.Enable(); + + Services.Logger.Debug("OpenAddon hooked successfully."); + } + catch (Exception e) + { + Services.Logger.Error(e, "Failed to hook MoveItemSlot"); + } + */ } private int MoveItemSlotDetour(InventoryManager* manager, @@ -53,8 +102,39 @@ public sealed unsafe class InventoryHooks : IDisposable return _moveItemSlotHook!.Original(manager, srcType, srcSlot, dstType, dstSlot, unk); } + /* + private void OpenInventoryDetour(UIModule* uiModule, byte type) + { + Services.Logger.Debug($"[OpenInventory Hook] Opening inventory of type {type}"); + _openInventoryHook?.Original(uiModule, type); + } + + private void HandleInventoryEventDetour(AgentInterface* eventInterface, AtkValue* atkValue, int valueCount) + { + for(int i = 0; i < valueCount; i++) + { + Services.Logger.Debug($"[HandleInventoryEvent Hook] AtkValue[{i}]: Type={atkValue[i].Type}, ToString: {atkValue[i].ToString()} "); + } + _handleInventoryEventHook?.Original(eventInterface, atkValue, valueCount); + } + + private ushort OpenAddonDetour(RaptureAtkModule* thisPtr, uint addonNameId, uint valueCount, AtkValue* values, AtkModuleInterface.AtkEventInterface* eventInterface, ulong eventKind, ushort parentAddonId, int depthLayer) + { + for(int i = 0; i < valueCount; i++) + { + Services.Logger.Debug($"[OpenAddon Hook] AtkValue[{i}]: ToString: {values[i].ToString()} "); + } + return _openAddonHook!.Original(thisPtr, addonNameId, valueCount, values, eventInterface, eventKind, parentAddonId, depthLayer); + } +*/ + public void Dispose() { _moveItemSlotHook?.Dispose(); + /* + _openInventoryHook?.Dispose(); + _handleInventoryEventHook?.Dispose(); + _openAddonHook?.Dispose(); + */ } } \ No newline at end of file diff --git a/AetherBags/Inventory/InventoryNotificationState.cs b/AetherBags/Inventory/InventoryNotificationState.cs index 065fdf5..fd82fc5 100644 --- a/AetherBags/Inventory/InventoryNotificationState.cs +++ b/AetherBags/Inventory/InventoryNotificationState.cs @@ -56,8 +56,8 @@ public class InventoryNotificationState return notificationCache.GetValueOrDefault((InventoryNotificationType)openTitleId); } - public record InventoryNotificationInfo(ReadOnlySeString Title, ReadOnlySeString Message); } +public record InventoryNotificationInfo(ReadOnlySeString Title, ReadOnlySeString Message); public enum InventoryNotificationType : uint { diff --git a/AetherBags/Nodes/Color/ColorInputRow.cs b/AetherBags/Nodes/Color/ColorInputRow.cs index c8e4ae3..e4e8535 100644 --- a/AetherBags/Nodes/Color/ColorInputRow.cs +++ b/AetherBags/Nodes/Color/ColorInputRow.cs @@ -8,17 +8,15 @@ namespace AetherBags.Nodes.Color; public class ColorInputRow : HorizontalListNode { - private readonly GridNode _gridNode; private ColorPickerAddon? _colorPickerAddon; private readonly LabelTextNode _labelTextNode; - private ColorPreviewButtonNode _colorPreview; - private Vector4 _initialColor; + private readonly ColorPreviewButtonNode _colorPreview; public ColorInputRow() { InitializeColorPicker(); - _initialColor = CurrentColor; + var initialColor = CurrentColor; _colorPreview = new ColorPreviewButtonNode { @@ -33,7 +31,7 @@ public class ColorInputRow : HorizontalListNode { CurrentColor = color; _colorPreview?.Color = color; - _initialColor = color; + initialColor = color; OnColorConfirmed?.Invoke(color); }; _colorPickerAddon?.OnColorPreviewed = color => @@ -41,7 +39,7 @@ public class ColorInputRow : HorizontalListNode _colorPreview?.Color = color; OnColorChange?.Invoke(color); }; - _colorPickerAddon?.OnColorCancelled = () => OnColorCanceled?.Invoke(_initialColor); + _colorPickerAddon?.OnColorCancelled = () => OnColorCanceled?.Invoke(initialColor); } }; _colorPreview.AttachNode(this); diff --git a/AetherBags/Nodes/Configuration/Category/StringListEditorNode.cs b/AetherBags/Nodes/Configuration/Category/StringListEditorNode.cs index 2a22566..cc13f98 100644 --- a/AetherBags/Nodes/Configuration/Category/StringListEditorNode.cs +++ b/AetherBags/Nodes/Configuration/Category/StringListEditorNode.cs @@ -50,7 +50,7 @@ public sealed class StringListEditorNode : VerticalListNode if (!string.IsNullOrWhiteSpace(value) && ! _list.Contains(value)) { _list.Add(value); - _addInput.String = ""; + _addInput?.String = ""; RefreshItems(); _onChanged?.Invoke(); } diff --git a/AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs b/AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs new file mode 100644 index 0000000..936548a --- /dev/null +++ b/AetherBags/Nodes/Configuration/General/FunctionalConfigurationNode.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using System.Numerics; +using AetherBags.Configuration; +using FFXIVClientStructs.FFXIV.Component.GUI; +using KamiToolKit.Nodes; + +namespace AetherBags.Nodes.Configuration.General; + +internal sealed class FunctionalConfigurationNode : TabbedVerticalListNode +{ + private readonly CheckboxNode _hideDefaultBagsCheckboxNode; + private readonly LabeledDropdownNode _stackDropDown; + + public FunctionalConfigurationNode() + { + GeneralSettings config = System.Config.General; + + var titleNode = new CategoryTextNode + { + Height = 18, + String = "Functional Configuration", + }; + AddNode(titleNode); + + AddTab(1); + + var showWithGameCheckBox = new CheckboxNode + { + Size = Size with { Y = 18 }, + IsVisible = true, + String = "Auto-open with game inventory", + IsChecked = config.OpenWithGameInventory, + OnClick = isChecked => + { + config.OpenWithGameInventory = isChecked; + _hideDefaultBagsCheckboxNode?.IsEnabled = isChecked; + } + }; + AddNode(showWithGameCheckBox); + + AddTab(1); + _hideDefaultBagsCheckboxNode = new CheckboxNode + { + Size = Size with { Y = 18 }, + IsVisible = true, + String = "Hide default inventory bags", + IsEnabled = config.OpenWithGameInventory, + IsChecked = config.HideGameInventory, + OnClick = isChecked => + { + config.HideGameInventory = isChecked; + } + }; + AddNode(_hideDefaultBagsCheckboxNode); + SubtractTab(1); + + _stackDropDown = new LabeledDropdownNode + { + Size = new Vector2(300, 20), + IsEnabled = true, + LabelText = "Stack Mode", + LabelTextFlags = TextFlags.AutoAdjustNodeSize, + Options = Enum.GetNames(typeof(InventoryStackMode)).ToList(), + SelectedOption = config.StackMode.ToString(), + OnOptionSelected = selected => + { + if (Enum.TryParse(selected, out var parsed)) + { + config.StackMode = parsed; + System.AddonInventoryWindow.ManualInventoryRefresh(); + } + } + }; + AddNode(_stackDropDown); + } +} \ No newline at end of file diff --git a/AetherBags/Nodes/Configuration/General/GeneralScrollingAreaNode.cs b/AetherBags/Nodes/Configuration/General/GeneralScrollingAreaNode.cs index 0a52953..1abe0e9 100644 --- a/AetherBags/Nodes/Configuration/General/GeneralScrollingAreaNode.cs +++ b/AetherBags/Nodes/Configuration/General/GeneralScrollingAreaNode.cs @@ -11,7 +11,6 @@ namespace AetherBags.Nodes.Configuration.General; public sealed class GeneralScrollingAreaNode : ScrollingAreaNode { private readonly CheckboxNode _debugCheckboxNode = null!; - private readonly LabeledDropdownNode _stackDropDown = null!; public GeneralScrollingAreaNode() { @@ -19,24 +18,7 @@ public sealed class GeneralScrollingAreaNode : ScrollingAreaNode - { - if (Enum.TryParse(selected, out var parsed)) - { - config.StackMode = parsed; - System.AddonInventoryWindow.ManualInventoryRefresh(); - } - } - }; - ContentNode.AddNode(_stackDropDown); + ContentNode.AddNode(new FunctionalConfigurationNode()); ContentNode.AddNode(new LayoutConfigurationNode()); diff --git a/AetherBags/Nodes/DragDropNode.cs b/AetherBags/Nodes/DragDropNode.cs index 4c0ec05..26bc018 100644 --- a/AetherBags/Nodes/DragDropNode.cs +++ b/AetherBags/Nodes/DragDropNode.cs @@ -17,8 +17,8 @@ public unsafe class DragDropNode : ComponentNode (AtkComponentDragDrop*)Node->Component; - private new AtkUldComponentDataDragDrop* Data => (AtkUldComponentDataDragDrop*)Component->UldManager.ComponentData; + private AtkComponentDragDrop* Component => (AtkComponentDragDrop*)Node->Component; + private AtkUldComponentDataDragDrop* Data => (AtkUldComponentDataDragDrop*)Component->UldManager.ComponentData; public readonly ImageNode DragDropBackgroundNode; public readonly IconNode IconNode; diff --git a/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs b/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs index a3d260d..a42f6d0 100644 --- a/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs +++ b/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs @@ -1,15 +1,9 @@ -using System.Collections.Generic; using System.Numerics; using AetherBags.Inventory; using FFXIVClientStructs.FFXIV.Component.GUI; -using KamiToolKit; using KamiToolKit.Classes; using KamiToolKit.Classes.Timelines; using KamiToolKit.Nodes; -using Lumina.Excel; -using Lumina.Excel.Sheets; -using Lumina.Text; -using Lumina.Text.ReadOnly; namespace AetherBags.Nodes.Inventory; @@ -75,30 +69,25 @@ public sealed class InventoryNotificationNode : SimpleComponentNode messageTextNode.Size = Size with { Y = 16 }; } - public InventoryNotificationType NotificationType + public InventoryNotificationInfo NotificationInfo { get; set { field = value; - if (value == InventoryNotificationType.None) + + titleTextNode.SeString = value.Title; + messageTextNode.SeString = value.Message; + + if (value.Title.IsEmpty && value.Message.IsEmpty) { - titleTextNode.String = string.Empty; - messageTextNode.String = string.Empty; - Timeline?.PlayAnimation(17); // Hide - } - else - { - var info = NotificationState.GetNotificationInfo((uint)value); - if (info != null) - { - titleTextNode.SeString = info.Title; - messageTextNode.SeString = info.Message; - Timeline?.PlayAnimation(101); // Show - } + Timeline?.PlayAnimation(17); + return; } + + Timeline?.PlayAnimation(101); } - } = InventoryNotificationType.None; + } = new("sdsdsd", "sdsd"); // Future Zeff, this always goes on a parent private Timeline ParentLabels => new TimelineBuilder() From e46be61b1c37f4365adc10950b39dea157a0080f Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Sun, 28 Dec 2025 15:26:49 +0100 Subject: [PATCH 3/3] Improve category matching --- .../AddonCategoryConfigurationWindow.cs | 2 + AetherBags/Addons/CategoryWrapper.cs | 6 +- AetherBags/Inventory/CategoryBucketManager.cs | 7 ++ AetherBags/Inventory/UserCategoryMatcher.cs | 103 ++++++++++++------ .../CategoryDefinitionConfigurationNode.cs | 15 ++- .../Inventory/InventoryNotificationNode.cs | 2 +- 6 files changed, 101 insertions(+), 34 deletions(-) diff --git a/AetherBags/Addons/AddonCategoryConfigurationWindow.cs b/AetherBags/Addons/AddonCategoryConfigurationWindow.cs index b3cf4d8..f40ea80 100644 --- a/AetherBags/Addons/AddonCategoryConfigurationWindow.cs +++ b/AetherBags/Addons/AddonCategoryConfigurationWindow.cs @@ -119,6 +119,7 @@ public class AddonCategoryConfigurationWindow : NativeAddon listNode.AddOption(newWrapper); RefreshSelectionList(); + System.AddonInventoryWindow.ManualInventoryRefresh(); } private void OnRemoveCategory(CategoryWrapper categoryWrapper) @@ -134,6 +135,7 @@ public class AddonCategoryConfigurationWindow : NativeAddon { OnOptionChanged(null); } + System.AddonInventoryWindow.ManualInventoryRefresh(); } private void RefreshSelectionList() diff --git a/AetherBags/Addons/CategoryWrapper.cs b/AetherBags/Addons/CategoryWrapper.cs index 68b7f85..9ccf226 100644 --- a/AetherBags/Addons/CategoryWrapper.cs +++ b/AetherBags/Addons/CategoryWrapper.cs @@ -1,5 +1,8 @@ using AetherBags.Configuration; +using AetherBags.Inventory; +using Dalamud.Game.Text.SeStringHandling; using KamiToolKit.Premade; +using SeStringBuilder = Lumina.Text.SeStringBuilder; namespace AetherBags.Addons; @@ -13,7 +16,8 @@ public class CategoryWrapper(UserCategoryDefinition categoryDefinition) : IInfoN } public string GetSubLabel() { - return CategoryDefinition!.Enabled ? "Enabled" : "Disabled"; + if(UserCategoryMatcher.IsCatchAll(CategoryDefinition!)) return " No valid rules!"; + return CategoryDefinition!.Enabled ? "✓ Enabled" : " Disabled"; } public uint? GetId() => null; diff --git a/AetherBags/Inventory/CategoryBucketManager.cs b/AetherBags/Inventory/CategoryBucketManager.cs index 6d67130..baf8a7f 100644 --- a/AetherBags/Inventory/CategoryBucketManager.cs +++ b/AetherBags/Inventory/CategoryBucketManager.cs @@ -54,6 +54,13 @@ public static class CategoryBucketManager for (int i = 0; i < sortedScratch.Count; i++) { UserCategoryDefinition category = sortedScratch[i]; + + if (!category.Enabled) + continue; + + if (UserCategoryMatcher.IsCatchAll(category)) + continue; + uint bucketKey = MakeUserCategoryKey(category.Order); if (!bucketsByKey.TryGetValue(bucketKey, out CategoryBucket? bucket)) diff --git a/AetherBags/Inventory/UserCategoryMatcher.cs b/AetherBags/Inventory/UserCategoryMatcher.cs index 3caf34a..7f84052 100644 --- a/AetherBags/Inventory/UserCategoryMatcher.cs +++ b/AetherBags/Inventory/UserCategoryMatcher.cs @@ -11,6 +11,44 @@ internal static class UserCategoryMatcher { var rules = userCategory.Rules; + bool hasIdentificationFilters = rules.AllowedItemIds.Count > 0 || rules.AllowedItemNamePatterns.Count > 0; + + if (hasIdentificationFilters) + { + bool matchesAnyIdentification = false; + + if (rules.AllowedItemIds.Count > 0 && rules.AllowedItemIds.Contains(item.Item.ItemId)) + { + matchesAnyIdentification = true; + } + + if (!matchesAnyIdentification && rules.AllowedItemNamePatterns.Count > 0) + { + for (int i = 0; i < rules.AllowedItemNamePatterns.Count; i++) + { + string pattern = rules.AllowedItemNamePatterns[i]; + if (string.IsNullOrWhiteSpace(pattern)) + continue; + + try + { + if (Regex.IsMatch(item.Name, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)) + { + matchesAnyIdentification = true; + break; + } + } + catch + { + // Invalid regex: ignore it. + } + } + } + + if (!matchesAnyIdentification) + return false; + } + if (rules.AllowedUiCategoryIds.Count > 0) { uint uiCategoryId = item.UiCategory.RowId; @@ -18,9 +56,6 @@ internal static class UserCategoryMatcher return false; } - if (rules.AllowedItemIds.Count > 0 && !rules.AllowedItemIds.Contains(item.Item.ItemId)) - return false; - if (rules.AllowedRarities.Count > 0 && !rules.AllowedRarities.Contains(item.Rarity)) return false; @@ -39,40 +74,46 @@ internal static class UserCategoryMatcher if (!MatchesToggle(rules.Dyeable, item.IsDyeable)) return false; if (!MatchesToggle(rules.Repairable, item.IsRepairable)) return false; - if (rules.AllowedItemNamePatterns.Count > 0) - { - bool any = false; - for (int i = 0; i < rules.AllowedItemNamePatterns.Count; i++) - { - string pattern = rules.AllowedItemNamePatterns[i]; - if (string.IsNullOrWhiteSpace(pattern)) - continue; - - // Treat patterns as regex for now. - try - { - if (Regex.IsMatch(item.Name, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)) - { - any = true; - break; - } - } - catch - { - // Invalid regex: ignore it. - } - } - - if (!any) - return false; - } - return true; } private static bool InRange(T value, T min, T max) where T : struct, IComparable => value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0; + public static bool IsCatchAll(UserCategoryDefinition userCategory) + { + var rules = userCategory.Rules; + + if (rules.AllowedItemIds.Count > 0) + return false; + if (rules.AllowedItemNamePatterns.Count > 0) + return false; + if (rules.AllowedUiCategoryIds.Count > 0) + return false; + if (rules.AllowedRarities.Count > 0) + return false; + + if (rules.Level.Enabled) + return false; + if (rules.ItemLevel.Enabled) + return false; + if (rules.VendorPrice.Enabled) + return false; + + if (rules.Untradable.ToggleState != ToggleFilterState.Ignored) + return false; + if (rules.Unique.ToggleState != ToggleFilterState.Ignored) + return false; + if (rules.Collectable.ToggleState != ToggleFilterState.Ignored) + return false; + if (rules.Dyeable.ToggleState != ToggleFilterState.Ignored) + return false; + if (rules.Repairable.ToggleState != ToggleFilterState.Ignored) + return false; + + return true; + } + private static bool MatchesToggle(StateFilter filter, bool itemHasProperty) => filter.ToggleState switch { diff --git a/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs b/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs index 97d8255..968f8f9 100644 --- a/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs +++ b/AetherBags/Nodes/Configuration/Category/CategoryDefinitionConfigurationNode.cs @@ -1,6 +1,7 @@ using System; using System.Numerics; using AetherBags.Configuration; +using AetherBags.Inventory; using AetherBags.Nodes.Color; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Component.GUI; @@ -8,6 +9,7 @@ using KamiToolKit.Classes; using KamiToolKit.Nodes; using Lumina.Excel; using Lumina.Excel.Sheets; +using Lumina.Text; using Action = System.Action; namespace AetherBags.Nodes.Configuration.Category; @@ -65,6 +67,17 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode FitContents = true; ItemSpacing = 4.0f; + var catchAllWarningNode = new TextNode + { + Size = new Vector2(300, 40), + TextFlags = TextFlags.MultiLine | TextFlags.AutoAdjustNodeSize, + SeString = new SeStringBuilder().Append(" Warning: No rules configured\nThis category won't match anything!").ToReadOnlySeString(), + TextColor = ColorHelper.GetColor(17), + LineSpacing = 20, + IsVisible = UserCategoryMatcher.IsCatchAll(CategoryDefinition), + }; + AddNode(catchAllWarningNode); + AddNode(CreateSectionHeader("Basic Settings")); _enabledCheckbox = new CheckboxNode @@ -297,7 +310,7 @@ public sealed class CategoryDefinitionConfigurationNode : VerticalListNode private static void NotifyChanged() { - System.AddonInventoryWindow?.ManualInventoryRefresh(); + System.AddonInventoryWindow.ManualInventoryRefresh(); } private void NotifyCategoryPropertyChanged() diff --git a/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs b/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs index a42f6d0..02e578c 100644 --- a/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs +++ b/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs @@ -87,7 +87,7 @@ public sealed class InventoryNotificationNode : SimpleComponentNode Timeline?.PlayAnimation(101); } - } = new("sdsdsd", "sdsd"); + } // Future Zeff, this always goes on a parent private Timeline ParentLabels => new TimelineBuilder()