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