Merge pull request #2420 from Haselnussbomber/add-agent-events

Add events based on AgentUpdateFlag
This commit is contained in:
goat 2025-11-08 11:44:44 +01:00 committed by GitHub
commit 3c3eb9159c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 79 additions and 18 deletions

View file

@ -0,0 +1,34 @@
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
namespace Dalamud.Game.Gui;
/// <summary>
/// Represents a flag set by the game used by agents to conditionally update their addons.
/// </summary>
[Flags]
public enum AgentUpdateFlag : byte
{
/// <summary> Set when an inventory has been updated. </summary>
InventoryUpdate = 1 << 0,
/// <summary> Set when a hotbar slot has been executed, or a Gearset or Macro has been changed. </summary>
ActionBarUpdate = 1 << 1,
/// <summary> Set when the RetainerMarket inventory has been updated. </summary>
RetainerMarketInventoryUpdate = 1 << 2,
// /// <summary> Unknown use case. </summary>
// NameplateUpdate = 1 << 3,
/// <summary> Set when the player unlocked collectibles, contents or systems. </summary>
UnlocksUpdate = 1 << 4,
/// <summary> Set when <see cref="AgentHUD.SetMainCommandEnabledState"/> was called. </summary>
MainCommandEnabledStateUpdate = 1 << 5,
/// <summary> Set when any housing inventory has been updated. </summary>
HousingInventoryUpdate = 1 << 6,
/// <summary> Set when any content inventory has been updated. </summary>
ContentInventoryUpdate = 1 << 7,
}

View file

@ -43,6 +43,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
private readonly Hook<HandleImmDelegate> handleImmHook;
private readonly Hook<RaptureAtkModule.Delegates.SetUiVisibility> setUiVisibilityHook;
private readonly Hook<Utf8String.Delegates.Ctor_FromSequence> utf8StringFromSequenceHook;
private readonly Hook<RaptureAtkModule.Delegates.Update> raptureAtkModuleUpdateHook;
[ServiceManager.ServiceConstructor]
private GameGui(TargetSigScanner sigScanner)
@ -65,6 +66,10 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
this.utf8StringFromSequenceHook = Hook<Utf8String.Delegates.Ctor_FromSequence>.FromAddress(Utf8String.Addresses.Ctor_FromSequence.Value, this.Utf8StringFromSequenceDetour);
this.raptureAtkModuleUpdateHook = Hook<RaptureAtkModule.Delegates.Update>.FromFunctionPointerVariable(
new(&RaptureAtkModule.StaticVirtualTablePointer->Update),
this.RaptureAtkModuleUpdateDetour);
this.handleItemHoverHook.Enable();
this.handleItemOutHook.Enable();
this.handleImmHook.Enable();
@ -72,6 +77,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
this.handleActionHoverHook.Enable();
this.handleActionOutHook.Enable();
this.utf8StringFromSequenceHook.Enable();
this.raptureAtkModuleUpdateHook.Enable();
}
// Hooked delegates
@ -88,6 +94,9 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
/// <inheritdoc/>
public event EventHandler<HoveredAction>? HoveredActionChanged;
/// <inheritdoc/>
public event Action<AgentUpdateFlag> AgentUpdate;
/// <inheritdoc/>
public bool GameUiHidden { get; private set; }
@ -238,6 +247,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
this.handleActionHoverHook.Dispose();
this.handleActionOutHook.Dispose();
this.utf8StringFromSequenceHook.Dispose();
this.raptureAtkModuleUpdateHook.Dispose();
}
/// <summary>
@ -354,6 +364,21 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
return thisPtr; // this function shouldn't need to return but the original asm moves this into rax before returning so be safe?
}
private void RaptureAtkModuleUpdateDetour(RaptureAtkModule* thisPtr, float delta)
{
// The game clears the AgentUpdateFlag in the original function, but it also updates agents in it too.
// We'll make a copy of the flags, so that we can fire events after the agents have been updated.
var agentUpdateFlag = thisPtr->AgentUpdateFlag;
this.raptureAtkModuleUpdateHook.Original(thisPtr, delta);
if (agentUpdateFlag != RaptureAtkModule.AgentUpdateFlags.None)
{
this.AgentUpdate.InvokeSafely((AgentUpdateFlag)agentUpdateFlag);
}
}
}
/// <summary>
@ -377,6 +402,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
this.gameGuiService.UiHideToggled += this.UiHideToggledForward;
this.gameGuiService.HoveredItemChanged += this.HoveredItemForward;
this.gameGuiService.HoveredActionChanged += this.HoveredActionForward;
this.gameGuiService.AgentUpdate += this.AgentUpdateForward;
}
/// <inheritdoc/>
@ -388,6 +414,9 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
/// <inheritdoc/>
public event EventHandler<HoveredAction>? HoveredActionChanged;
/// <inheritdoc/>
public event Action<AgentUpdateFlag> AgentUpdate;
/// <inheritdoc/>
public bool GameUiHidden => this.gameGuiService.GameUiHidden;
@ -407,6 +436,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
this.gameGuiService.UiHideToggled -= this.UiHideToggledForward;
this.gameGuiService.HoveredItemChanged -= this.HoveredItemForward;
this.gameGuiService.HoveredActionChanged -= this.HoveredActionForward;
this.gameGuiService.AgentUpdate -= this.AgentUpdateForward;
this.UiHideToggled = null;
this.HoveredItemChanged = null;
@ -458,4 +488,6 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
private void HoveredItemForward(object sender, ulong itemId) => this.HoveredItemChanged?.Invoke(sender, itemId);
private void HoveredActionForward(object sender, HoveredAction hoverAction) => this.HoveredActionChanged?.Invoke(sender, hoverAction);
private void AgentUpdateForward(AgentUpdateFlag agentUpdateFlag) => this.AgentUpdate.InvokeSafely(agentUpdateFlag);
}

