Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions Dalamud/Game/Gui/GameGui.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -65,13 +66,18 @@ private GameGui(TargetSigScanner sigScanner)

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();
this.setUiVisibilityHook.Enable();
this.handleActionHoverHook.Enable();
this.handleActionOutHook.Enable();
this.utf8StringFromSequenceHook.Enable();
this.raptureAtkModuleUpdateHook.Enable();
}

// Hooked delegates
Expand All @@ -88,6 +94,18 @@ private GameGui(TargetSigScanner sigScanner)
/// <inheritdoc/>
public event EventHandler<HoveredAction>? HoveredActionChanged;

/// <inheritdoc/>
public event Action InventoryUpdate;

/// <inheritdoc/>
public event Action ActionBarUpdate;

/// <inheritdoc/>
public event Action UnlocksUpdate;

/// <inheritdoc/>
public event Action MainCommandEnabledStateUpdate;

/// <inheritdoc/>
public bool GameUiHidden { get; private set; }

Expand Down Expand Up @@ -238,6 +256,7 @@ void IInternalDisposableService.DisposeService()
this.handleActionHoverHook.Dispose();
this.handleActionOutHook.Dispose();
this.utf8StringFromSequenceHook.Dispose();
this.raptureAtkModuleUpdateHook.Dispose();
}

/// <summary>
Expand Down Expand Up @@ -362,6 +381,34 @@ private char HandleImmDetour(IntPtr framework, char a2, byte a3)

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)
{
if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.InventoryUpdate) ||
agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.RetainerUpdate) || // misleading name, only used for RetainerMarket update (pending CS bump)
agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.HousingInventoryUpdate) ||
((byte)agentUpdateFlag & 0x80) != 0) // ContentInventory changed (pending CS bump)
this.InventoryUpdate.InvokeSafely();

if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.ActionBarUpdate))
this.ActionBarUpdate.InvokeSafely();

if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.UnlocksUpdate))
this.UnlocksUpdate.InvokeSafely();

if (agentUpdateFlag.HasFlag(RaptureAtkModule.AgentUpdateFlags.MainCommandEnabledStateUpdate))
this.MainCommandEnabledStateUpdate.InvokeSafely();
}
}
}

/// <summary>
Expand All @@ -385,6 +432,10 @@ internal GameGuiPluginScoped()
this.gameGuiService.UiHideToggled += this.UiHideToggledForward;
this.gameGuiService.HoveredItemChanged += this.HoveredItemForward;
this.gameGuiService.HoveredActionChanged += this.HoveredActionForward;
this.gameGuiService.InventoryUpdate += this.InventoryUpdateForward;
this.gameGuiService.ActionBarUpdate += this.ActionBarUpdateForward;
this.gameGuiService.UnlocksUpdate += this.UnlocksUpdateForward;
this.gameGuiService.MainCommandEnabledStateUpdate += this.MainCommandEnabledStateUpdateForward;
}

/// <inheritdoc/>
Expand All @@ -396,6 +447,18 @@ internal GameGuiPluginScoped()
/// <inheritdoc/>
public event EventHandler<HoveredAction>? HoveredActionChanged;

/// <inheritdoc/>
public event Action InventoryUpdate;

/// <inheritdoc/>
public event Action ActionBarUpdate;

/// <inheritdoc/>
public event Action UnlocksUpdate;

/// <inheritdoc/>
public event Action MainCommandEnabledStateUpdate;

/// <inheritdoc/>
public bool GameUiHidden => this.gameGuiService.GameUiHidden;

Expand All @@ -415,6 +478,10 @@ void IInternalDisposableService.DisposeService()
this.gameGuiService.UiHideToggled -= this.UiHideToggledForward;
this.gameGuiService.HoveredItemChanged -= this.HoveredItemForward;
this.gameGuiService.HoveredActionChanged -= this.HoveredActionForward;
this.gameGuiService.InventoryUpdate -= this.InventoryUpdateForward;
this.gameGuiService.ActionBarUpdate -= this.ActionBarUpdateForward;
this.gameGuiService.UnlocksUpdate -= this.UnlocksUpdateForward;
this.gameGuiService.MainCommandEnabledStateUpdate -= this.MainCommandEnabledStateUpdateForward;

