Open Inventory, different InventoryNotificationType

This commit is contained in:
Zeffuro
2025-12-28 13:36:41 +01:00
parent 75f278c945
commit 98742482e3
11 changed files with 230 additions and 58 deletions
@@ -1,6 +1,13 @@
using System; using System;
using System.Linq;
using AetherBags.Configuration;
using AetherBags.Inventory;
using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; 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; namespace AetherBags.AddonLifecycles;
@@ -9,17 +16,49 @@ public class InventoryLifecycles : IDisposable
public InventoryLifecycles() 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"); 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() public void Dispose()
{ {
Services.AddonLifecycle.UnregisterListener(AddonEvent.PreOpen, ["Inventory", "InventoryLarge", "InventoryExpansion"]); Services.AddonLifecycle.UnregisterListener(AddonEvent.PreRefresh, ["Inventory", "InventoryLarge", "InventoryExpansion"]);
} }
} }
+8 -3
View File
@@ -125,9 +125,6 @@ public class AddonInventoryWindow : NativeAddon
RefreshCategoriesCore(doAutosize); RefreshCategoriesCore(doAutosize);
} }
InventoryNotificationType currentNotificationType = (InventoryNotificationType) AgentInventory.Instance()->OpenTitleId;
if(currentNotificationType != _notificationNode.NotificationType) _notificationNode.NotificationType = currentNotificationType;
base.OnUpdate(addon); base.OnUpdate(addon);
} }
@@ -292,6 +289,14 @@ public class AddonInventoryWindow : NativeAddon
private void ResizeWindow(float width, float height) private void ResizeWindow(float width, float height)
=> ResizeWindow(width, height, recalcLayout: true); => 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) protected override unsafe void OnFinalize(AtkUnitBase* addon)
{ {
Services.AddonLifecycle.UnregisterListener(OnInventoryUpdate); Services.AddonLifecycle.UnregisterListener(OnInventoryUpdate);
@@ -11,6 +11,8 @@ public class GeneralSettings
public int CompactLookahead { get; set; } = 24; public int CompactLookahead { get; set; } = 24;
public bool CompactPreferLargestFit { get; set; } = true; public bool CompactPreferLargestFit { get; set; } = true;
public bool CompactStableInsert { 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 public enum InventoryStackMode : byte
+80
View File
@@ -3,6 +3,7 @@ using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
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;
namespace AetherBags.Hooks; namespace AetherBags.Hooks;
@@ -19,7 +20,14 @@ public sealed unsafe class InventoryHooks : IDisposable
ushort dstSlot, ushort dstSlot,
bool unk); bool unk);
private delegate void HandleInventoryEventDelegate(AgentInterface* eventInterface, AtkValue* atkValue, int valueCount);
private readonly Hook<MoveItemSlotDelegate>? _moveItemSlotHook; private readonly Hook<MoveItemSlotDelegate>? _moveItemSlotHook;
/*
private readonly Hook<UIModule.Delegates.OpenInventory>? _openInventoryHook;
private readonly Hook<HandleInventoryEventDelegate>? _handleInventoryEventHook;
private readonly Hook<RaptureAtkModule.Delegates.OpenAddon>? _openAddonHook;
*/
public InventoryHooks() public InventoryHooks()
{ {
@@ -36,6 +44,47 @@ public sealed unsafe class InventoryHooks : IDisposable
{ {
Services.Logger.Error(e, "Failed to hook MoveItemSlot"); Services.Logger.Error(e, "Failed to hook MoveItemSlot");
} }
/*
try
{
_openInventoryHook = Services.GameInteropProvider.HookFromAddress<UIModule.Delegates.OpenInventory>(
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<HandleInventoryEventDelegate>(
"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.Delegates.OpenAddon>(
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, private int MoveItemSlotDetour(InventoryManager* manager,
@@ -53,8 +102,39 @@ public sealed unsafe class InventoryHooks : IDisposable
return _moveItemSlotHook!.Original(manager, srcType, srcSlot, dstType, dstSlot, unk); 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() public void Dispose()
{ {
_moveItemSlotHook?.Dispose(); _moveItemSlotHook?.Dispose();
/*
_openInventoryHook?.Dispose();
_handleInventoryEventHook?.Dispose();
_openAddonHook?.Dispose();
*/
} }
} }
@@ -56,8 +56,8 @@ public class InventoryNotificationState
return notificationCache.GetValueOrDefault((InventoryNotificationType)openTitleId); return notificationCache.GetValueOrDefault((InventoryNotificationType)openTitleId);
} }
public record InventoryNotificationInfo(ReadOnlySeString Title, ReadOnlySeString Message);
} }
public record InventoryNotificationInfo(ReadOnlySeString Title, ReadOnlySeString Message);
public enum InventoryNotificationType : uint public enum InventoryNotificationType : uint
{ {
+4 -6
View File
@@ -8,17 +8,15 @@ namespace AetherBags.Nodes.Color;
public class ColorInputRow : HorizontalListNode public class ColorInputRow : HorizontalListNode
{ {
private readonly GridNode _gridNode;
private ColorPickerAddon? _colorPickerAddon; private ColorPickerAddon? _colorPickerAddon;
private readonly LabelTextNode _labelTextNode; private readonly LabelTextNode _labelTextNode;
private ColorPreviewButtonNode _colorPreview; private readonly ColorPreviewButtonNode _colorPreview;
private Vector4 _initialColor;
public ColorInputRow() public ColorInputRow()
{ {
InitializeColorPicker(); InitializeColorPicker();
_initialColor = CurrentColor; var initialColor = CurrentColor;
_colorPreview = new ColorPreviewButtonNode _colorPreview = new ColorPreviewButtonNode
{ {
@@ -33,7 +31,7 @@ public class ColorInputRow : HorizontalListNode
{ {
CurrentColor = color; CurrentColor = color;
_colorPreview?.Color = color; _colorPreview?.Color = color;
_initialColor = color; initialColor = color;
OnColorConfirmed?.Invoke(color); OnColorConfirmed?.Invoke(color);
}; };
_colorPickerAddon?.OnColorPreviewed = color => _colorPickerAddon?.OnColorPreviewed = color =>
@@ -41,7 +39,7 @@ public class ColorInputRow : HorizontalListNode
_colorPreview?.Color = color; _colorPreview?.Color = color;
OnColorChange?.Invoke(color); OnColorChange?.Invoke(color);
}; };
_colorPickerAddon?.OnColorCancelled = () => OnColorCanceled?.Invoke(_initialColor); _colorPickerAddon?.OnColorCancelled = () => OnColorCanceled?.Invoke(initialColor);
} }
}; };
_colorPreview.AttachNode(this); _colorPreview.AttachNode(this);
@@ -50,7 +50,7 @@ public sealed class StringListEditorNode : VerticalListNode
if (!string.IsNullOrWhiteSpace(value) && ! _list.Contains(value)) if (!string.IsNullOrWhiteSpace(value) && ! _list.Contains(value))
{ {
_list.Add(value); _list.Add(value);
_addInput.String = ""; _addInput?.String = "";
RefreshItems(); RefreshItems();
_onChanged?.Invoke(); _onChanged?.Invoke();
} }
@@ -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<InventoryStackMode>(selected, out var parsed))
{
config.StackMode = parsed;
System.AddonInventoryWindow.ManualInventoryRefresh();
}
}
};
AddNode(_stackDropDown);
}
}
@@ -11,7 +11,6 @@ namespace AetherBags.Nodes.Configuration.General;
public sealed class GeneralScrollingAreaNode : ScrollingAreaNode<VerticalListNode> public sealed class GeneralScrollingAreaNode : ScrollingAreaNode<VerticalListNode>
{ {
private readonly CheckboxNode _debugCheckboxNode = null!; private readonly CheckboxNode _debugCheckboxNode = null!;
private readonly LabeledDropdownNode _stackDropDown = null!;
public GeneralScrollingAreaNode() public GeneralScrollingAreaNode()
{ {
@@ -19,24 +18,7 @@ public sealed class GeneralScrollingAreaNode : ScrollingAreaNode<VerticalListNod
ContentNode.ItemSpacing = 32; ContentNode.ItemSpacing = 32;
_stackDropDown = new LabeledDropdownNode ContentNode.AddNode(new FunctionalConfigurationNode());
{
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<InventoryStackMode>(selected, out var parsed))
{
config.StackMode = parsed;
System.AddonInventoryWindow.ManualInventoryRefresh();
}
}
};
ContentNode.AddNode(_stackDropDown);
ContentNode.AddNode(new LayoutConfigurationNode()); ContentNode.AddNode(new LayoutConfigurationNode());
+2 -2
View File
@@ -17,8 +17,8 @@ public unsafe class DragDropNode : ComponentNode<AtkComponentDragDrop, AtkUldCom
// FIX: Manually expose the pointers that are 'internal' in KamiToolKit // FIX: Manually expose the pointers that are 'internal' in KamiToolKit
// We access the raw AtkComponentNode* via 'this.ResNode' and cast from there. // We access the raw AtkComponentNode* via 'this.ResNode' and cast from there.
private new AtkComponentDragDrop* Component => (AtkComponentDragDrop*)Node->Component; private AtkComponentDragDrop* Component => (AtkComponentDragDrop*)Node->Component;
private new AtkUldComponentDataDragDrop* Data => (AtkUldComponentDataDragDrop*)Component->UldManager.ComponentData; private AtkUldComponentDataDragDrop* Data => (AtkUldComponentDataDragDrop*)Component->UldManager.ComponentData;
public readonly ImageNode DragDropBackgroundNode; public readonly ImageNode DragDropBackgroundNode;
public readonly IconNode IconNode; public readonly IconNode IconNode;
@@ -1,15 +1,9 @@
using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using AetherBags.Inventory; using AetherBags.Inventory;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit;
using KamiToolKit.Classes; using KamiToolKit.Classes;
using KamiToolKit.Classes.Timelines; using KamiToolKit.Classes.Timelines;
using KamiToolKit.Nodes; using KamiToolKit.Nodes;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Lumina.Text;
using Lumina.Text.ReadOnly;
namespace AetherBags.Nodes.Inventory; namespace AetherBags.Nodes.Inventory;
@@ -75,30 +69,25 @@ public sealed class InventoryNotificationNode : SimpleComponentNode
messageTextNode.Size = Size with { Y = 16 }; messageTextNode.Size = Size with { Y = 16 };
} }
public InventoryNotificationType NotificationType public InventoryNotificationInfo NotificationInfo
{ {
get; get;
set set
{ {
field = value; 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; Timeline?.PlayAnimation(17);
messageTextNode.String = string.Empty; return;
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(101);
} }
} = InventoryNotificationType.None; } = new("sdsdsd", "sdsd");
// Future Zeff, this always goes on a parent // Future Zeff, this always goes on a parent
private Timeline ParentLabels => new TimelineBuilder() private Timeline ParentLabels => new TimelineBuilder()