View file

@ -2,16 +2,14 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.Gui;
using Dalamud.Game.Inventory.InventoryEventArgTypes;
using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
namespace Dalamud.Game.Inventory;
/// <summary>
@ -33,7 +31,8 @@ internal class GameInventory : IInternalDisposableService
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service<Framework>.Get();
private readonly Hook<RaptureAtkModuleUpdateDelegate> raptureAtkModuleUpdateHook;
[ServiceManager.ServiceDependency]
private readonly GameGui gameGui = Service<GameGui>.Get();
private readonly GameInventoryType[] inventoryTypes;
private readonly GameInventoryItem[]?[] inventoryItems;
@ -47,18 +46,9 @@ internal class GameInventory : IInternalDisposableService
this.inventoryTypes = Enum.GetValues<GameInventoryType>();
this.inventoryItems = new GameInventoryItem[this.inventoryTypes.Length][];
unsafe
{
this.raptureAtkModuleUpdateHook = Hook<RaptureAtkModuleUpdateDelegate>.FromFunctionPointerVariable(
new(&RaptureAtkModule.StaticVirtualTablePointer->Update),
this.RaptureAtkModuleUpdateDetour);
this.gameGui.AgentUpdate += this.OnAgentUpdate;
}
this.raptureAtkModuleUpdateHook.Enable();
}
private unsafe delegate void RaptureAtkModuleUpdateDelegate(RaptureAtkModule* ram, float f1);
/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
{
@ -68,7 +58,7 @@ internal class GameInventory : IInternalDisposableService
this.subscribersPendingChange.Clear();
this.subscribersChanged = false;
this.framework.Update -= this.OnFrameworkUpdate;
this.raptureAtkModuleUpdateHook.Dispose();
this.gameGui.AgentUpdate -= this.OnAgentUpdate;
}
}
@ -319,10 +309,9 @@ internal class GameInventory : IInternalDisposableService
return items;
}
private unsafe void RaptureAtkModuleUpdateDetour(RaptureAtkModule* ram, float f1)
private void OnAgentUpdate(AgentUpdateFlag agentUpdateFlag)
{
this.inventoriesMightBeChanged |= ram->AgentUpdateFlag != 0;
this.raptureAtkModuleUpdateHook.Original(ram, f1);
this.inventoriesMightBeChanged |= true;
}
/// <summary>

View file

@ -26,6 +26,12 @@ public unsafe interface IGameGui
/// </summary>
public event EventHandler<HoveredAction> HoveredActionChanged;
/// <summary>
/// Fired when the game sets one or more <see cref="AgentUpdateFlag"/> values,
/// used by agents to conditionally update their addons.
/// </summary>
event Action<AgentUpdateFlag> AgentUpdate;
/// <summary>
/// Gets a value indicating whether the game UI is hidden.
/// </summary>