this.UiHideToggled = null;
this.HoveredItemChanged = null;
Expand Down Expand Up @@ -466,4 +533,12 @@ public AgentInterfacePtr FindAgentInterface(AtkUnitBasePtr addon)
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 InventoryUpdateForward() => this.InventoryUpdate.InvokeSafely();

private void ActionBarUpdateForward() => this.ActionBarUpdate.InvokeSafely();

private void UnlocksUpdateForward() => this.UnlocksUpdate.InvokeSafely();

private void MainCommandEnabledStateUpdateForward() => this.MainCommandEnabledStateUpdate.InvokeSafely();
}
25 changes: 7 additions & 18 deletions Dalamud/Game/Inventory/GameInventory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
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>
Expand All @@ -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;
Expand All @@ -47,18 +46,9 @@ private GameInventory()
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.raptureAtkModuleUpdateHook.Enable();
this.gameGui.InventoryUpdate += this.OnInventoryUpdate;
}

private unsafe delegate void RaptureAtkModuleUpdateDelegate(RaptureAtkModule* ram, float f1);

/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
{
Expand All @@ -68,7 +58,7 @@ void IInternalDisposableService.DisposeService()
this.subscribersPendingChange.Clear();
this.subscribersChanged = false;
this.framework.Update -= this.OnFrameworkUpdate;
this.raptureAtkModuleUpdateHook.Dispose();
this.gameGui.InventoryUpdate -= this.OnInventoryUpdate;
}
}

Expand Down Expand Up @@ -319,10 +309,9 @@ private GameInventoryItem[] CreateItemsArray(int length)
return items;
}

private unsafe void RaptureAtkModuleUpdateDetour(RaptureAtkModule* ram, float f1)
private void OnInventoryUpdate()
{
this.inventoriesMightBeChanged |= ram->AgentUpdateFlag != 0;
this.raptureAtkModuleUpdateHook.Original(ram, f1);
this.inventoriesMightBeChanged |= true;
}

/// <summary>
Expand Down
76 changes: 76 additions & 0 deletions Dalamud/Game/ItemActionType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Lumina.Excel.Sheets;

namespace Dalamud.Game;

/// <summary>
/// Enum for <see cref="ItemAction.Type"/>.
/// </summary>
public enum ItemActionType : ushort
{
/// <summary>
/// Used to unlock a companion (minion).
/// </summary>
Companion = 853,

/// <summary>
/// Used to unlock a chocobo companion barding.
/// </summary>
BuddyEquip = 1013,

/// <summary>
/// Used to unlock a mount.
/// </summary>
Mount = 1322,

/// <summary>
/// Used to unlock recipes from a crafting recipe book.
/// </summary>
SecretRecipeBook = 2136,

/// <summary>
/// Used to unlock various types of content (e.g. Riding Maps, Blue Mage Totems, Emotes, Hairstyles).
/// </summary>
UnlockLink = 2633,

/// <summary>
/// Used to unlock a Triple Triad Card.
/// </summary>
TripleTriadCard = 3357,

/// <summary>
/// Used to unlock gathering nodes of a Folklore Tome.
/// </summary>
FolkloreTome = 4107,

/// <summary>
/// Used to unlock an Orchestrion Roll.
/// </summary>
OrchestrionRoll = 25183,

/// <summary>
/// Used to unlock portrait designs.
/// </summary>
FramersKit = 29459,

/// <summary>
/// Used to unlock Bozjan Field Notes. These are server-side but are cached client-side.
/// </summary>
FieldNotes = 19743,

/// <summary>
/// Used to unlock an Ornament (fashion accessory).
/// </summary>
Ornament = 20086,

/// <summary>
/// Used to unlock glasses.
/// </summary>
Glasses = 37312,

/// <summary>
/// Used for Company Seal Vouchers, which convert the item into Company Seals when used.<br/>
/// Can be used only if in a Grand Company.<br/>
/// IsUnlocked always returns false.
/// </summary>
CompanySealVouchers = 41120,
}
Loading
Loading