Inherited Improvements

This commit is contained in:
Zeffuro
2026-01-02 11:37:55 +01:00
parent 3044e63106
commit 79a16b89eb
9 changed files with 121 additions and 173 deletions
+5 -48
View File
@@ -4,8 +4,6 @@ using AetherBags.Inventory.State;
using AetherBags.Nodes.Input; using AetherBags.Nodes.Input;
using AetherBags.Nodes.Inventory; using AetherBags.Nodes.Inventory;
using AetherBags.Nodes.Layout; using AetherBags.Nodes.Layout;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
@@ -35,36 +33,26 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
}; };
CategoriesNode.AttachNode(this); CategoriesNode.AttachNode(this);
var size = new Vector2(addon->Size.X / 2.0f, 28.0f); var header = CalculateHeaderLayout(addon);
var header = addon->WindowHeaderCollisionNode;
float headerX = header->X;
float headerY = header->Y;
float headerW = header->Width;
float headerH = header->Height;
float x = headerX + (headerW - size.X) * 0.5f;
float y = headerY + (headerH - size.Y) * 0.5f;
_notificationNode = new InventoryNotificationNode _notificationNode = new InventoryNotificationNode
{ {
Position = new Vector2(WindowNode!.X - 4f, WindowNode!.Y - 32f), Position = new Vector2(WindowNode!.X - 4f, WindowNode!.Y - 32f),
Size = new Vector2(headerW, 28f), Size = new Vector2(header.HeaderWidth, 28f),
}; };
_notificationNode.AttachNode(this); _notificationNode.AttachNode(this);
SearchInputNode = new TextInputWithHintNode SearchInputNode = new TextInputWithHintNode
{ {
Position = new Vector2(x, y), Position = header.SearchPosition,
Size = size, Size = header.SearchSize,
OnInputReceived = _ => RefreshCategoriesCore(autosize: false), OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
}; };
SearchInputNode.AttachNode(this); SearchInputNode.AttachNode(this);
SettingsButtonNode = new CircleButtonNode SettingsButtonNode = new CircleButtonNode
{ {
Position = new Vector2(headerW - 48f, y), Position = new Vector2(header.HeaderWidth - SettingsButtonOffset, header.HeaderY),
Size = new Vector2(28f), Size = new Vector2(28f),
Icon = ButtonIcon.GearCog, Icon = ButtonIcon.GearCog,
OnClick = System.AddonConfigurationWindow.Toggle OnClick = System.AddonConfigurationWindow.Toggle
@@ -90,34 +78,12 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
base.OnSetup(addon); base.OnSetup(addon);
} }
protected override void OnUpdate(AtkUnitBase* addon)
{
if (RefreshQueued)
{
bool doAutosize = RefreshAutosizeQueued;
RefreshQueued = false;
RefreshAutosizeQueued = false;
RefreshCategoriesCore(doAutosize);
}
base.OnUpdate(addon);
}
public void ManualCurrencyRefresh() public void ManualCurrencyRefresh()
{ {
if (!Services.ClientState.IsLoggedIn) return; if (!Services.ClientState.IsLoggedIn) return;
FooterNode.RefreshCurrencies(); FooterNode.RefreshCurrencies();
} }
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
_inventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
}
public void SetNotification(InventoryNotificationInfo info) public void SetNotification(InventoryNotificationInfo info)
{ {
Services.Framework.RunOnTick(() => Services.Framework.RunOnTick(() =>
@@ -126,15 +92,6 @@ public unsafe class AddonInventoryWindow : InventoryAddonBase
}, delayTicks: 1); }, delayTicks: 1);
} }
public void SetSearchText(string searchText)
{
Services.Framework.RunOnTick(() =>
{
if (IsOpen) SearchInputNode.SearchString = searchText;
RefreshCategoriesCore(autosize: true);
}, delayTicks: 1);
}
protected override void OnFinalize(AtkUnitBase* addon) protected override void OnFinalize(AtkUnitBase* addon)
{ {
ref var blockingAddonId = ref AgentInventoryContext.Instance()->BlockingAddonId; ref var blockingAddonId = ref AgentInventoryContext.Instance()->BlockingAddonId;
+22 -58
View File
@@ -1,11 +1,10 @@
using System.Linq;
using System.Numerics; using System.Numerics;
using AetherBags.Inventory; using AetherBags.Inventory;
using AetherBags.Inventory.State; using AetherBags.Inventory.State;
using AetherBags.Nodes.Input; using AetherBags.Nodes.Input;
using AetherBags.Nodes.Inventory; using AetherBags.Nodes.Inventory;
using AetherBags.Nodes.Layout; using AetherBags.Nodes.Layout;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
@@ -31,6 +30,8 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
protected override float MinWindowWidth => 400; protected override float MinWindowWidth => 400;
protected override float MaxWindowWidth => 700; protected override float MaxWindowWidth => 700;
private readonly string[] _retainerAddonNames = { "InventoryRetainer", "InventoryRetainerLarge" };
protected override void OnSetup(AtkUnitBase* addon) protected override void OnSetup(AtkUnitBase* addon)
{ {
InitializeBackgroundDropTarget(); InitializeBackgroundDropTarget();
@@ -48,29 +49,19 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
}; };
CategoriesNode.AttachNode(this); CategoriesNode.AttachNode(this);
var size = new Vector2(addon->Size.X / 2.0f, 28.0f); var header = CalculateHeaderLayout(addon);
var header = addon->WindowHeaderCollisionNode;
float headerX = header->X;
float headerY = header->Y;
float headerW = header->Width;
float headerH = header->Height;
float x = headerX + (headerW - size.X) * 0.5f;
float y = headerY + (headerH - size.Y) * 0.5f;
SearchInputNode = new TextInputWithHintNode SearchInputNode = new TextInputWithHintNode
{ {
Position = new Vector2(x, y), Position = header.SearchPosition,
Size = size, Size = header.SearchSize,
OnInputReceived = _ => RefreshCategoriesCore(autosize: false), OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
}; };
SearchInputNode.AttachNode(this); SearchInputNode.AttachNode(this);
SettingsButtonNode = new CircleButtonNode SettingsButtonNode = new CircleButtonNode
{ {
Position = new Vector2(headerW - 48f, y), Position = new Vector2(header.HeaderWidth - SettingsButtonOffset, header.HeaderY),
Size = new Vector2(28f), Size = new Vector2(28f),
Icon = ButtonIcon.GearCog, Icon = ButtonIcon.GearCog,
OnClick = System.AddonConfigurationWindow.Toggle OnClick = System.AddonConfigurationWindow.Toggle
@@ -152,18 +143,23 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
SlotCounterNode.Position = new Vector2(contentSize.X - 80f, footerY); SlotCounterNode.Position = new Vector2(contentSize.X - 80f, footerY);
} }
protected override void OnUpdate(AtkUnitBase* addon) private void CloseRetainerWindows()
{ {
if (RefreshQueued) var manager = RaptureAtkUnitManager.Instance();
foreach (var name in _retainerAddonNames)
{ {
bool doAutosize = RefreshAutosizeQueued; var addon = manager->GetAddonByName(name);
RefreshQueued = false; if (addon != null)
RefreshAutosizeQueued = false; {
addon->IsVisible = true;
RefreshCategoriesCore(doAutosize); addon->Close(true);
}
} }
}
base.OnUpdate(addon); private bool IsAnyRetainerWindowLoaded()
{
return _retainerAddonNames.Any(name => RaptureAtkUnitManager.Instance()->GetAddonByName(name) != null);
} }
protected override void OnShow(AtkUnitBase* addon) protected override void OnShow(AtkUnitBase* addon)
@@ -175,48 +171,16 @@ public unsafe class AddonRetainerWindow : InventoryAddonBase
private void OnEntrustDuplicates() private void OnEntrustDuplicates()
{ {
// TODO: Implement checking if the retainer bag is def open if (!IsAnyRetainerWindowLoaded()) return;
var agent = AgentModule.Instance()->GetAgentByInternalId(AgentId.Retainer); var agent = AgentModule.Instance()->GetAgentByInternalId(AgentId.Retainer);
agent->SendCommand(0, [0]); agent->SendCommand(0, [0]);
} }
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
_inventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
}
public void SetSearchText(string searchText)
{
Services.Framework.RunOnTick(() =>
{
if (IsOpen) SearchInputNode.SearchString = searchText;
RefreshCategoriesCore(autosize: true);
}, delayTicks: 1);
}
protected override void OnFinalize(AtkUnitBase* addon) protected override void OnFinalize(AtkUnitBase* addon)
{ {
_isSetupComplete = false; _isSetupComplete = false;
if (System.Config.General.HideGameRetainer) CloseRetainerWindows();
{
var retainerAddon = RaptureAtkUnitManager.Instance()->GetAddonByName("InventoryRetainer");
if (retainerAddon != null)
{
retainerAddon->IsVisible = true;
retainerAddon->Close(true);
}
retainerAddon = RaptureAtkUnitManager.Instance()->GetAddonByName("InventoryRetainerLarge");
if (retainerAddon != null)
{
retainerAddon->IsVisible = true;
retainerAddon->Close(true);
}
}
base.OnFinalize(addon); base.OnFinalize(addon);
} }
+5 -46
View File
@@ -42,29 +42,19 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
}; };
CategoriesNode.AttachNode(this); CategoriesNode.AttachNode(this);
var size = new Vector2(addon->Size.X / 2.0f, 28.0f); var header = CalculateHeaderLayout(addon);
var header = addon->WindowHeaderCollisionNode;
float headerX = header->X;
float headerY = header->Y;
float headerW = header->Width;
float headerH = header->Height;
float x = headerX + (headerW - size.X) * 0.5f;
float y = headerY + (headerH - size.Y) * 0.5f;
SearchInputNode = new TextInputWithHintNode SearchInputNode = new TextInputWithHintNode
{ {
Position = new Vector2(x, y), Position = header.SearchPosition,
Size = size, Size = header.SearchSize,
OnInputReceived = _ => RefreshCategoriesCore(autosize: false), OnInputReceived = _ => RefreshCategoriesCore(autosize: false),
}; };
SearchInputNode.AttachNode(this); SearchInputNode.AttachNode(this);
SettingsButtonNode = new CircleButtonNode SettingsButtonNode = new CircleButtonNode
{ {
Position = new Vector2(headerW - 48f, y), Position = new Vector2(header.HeaderWidth - SettingsButtonOffset, header.HeaderY),
Size = new Vector2(28f), Size = new Vector2(28f),
AddColor = _tintColor, AddColor = _tintColor,
Icon = ButtonIcon.GearCog, Icon = ButtonIcon.GearCog,
@@ -80,7 +70,7 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
FontType = FontType.MiedingerMed, FontType = FontType.MiedingerMed,
TextFlags = TextFlags.Glare, TextFlags = TextFlags.Glare,
TextColor = ColorHelper.GetColor(50), TextColor = ColorHelper.GetColor(50),
TextOutlineColor = ColorHelper.GetColor(32) // Could also be Color 65 TextOutlineColor = ColorHelper.GetColor(32)
}; };
_slotCounterNode.AttachNode(this); _slotCounterNode.AttachNode(this);
SlotCounterNode = _slotCounterNode; SlotCounterNode = _slotCounterNode;
@@ -106,37 +96,6 @@ public unsafe class AddonSaddleBagWindow : InventoryAddonBase
base.RefreshCategoriesCore(autosize); base.RefreshCategoriesCore(autosize);
} }
protected override void OnUpdate(AtkUnitBase* addon)
{
if (RefreshQueued)
{
bool doAutosize = RefreshAutosizeQueued;
RefreshQueued = false;
RefreshAutosizeQueued = false;
RefreshCategoriesCore(doAutosize);
}
base.OnUpdate(addon);
}
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
_inventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
}
public void SetSearchText(string searchText)
{
Services.Framework.RunOnTick(() =>
{
if (IsOpen) SearchInputNode.SearchString = searchText;
RefreshCategoriesCore(autosize: true);
}, delayTicks: 1);
}
protected override void OnFinalize(AtkUnitBase* addon) protected override void OnFinalize(AtkUnitBase* addon)
{ {
_isSetupComplete = false; _isSetupComplete = false;
+10
View File
@@ -0,0 +1,10 @@
namespace AetherBags.Addons;
public interface IInventoryWindow
{
bool IsOpen { get; }
void Toggle();
void Close();
void ManualRefresh();
void SetSearchText(string searchText);
}
+61 -1
View File
@@ -18,7 +18,7 @@ using KamiToolKit.Nodes;
namespace AetherBags.Addons; namespace AetherBags.Addons;
public abstract unsafe class InventoryAddonBase : NativeAddon public abstract unsafe class InventoryAddonBase : NativeAddon, IInventoryWindow
{ {
protected readonly InventoryCategoryHoverCoordinator HoverCoordinator = new(); protected readonly InventoryCategoryHoverCoordinator HoverCoordinator = new();
protected readonly InventoryCategoryPinCoordinator PinCoordinator = new(); protected readonly InventoryCategoryPinCoordinator PinCoordinator = new();
@@ -41,6 +41,7 @@ public abstract unsafe class InventoryAddonBase : NativeAddon
protected const float ItemPadding = 4; protected const float ItemPadding = 4;
protected const float FooterHeight = 28f; protected const float FooterHeight = 28f;
protected const float FooterTopSpacing = 4f; protected const float FooterTopSpacing = 4f;
protected const float SettingsButtonOffset = 48f;
protected bool RefreshQueued; protected bool RefreshQueued;
protected bool RefreshAutosizeQueued; protected bool RefreshAutosizeQueued;
@@ -72,6 +73,15 @@ public abstract unsafe class InventoryAddonBase : NativeAddon
} }
} }
public virtual void SetSearchText(string searchText)
{
Services.Framework.RunOnTick(() =>
{
if (IsOpen) SearchInputNode.SearchString = searchText;
RefreshCategoriesCore(autosize: true);
}, delayTicks: 1);
}
public void RefreshFromLifecycle() public void RefreshFromLifecycle()
{ {
if (!_isSetupComplete) return; if (!_isSetupComplete) return;
@@ -135,6 +145,34 @@ public abstract unsafe class InventoryAddonBase : NativeAddon
} }
} }
protected readonly struct HeaderLayout
{
public Vector2 SearchPosition { get; init; }
public Vector2 SearchSize { get; init; }
public float HeaderWidth { get; init; }
public float HeaderY { get; init; }
}
protected HeaderLayout CalculateHeaderLayout(AtkUnitBase* addon)
{
var size = new Vector2(addon->Size.X / 2.0f, 28.0f);
var header = addon->WindowHeaderCollisionNode;
float headerW = header->Width;
float headerH = header->Height;
float x = header->X + (headerW - size.X) * 0.5f;
float y = header->Y + (headerH - size.Y) * 0.5f;
return new HeaderLayout
{
SearchPosition = new Vector2(x, y),
SearchSize = size,
HeaderWidth = headerW,
HeaderY = y,
};
}
protected void InitializeBackgroundDropTarget() protected void InitializeBackgroundDropTarget()
{ {
BackgroundDropTarget = new DragDropNode BackgroundDropTarget = new DragDropNode
@@ -306,6 +344,28 @@ public abstract unsafe class InventoryAddonBase : NativeAddon
protected void ResizeWindow(float width, float height) protected void ResizeWindow(float width, float height)
=> ResizeWindow(width, height, recalcLayout: true); => ResizeWindow(width, height, recalcLayout: true);
protected override void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{
base.OnRequestedUpdate(addon, numberArrayData, stringArrayData);
InventoryState.RefreshFromGame();
RefreshCategoriesCore(autosize: true);
}
protected override void OnUpdate(AtkUnitBase* addon)
{
if (RefreshQueued)
{
bool doAutosize = RefreshAutosizeQueued;
RefreshQueued = false;
RefreshAutosizeQueued = false;
RefreshCategoriesCore(doAutosize);
}
base.OnUpdate(addon);
}
protected override void OnFinalize(AtkUnitBase* addon) protected override void OnFinalize(AtkUnitBase* addon)
{ {
HoverSubscribed.Clear(); HoverSubscribed.Clear();
+14 -11
View File
@@ -1,3 +1,5 @@
using System.Collections.Generic;
using AetherBags.Addons;
using AetherBags.Inventory.Context; using AetherBags.Inventory.Context;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
@@ -9,32 +11,33 @@ public static unsafe class InventoryOrchestrator
public static void RefreshAll(bool updateMaps = true) public static void RefreshAll(bool updateMaps = true)
{ {
// 1. Update the mapping data (Context menus / Visual slots)
if (updateMaps) if (updateMaps)
{ {
InventoryContextState.RefreshMaps(); InventoryContextState.RefreshMaps();
InventoryContextState.RefreshBlockedSlots(); InventoryContextState.RefreshBlockedSlots();
} }
// 2. Fetch the current context (Are we selling? Trading? Talking to a retainer?)
var agent = AgentInventory.Instance(); var agent = AgentInventory.Instance();
var contextId = agent != null ? agent->OpenTitleId : 0; var contextId = agent != null ? agent->OpenTitleId : 0;
var notification = NotificationState.GetNotificationInfo(contextId); var notification = NotificationState.GetNotificationInfo(contextId);
// 3. Trigger UI refreshes
Services.Framework.RunOnTick(() => Services.Framework.RunOnTick(() =>
{ {
if (System.AddonInventoryWindow.IsOpen) if (System.AddonInventoryWindow.IsOpen)
{
System.AddonInventoryWindow.SetNotification(notification!); System.AddonInventoryWindow.SetNotification(notification!);
System.AddonInventoryWindow.ManualRefresh();
foreach (var window in GetAllWindows())
{
if (window.IsOpen)
window.ManualRefresh();
} }
if (System.AddonSaddleBagWindow.IsOpen)
System.AddonSaddleBagWindow.ManualRefresh();
if (System.AddonRetainerWindow.IsOpen)
System.AddonRetainerWindow.ManualRefresh();
}); });
} }
private static IEnumerable<IInventoryWindow> GetAllWindows()
{
yield return System.AddonInventoryWindow;
yield return System.AddonSaddleBagWindow;
yield return System.AddonRetainerWindow;
}
} }
@@ -25,7 +25,7 @@ public sealed class InventoryFooterNode : SimpleComponentNode
FontType = FontType.MiedingerMed, FontType = FontType.MiedingerMed,
TextFlags = TextFlags.Glare, TextFlags = TextFlags.Glare,
TextColor = ColorHelper.GetColor(50), TextColor = ColorHelper.GetColor(50),
TextOutlineColor = ColorHelper.GetColor(32) // Could also be Color 65 TextOutlineColor = ColorHelper.GetColor(32)
}; };
_slotAmountTextNode.AttachNode(this); _slotAmountTextNode.AttachNode(this);
+3 -7
View File
@@ -16,11 +16,9 @@ namespace AetherBags;
public unsafe class Plugin : IDalamudPlugin public unsafe class Plugin : IDalamudPlugin
{ {
private static string HelpDescription => "Opens your inventory.";
private readonly CommandHandler _commandHandler; private readonly CommandHandler _commandHandler;
private readonly InventoryHooks _inventoryHooks; private readonly InventoryHooks _inventoryHooks;
private readonly InventoryLifecycles inventoryLifecycles; private readonly InventoryLifecycles _inventoryLifecycles;
public Plugin(IDalamudPluginInterface pluginInterface) public Plugin(IDalamudPluginInterface pluginInterface)
{ {
@@ -66,8 +64,6 @@ public unsafe class Plugin : IDalamudPlugin
_commandHandler = new CommandHandler(); _commandHandler = new CommandHandler();
// Services.GameInventory.InventoryChanged += InventoryState.OnRawItemAdded;
Services.ClientState.Login += OnLogin; Services.ClientState.Login += OnLogin;
Services.ClientState.Logout += OnLogout; Services.ClientState.Logout += OnLogout;
@@ -76,7 +72,7 @@ public unsafe class Plugin : IDalamudPlugin
} }
_inventoryHooks = new InventoryHooks(); _inventoryHooks = new InventoryHooks();
inventoryLifecycles = new InventoryLifecycles(); _inventoryLifecycles = new InventoryLifecycles();
} }
public void Dispose() public void Dispose()
@@ -96,7 +92,7 @@ public unsafe class Plugin : IDalamudPlugin
KamiToolKitLibrary.Dispose(); KamiToolKitLibrary.Dispose();
_inventoryHooks.Dispose(); _inventoryHooks.Dispose();
inventoryLifecycles.Dispose(); _inventoryLifecycles.Dispose();
} }
private void OnLogin() private void OnLogin()
-1
View File
@@ -21,7 +21,6 @@ public class Services
[PluginService] public static IPluginLog Logger { get; private set; } = null!; [PluginService] public static IPluginLog Logger { get; private set; } = null!;
[PluginService] public static INotificationManager NotificationManager { get; private set; } = null!; [PluginService] public static INotificationManager NotificationManager { get; private set; } = null!;
[PluginService] public static IObjectTable ObjectTable { get; private set; } = null!; [PluginService] public static IObjectTable ObjectTable { get; private set; } = null!;
// TODO: Remove cause temp
[PluginService] public static ISigScanner SigScanner { get; private set; } = null!; [PluginService] public static ISigScanner SigScanner { get; private set; } = null!;
[PluginService] public static IGameInteropProvider GameInteropProvider { get; private set; } = null!; [PluginService] public static IGameInteropProvider GameInteropProvider { get; private set; } = null!;
} }