refactor: remove context menu support
This commit is contained in:
parent
51e3ba33e1
commit
1a4a3a065c
|
@ -1,54 +0,0 @@
|
|||
using System;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu {
|
||||
/// <summary>
|
||||
/// The base class for context menu arguments
|
||||
/// </summary>
|
||||
public abstract class BaseContextMenuArgs {
|
||||
/// <summary>
|
||||
/// Pointer to the context menu addon.
|
||||
/// </summary>
|
||||
public IntPtr Addon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the context menu agent.
|
||||
/// </summary>
|
||||
public IntPtr Agent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the addon containing this context menu, if any.
|
||||
/// </summary>
|
||||
public string? ParentAddonName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The object ID for this context menu. May be invalid (0xE0000000).
|
||||
/// </summary>
|
||||
public uint ObjectId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The lower half of the content ID of the object for this context menu. May be zero.
|
||||
/// </summary>
|
||||
public uint ContentIdLower { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The text related to this context menu, usually an object name.
|
||||
/// </summary>
|
||||
public SeString? Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The world of the object this context menu is for, if any.
|
||||
/// </summary>
|
||||
public ushort ObjectWorld { get; }
|
||||
|
||||
internal BaseContextMenuArgs(IntPtr addon, IntPtr agent, string? parentAddonName, uint objectId, uint contentIdLower, SeString? text, ushort objectWorld) {
|
||||
this.Addon = addon;
|
||||
this.Agent = agent;
|
||||
this.ParentAddonName = parentAddonName;
|
||||
this.ObjectId = objectId;
|
||||
this.ContentIdLower = contentIdLower;
|
||||
this.Text = text;
|
||||
this.ObjectWorld = objectWorld;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
namespace XivCommon.Functions.ContextMenu {
|
||||
/// <summary>
|
||||
/// A base context menu item
|
||||
/// </summary>
|
||||
public abstract class BaseContextMenuItem {
|
||||
/// <summary>
|
||||
/// If this item should be enabled in the menu.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// If this item should have the submenu arrow in the menu.
|
||||
/// </summary>
|
||||
public bool IsSubMenu { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,774 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Dalamud;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using XivCommon.Functions.ContextMenu.Inventory;
|
||||
using Framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu {
|
||||
/// <summary>
|
||||
/// Context menu functions
|
||||
/// </summary>
|
||||
public class ContextMenu : IDisposable {
|
||||
private static class Signatures {
|
||||
internal const string SomeOpenAddonThing = "E8 ?? ?? ?? ?? 0F B7 C0 48 83 C4 60";
|
||||
internal const string ContextMenuOpen = "48 8B C4 57 41 56 41 57 48 81 EC";
|
||||
internal const string ContextMenuSelected = "48 89 5C 24 ?? 55 57 41 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? 80 B9";
|
||||
internal const string ContextMenuEvent66 = "E8 ?? ?? ?? ?? 44 39 A3 ?? ?? ?? ?? 0F 84";
|
||||
internal const string InventoryContextMenuEvent30 = "E8 ?? ?? ?? ?? 48 83 C4 30 5B C3 8B 83";
|
||||
internal const string SetUpContextSubMenu = "E8 ?? ?? ?? ?? 44 39 A3 ?? ?? ?? ?? 0F 86";
|
||||
internal const string SetUpInventoryContextSubMenu = "44 88 44 24 ?? 88 54 24 10 53";
|
||||
internal const string TitleContextMenuOpen = "48 8B C4 57 41 55 41 56 48 81 EC";
|
||||
internal const string AtkValueChangeType = "E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24";
|
||||
internal const string AtkValueSetString = "E8 ?? ?? ?? ?? 41 03 ED";
|
||||
internal const string GetAddonByInternalId = "E8 ?? ?? ?? ?? 8B 6B 20";
|
||||
}
|
||||
|
||||
#region Offsets and other constants
|
||||
|
||||
private const int MaxItems = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Offset from addon to menu type
|
||||
/// </summary>
|
||||
private const int ParentAddonIdOffset = 0x1D2;
|
||||
|
||||
private const int AddonArraySizeOffset = 0x1CA;
|
||||
private const int AddonArrayOffset = 0x160;
|
||||
|
||||
private const int ContextMenuItemOffset = 7;
|
||||
|
||||
/// <summary>
|
||||
/// Offset from agent to actions byte array pointer (have to add the actions offset after)
|
||||
/// </summary>
|
||||
private const int MenuActionsPointerOffset = 0xD18;
|
||||
|
||||
/// <summary>
|
||||
/// SetUpContextSubMenu checks this
|
||||
/// </summary>
|
||||
private const int BooleanOffsetCheck = 0x690;
|
||||
|
||||
/// <summary>
|
||||
/// Offset from [MenuActionsPointer] to actions byte array
|
||||
/// </summary>
|
||||
private const int MenuActionsOffset = 0x428;
|
||||
|
||||
/// <summary>
|
||||
/// Offset from inventory context agent to actions byte array
|
||||
/// </summary>
|
||||
private const int InventoryMenuActionsOffset = 0x558;
|
||||
|
||||
private const int ObjectIdOffset = 0xEF0;
|
||||
private const int ContentIdLowerOffset = 0xEE0;
|
||||
private const int TextPointerOffset = 0xE08;
|
||||
private const int WorldOffset = 0xF00;
|
||||
|
||||
private const int ItemIdOffset = 0x5F8;
|
||||
private const int ItemAmountOffset = 0x5FC;
|
||||
private const int ItemHqOffset = 0x604;
|
||||
|
||||
// Found in the first function in the agent's vtable
|
||||
private const byte NoopContextId = 0x67;
|
||||
private const byte InventoryNoopContextId = 0xFF;
|
||||
private const byte ContextSubId = 0x66;
|
||||
private const byte InventoryContextSubId = 0x30;
|
||||
|
||||
#endregion
|
||||
|
||||
// 82C570 called when you click on a title menu context item
|
||||
|
||||
/// <summary>
|
||||
/// The delegate for context menu events.
|
||||
/// </summary>
|
||||
public delegate void ContextMenuOpenEventDelegate(ContextMenuOpenArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when a context menu is being prepared for opening.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.ContextMenu"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event ContextMenuOpenEventDelegate? OpenContextMenu;
|
||||
|
||||
/// <summary>
|
||||
/// The delegate for inventory context menu events.
|
||||
/// </summary>
|
||||
public delegate void InventoryContextMenuOpenEventDelegate(InventoryContextMenuOpenArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// The event that is fired when an inventory context menu is being prepared for opening.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Requires the <see cref="Hooks.ContextMenu"/> hook to be enabled.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public event InventoryContextMenuOpenEventDelegate? OpenInventoryContextMenu;
|
||||
|
||||
/// <summary>
|
||||
/// The delegate that is run when a context menu item is selected.
|
||||
/// </summary>
|
||||
public delegate void ContextMenuItemSelectedDelegate(ContextMenuItemSelectedArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// The delegate that is run when an inventory context menu item is selected.
|
||||
/// </summary>
|
||||
public delegate void InventoryContextMenuItemSelectedDelegate(InventoryContextMenuItemSelectedArgs args);
|
||||
|
||||
private delegate IntPtr SomeOpenAddonThingDelegate(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, IntPtr a6, IntPtr a7, ushort a8);
|
||||
|
||||
private Hook<SomeOpenAddonThingDelegate>? SomeOpenAddonThingHook { get; }
|
||||
|
||||
private unsafe delegate byte ContextMenuOpenDelegate(IntPtr addon, int menuSize, AtkValue* atkValueArgs);
|
||||
|
||||
private delegate IntPtr GetAddonByInternalIdDelegate(IntPtr raptureAtkUnitManager, short id);
|
||||
|
||||
private readonly GetAddonByInternalIdDelegate _getAddonByInternalId = null!;
|
||||
|
||||
private Hook<ContextMenuOpenDelegate>? ContextMenuOpenHook { get; }
|
||||
private Hook<ContextMenuOpenDelegate>? TitleContextMenuOpenHook { get; }
|
||||
|
||||
private delegate byte ContextMenuItemSelectedInternalDelegate(IntPtr addon, int index, byte a3);
|
||||
|
||||
private Hook<ContextMenuItemSelectedInternalDelegate>? ContextMenuItemSelectedHook { get; }
|
||||
|
||||
private delegate byte SetUpContextSubMenuDelegate(IntPtr agent);
|
||||
|
||||
private readonly SetUpContextSubMenuDelegate _setUpContextSubMenu = null!;
|
||||
|
||||
private delegate IntPtr SetUpInventoryContextSubMenuDelegate(IntPtr agent, byte hasTitle, byte zero);
|
||||
|
||||
private readonly SetUpInventoryContextSubMenuDelegate _setUpInventoryContextSubMenu = null!;
|
||||
|
||||
private delegate byte ContextMenuEvent66Delegate(IntPtr agent);
|
||||
|
||||
private Hook<ContextMenuEvent66Delegate>? ContextMenuEvent66Hook { get; }
|
||||
|
||||
private delegate void InventoryContextMenuEvent30Delegate(IntPtr agent, IntPtr a2, int a3, int a4, short a5);
|
||||
|
||||
private Hook<InventoryContextMenuEvent30Delegate>? InventoryContextMenuEvent30Hook { get; }
|
||||
|
||||
private unsafe delegate void AtkValueChangeTypeDelegate(AtkValue* thisPtr, ValueType type);
|
||||
|
||||
private readonly AtkValueChangeTypeDelegate _atkValueChangeType = null!;
|
||||
|
||||
private unsafe delegate void AtkValueSetStringDelegate(AtkValue* thisPtr, byte* bytes);
|
||||
|
||||
private readonly AtkValueSetStringDelegate _atkValueSetString = null!;
|
||||
|
||||
private GameFunctions Functions { get; }
|
||||
private ClientLanguage Language { get; }
|
||||
private IntPtr Agent { get; set; } = IntPtr.Zero;
|
||||
private List<BaseContextMenuItem> Items { get; } = new();
|
||||
private int NormalSize { get; set; }
|
||||
|
||||
internal ContextMenu(GameFunctions functions, SigScanner scanner, ClientLanguage language, Hooks hooks) {
|
||||
this.Functions = functions;
|
||||
this.Language = language;
|
||||
|
||||
if (!hooks.HasFlag(Hooks.ContextMenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.AtkValueChangeType, out var changeTypePtr, "Context Menu (change type)")) {
|
||||
this._atkValueChangeType = Marshal.GetDelegateForFunctionPointer<AtkValueChangeTypeDelegate>(changeTypePtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.AtkValueSetString, out var setStringPtr, "Context Menu (set string)")) {
|
||||
this._atkValueSetString = Marshal.GetDelegateForFunctionPointer<AtkValueSetStringDelegate>(setStringPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.GetAddonByInternalId, out var getAddonPtr, "Context Menu (get addon)")) {
|
||||
this._getAddonByInternalId = Marshal.GetDelegateForFunctionPointer<GetAddonByInternalIdDelegate>(getAddonPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.SetUpContextSubMenu, out var setUpSubPtr, "Context Menu (set up submenu)")) {
|
||||
this._setUpContextSubMenu = Marshal.GetDelegateForFunctionPointer<SetUpContextSubMenuDelegate>(setUpSubPtr);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: uncomment when inv submenus
|
||||
// if (scanner.TryScanText(Signatures.SetUpInventoryContextSubMenu, out var setUpInvSubPtr, "Context Menu (set up inventory submenu)")) {
|
||||
// this._setUpInventoryContextSubMenu = Marshal.GetDelegateForFunctionPointer<SetUpInventoryContextSubMenuDelegate>(setUpInvSubPtr);
|
||||
// } else {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (scanner.TryScanText(Signatures.SomeOpenAddonThing, out var thingPtr, "Context Menu (some OpenAddon thing)")) {
|
||||
this.SomeOpenAddonThingHook = new Hook<SomeOpenAddonThingDelegate>(thingPtr, this.SomeOpenAddonThingDetour);
|
||||
this.SomeOpenAddonThingHook.Enable();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.ContextMenuOpen, out var openPtr, "Context Menu open")) {
|
||||
unsafe {
|
||||
this.ContextMenuOpenHook = new Hook<ContextMenuOpenDelegate>(openPtr, this.OpenMenuDetour);
|
||||
}
|
||||
|
||||
this.ContextMenuOpenHook.Enable();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.ContextMenuSelected, out var selectedPtr, "Context Menu selected")) {
|
||||
this.ContextMenuItemSelectedHook = new Hook<ContextMenuItemSelectedInternalDelegate>(selectedPtr, this.ItemSelectedDetour);
|
||||
this.ContextMenuItemSelectedHook.Enable();
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.TitleContextMenuOpen, out var titleOpenPtr, "Context Menu (title menu open)")) {
|
||||
unsafe {
|
||||
this.TitleContextMenuOpenHook = new Hook<ContextMenuOpenDelegate>(titleOpenPtr, this.TitleContextMenuOpenDetour);
|
||||
}
|
||||
|
||||
this.TitleContextMenuOpenHook.Enable();
|
||||
}
|
||||
|
||||
if (scanner.TryScanText(Signatures.ContextMenuEvent66, out var event66Ptr, "Context Menu (event 66)")) {
|
||||
this.ContextMenuEvent66Hook = new Hook<ContextMenuEvent66Delegate>(event66Ptr, this.ContextMenuEvent66Detour);
|
||||
this.ContextMenuEvent66Hook.Enable();
|
||||
}
|
||||
|
||||
// TODO: uncomment when inv submenus
|
||||
// if (scanner.TryScanText(Signatures.InventoryContextMenuEvent30, out var event30Ptr, "Context Menu (inv event 30)")) {
|
||||
// this.InventoryContextMenuEvent30Hook = new Hook<InventoryContextMenuEvent30Delegate>(event30Ptr, new InventoryContextMenuEvent30Delegate(this.InventoryContextMenuEvent30Detour));
|
||||
// this.InventoryContextMenuEvent30Hook.Enable();
|
||||
// }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
this.SomeOpenAddonThingHook?.Dispose();
|
||||
this.ContextMenuOpenHook?.Dispose();
|
||||
this.TitleContextMenuOpenHook?.Dispose();
|
||||
this.ContextMenuItemSelectedHook?.Dispose();
|
||||
this.ContextMenuEvent66Hook?.Dispose();
|
||||
this.InventoryContextMenuEvent30Hook?.Dispose();
|
||||
}
|
||||
|
||||
private IntPtr SomeOpenAddonThingDetour(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, IntPtr a6, IntPtr a7, ushort a8) {
|
||||
this.Agent = a6;
|
||||
return this.SomeOpenAddonThingHook!.Original(a1, a2, a3, a4, a5, a6, a7, a8);
|
||||
}
|
||||
|
||||
private unsafe byte TitleContextMenuOpenDetour(IntPtr addon, int menuSize, AtkValue* atkValueArgs) {
|
||||
if (this.SubMenuTitle == IntPtr.Zero) {
|
||||
this.Items.Clear();
|
||||
}
|
||||
|
||||
return this.TitleContextMenuOpenHook!.Original(addon, menuSize, atkValueArgs);
|
||||
}
|
||||
|
||||
private enum AgentType {
|
||||
Normal,
|
||||
Inventory,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
private unsafe (AgentType agentType, IntPtr agent) GetContextMenuAgent(IntPtr? agent = null) {
|
||||
agent ??= this.Agent;
|
||||
|
||||
IntPtr GetAgent(AgentId id) {
|
||||
return (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(id);
|
||||
}
|
||||
|
||||
var agentType = AgentType.Unknown;
|
||||
if (agent == GetAgent(AgentId.Context)) {
|
||||
agentType = AgentType.Normal;
|
||||
} else if (agent == GetAgent(AgentId.InventoryContext)) {
|
||||
agentType = AgentType.Inventory;
|
||||
}
|
||||
|
||||
return (agentType, agent.Value);
|
||||
}
|
||||
|
||||
private unsafe string? GetParentAddonName(IntPtr addon) {
|
||||
var parentAddonId = Marshal.ReadInt16(addon + ParentAddonIdOffset);
|
||||
if (parentAddonId == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var stage = AtkStage.GetSingleton();
|
||||
var parentAddon = this._getAddonByInternalId((IntPtr) stage->RaptureAtkUnitManager, parentAddonId);
|
||||
return Encoding.UTF8.GetString(Util.ReadTerminated(parentAddon + 8));
|
||||
}
|
||||
|
||||
private unsafe IntPtr GetAddonFromAgent(IntPtr agent) {
|
||||
var addonId = *(byte*) (agent + 0x20);
|
||||
if (addonId == 0) {
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
var stage = AtkStage.GetSingleton();
|
||||
return this._getAddonByInternalId((IntPtr) stage->RaptureAtkUnitManager, addonId);
|
||||
}
|
||||
|
||||
private unsafe (uint objectId, uint contentIdLower, SeString? text, ushort objectWorld) GetAgentInfo(IntPtr agent) {
|
||||
var objectId = *(uint*) (agent + ObjectIdOffset);
|
||||
var contentIdLower = *(uint*) (agent + ContentIdLowerOffset);
|
||||
var textBytes = Util.ReadTerminated(Marshal.ReadIntPtr(agent + TextPointerOffset));
|
||||
var text = textBytes.Length == 0 ? null : SeString.Parse(textBytes);
|
||||
var objectWorld = *(ushort*) (agent + WorldOffset);
|
||||
return (objectId, contentIdLower, text, objectWorld);
|
||||
}
|
||||
|
||||
private static unsafe (uint itemId, uint itemAmount, bool itemHq) GetInventoryAgentInfo(IntPtr agent) {
|
||||
var itemId = *(uint*) (agent + ItemIdOffset);
|
||||
var itemAmount = *(uint*) (agent + ItemAmountOffset);
|
||||
var itemHq = *(byte*) (agent + ItemHqOffset) == 1;
|
||||
return (itemId, itemAmount, itemHq);
|
||||
}
|
||||
|
||||
[HandleProcessCorruptedStateExceptions]
|
||||
private unsafe byte OpenMenuDetour(IntPtr addon, int menuSize, AtkValue* atkValueArgs) {
|
||||
try {
|
||||
this.OpenMenuDetourInner(addon, ref menuSize, ref atkValueArgs);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in OpenMenuDetour");
|
||||
}
|
||||
|
||||
return this.ContextMenuOpenHook!.Original(addon, menuSize, atkValueArgs);
|
||||
}
|
||||
|
||||
private unsafe AtkValue* ExpandContextMenuArray(IntPtr addon) {
|
||||
const ulong newItemCount = MaxItems * 2 + ContextMenuItemOffset;
|
||||
|
||||
var oldArray = *(AtkValue**) (addon + AddonArrayOffset);
|
||||
var oldArrayItemCount = *(ushort*) (addon + AddonArraySizeOffset);
|
||||
|
||||
// if the array has enough room, don't reallocate
|
||||
if (oldArrayItemCount >= newItemCount) {
|
||||
return oldArray;
|
||||
}
|
||||
|
||||
// reallocate
|
||||
var size = (ulong) sizeof(AtkValue) * newItemCount + 8;
|
||||
var newArray = this.Functions.UiAlloc.Alloc(size);
|
||||
// zero new memory
|
||||
Marshal.Copy(new byte[size], 0, newArray, (int) size);
|
||||
// update size and pointer
|
||||
*(ulong*) newArray = newItemCount;
|
||||
*(void**) (addon + AddonArrayOffset) = (void*) (newArray + 8);
|
||||
*(ushort*) (addon + AddonArraySizeOffset) = (ushort) newItemCount;
|
||||
|
||||
// copy old memory if existing
|
||||
if (oldArray != null) {
|
||||
Buffer.MemoryCopy(oldArray, (void*) (newArray + 8), size, (ulong) sizeof(AtkValue) * oldArrayItemCount);
|
||||
this.Functions.UiAlloc.Free((IntPtr) oldArray - 8);
|
||||
}
|
||||
|
||||
return (AtkValue*) (newArray + 8);
|
||||
}
|
||||
|
||||
private unsafe void OpenMenuDetourInner(IntPtr addon, ref int menuSize, ref AtkValue* atkValueArgs) {
|
||||
this.Items.Clear();
|
||||
this.FreeSubMenuTitle();
|
||||
|
||||
var (agentType, agent) = this.GetContextMenuAgent();
|
||||
if (agent == IntPtr.Zero) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (agentType == AgentType.Unknown) {
|
||||
return;
|
||||
}
|
||||
|
||||
atkValueArgs = this.ExpandContextMenuArray(addon);
|
||||
|
||||
var inventory = agentType == AgentType.Inventory;
|
||||
var offset = ContextMenuItemOffset + (inventory ? 0 : *(long*) (agent + BooleanOffsetCheck) != 0 ? 1 : 0);
|
||||
|
||||
this.NormalSize = (int) (&atkValueArgs[0])->UInt;
|
||||
|
||||
// idx 3 is bitmask of indices that are submenus
|
||||
var submenuArg = &atkValueArgs[3];
|
||||
var submenus = (int) submenuArg->UInt;
|
||||
|
||||
var hasGameDisabled = menuSize - offset - this.NormalSize > 0;
|
||||
|
||||
var menuActions = inventory
|
||||
? (byte*) (agent + InventoryMenuActionsOffset)
|
||||
: (byte*) (Marshal.ReadIntPtr(agent + MenuActionsPointerOffset) + MenuActionsOffset);
|
||||
|
||||
var nativeItems = new List<NativeContextMenuItem>();
|
||||
for (var i = 0; i < this.NormalSize; i++) {
|
||||
var atkItem = &atkValueArgs[offset + i];
|
||||
|
||||
var name = Util.ReadSeString((IntPtr) atkItem->String);
|
||||
|
||||
var enabled = true;
|
||||
if (hasGameDisabled) {
|
||||
var disabledItem = &atkValueArgs[offset + this.NormalSize + i];
|
||||
enabled = disabledItem->Int == 0;
|
||||
}
|
||||
|
||||
var action = *(menuActions + offset + i);
|
||||
|
||||
var isSubMenu = (submenus & (1 << i)) > 0;
|
||||
|
||||
nativeItems.Add(new NativeContextMenuItem(action, name, enabled, isSubMenu));
|
||||
}
|
||||
|
||||
if (this.PopulateItems(addon, agent, this.OpenContextMenu, this.OpenInventoryContextMenu, nativeItems)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hasCustomDisabled = this.Items.Any(item => !item.Enabled);
|
||||
var hasAnyDisabled = hasGameDisabled || hasCustomDisabled;
|
||||
|
||||
// clear all submenu flags
|
||||
submenuArg->UInt = 0;
|
||||
|
||||
for (var i = 0; i < this.Items.Count; i++) {
|
||||
var item = this.Items[i];
|
||||
|
||||
if (hasAnyDisabled) {
|
||||
var disabledArg = &atkValueArgs[offset + this.Items.Count + i];
|
||||
this._atkValueChangeType(disabledArg, ValueType.Int);
|
||||
disabledArg->Int = item.Enabled ? 0 : 1;
|
||||
}
|
||||
|
||||
// set up the agent to take the appropriate action for this item
|
||||
*(menuActions + offset + i) = item switch {
|
||||
NativeContextMenuItem nativeItem => nativeItem.InternalAction,
|
||||
NormalContextSubMenuItem => ContextSubId,
|
||||
// TODO: uncomment when inv submenus
|
||||
// InventoryContextSubMenuItem => InventoryContextSubId,
|
||||
_ => inventory ? InventoryNoopContextId : NoopContextId,
|
||||
};
|
||||
|
||||
// set submenu flag
|
||||
if (item.IsSubMenu) {
|
||||
submenuArg->UInt |= (uint) (1 << i);
|
||||
}
|
||||
|
||||
// set up the menu item
|
||||
var newItem = &atkValueArgs[offset + i];
|
||||
this._atkValueChangeType(newItem, ValueType.String);
|
||||
|
||||
var name = this.GetItemName(item);
|
||||
fixed (byte* nameBytesPtr = name.Encode().Terminate()) {
|
||||
this._atkValueSetString(newItem, nameBytesPtr);
|
||||
}
|
||||
}
|
||||
|
||||
(&atkValueArgs[0])->UInt = (uint) this.Items.Count;
|
||||
|
||||
menuSize = (int) (&atkValueArgs[0])->UInt;
|
||||
if (hasAnyDisabled) {
|
||||
menuSize *= 2;
|
||||
}
|
||||
|
||||
menuSize += offset;
|
||||
}
|
||||
|
||||
/// <returns>true on error</returns>
|
||||
private bool PopulateItems(IntPtr addon, IntPtr agent, ContextMenuOpenEventDelegate? normalAction, InventoryContextMenuOpenEventDelegate? inventoryAction, IReadOnlyCollection<NativeContextMenuItem>? nativeItems = null) {
|
||||
var (agentType, _) = this.GetContextMenuAgent(agent);
|
||||
if (agentType == AgentType.Unknown) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var inventory = agentType == AgentType.Inventory;
|
||||
var parentAddonName = this.GetParentAddonName(addon);
|
||||
|
||||
if (inventory) {
|
||||
var info = GetInventoryAgentInfo(agent);
|
||||
|
||||
var args = new InventoryContextMenuOpenArgs(
|
||||
addon,
|
||||
agent,
|
||||
parentAddonName,
|
||||
info.itemId,
|
||||
info.itemAmount,
|
||||
info.itemHq
|
||||
);
|
||||
if (nativeItems != null) {
|
||||
args.Items.AddRange(nativeItems);
|
||||
}
|
||||
|
||||
try {
|
||||
inventoryAction?.Invoke(args);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in OpenMenuDetour");
|
||||
return true;
|
||||
}
|
||||
|
||||
// remove any NormalContextMenuItems that may have been added - these will crash the game
|
||||
args.Items.RemoveAll(item => item is NormalContextMenuItem);
|
||||
|
||||
// set the agent of any remaining custom items
|
||||
foreach (var item in args.Items) {
|
||||
switch (item) {
|
||||
case InventoryContextMenuItem custom:
|
||||
custom.Agent = agent;
|
||||
break;
|
||||
case InventoryContextSubMenuItem custom:
|
||||
custom.Agent = agent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.Items.AddRange(args.Items);
|
||||
} else {
|
||||
var info = this.GetAgentInfo(agent);
|
||||
|
||||
var args = new ContextMenuOpenArgs(
|
||||
addon,
|
||||
agent,
|
||||
parentAddonName,
|
||||
info.objectId,
|
||||
info.contentIdLower,
|
||||
info.text,
|
||||
info.objectWorld
|
||||
);
|
||||
if (nativeItems != null) {
|
||||
args.Items.AddRange(nativeItems);
|
||||
}
|
||||
|
||||
try {
|
||||
normalAction?.Invoke(args);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in OpenMenuDetour");
|
||||
return true;
|
||||
}
|
||||
|
||||
// remove any InventoryContextMenuItems that may have been added - these will crash the game
|
||||
args.Items.RemoveAll(item => item is InventoryContextMenuItem);
|
||||
|
||||
// set the agent of any remaining custom items
|
||||
foreach (var item in args.Items) {
|
||||
switch (item) {
|
||||
case NormalContextMenuItem custom:
|
||||
custom.Agent = agent;
|
||||
break;
|
||||
case NormalContextSubMenuItem custom:
|
||||
custom.Agent = agent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.Items.AddRange(args.Items);
|
||||
}
|
||||
|
||||
if (this.Items.Count > MaxItems) {
|
||||
var toRemove = this.Items.Count - MaxItems;
|
||||
this.Items.RemoveRange(MaxItems, toRemove);
|
||||
Logger.LogWarning($"Context menu item limit ({MaxItems}) exceeded. Removing {toRemove} item(s).");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private SeString GetItemName(BaseContextMenuItem item) {
|
||||
return item switch {
|
||||
NormalContextMenuItem custom => this.Language switch {
|
||||
ClientLanguage.Japanese => custom.NameJapanese,
|
||||
ClientLanguage.English => custom.NameEnglish,
|
||||
ClientLanguage.German => custom.NameGerman,
|
||||
ClientLanguage.French => custom.NameFrench,
|
||||
_ => custom.NameEnglish,
|
||||
},
|
||||
InventoryContextMenuItem custom => this.Language switch {
|
||||
ClientLanguage.Japanese => custom.NameJapanese,
|
||||
ClientLanguage.English => custom.NameEnglish,
|
||||
ClientLanguage.German => custom.NameGerman,
|
||||
ClientLanguage.French => custom.NameFrench,
|
||||
_ => custom.NameEnglish,
|
||||
},
|
||||
NormalContextSubMenuItem custom => this.Language switch {
|
||||
ClientLanguage.Japanese => custom.NameJapanese,
|
||||
ClientLanguage.English => custom.NameEnglish,
|
||||
ClientLanguage.German => custom.NameGerman,
|
||||
ClientLanguage.French => custom.NameFrench,
|
||||
_ => custom.NameEnglish,
|
||||
},
|
||||
InventoryContextSubMenuItem custom => this.Language switch {
|
||||
ClientLanguage.Japanese => custom.NameJapanese,
|
||||
ClientLanguage.English => custom.NameEnglish,
|
||||
ClientLanguage.German => custom.NameGerman,
|
||||
ClientLanguage.French => custom.NameFrench,
|
||||
_ => custom.NameEnglish,
|
||||
},
|
||||
NativeContextMenuItem native => native.Name,
|
||||
_ => "Invalid context menu item",
|
||||
};
|
||||
}
|
||||
|
||||
private BaseContextMenuItem? SubMenuItem { get; set; }
|
||||
|
||||
private byte ItemSelectedDetour(IntPtr addon, int index, byte a3) {
|
||||
this.FreeSubMenuTitle();
|
||||
|
||||
if (index < 0 || index >= this.Items.Count) {
|
||||
goto Original;
|
||||
}
|
||||
|
||||
var item = this.Items[index];
|
||||
switch (item) {
|
||||
case NormalContextSubMenuItem sub: {
|
||||
this.SubMenuItem = sub;
|
||||
break;
|
||||
}
|
||||
case InventoryContextSubMenuItem sub: {
|
||||
this.SubMenuItem = sub;
|
||||
break;
|
||||
}
|
||||
case NormalContextMenuItem custom: {
|
||||
var addonName = this.GetParentAddonName(addon);
|
||||
var info = this.GetAgentInfo(custom.Agent);
|
||||
|
||||
var args = new ContextMenuItemSelectedArgs(
|
||||
addon,
|
||||
custom.Agent,
|
||||
addonName,
|
||||
info.objectId,
|
||||
info.contentIdLower,
|
||||
info.text,
|
||||
info.objectWorld
|
||||
);
|
||||
|
||||
try {
|
||||
custom.Action(args);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in custom context menu item");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case InventoryContextMenuItem custom: {
|
||||
var addonName = this.GetParentAddonName(addon);
|
||||
var info = GetInventoryAgentInfo(custom.Agent);
|
||||
|
||||
var args = new InventoryContextMenuItemSelectedArgs(
|
||||
addon,
|
||||
custom.Agent,
|
||||
addonName,
|
||||
info.itemId,
|
||||
info.itemAmount,
|
||||
info.itemHq
|
||||
);
|
||||
|
||||
try {
|
||||
custom.Action(args);
|
||||
} catch (Exception ex) {
|
||||
Logger.LogError(ex, "Exception in custom context menu item");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Original:
|
||||
return this.ContextMenuItemSelectedHook!.Original(addon, index, a3);
|
||||
}
|
||||
|
||||
private IntPtr SubMenuTitle { get; set; } = IntPtr.Zero;
|
||||
|
||||
private void FreeSubMenuTitle() {
|
||||
if (this.SubMenuTitle == IntPtr.Zero) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.Functions.UiAlloc.Free(this.SubMenuTitle);
|
||||
this.SubMenuTitle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <returns>false if original should be called</returns>
|
||||
private unsafe bool SubMenuInner(IntPtr agent) {
|
||||
if (this.SubMenuItem == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var subMenuItem = this.SubMenuItem;
|
||||
this.SubMenuItem = null;
|
||||
|
||||
// free our workaround pointer
|
||||
this.FreeSubMenuTitle();
|
||||
|
||||
this.Items.Clear();
|
||||
|
||||
var name = this.GetItemName(subMenuItem);
|
||||
|
||||
// Since the game checks the agent's AtkValue array for the submenu title, and since we
|
||||
// don't update that array, we need to work around this check.
|
||||
// First, we will convince the game to make the submenu title pointer null by telling it
|
||||
// that an invalid index was selected.
|
||||
// Second, we will replace the null pointer with our own pointer.
|
||||
// Third, we will restore the original selected index.
|
||||
|
||||
// step 1
|
||||
var selectedIdx = (byte*) (agent + 0x670);
|
||||
var wasSelected = *selectedIdx;
|
||||
*selectedIdx = 0xFF;
|
||||
this._setUpContextSubMenu(agent);
|
||||
|
||||
// step 2 (see SetUpContextSubMenu)
|
||||
var nameBytes = name.Encode().Terminate();
|
||||
this.SubMenuTitle = this.Functions.UiAlloc.Alloc((ulong) nameBytes.Length);
|
||||
Marshal.Copy(nameBytes, 0, this.SubMenuTitle, nameBytes.Length);
|
||||
var v10 = agent + 0x678 * *(byte*) (agent + 0x1740) + 0x28;
|
||||
*(byte**) (v10 + 0x668) = (byte*) this.SubMenuTitle;
|
||||
|
||||
// step 3
|
||||
*selectedIdx = wasSelected;
|
||||
|
||||
var secondaryArgsPtr = Marshal.ReadIntPtr(agent + MenuActionsPointerOffset);
|
||||
var submenuArgs = (AtkValue*) (secondaryArgsPtr + 8);
|
||||
|
||||
var addon = this.GetAddonFromAgent(agent);
|
||||
var normalAction = (subMenuItem as NormalContextSubMenuItem)?.Action;
|
||||
var inventoryAction = (subMenuItem as InventoryContextSubMenuItem)?.Action;
|
||||
if (this.PopulateItems(addon, agent, normalAction, inventoryAction)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var booleanOffset = *(long*) (agent + *(byte*) (agent + 0x1740) * 0x678 + 0x690) != 0 ? 1 : 0;
|
||||
|
||||
for (var i = 0; i < this.Items.Count; i++) {
|
||||
var item = this.Items[i];
|
||||
|
||||
*(ushort*) secondaryArgsPtr += 1;
|
||||
var arg = &submenuArgs[ContextMenuItemOffset + i + 1];
|
||||
this._atkValueChangeType(arg, ValueType.String);
|
||||
var itemName = this.GetItemName(item);
|
||||
fixed (byte* namePtr = itemName.Encode().Terminate()) {
|
||||
this._atkValueSetString(arg, namePtr);
|
||||
}
|
||||
|
||||
// set action to no-op
|
||||
*(byte*) (secondaryArgsPtr + booleanOffset + i + ContextMenuItemOffset + 0x428) = NoopContextId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private byte ContextMenuEvent66Detour(IntPtr agent) {
|
||||
return this.SubMenuInner(agent) ? (byte) 0 : this.ContextMenuEvent66Hook!.Original(agent);
|
||||
}
|
||||
|
||||
private void InventoryContextMenuEvent30Detour(IntPtr agent, IntPtr a2, int a3, int a4, short a5) {
|
||||
if (!this.SubMenuInner(agent)) {
|
||||
this.InventoryContextMenuEvent30Hook!.Original(agent, a2, a3, a4, a5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using System;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu {
|
||||
/// <summary>
|
||||
/// Arguments for the context menu item selected delegate.
|
||||
/// </summary>
|
||||
public class ContextMenuItemSelectedArgs : BaseContextMenuArgs {
|
||||
internal ContextMenuItemSelectedArgs(IntPtr addon, IntPtr agent, string? parentAddonName, uint objectId, uint contentIdLower, SeString? text, ushort objectWorld) : base(addon, agent, parentAddonName, objectId, contentIdLower, text, objectWorld) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu {
|
||||
/// <summary>
|
||||
/// Arguments for the context menu event.
|
||||
/// </summary>
|
||||
public class ContextMenuOpenArgs : BaseContextMenuArgs {
|
||||
/// <summary>
|
||||
/// Context menu items in this menu.
|
||||
/// </summary>
|
||||
public List<BaseContextMenuItem> Items { get; } = new();
|
||||
|
||||
internal ContextMenuOpenArgs(IntPtr addon, IntPtr agent, string? parentAddonName, uint objectId, uint contentIdLower, SeString? text, ushort objectWorld) : base(addon, agent, parentAddonName, objectId, contentIdLower, text, objectWorld) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
using System;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu {
|
||||
/// <summary>
|
||||
/// A custom context menu item
|
||||
/// </summary>
|
||||
public abstract class CustomContextMenuItem<T> : BaseContextMenuItem
|
||||
where T : Delegate {
|
||||
internal IntPtr Agent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the context item to be shown for English clients.
|
||||
/// </summary>
|
||||
public SeString NameEnglish { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the context item to be shown for Japanese clients.
|
||||
/// </summary>
|
||||
public SeString NameJapanese { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the context item to be shown for French clients.
|
||||
/// </summary>
|
||||
public SeString NameFrench { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the context item to be shown for German clients.
|
||||
/// </summary>
|
||||
public SeString NameGerman { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The action to perform when this item is clicked.
|
||||
/// </summary>
|
||||
public T Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new context menu item.
|
||||
/// </summary>
|
||||
/// <param name="name">the English name of the item, copied to other languages</param>
|
||||
/// <param name="action">the action to perform on click</param>
|
||||
internal CustomContextMenuItem(SeString name, T action) {
|
||||
this.NameEnglish = name;
|
||||
this.NameJapanese = name;
|
||||
this.NameFrench = name;
|
||||
this.NameGerman = name;
|
||||
|
||||
this.Action = action;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu.Inventory {
|
||||
/// <summary>
|
||||
/// The base class for inventory context menu arguments
|
||||
/// </summary>
|
||||
public abstract class BaseInventoryContextMenuArgs {
|
||||
/// <summary>
|
||||
/// Pointer to the context menu addon.
|
||||
/// </summary>
|
||||
public IntPtr Addon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the context menu agent.
|
||||
/// </summary>
|
||||
public IntPtr Agent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the addon containing this context menu, if any.
|
||||
/// </summary>
|
||||
public string? ParentAddonName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the item this context menu is for.
|
||||
/// </summary>
|
||||
public uint ItemId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of the item this context menu is for.
|
||||
/// </summary>
|
||||
public uint ItemAmount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the item this context menu is for is high-quality.
|
||||
/// </summary>
|
||||
public bool ItemHq { get; }
|
||||
|
||||
internal BaseInventoryContextMenuArgs(IntPtr addon, IntPtr agent, string? parentAddonName, uint itemId, uint itemAmount, bool itemHq) {
|
||||
this.Addon = addon;
|
||||
this.Agent = agent;
|
||||
this.ParentAddonName = parentAddonName;
|
||||
this.ItemId = itemId;
|
||||
this.ItemAmount = itemAmount;
|
||||
this.ItemHq = itemHq;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu.Inventory {
|
||||
/// <summary>
|
||||
/// A custom context menu item for inventory items.
|
||||
/// </summary>
|
||||
public class InventoryContextMenuItem : CustomContextMenuItem<ContextMenu.InventoryContextMenuItemSelectedDelegate> {
|
||||
/// <summary>
|
||||
/// Create a new context menu item for inventory items.
|
||||
/// </summary>
|
||||
/// <param name="name">the English name of the item, copied to other languages</param>
|
||||
/// <param name="action">the action to perform on click</param>
|
||||
public InventoryContextMenuItem(SeString name, ContextMenu.InventoryContextMenuItemSelectedDelegate action) : base(name, action) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu.Inventory {
|
||||
/// <summary>
|
||||
/// The arguments for when an inventory context menu item is selected
|
||||
/// </summary>
|
||||
public class InventoryContextMenuItemSelectedArgs : BaseInventoryContextMenuArgs {
|
||||
internal InventoryContextMenuItemSelectedArgs(IntPtr addon, IntPtr agent, string? parentAddonName, uint itemId, uint itemAmount, bool itemHq) : base(addon, agent, parentAddonName, itemId, itemAmount, itemHq) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu.Inventory {
|
||||
/// <summary>
|
||||
/// The arguments for when an inventory context menu is opened
|
||||
/// </summary>
|
||||
public class InventoryContextMenuOpenArgs : BaseInventoryContextMenuArgs {
|
||||
/// <summary>
|
||||
/// Context menu items in this menu.
|
||||
/// </summary>
|
||||
public List<BaseContextMenuItem> Items { get; } = new();
|
||||
|
||||
internal InventoryContextMenuOpenArgs(IntPtr addon, IntPtr agent, string? parentAddonName, uint itemId, uint itemAmount, bool itemHq) : base(addon, agent, parentAddonName, itemId, itemAmount, itemHq) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
using System;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu.Inventory {
|
||||
/// <summary>
|
||||
/// A custom inventory context menu item that will open a submenu
|
||||
/// </summary>
|
||||
public class InventoryContextSubMenuItem : CustomContextMenuItem<ContextMenu.InventoryContextMenuOpenEventDelegate> {
|
||||
/// <summary>
|
||||
/// Create a new context menu item for inventory items that will open a submenu.
|
||||
/// </summary>
|
||||
/// <param name="name">the English name of the item, copied to other languages</param>
|
||||
/// <param name="action">the action to perform on click</param>
|
||||
[Obsolete("Inventory context submenus do not work yet", true)]
|
||||
public InventoryContextSubMenuItem(SeString name, ContextMenu.InventoryContextMenuOpenEventDelegate action) : base(name, action) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu {
|
||||
/// <summary>
|
||||
/// A native context menu item
|
||||
/// </summary>
|
||||
public sealed class NativeContextMenuItem : BaseContextMenuItem {
|
||||
/// <summary>
|
||||
/// The action code to be used in the context menu agent for this item.
|
||||
/// </summary>
|
||||
public byte InternalAction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the context item.
|
||||
/// </summary>
|
||||
public SeString Name { get; set; }
|
||||
|
||||
internal NativeContextMenuItem(byte action, SeString name, bool enabled, bool isSubMenu) {
|
||||
this.Name = name;
|
||||
this.InternalAction = action;
|
||||
this.Enabled = enabled;
|
||||
this.IsSubMenu = isSubMenu;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu {
|
||||
/// <summary>
|
||||
/// A custom normal context menu item
|
||||
/// </summary>
|
||||
public class NormalContextMenuItem : CustomContextMenuItem<ContextMenu.ContextMenuItemSelectedDelegate> {
|
||||
/// <summary>
|
||||
/// Create a new custom context menu item.
|
||||
/// </summary>
|
||||
/// <param name="name">the English name of the item, copied to other languages</param>
|
||||
/// <param name="action">the action to perform on click</param>
|
||||
public NormalContextMenuItem(SeString name, ContextMenu.ContextMenuItemSelectedDelegate action) : base(name, action) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace XivCommon.Functions.ContextMenu {
|
||||
/// <summary>
|
||||
/// A custom context menu item that will open a submenu
|
||||
/// </summary>
|
||||
public class NormalContextSubMenuItem : CustomContextMenuItem<ContextMenu.ContextMenuOpenEventDelegate> {
|
||||
/// <summary>
|
||||
/// Create a new custom context menu item that will open a submenu.
|
||||
/// </summary>
|
||||
/// <param name="name">the English name of the item, copied to other languages</param>
|
||||
/// <param name="action">the action to perform on click</param>
|
||||
public NormalContextSubMenuItem(SeString name, ContextMenu.ContextMenuOpenEventDelegate action) : base(name, action) {
|
||||
this.IsSubMenu = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
using System;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Gui.PartyFinder;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using XivCommon.Functions;
|
||||
using XivCommon.Functions.ContextMenu;
|
||||
using XivCommon.Functions.FriendList;
|
||||
using XivCommon.Functions.Housing;
|
||||
using XivCommon.Functions.NamePlates;
|
||||
|
@ -55,11 +53,6 @@ namespace XivCommon {
|
|||
/// </summary>
|
||||
public ChatBubbles ChatBubbles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Context menu functions
|
||||
/// </summary>
|
||||
public ContextMenu ContextMenu { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip events
|
||||
/// </summary>
|
||||
|
@ -94,7 +87,6 @@ namespace XivCommon {
|
|||
this.Framework = Util.GetService<Dalamud.Game.Framework>();
|
||||
this.GameGui = Util.GetService<GameGui>();
|
||||
|
||||
var clientState = Util.GetService<ClientState>();
|
||||
var objectTable = Util.GetService<ObjectTable>();
|
||||
var partyFinderGui = Util.GetService<PartyFinderGui>();
|
||||
var scanner = Util.GetService<SigScanner>();
|
||||
|
@ -106,7 +98,6 @@ namespace XivCommon {
|
|||
this.Examine = new Examine(scanner);
|
||||
this.Talk = new Talk(scanner, hooks.HasFlag(Hooks.Talk));
|
||||
this.ChatBubbles = new ChatBubbles(objectTable, scanner, hooks.HasFlag(Hooks.ChatBubbles));
|
||||
this.ContextMenu = new ContextMenu(this, scanner, clientState.ClientLanguage, hooks);
|
||||
this.Tooltips = new Tooltips(scanner, this.GameGui, hooks.HasFlag(Hooks.Tooltips));
|
||||
this.NamePlates = new NamePlates(this, scanner, hooks.HasFlag(Hooks.NamePlates));
|
||||
this.DutyFinder = new DutyFinder(scanner);
|
||||
|
@ -119,7 +110,6 @@ namespace XivCommon {
|
|||
public void Dispose() {
|
||||
this.NamePlates.Dispose();
|
||||
this.Tooltips.Dispose();
|
||||
this.ContextMenu.Dispose();
|
||||
this.ChatBubbles.Dispose();
|
||||
this.Talk.Dispose();
|
||||
this.BattleTalk.Dispose();
|
||||
|
|
|
@ -58,12 +58,7 @@ namespace XivCommon {
|
|||
/// </summary>
|
||||
ChatBubbles = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// The context menu hooks.
|
||||
///
|
||||
/// This hook is used in order to enable context menu functions.
|
||||
/// </summary>
|
||||
ContextMenu = 1 << 6,
|
||||
// 1 << 6 used to be ContextMenu
|
||||
|
||||
/// <summary>
|
||||
/// The name plate hooks.
|
||||
|
|
Loading…
Reference in New Issue