From acdd79f73a8b2b9b17cd062fe3adc7d71b446999 Mon Sep 17 00:00:00 2001 From: Zeffuro Date: Tue, 30 Dec 2025 00:12:20 +0100 Subject: [PATCH] More changes --- AetherBags/Addons/AddonSaddleBagWindow.cs | 120 ++++++++++++++++++ AetherBags/Commands/CommandHandler.cs | 4 + .../Inventory/Scanning/InventoryScanner.cs | 2 +- .../Inventory/Scanning/InventorySource.cs | 18 ++- AetherBags/Inventory/State/SaddleBagState.cs | 21 ++- .../Inventory/InventoryNotificationNode.cs | 2 +- .../Nodes/Inventory/SaddleBagFooterNode.cs | 32 +++++ AetherBags/Plugin.cs | 9 +- AetherBags/Services.cs | 2 + AetherBags/System.cs | 4 +- 10 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 AetherBags/Addons/AddonSaddleBagWindow.cs create mode 100644 AetherBags/Nodes/Inventory/SaddleBagFooterNode.cs diff --git a/AetherBags/Addons/AddonSaddleBagWindow.cs b/AetherBags/Addons/AddonSaddleBagWindow.cs new file mode 100644 index 0000000..5078b3b --- /dev/null +++ b/AetherBags/Addons/AddonSaddleBagWindow.cs @@ -0,0 +1,120 @@ +using System.Numerics; +using AetherBags.Inventory.State; +using AetherBags.Nodes.Input; +using AetherBags.Nodes.Inventory; +using AetherBags.Nodes.Layout; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; +using KamiToolKit.Nodes; + +namespace AetherBags.Addons; + +public unsafe class AddonSaddleBagWindow : InventoryAddonBase +{ + private readonly SaddleBagState _inventoryState = new(); + + protected override InventoryStateBase InventoryState => _inventoryState; + + protected override bool HasFooter => false; + + protected override float MinWindowWidth => 400; + protected override float MaxWindowWidth => 600; + + protected override void OnSetup(AtkUnitBase* addon) + { + CategoriesNode = new WrappingGridNode + { + Position = ContentStartPosition, + Size = ContentSize, + HorizontalSpacing = CategorySpacing, + VerticalSpacing = CategorySpacing, + TopPadding = 4.0f, + BottomPadding = 4.0f, + }; + CategoriesNode.AttachNode(this); + + var size = new Vector2(addon->Size.X / 2.0f, 28.0f); + + 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 + { + Position = new Vector2(x, y), + Size = size, + OnInputReceived = _ => RefreshCategoriesCore(autosize: false), + }; + SearchInputNode.AttachNode(this); + + SettingsButtonNode = new CircleButtonNode + { + Position = new Vector2(headerW - 48f, y), + Size = new Vector2(28f), + Icon = ButtonIcon.GearCog, + OnClick = System.AddonConfigurationWindow.Toggle + }; + SettingsButtonNode.AttachNode(this); + + LayoutContent(); + + Services.AddonLifecycle.RegisterListener(AddonEvent.PostRequestedUpdate, "InventoryBuddy", OnSaddleBagUpdate); + + _inventoryState.RefreshFromGame(); + RefreshCategoriesCore(autosize: true); + + base.OnSetup(addon); + } + + protected override void OnUpdate(AtkUnitBase* addon) + { + if (RefreshQueued) + { + bool doAutosize = RefreshAutosizeQueued; + RefreshQueued = false; + RefreshAutosizeQueued = false; + + RefreshCategoriesCore(doAutosize); + } + + base.OnUpdate(addon); + } + + private void OnSaddleBagUpdate(AddonEvent type, AddonArgs args) + { + _inventoryState.RefreshFromGame(); + RefreshCategoriesCore(autosize: true); + } + + 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) + { + Services.AddonLifecycle.UnregisterListener(OnSaddleBagUpdate); + + base.OnFinalize(addon); + } +} \ No newline at end of file diff --git a/AetherBags/Commands/CommandHandler.cs b/AetherBags/Commands/CommandHandler.cs index a5fab08..e93a3ea 100644 --- a/AetherBags/Commands/CommandHandler.cs +++ b/AetherBags/Commands/CommandHandler.cs @@ -91,6 +91,10 @@ public class CommandHandler : IDisposable PrintChat($"{stats.UsedSlots}/{stats.TotalSlots} slots used ({stats.UsagePercent:F0}%) | {stats.TotalItems} unique items | {stats.CategoryCount} categories"); break; + case "saddle": + System.AddonSaddleBagWindow.Toggle(); + break; + case "help": case "?": PrintHelp(); diff --git a/AetherBags/Inventory/Scanning/InventoryScanner.cs b/AetherBags/Inventory/Scanning/InventoryScanner.cs index 28c64cc..c957669 100644 --- a/AetherBags/Inventory/Scanning/InventoryScanner.cs +++ b/AetherBags/Inventory/Scanning/InventoryScanner.cs @@ -47,7 +47,7 @@ public static unsafe class InventoryScanner public static ulong MakeNaturalSlotKey(InventoryType container, int slot) => ((ulong)(uint)container << 32) | (uint)slot; - // Backwards compatible + // Backwards compatible TODO: Remove public static void ScanBags( InventoryManager* inventoryManager, InventoryStackMode stackMode, diff --git a/AetherBags/Inventory/Scanning/InventorySource.cs b/AetherBags/Inventory/Scanning/InventorySource.cs index 47ff7ba..213d6ee 100644 --- a/AetherBags/Inventory/Scanning/InventorySource.cs +++ b/AetherBags/Inventory/Scanning/InventorySource.cs @@ -6,6 +6,8 @@ public enum InventorySourceType { MainBags, SaddleBag, + PremiumSaddleBag, + AllSaddleBags, Retainer, } @@ -20,6 +22,18 @@ public static class InventorySourceDefinitions ]; public static readonly InventoryType[] SaddleBag = + [ + InventoryType.SaddleBag1, + InventoryType.SaddleBag2, + ]; + + public static readonly InventoryType[] PremiumSaddleBag = + [ + InventoryType.PremiumSaddleBag1, + InventoryType.PremiumSaddleBag2, + ]; + + public static readonly InventoryType[] AllSaddleBags = [ InventoryType.SaddleBag1, InventoryType.SaddleBag2, @@ -49,7 +63,9 @@ public static class InventorySourceDefinitions public static int GetTotalSlots(InventorySourceType source) => source switch { InventorySourceType.MainBags => 140, // 4 * 35 - InventorySourceType.SaddleBag => 70, // 2 * 35 TODO: Premium adds another 70 + InventorySourceType.SaddleBag => 70, // 2 * 35 + InventorySourceType.PremiumSaddleBag => 70, // 2 * 35 + InventorySourceType.AllSaddleBags => 140, // 2 * 35 InventorySourceType.Retainer => 175, // 7 * 25 _ => 140, }; diff --git a/AetherBags/Inventory/State/SaddleBagState.cs b/AetherBags/Inventory/State/SaddleBagState.cs index d0fe562..608d229 100644 --- a/AetherBags/Inventory/State/SaddleBagState.cs +++ b/AetherBags/Inventory/State/SaddleBagState.cs @@ -1,10 +1,27 @@ using AetherBags.Inventory.Scanning; using FFXIVClientStructs.FFXIV.Client.Game; +using FFXIVClientStructs.FFXIV.Client.Game.UI; namespace AetherBags.Inventory.State; public class SaddleBagState : InventoryStateBase { - public override InventorySourceType SourceType => InventorySourceType.SaddleBag; - public override InventoryType[] Inventories => InventorySourceDefinitions.SaddleBag; + public override InventorySourceType SourceType => HasPremiumSaddlebag + ? InventorySourceType.AllSaddleBags + : InventorySourceType.SaddleBag; + + public override InventoryType[] Inventories => HasPremiumSaddlebag + ? InventorySourceDefinitions.AllSaddleBags + : InventorySourceDefinitions.SaddleBag; + + private static unsafe bool HasPremiumSaddlebag + { + get + { + if (!Services.ClientState.IsLoggedIn) return false; + + var playerState = PlayerState.Instance(); + return playerState != null && playerState->HasPremiumSaddlebag; + } + } } \ No newline at end of file diff --git a/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs b/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs index 1645ed7..550dc72 100644 --- a/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs +++ b/AetherBags/Nodes/Inventory/InventoryNotificationNode.cs @@ -88,7 +88,7 @@ public sealed class InventoryNotificationNode : SimpleComponentNode Timeline?.PlayAnimation(101); } - } + } = null!; // Future Zeff, this always goes on a parent private Timeline ParentLabels => new TimelineBuilder() diff --git a/AetherBags/Nodes/Inventory/SaddleBagFooterNode.cs b/AetherBags/Nodes/Inventory/SaddleBagFooterNode.cs new file mode 100644 index 0000000..1080390 --- /dev/null +++ b/AetherBags/Nodes/Inventory/SaddleBagFooterNode.cs @@ -0,0 +1,32 @@ +using System. Numerics; +using FFXIVClientStructs.FFXIV.Component.GUI; +using KamiToolKit.Classes; +using KamiToolKit.Nodes; + +namespace AetherBags.Nodes.Inventory; + +public class SaddleBagFooterNode : SimpleComponentNode +{ + private readonly TextNode _slotCounterNode; + + private const float Padding = 8f; + + public SaddleBagFooterNode() + { + _slotCounterNode = new TextNode + { + Position = new Vector2(Padding, 4f), + Size = new Vector2(100, 20), + AlignmentType = AlignmentType.Left, + TextColor = new Vector4(1f, 1f, 1f, 1f), + FontSize = 14, + }; + _slotCounterNode.AttachNode(this); + } + + public string SlotAmountText + { + get => _slotCounterNode.String; + set => _slotCounterNode.String = $"Slots: {value}"; + } +} \ No newline at end of file diff --git a/AetherBags/Plugin.cs b/AetherBags/Plugin.cs index 0528874..074c8fe 100644 --- a/AetherBags/Plugin.cs +++ b/AetherBags/Plugin.cs @@ -31,7 +31,14 @@ public unsafe class Plugin : IDalamudPlugin System.AddonInventoryWindow = new AddonInventoryWindow { - InternalName = "AetherBags", + InternalName = "AetherBags_MainBags", + Title = "AetherBags", + Size = new Vector2(750, 750), + }; + + System.AddonSaddleBagWindow = new AddonSaddleBagWindow + { + InternalName = "AetherBags_SaddleBag", Title = "AetherBags", Size = new Vector2(750, 750), }; diff --git a/AetherBags/Services.cs b/AetherBags/Services.cs index bf7a595..38d2ea1 100644 --- a/AetherBags/Services.cs +++ b/AetherBags/Services.cs @@ -16,8 +16,10 @@ public class Services [PluginService] public static IGameGui GameGui { get; private set; } = null!; [PluginService] public static IGameInventory GameInventory { get; set; } = null!; [PluginService] public static IKeyState KeyState { get; private set; } = null!; + [PluginService] public static IPlayerState PlayerState { 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 IObjectTable ObjectTable { get; private set; } = null!; // TODO: Remove cause temp [PluginService] public static ISigScanner SigScanner { get; private set; } = null!; [PluginService] public static IGameInteropProvider GameInteropProvider { get; private set; } = null!; diff --git a/AetherBags/System.cs b/AetherBags/System.cs index 3e35168..db4b448 100644 --- a/AetherBags/System.cs +++ b/AetherBags/System.cs @@ -6,8 +6,8 @@ namespace AetherBags; public static class System { public static AddonInventoryWindow AddonInventoryWindow { get; set; } = null!; - public static AddonInventoryWindow AddonSaddleBagWindow { get; set; } = null!; - public static AddonInventoryWindow AddonRetainerWindow { get; set; } = null!; + public static AddonSaddleBagWindow AddonSaddleBagWindow { get; set; } = null!; + //public static AddonRetainerWindow AddonRetainerWindow { get; set; } = null!; public static AddonConfigurationWindow AddonConfigurationWindow { get; set; } = null!; public static SystemConfiguration Config { get; set; } = null!; } \ No newline at end of file