mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +01:00
chore: remove Dalamud ContextMenu API
...to open up the chance for someone else to redo it in the future
This commit is contained in:
parent
bdd7f32c12
commit
e05b9e20f8
19 changed files with 15 additions and 1562 deletions
|
|
@ -1,463 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Dalamud.Configuration.Internal;
|
|
||||||
using Dalamud.Game.Gui.ContextMenus.OldStructs;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Dalamud.IoC;
|
|
||||||
using Dalamud.IoC.Internal;
|
|
||||||
using Dalamud.Logging;
|
|
||||||
using Dalamud.Memory;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides an interface to modify context menus.
|
|
||||||
/// </summary>
|
|
||||||
[PluginInterface]
|
|
||||||
[InterfaceVersion("1.0")]
|
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
|
||||||
public sealed class ContextMenu : IDisposable, IServiceType
|
|
||||||
{
|
|
||||||
private const int MaxContextMenuItemsPerContextMenu = 32;
|
|
||||||
|
|
||||||
private readonly OpenSubContextMenuDelegate? openSubContextMenu;
|
|
||||||
|
|
||||||
#region Hooks
|
|
||||||
|
|
||||||
private readonly Hook<ContextMenuOpenedDelegate> contextMenuOpenedHook;
|
|
||||||
private readonly Hook<ContextMenuOpenedDelegate> subContextMenuOpenedHook;
|
|
||||||
private readonly Hook<ContextMenuItemSelectedDelegate> contextMenuItemSelectedHook;
|
|
||||||
private readonly Hook<ContextMenuOpeningDelegate> contextMenuOpeningHook;
|
|
||||||
private readonly Hook<SubContextMenuOpeningDelegate> subContextMenuOpeningHook;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private unsafe OldAgentContextInterface* currentAgentContextInterface;
|
|
||||||
|
|
||||||
private IntPtr currentSubContextMenuTitle;
|
|
||||||
|
|
||||||
private OpenSubContextMenuItem? selectedOpenSubContextMenuItem;
|
|
||||||
private ContextMenuOpenedArgs? currentContextMenuOpenedArgs;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
private ContextMenu(SigScanner sigScanner)
|
|
||||||
{
|
|
||||||
this.Address = new ContextMenuAddressResolver();
|
|
||||||
this.Address.Setup(sigScanner);
|
|
||||||
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
this.openSubContextMenu = Marshal.GetDelegateForFunctionPointer<OpenSubContextMenuDelegate>(this.Address.OpenSubContextMenuPtr);
|
|
||||||
|
|
||||||
this.contextMenuOpeningHook = Hook<ContextMenuOpeningDelegate>.FromAddress(this.Address.ContextMenuOpeningPtr, this.ContextMenuOpeningDetour);
|
|
||||||
this.contextMenuOpenedHook = Hook<ContextMenuOpenedDelegate>.FromAddress(this.Address.ContextMenuOpenedPtr, this.ContextMenuOpenedDetour);
|
|
||||||
this.contextMenuItemSelectedHook = Hook<ContextMenuItemSelectedDelegate>.FromAddress(this.Address.ContextMenuItemSelectedPtr, this.ContextMenuItemSelectedDetour);
|
|
||||||
this.subContextMenuOpeningHook = Hook<SubContextMenuOpeningDelegate>.FromAddress(this.Address.SubContextMenuOpeningPtr, this.SubContextMenuOpeningDetour);
|
|
||||||
this.subContextMenuOpenedHook = Hook<ContextMenuOpenedDelegate>.FromAddress(this.Address.SubContextMenuOpenedPtr, this.SubContextMenuOpenedDetour);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Delegates
|
|
||||||
|
|
||||||
private unsafe delegate bool OpenSubContextMenuDelegate(OldAgentContext* agentContext);
|
|
||||||
|
|
||||||
private unsafe delegate IntPtr ContextMenuOpeningDelegate(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, OldAgentContextInterface* agentContextInterface, IntPtr a7, ushort a8);
|
|
||||||
|
|
||||||
private unsafe delegate bool ContextMenuOpenedDelegate(AddonContextMenu* addonContextMenu, int menuSize, AtkValue* atkValueArgs);
|
|
||||||
|
|
||||||
private unsafe delegate bool ContextMenuItemSelectedDelegate(AddonContextMenu* addonContextMenu, int selectedIndex, byte a3);
|
|
||||||
|
|
||||||
private unsafe delegate bool SubContextMenuOpeningDelegate(OldAgentContext* agentContext);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when a context menu is opened by the game.
|
|
||||||
/// </summary>
|
|
||||||
public event ContextMenus.ContextMenuOpenedDelegate? ContextMenuOpened;
|
|
||||||
|
|
||||||
private ContextMenuAddressResolver Address { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
void IDisposable.Dispose()
|
|
||||||
{
|
|
||||||
this.subContextMenuOpeningHook.Disable();
|
|
||||||
this.contextMenuItemSelectedHook.Disable();
|
|
||||||
this.subContextMenuOpenedHook.Disable();
|
|
||||||
this.contextMenuOpenedHook.Disable();
|
|
||||||
this.contextMenuOpeningHook.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static unsafe bool IsInventoryContext(OldAgentContextInterface* agentContextInterface)
|
|
||||||
{
|
|
||||||
return agentContextInterface == AgentInventoryContext.Instance();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetContextMenuItemsHashCode(IEnumerable<ContextMenuItem> contextMenuItems)
|
|
||||||
{
|
|
||||||
unchecked
|
|
||||||
{
|
|
||||||
return contextMenuItems.Aggregate(17, (current, item) => (current * 23) + item.GetHashCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
|
||||||
private void ContinueConstruction(GameGui gameGui)
|
|
||||||
{
|
|
||||||
if (!EnvironmentConfiguration.DalamudDoContextMenu)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.contextMenuOpeningHook.Enable();
|
|
||||||
this.contextMenuOpenedHook.Enable();
|
|
||||||
this.contextMenuItemSelectedHook.Enable();
|
|
||||||
this.subContextMenuOpeningHook.Enable();
|
|
||||||
this.subContextMenuOpenedHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe IntPtr ContextMenuOpeningDetour(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, OldAgentContextInterface* agentContextInterface, IntPtr a7, ushort a8)
|
|
||||||
{
|
|
||||||
this.currentAgentContextInterface = agentContextInterface;
|
|
||||||
return this.contextMenuOpeningHook!.Original(a1, a2, a3, a4, a5, agentContextInterface, a7, a8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe bool ContextMenuOpenedDetour(AddonContextMenu* addonContextMenu, int atkValueCount, AtkValue* atkValues)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.ContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
PluginLog.Error(ex, "ContextMenuOpenedDetour");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.contextMenuOpenedHook.Original(addonContextMenu, atkValueCount, atkValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void ContextMenuOpenedImplementation(AddonContextMenu* addonContextMenu, ref int atkValueCount, ref AtkValue* atkValues)
|
|
||||||
{
|
|
||||||
if (this.ContextMenuOpened == null
|
|
||||||
|| this.currentAgentContextInterface == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var contextMenuReaderWriter = new ContextMenuReaderWriter(this.currentAgentContextInterface, atkValueCount, atkValues);
|
|
||||||
|
|
||||||
// Check for a title.
|
|
||||||
string? title = null;
|
|
||||||
if (this.selectedOpenSubContextMenuItem != null)
|
|
||||||
{
|
|
||||||
title = this.selectedOpenSubContextMenuItem.Name.TextValue;
|
|
||||||
|
|
||||||
// Write the custom title
|
|
||||||
var titleAtkValue = &atkValues[1];
|
|
||||||
fixed (byte* titlePtr = this.selectedOpenSubContextMenuItem.Name.Encode().NullTerminate())
|
|
||||||
{
|
|
||||||
titleAtkValue->SetString(titlePtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (contextMenuReaderWriter.Title != null)
|
|
||||||
{
|
|
||||||
title = contextMenuReaderWriter.Title.TextValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine which event to raise.
|
|
||||||
var contextMenuOpenedDelegate = this.ContextMenuOpened;
|
|
||||||
|
|
||||||
// this.selectedOpenSubContextMenuItem is OpenSubContextMenuItem openSubContextMenuItem
|
|
||||||
if (this.selectedOpenSubContextMenuItem != null)
|
|
||||||
{
|
|
||||||
contextMenuOpenedDelegate = this.selectedOpenSubContextMenuItem.Opened;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the existing items from the game.
|
|
||||||
// TODO: For inventory sub context menus, we take only the last item -- the return item.
|
|
||||||
// This is because we're doing a hack to spawn a Second Tier sub context menu and then appropriating it.
|
|
||||||
var contextMenuItems = contextMenuReaderWriter.Read();
|
|
||||||
if (contextMenuItems == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (IsInventoryContext(this.currentAgentContextInterface) && this.selectedOpenSubContextMenuItem != null)
|
|
||||||
{
|
|
||||||
contextMenuItems = contextMenuItems.TakeLast(1).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
var beforeHashCode = GetContextMenuItemsHashCode(contextMenuItems);
|
|
||||||
|
|
||||||
// Raise the event and get the context menu changes.
|
|
||||||
this.currentContextMenuOpenedArgs = this.NotifyContextMenuOpened(addonContextMenu, this.currentAgentContextInterface, title, contextMenuOpenedDelegate, contextMenuItems);
|
|
||||||
if (this.currentContextMenuOpenedArgs == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var afterHashCode = GetContextMenuItemsHashCode(this.currentContextMenuOpenedArgs.Items);
|
|
||||||
|
|
||||||
PluginLog.Warning($"{beforeHashCode}={afterHashCode}");
|
|
||||||
|
|
||||||
// Only write to memory if the items were actually changed.
|
|
||||||
if (beforeHashCode != afterHashCode)
|
|
||||||
{
|
|
||||||
// Write the new changes.
|
|
||||||
contextMenuReaderWriter.Write(this.currentContextMenuOpenedArgs.Items);
|
|
||||||
|
|
||||||
// Update the addon.
|
|
||||||
atkValueCount = *(&addonContextMenu->AtkValuesCount) = (ushort)contextMenuReaderWriter.AtkValueCount;
|
|
||||||
atkValues = *(&addonContextMenu->AtkValues) = contextMenuReaderWriter.AtkValues;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe bool SubContextMenuOpeningDetour(OldAgentContext* agentContext)
|
|
||||||
{
|
|
||||||
return this.SubContextMenuOpeningImplementation(agentContext) || this.subContextMenuOpeningHook.Original(agentContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe bool SubContextMenuOpeningImplementation(OldAgentContext* agentContext)
|
|
||||||
{
|
|
||||||
if (this.openSubContextMenu == null || this.selectedOpenSubContextMenuItem == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The important things to make this work are:
|
|
||||||
// 1. Allocate a temporary sub context menu title. The value doesn't matter, we'll set it later.
|
|
||||||
// 2. Context menu item count must equal 1 to tell the game there is enough space for the "< Return" item.
|
|
||||||
// 3. Atk value count must equal the index of the first context menu item.
|
|
||||||
// This is enough to keep the base data, but excludes the context menu item data.
|
|
||||||
// We want to exclude context menu item data in this function because the game sometimes includes garbage items which can cause problems.
|
|
||||||
// After this function, the game adds the "< Return" item, and THEN we add our own items after that.
|
|
||||||
|
|
||||||
this.openSubContextMenu(agentContext);
|
|
||||||
|
|
||||||
// Allocate a new 1 byte title. This is required for the game to render the titled context menu style.
|
|
||||||
// The actual value doesn't matter at this point, we'll set it later.
|
|
||||||
MemoryHelper.GameFree(ref this.currentSubContextMenuTitle, (ulong)IntPtr.Size);
|
|
||||||
this.currentSubContextMenuTitle = MemoryHelper.GameAllocateUi(1);
|
|
||||||
*(&(&agentContext->AgentContextInterface)->SubContextMenuTitle) = (byte*)this.currentSubContextMenuTitle;
|
|
||||||
*(byte*)this.currentSubContextMenuTitle = 0;
|
|
||||||
|
|
||||||
// Expect at least 1 context menu item.
|
|
||||||
(&agentContext->Items->AtkValues)[0].UInt = 1;
|
|
||||||
|
|
||||||
// Expect a title. This isn't needed by the game, it's needed by ContextMenuReaderWriter which uses this to check if it's a context menu
|
|
||||||
(&agentContext->Items->AtkValues)[1].ChangeType(ValueType.String);
|
|
||||||
|
|
||||||
(&agentContext->Items->AtkValues)[1].String = (byte*)0;
|
|
||||||
|
|
||||||
ContextMenuReaderWriter contextMenuReaderWriter = new ContextMenuReaderWriter(&agentContext->AgentContextInterface, agentContext->Items->AtkValueCount, &agentContext->Items->AtkValues);
|
|
||||||
*(&agentContext->Items->AtkValueCount) = (ushort)contextMenuReaderWriter.FirstContextMenuItemIndex;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe bool SubContextMenuOpenedDetour(AddonContextMenu* addonContextMenu, int atkValueCount, AtkValue* atkValues)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.SubContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
PluginLog.Error(ex, "SubContextMenuOpenedDetour");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.subContextMenuOpenedHook.Original(addonContextMenu, atkValueCount, atkValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void SubContextMenuOpenedImplementation(AddonContextMenu* addonContextMenu, ref int atkValueCount, ref AtkValue* atkValues)
|
|
||||||
{
|
|
||||||
this.ContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe ContextMenuOpenedArgs? NotifyContextMenuOpened(AddonContextMenu* addonContextMenu, OldAgentContextInterface* agentContextInterface, string? title, ContextMenus.ContextMenuOpenedDelegate contextMenuOpenedDelegate, IEnumerable<ContextMenuItem> initialContextMenuItems)
|
|
||||||
{
|
|
||||||
var parentAddonName = this.GetParentAddonName(&addonContextMenu->AtkUnitBase);
|
|
||||||
|
|
||||||
Log.Warning($"AgentContextInterface at: {new IntPtr(agentContextInterface):X}");
|
|
||||||
|
|
||||||
InventoryItemContext? inventoryItemContext = null;
|
|
||||||
GameObjectContext? gameObjectContext = null;
|
|
||||||
if (IsInventoryContext(agentContextInterface))
|
|
||||||
{
|
|
||||||
var agentInventoryContext = (AgentInventoryContext*)agentContextInterface;
|
|
||||||
inventoryItemContext = new InventoryItemContext(agentInventoryContext->TargetDummyItem.ItemID, agentInventoryContext->TargetDummyItem.Quantity, agentInventoryContext->TargetDummyItem.Flags.HasFlag(InventoryItem.ItemFlags.HQ));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var agentContext = (OldAgentContext*)agentContextInterface;
|
|
||||||
|
|
||||||
uint? id = agentContext->GameObjectId;
|
|
||||||
if (id == 0)
|
|
||||||
{
|
|
||||||
id = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong? contentId = agentContext->GameObjectContentId;
|
|
||||||
if (contentId == 0)
|
|
||||||
{
|
|
||||||
contentId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = MemoryHelper.ReadSeStringNullTerminated((IntPtr)agentContext->GameObjectName.StringPtr).TextValue;
|
|
||||||
if (string.IsNullOrEmpty(name))
|
|
||||||
{
|
|
||||||
name = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ushort? worldId = agentContext->GameObjectWorldId;
|
|
||||||
if (worldId == 0)
|
|
||||||
{
|
|
||||||
worldId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id != null
|
|
||||||
|| contentId != null
|
|
||||||
|| name != null
|
|
||||||
|| worldId != null)
|
|
||||||
{
|
|
||||||
gameObjectContext = new GameObjectContext(id, contentId, name, worldId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temporarily remove the < Return item, for UX we should enforce that it is always last in the list.
|
|
||||||
var lastContextMenuItem = initialContextMenuItems.LastOrDefault();
|
|
||||||
if (lastContextMenuItem is GameContextMenuItem gameContextMenuItem && gameContextMenuItem.SelectedAction == 102)
|
|
||||||
{
|
|
||||||
initialContextMenuItems = initialContextMenuItems.SkipLast(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var contextMenuOpenedArgs = new ContextMenuOpenedArgs(addonContextMenu, agentContextInterface, parentAddonName, initialContextMenuItems)
|
|
||||||
{
|
|
||||||
Title = title,
|
|
||||||
InventoryItemContext = inventoryItemContext,
|
|
||||||
GameObjectContext = gameObjectContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
contextMenuOpenedDelegate.Invoke(contextMenuOpenedArgs);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
PluginLog.LogError(ex, "NotifyContextMenuOpened");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readd the < Return item
|
|
||||||
if (lastContextMenuItem is GameContextMenuItem gameContextMenuItem1 && gameContextMenuItem1.SelectedAction == 102)
|
|
||||||
{
|
|
||||||
contextMenuOpenedArgs.Items.Add(lastContextMenuItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var contextMenuItem in contextMenuOpenedArgs.Items.ToArray())
|
|
||||||
{
|
|
||||||
// TODO: Game doesn't support nested sub context menus, but we might be able to.
|
|
||||||
if (contextMenuItem is OpenSubContextMenuItem && contextMenuOpenedArgs.Title != null)
|
|
||||||
{
|
|
||||||
contextMenuOpenedArgs.Items.Remove(contextMenuItem);
|
|
||||||
PluginLog.Warning($"Context menu '{contextMenuOpenedArgs.Title}' item '{contextMenuItem}' has been removed because nested sub context menus are not supported.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contextMenuOpenedArgs.Items.Count > MaxContextMenuItemsPerContextMenu)
|
|
||||||
{
|
|
||||||
PluginLog.LogWarning($"Context menu requesting {contextMenuOpenedArgs.Items.Count} of max {MaxContextMenuItemsPerContextMenu} items. Resizing list to compensate.");
|
|
||||||
contextMenuOpenedArgs.Items.RemoveRange(MaxContextMenuItemsPerContextMenu, contextMenuOpenedArgs.Items.Count - MaxContextMenuItemsPerContextMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
return contextMenuOpenedArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe bool ContextMenuItemSelectedDetour(AddonContextMenu* addonContextMenu, int selectedIndex, byte a3)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.ContextMenuItemSelectedImplementation(addonContextMenu, selectedIndex);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
PluginLog.Error(ex, "ContextMenuItemSelectedDetour");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.contextMenuItemSelectedHook.Original(addonContextMenu, selectedIndex, a3);
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void ContextMenuItemSelectedImplementation(AddonContextMenu* addonContextMenu, int selectedIndex)
|
|
||||||
{
|
|
||||||
if (this.currentContextMenuOpenedArgs == null || selectedIndex == -1)
|
|
||||||
{
|
|
||||||
this.currentContextMenuOpenedArgs = null;
|
|
||||||
this.selectedOpenSubContextMenuItem = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the selected item directly from the game
|
|
||||||
ContextMenuReaderWriter contextMenuReaderWriter = new ContextMenuReaderWriter(this.currentAgentContextInterface, addonContextMenu->AtkValuesCount, addonContextMenu->AtkValues);
|
|
||||||
var gameContextMenuItems = contextMenuReaderWriter.Read();
|
|
||||||
if (gameContextMenuItems == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var gameSelectedItem = gameContextMenuItems.ElementAtOrDefault(selectedIndex);
|
|
||||||
|
|
||||||
// This should be impossible
|
|
||||||
if (gameSelectedItem == null)
|
|
||||||
{
|
|
||||||
this.currentContextMenuOpenedArgs = null;
|
|
||||||
this.selectedOpenSubContextMenuItem = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match it with the items we already know about based on its name.
|
|
||||||
// We can get into a state where we have a game item we don't recognize when another plugin has added one.
|
|
||||||
var selectedItem = this.currentContextMenuOpenedArgs.Items.FirstOrDefault(item => item.Name.Encode().SequenceEqual(gameSelectedItem.Name.Encode()));
|
|
||||||
|
|
||||||
this.selectedOpenSubContextMenuItem = null;
|
|
||||||
if (selectedItem is CustomContextMenuItem customContextMenuItem)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var customContextMenuItemSelectedArgs = new CustomContextMenuItemSelectedArgs(this.currentContextMenuOpenedArgs, customContextMenuItem);
|
|
||||||
customContextMenuItem.ItemSelected(customContextMenuItemSelectedArgs);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
PluginLog.LogError(ex, "ContextMenuItemSelectedImplementation");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (selectedItem is OpenSubContextMenuItem openSubContextMenuItem)
|
|
||||||
{
|
|
||||||
this.selectedOpenSubContextMenuItem = openSubContextMenuItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentContextMenuOpenedArgs = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe string? GetParentAddonName(AtkUnitBase* addonInterface)
|
|
||||||
{
|
|
||||||
var parentAddonId = addonInterface->ContextMenuParentID;
|
|
||||||
if (parentAddonId == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var atkStage = AtkStage.GetSingleton();
|
|
||||||
var parentAddon = atkStage->RaptureAtkUnitManager->GetAddonById(parentAddonId);
|
|
||||||
return Marshal.PtrToStringUTF8(new IntPtr(parentAddon->Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe AtkUnitBase* GetAddonFromAgent(AgentInterface* agentInterface)
|
|
||||||
{
|
|
||||||
return agentInterface->AddonId == 0 ? null : AtkStage.GetSingleton()->RaptureAtkUnitManager->GetAddonById((ushort)agentInterface->AddonId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Address resolver for context menu functions.
|
|
||||||
/// </summary>
|
|
||||||
public class ContextMenuAddressResolver : BaseAddressResolver
|
|
||||||
{
|
|
||||||
private const string SigOpenSubContextMenu = "E8 ?? ?? ?? ?? 44 39 A3 ?? ?? ?? ?? 0F 86";
|
|
||||||
private const string SigContextMenuOpening = "E8 ?? ?? ?? ?? 0F B7 C0 48 83 C4 60";
|
|
||||||
private const string SigContextMenuOpened = "48 8B C4 57 41 56 41 57 48 81 EC";
|
|
||||||
private const string SigContextMenuItemSelected = "48 89 5C 24 ?? 55 57 41 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? 80 B9";
|
|
||||||
private const string SigSubContextMenuOpening = "E8 ?? ?? ?? ?? 44 39 A3 ?? ?? ?? ?? 0F 84";
|
|
||||||
private const string SigSubContextMenuOpened = "48 8B C4 57 41 55 41 56 48 81 EC";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the OpenSubContextMenu function address.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr OpenSubContextMenuPtr { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the ContextMenuOpening function address.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr ContextMenuOpeningPtr { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the ContextMenuOpened function address.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr ContextMenuOpenedPtr { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the ContextMenuItemSelected function address.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr ContextMenuItemSelectedPtr { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the SubContextMenuOpening function address.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr SubContextMenuOpeningPtr { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the SubContextMenuOpened function address.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr SubContextMenuOpenedPtr { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void Setup64Bit(SigScanner scanner)
|
|
||||||
{
|
|
||||||
this.OpenSubContextMenuPtr = scanner.ScanText(SigOpenSubContextMenu);
|
|
||||||
this.ContextMenuOpeningPtr = scanner.ScanText(SigContextMenuOpening);
|
|
||||||
this.ContextMenuOpenedPtr = scanner.ScanText(SigContextMenuOpened);
|
|
||||||
this.ContextMenuItemSelectedPtr = scanner.ScanText(SigContextMenuItemSelected);
|
|
||||||
this.SubContextMenuOpeningPtr = scanner.ScanText(SigSubContextMenuOpening);
|
|
||||||
this.SubContextMenuOpenedPtr = scanner.ScanText(SigSubContextMenuOpened);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An item in a context menu.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ContextMenuItem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ContextMenuItem" /> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name of the item.</param>
|
|
||||||
internal ContextMenuItem(SeString name)
|
|
||||||
{
|
|
||||||
this.Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the item.
|
|
||||||
/// </summary>
|
|
||||||
public SeString Name { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether or not the item is enabled. When enabled, an item is selectable.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsEnabled { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the indicator of the item.
|
|
||||||
/// </summary>
|
|
||||||
public ContextMenuItemIndicator Indicator { get; set; } = ContextMenuItemIndicator.None;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return this.Name.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
unchecked
|
|
||||||
{
|
|
||||||
var hash = 17;
|
|
||||||
hash = (hash * 23) + new BigInteger(this.Name.Encode()).GetHashCode();
|
|
||||||
hash = (hash * 23) + this.IsEnabled.GetHashCode();
|
|
||||||
hash = (hash * 23) + ((int)this.Indicator).GetHashCode();
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An indicator displayed on a context menu item.
|
|
||||||
/// </summary>
|
|
||||||
public enum ContextMenuItemIndicator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The item has no indicator.
|
|
||||||
/// </summary>
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The item has a previous indicator.
|
|
||||||
/// </summary>
|
|
||||||
Previous,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The item has a next indicator.
|
|
||||||
/// </summary>
|
|
||||||
Next,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using Dalamud.Game.Gui.ContextMenus.OldStructs;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides data for <see cref="ContextMenuOpenedDelegate"/> methods.
|
|
||||||
/// </summary>
|
|
||||||
public sealed unsafe class ContextMenuOpenedArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ContextMenuOpenedArgs"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="addon">The addon associated with the context menu.</param>
|
|
||||||
/// <param name="agent">The agent associated with the context menu.</param>
|
|
||||||
/// <param name="parentAddonName">The the name of the parent addon associated with the context menu.</param>
|
|
||||||
/// <param name="items">The items in the context menu.</param>
|
|
||||||
public ContextMenuOpenedArgs(AddonContextMenu* addon, OldAgentContextInterface* agent, string? parentAddonName, IEnumerable<ContextMenuItem> items)
|
|
||||||
{
|
|
||||||
this.Addon = addon;
|
|
||||||
this.Agent = agent;
|
|
||||||
this.ParentAddonName = parentAddonName;
|
|
||||||
this.Items = new List<ContextMenuItem>(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the addon associated with the context menu.
|
|
||||||
/// </summary>
|
|
||||||
public AddonContextMenu* Addon { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the agent associated with the context menu.
|
|
||||||
/// </summary>
|
|
||||||
public OldAgentContextInterface* Agent { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the parent addon associated with the context menu.
|
|
||||||
/// </summary>
|
|
||||||
public string? ParentAddonName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the title of the context menu.
|
|
||||||
/// </summary>
|
|
||||||
public string? Title { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the game object context associated with the context menu.
|
|
||||||
/// </summary>
|
|
||||||
public GameObjectContext? GameObjectContext { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the item context associated with the context menu.
|
|
||||||
/// </summary>
|
|
||||||
public InventoryItemContext? InventoryItemContext { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the items in the context menu.
|
|
||||||
/// </summary>
|
|
||||||
internal List<ContextMenuItem> Items { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Append a custom context menu item to this context menu.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name of the item.</param>
|
|
||||||
/// <param name="selected">The action to be executed once selected.</param>
|
|
||||||
public void AddCustomItem(SeString name, CustomContextMenuItemSelectedDelegate selected) =>
|
|
||||||
this.Items.Add(new CustomContextMenuItem(name, selected));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Append a custom submenu to this context menu.
|
|
||||||
/// Note that these cannot be nested, and will be ignored if they are.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name of the submenu.</param>
|
|
||||||
/// <param name="opened">The action to be executed once opened.</param>
|
|
||||||
public void AddCustomSubMenu(SeString name, ContextMenuOpenedDelegate opened)
|
|
||||||
{
|
|
||||||
if (this.GameObjectContext != null)
|
|
||||||
throw new Exception("Submenus on GameObjects are not supported yet.");
|
|
||||||
|
|
||||||
this.Items.Add(new OpenSubContextMenuItem(name, opened));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the method the <see cref="ContextMenu.ContextMenuOpened"/> event.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="args">The data associated with the <see cref="ContextMenu.ContextMenuOpened"/> event.</param>
|
|
||||||
public delegate void ContextMenuOpenedDelegate(ContextMenuOpenedArgs args);
|
|
||||||
}
|
|
||||||
|
|
@ -1,553 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using Dalamud.Game.Gui.ContextMenus.OldStructs;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Dalamud.Logging;
|
|
||||||
using Dalamud.Memory;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
|
|
||||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class responsible for reading and writing to context menu data.
|
|
||||||
/// </summary>
|
|
||||||
internal unsafe class ContextMenuReaderWriter
|
|
||||||
{
|
|
||||||
private readonly OldAgentContextInterface* agentContextInterface;
|
|
||||||
|
|
||||||
private int atkValueCount;
|
|
||||||
private AtkValue* atkValues;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ContextMenuReaderWriter"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="agentContextInterface">The AgentContextInterface to act upon.</param>
|
|
||||||
/// <param name="atkValueCount">The number of ATK values to consider.</param>
|
|
||||||
/// <param name="atkValues">Pointer to the array of ATK values.</param>
|
|
||||||
public ContextMenuReaderWriter(OldAgentContextInterface* agentContextInterface, int atkValueCount, AtkValue* atkValues)
|
|
||||||
{
|
|
||||||
PluginLog.Warning($"{(IntPtr)atkValues:X}");
|
|
||||||
|
|
||||||
this.agentContextInterface = agentContextInterface;
|
|
||||||
this.atkValueCount = atkValueCount;
|
|
||||||
this.atkValues = atkValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum SubContextMenuStructLayout
|
|
||||||
{
|
|
||||||
Main,
|
|
||||||
Alternate,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the number of AtkValues for this context menu.
|
|
||||||
/// </summary>
|
|
||||||
public int AtkValueCount => this.atkValueCount;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the AtkValues for this context menu.
|
|
||||||
/// </summary>
|
|
||||||
public AtkValue* AtkValues => this.atkValues;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the amount of items in the context menu.
|
|
||||||
/// </summary>
|
|
||||||
public int ContextMenuItemCount => this.atkValues[0].Int;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the context menu has a title.
|
|
||||||
/// </summary>
|
|
||||||
public bool HasTitle
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
bool isStringType =
|
|
||||||
(int)this.atkValues[1].Type == 8
|
|
||||||
|| (int)this.atkValues[1].Type == 38
|
|
||||||
|| this.atkValues[1].Type == FFXIVClientStructs.FFXIV.Component.GUI.ValueType.String;
|
|
||||||
|
|
||||||
return isStringType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the title of the context menu.
|
|
||||||
/// </summary>
|
|
||||||
public SeString? Title
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.HasTitle && (&this.atkValues[1])->String != null)
|
|
||||||
{
|
|
||||||
MemoryHelper.ReadSeStringNullTerminated((IntPtr)(&this.atkValues[1])->String, out var str);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the index of the first context menu item.
|
|
||||||
/// </summary>
|
|
||||||
public int FirstContextMenuItemIndex
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.HasTitle)
|
|
||||||
{
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the position of the context menu.
|
|
||||||
/// </summary>
|
|
||||||
public Vector2? Position
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.HasTitle) return new Vector2(this.atkValues[2].Int, this.atkValues[3].Int);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int HasPreviousIndicatorFlagsIndex
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.HasTitle)
|
|
||||||
{
|
|
||||||
return 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int HasNextIndicatorFlagsIndex
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.HasTitle)
|
|
||||||
{
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int NameIndexOffset
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.HasTitle && this.StructLayout == SubContextMenuStructLayout.Alternate)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int IsDisabledIndexOffset
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.HasTitle && this.StructLayout == SubContextMenuStructLayout.Alternate)
|
|
||||||
{
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.ContextMenuItemCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
/// <summary>
|
|
||||||
/// 0x14000000 | action
|
|
||||||
/// </summary>
|
|
||||||
public int? MaskedActionIndexOffset
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.HasTitle && this.StructLayout == SubContextMenuStructLayout.Alternate) return 3;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
private int SequentialAtkValuesPerContextMenuItem
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.HasTitle && this.StructLayout == SubContextMenuStructLayout.Alternate) return 4;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int TotalDesiredAtkValuesPerContextMenuItem
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.HasTitle && this.StructLayout == SubContextMenuStructLayout.Alternate) return 4;
|
|
||||||
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsInventoryContext
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if ((IntPtr)this.agentContextInterface == (IntPtr)AgentInventoryContext.Instance())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SubContextMenuStructLayout? StructLayout
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.HasTitle)
|
|
||||||
{
|
|
||||||
if (this.atkValues[7].Int == 8)
|
|
||||||
return SubContextMenuStructLayout.Alternate;
|
|
||||||
|
|
||||||
if (this.atkValues[7].Int == 1) return SubContextMenuStructLayout.Main;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte NoopAction
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.IsInventoryContext)
|
|
||||||
return 0xff;
|
|
||||||
return 0x67;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte OpenSubContextMenuAction
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.IsInventoryContext)
|
|
||||||
{
|
|
||||||
// This is actually the action to open the Second Tier context menu and we just hack around it
|
|
||||||
return 0x31;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0x66;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte? FirstUnhandledAction
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.StructLayout is SubContextMenuStructLayout.Alternate)
|
|
||||||
return 0x68;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read the context menu from the agent.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Read menu items.</returns>
|
|
||||||
public GameContextMenuItem[]? Read()
|
|
||||||
{
|
|
||||||
var gameContextMenuItems = new List<GameContextMenuItem>();
|
|
||||||
for (var contextMenuItemIndex = 0; contextMenuItemIndex < this.ContextMenuItemCount; contextMenuItemIndex++)
|
|
||||||
{
|
|
||||||
var contextMenuItemAtkValueBaseIndex = this.FirstContextMenuItemIndex + (contextMenuItemIndex * this.SequentialAtkValuesPerContextMenuItem);
|
|
||||||
|
|
||||||
// Get the name
|
|
||||||
var nameAtkValue = &this.atkValues[contextMenuItemAtkValueBaseIndex + this.NameIndexOffset];
|
|
||||||
if (nameAtkValue->Type == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = MemoryHelper.ReadSeStringNullTerminated((IntPtr)nameAtkValue->String);
|
|
||||||
|
|
||||||
// Get the enabled state. Note that SE stores this as IsDisabled, NOT IsEnabled (those heathens)
|
|
||||||
var isEnabled = true;
|
|
||||||
var isDisabledDefined = this.FirstContextMenuItemIndex + this.ContextMenuItemCount < this.AtkValueCount;
|
|
||||||
if (isDisabledDefined)
|
|
||||||
{
|
|
||||||
var isDisabledAtkValue = &this.atkValues[contextMenuItemAtkValueBaseIndex + this.IsDisabledIndexOffset];
|
|
||||||
isEnabled = isDisabledAtkValue->Int == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the action
|
|
||||||
byte action;
|
|
||||||
if (this.IsInventoryContext)
|
|
||||||
{
|
|
||||||
var actions = &((OldAgentInventoryContext*)this.agentContextInterface)->Actions;
|
|
||||||
action = *(actions + contextMenuItemAtkValueBaseIndex);
|
|
||||||
}
|
|
||||||
else if (this.StructLayout is SubContextMenuStructLayout.Alternate)
|
|
||||||
{
|
|
||||||
var redButtonActions = &((OldAgentContext*)this.agentContextInterface)->Items->RedButtonActions;
|
|
||||||
action = (byte)*(redButtonActions + contextMenuItemIndex);
|
|
||||||
}
|
|
||||||
else if (((OldAgentContext*)this.agentContextInterface)->Items != null)
|
|
||||||
{
|
|
||||||
var actions = &((OldAgentContext*)this.agentContextInterface)->Items->Actions;
|
|
||||||
action = *(actions + contextMenuItemAtkValueBaseIndex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PluginLog.Warning("Context Menu action failed, Items pointer was unexpectedly null.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the has previous indicator flag
|
|
||||||
var hasPreviousIndicatorFlagsAtkValue = &this.atkValues[this.HasPreviousIndicatorFlagsIndex];
|
|
||||||
var hasPreviousIndicator = this.HasFlag(hasPreviousIndicatorFlagsAtkValue->UInt, contextMenuItemIndex);
|
|
||||||
|
|
||||||
// Get the has next indicator flag
|
|
||||||
var hasNextIndicatorFlagsAtkValue = &this.atkValues[this.HasNextIndicatorFlagsIndex];
|
|
||||||
var hasNextIndicator = this.HasFlag(hasNextIndicatorFlagsAtkValue->UInt, contextMenuItemIndex);
|
|
||||||
|
|
||||||
var indicator = ContextMenuItemIndicator.None;
|
|
||||||
if (hasPreviousIndicator)
|
|
||||||
{
|
|
||||||
indicator = ContextMenuItemIndicator.Previous;
|
|
||||||
}
|
|
||||||
else if (hasNextIndicator)
|
|
||||||
{
|
|
||||||
indicator = ContextMenuItemIndicator.Next;
|
|
||||||
}
|
|
||||||
|
|
||||||
var gameContextMenuItem = new GameContextMenuItem(name, action)
|
|
||||||
{
|
|
||||||
IsEnabled = isEnabled,
|
|
||||||
Indicator = indicator,
|
|
||||||
};
|
|
||||||
|
|
||||||
gameContextMenuItems.Add(gameContextMenuItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
return gameContextMenuItems.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write items to the context menu.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="contextMenuItems">The items to write.</param>
|
|
||||||
/// <param name="allowReallocate">Whether or not reallocation is allowed.</param>
|
|
||||||
public void Write(IEnumerable<ContextMenuItem> contextMenuItems, bool allowReallocate = true)
|
|
||||||
{
|
|
||||||
if (allowReallocate)
|
|
||||||
{
|
|
||||||
var newAtkValuesCount = this.FirstContextMenuItemIndex + (contextMenuItems.Count() * this.TotalDesiredAtkValuesPerContextMenuItem);
|
|
||||||
|
|
||||||
// Allocate the new array. We have to do a little dance with the first 8 bytes which represents the array count
|
|
||||||
const int arrayCountSize = 8;
|
|
||||||
var newAtkValuesArraySize = arrayCountSize + (Marshal.SizeOf<AtkValue>() * newAtkValuesCount);
|
|
||||||
var newAtkValuesArray = MemoryHelper.GameAllocateUi((ulong)newAtkValuesArraySize);
|
|
||||||
if (newAtkValuesArray == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newAtkValues = (AtkValue*)(newAtkValuesArray + arrayCountSize);
|
|
||||||
|
|
||||||
// Zero the memory, then copy the atk values up to the first context menu item atk value
|
|
||||||
Marshal.Copy(new byte[newAtkValuesArraySize], 0, newAtkValuesArray, newAtkValuesArraySize);
|
|
||||||
Buffer.MemoryCopy(this.atkValues, newAtkValues, newAtkValuesArraySize - arrayCountSize, (long)sizeof(AtkValue) * this.FirstContextMenuItemIndex);
|
|
||||||
|
|
||||||
// Free the old array
|
|
||||||
var oldArray = (IntPtr)this.atkValues - arrayCountSize;
|
|
||||||
var oldArrayCount = *(ulong*)oldArray;
|
|
||||||
var oldArraySize = arrayCountSize + ((ulong)sizeof(AtkValue) * oldArrayCount);
|
|
||||||
MemoryHelper.GameFree(ref oldArray, oldArraySize);
|
|
||||||
|
|
||||||
// Set the array count
|
|
||||||
*(ulong*)newAtkValuesArray = (ulong)newAtkValuesCount;
|
|
||||||
|
|
||||||
this.atkValueCount = newAtkValuesCount;
|
|
||||||
this.atkValues = newAtkValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the context menu item count
|
|
||||||
const int contextMenuItemCountAtkValueIndex = 0;
|
|
||||||
var contextMenuItemCountAtkValue = &this.atkValues[contextMenuItemCountAtkValueIndex];
|
|
||||||
contextMenuItemCountAtkValue->UInt = (uint)contextMenuItems.Count();
|
|
||||||
|
|
||||||
// Clear the previous arrow flags
|
|
||||||
var hasPreviousIndicatorAtkValue = &this.atkValues[this.HasPreviousIndicatorFlagsIndex];
|
|
||||||
hasPreviousIndicatorAtkValue->UInt = 0;
|
|
||||||
|
|
||||||
// Clear the next arrow flags
|
|
||||||
var hasNextIndiactorFlagsAtkValue = &this.atkValues[this.HasNextIndicatorFlagsIndex];
|
|
||||||
hasNextIndiactorFlagsAtkValue->UInt = 0;
|
|
||||||
|
|
||||||
for (var contextMenuItemIndex = 0; contextMenuItemIndex < contextMenuItems.Count(); ++contextMenuItemIndex)
|
|
||||||
{
|
|
||||||
var contextMenuItem = contextMenuItems.ElementAt(contextMenuItemIndex);
|
|
||||||
|
|
||||||
var contextMenuItemAtkValueBaseIndex = this.FirstContextMenuItemIndex + (contextMenuItemIndex * this.SequentialAtkValuesPerContextMenuItem);
|
|
||||||
|
|
||||||
// Set the name
|
|
||||||
var nameAtkValue = &this.atkValues[contextMenuItemAtkValueBaseIndex + this.NameIndexOffset];
|
|
||||||
nameAtkValue->ChangeType(ValueType.String);
|
|
||||||
fixed (byte* nameBytesPtr = contextMenuItem.Name.Encode().NullTerminate())
|
|
||||||
{
|
|
||||||
nameAtkValue->SetString(nameBytesPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the enabled state. Note that SE stores this as IsDisabled, NOT IsEnabled (those heathens)
|
|
||||||
var disabledAtkValue = &this.atkValues[contextMenuItemAtkValueBaseIndex + this.IsDisabledIndexOffset];
|
|
||||||
disabledAtkValue->ChangeType(ValueType.Int);
|
|
||||||
disabledAtkValue->Int = contextMenuItem.IsEnabled ? 0 : 1;
|
|
||||||
|
|
||||||
// Set the action
|
|
||||||
byte action = 0;
|
|
||||||
if (contextMenuItem is GameContextMenuItem gameContextMenuItem)
|
|
||||||
{
|
|
||||||
action = gameContextMenuItem.SelectedAction;
|
|
||||||
}
|
|
||||||
else if (contextMenuItem is CustomContextMenuItem customContextMenuItem)
|
|
||||||
{
|
|
||||||
action = this.NoopAction;
|
|
||||||
}
|
|
||||||
else if (contextMenuItem is OpenSubContextMenuItem openSubContextMenuItem)
|
|
||||||
{
|
|
||||||
action = this.OpenSubContextMenuAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.IsInventoryContext)
|
|
||||||
{
|
|
||||||
var actions = &((OldAgentInventoryContext*)this.agentContextInterface)->Actions;
|
|
||||||
*(actions + this.FirstContextMenuItemIndex + contextMenuItemIndex) = action;
|
|
||||||
}
|
|
||||||
else if (this.StructLayout is SubContextMenuStructLayout.Alternate && this.FirstUnhandledAction != null)
|
|
||||||
{
|
|
||||||
// Some weird placeholder goes here
|
|
||||||
var actions = &((OldAgentContext*)this.agentContextInterface)->Items->Actions;
|
|
||||||
*(actions + this.FirstContextMenuItemIndex + contextMenuItemIndex) = (byte)(this.FirstUnhandledAction.Value + contextMenuItemIndex);
|
|
||||||
|
|
||||||
// Make sure there's one of these function pointers for every item.
|
|
||||||
// The function needs to be the same, so we just copy the first one into every index.
|
|
||||||
var unkFunctionPointers = &((OldAgentContext*)this.agentContextInterface)->Items->UnkFunctionPointers;
|
|
||||||
*(unkFunctionPointers + this.FirstContextMenuItemIndex + contextMenuItemIndex) = *(unkFunctionPointers + this.FirstContextMenuItemIndex);
|
|
||||||
|
|
||||||
// The real action goes here
|
|
||||||
var redButtonActions = &((OldAgentContext*)this.agentContextInterface)->Items->RedButtonActions;
|
|
||||||
*(redButtonActions + contextMenuItemIndex) = action;
|
|
||||||
}
|
|
||||||
else if (((OldAgentContext*)this.agentContextInterface)->Items != null)
|
|
||||||
{
|
|
||||||
// TODO: figure out why this branch is reached on inventory contexts and why Items is sometimes null.
|
|
||||||
var actions = &((OldAgentContext*)this.agentContextInterface)->Items->Actions;
|
|
||||||
*(actions + this.FirstContextMenuItemIndex + contextMenuItemIndex) = action;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PluginLog.Warning("Context Menu action failed, Items pointer was unexpectedly null.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contextMenuItem.Indicator == ContextMenuItemIndicator.Previous)
|
|
||||||
{
|
|
||||||
this.SetFlag(ref hasPreviousIndicatorAtkValue->UInt, contextMenuItemIndex, true);
|
|
||||||
}
|
|
||||||
else if (contextMenuItem.Indicator == ContextMenuItemIndicator.Next)
|
|
||||||
{
|
|
||||||
this.SetFlag(ref hasNextIndiactorFlagsAtkValue->UInt, contextMenuItemIndex, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HasFlag(uint mask, int itemIndex)
|
|
||||||
{
|
|
||||||
return (mask & (1 << itemIndex)) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetFlag(ref uint mask, int itemIndex, bool value)
|
|
||||||
{
|
|
||||||
mask &= ~(1U << itemIndex);
|
|
||||||
|
|
||||||
if (value)
|
|
||||||
{
|
|
||||||
mask |= (uint)(1 << itemIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
private void Log()
|
|
||||||
{
|
|
||||||
Log(this.atkValueCount, this.atkValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Log(int atkValueCount, AtkValue* atkValues)
|
|
||||||
{
|
|
||||||
PluginLog.Debug($"ContextMenuReader.Log");
|
|
||||||
|
|
||||||
for (int atkValueIndex = 0; atkValueIndex < atkValueCount; ++atkValueIndex)
|
|
||||||
{
|
|
||||||
var atkValue = &atkValues[atkValueIndex];
|
|
||||||
|
|
||||||
object? value;
|
|
||||||
switch (atkValue->Type)
|
|
||||||
{
|
|
||||||
case ValueType.Int:
|
|
||||||
value = atkValue->Int;
|
|
||||||
break;
|
|
||||||
case ValueType.Bool:
|
|
||||||
value = atkValue->Byte;
|
|
||||||
break;
|
|
||||||
case ValueType.UInt:
|
|
||||||
value = atkValue->UInt;
|
|
||||||
break;
|
|
||||||
case ValueType.Float:
|
|
||||||
value = atkValue->Float;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
if (atkValue->Type == ValueType.String
|
|
||||||
|| (int)atkValue->Type == 38
|
|
||||||
|| (int)atkValue->Type == 8)
|
|
||||||
{
|
|
||||||
value = MemoryHelper.ReadSeStringNullTerminated((IntPtr)atkValue->String);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value = $"{(IntPtr)atkValue->String:X}";
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginLog.Debug($"atkValues[{atkValueIndex}]={(IntPtr)atkValue:X} {atkValue->Type}={value}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
using Dalamud.Game.Text;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An item in a context menu with a user defined action.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class CustomContextMenuItem : ContextMenuItem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="CustomContextMenuItem"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name of the item.</param>
|
|
||||||
/// <param name="itemSelected">The action that will be called when the item is selected.</param>
|
|
||||||
public CustomContextMenuItem(SeString name, CustomContextMenuItemSelectedDelegate itemSelected)
|
|
||||||
: base(new SeString().Append(new UIForegroundPayload(539)).Append($"{SeIconChar.BoxedLetterD.ToIconString()} ").Append(new UIForegroundPayload(0)).Append(name))
|
|
||||||
{
|
|
||||||
this.ItemSelected = itemSelected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the action that will be called when the item is selected.
|
|
||||||
/// </summary>
|
|
||||||
public CustomContextMenuItemSelectedDelegate ItemSelected { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
unchecked
|
|
||||||
{
|
|
||||||
var hash = base.GetHashCode();
|
|
||||||
hash = (hash * 23) + this.ItemSelected.GetHashCode();
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides data for <see cref="CustomContextMenuItemSelectedDelegate"/> methods.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class CustomContextMenuItemSelectedArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="CustomContextMenuItemSelectedArgs"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="contextMenuOpenedArgs">The currently opened context menu.</param>
|
|
||||||
/// <param name="selectedItem">The selected item within the currently opened context menu.</param>
|
|
||||||
internal CustomContextMenuItemSelectedArgs(ContextMenuOpenedArgs contextMenuOpenedArgs, CustomContextMenuItem selectedItem)
|
|
||||||
{
|
|
||||||
this.ContextMenuOpenedArgs = contextMenuOpenedArgs;
|
|
||||||
this.SelectedItem = selectedItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the currently opened context menu.
|
|
||||||
/// </summary>
|
|
||||||
public ContextMenuOpenedArgs ContextMenuOpenedArgs { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the selected item within the currently opened context menu.
|
|
||||||
/// </summary>
|
|
||||||
public CustomContextMenuItem SelectedItem { get; init; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the method that handles when a <see cref="CustomContextMenuItem"/> is selected.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="args">The data associated with the selected <see cref="CustomContextMenuItem"/>.</param>
|
|
||||||
public delegate void CustomContextMenuItemSelectedDelegate(CustomContextMenuItemSelectedArgs args);
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An item in a context menu that with a specific game action.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class GameContextMenuItem : ContextMenuItem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="GameContextMenuItem"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name of the item.</param>
|
|
||||||
/// <param name="selectedAction">The game action that will be handled when the item is selected.</param>
|
|
||||||
internal GameContextMenuItem(SeString name, byte selectedAction)
|
|
||||||
: base(name)
|
|
||||||
{
|
|
||||||
this.SelectedAction = selectedAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the game action that will be handled when the item is selected.
|
|
||||||
/// </summary>
|
|
||||||
public byte SelectedAction { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
unchecked
|
|
||||||
{
|
|
||||||
var hash = base.GetHashCode();
|
|
||||||
hash = (hash * 23) + this.SelectedAction;
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides game object context to a context menu.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class GameObjectContext
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="GameObjectContext"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The id of the game object.</param>
|
|
||||||
/// <param name="contentId">The lower content id of the game object.</param>
|
|
||||||
/// <param name="name">The name of the game object.</param>
|
|
||||||
/// <param name="worldId">The world id of the game object.</param>
|
|
||||||
internal GameObjectContext(uint? id, ulong? contentId, string? name, ushort? worldId)
|
|
||||||
{
|
|
||||||
this.Id = id;
|
|
||||||
this.ContentId = contentId;
|
|
||||||
this.Name = name;
|
|
||||||
this.WorldId = worldId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the id of the game object.
|
|
||||||
/// </summary>
|
|
||||||
public uint? Id { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the content id of the game object.
|
|
||||||
/// </summary>
|
|
||||||
public ulong? ContentId { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the game object.
|
|
||||||
/// </summary>
|
|
||||||
public string? Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the world id of the game object.
|
|
||||||
/// </summary>
|
|
||||||
public ushort? WorldId { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides inventory item context to a context menu.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class InventoryItemContext
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="InventoryItemContext"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The id of the item.</param>
|
|
||||||
/// <param name="count">The count of the item in the stack.</param>
|
|
||||||
/// <param name="isHighQuality">Whether the item is high quality.</param>
|
|
||||||
internal InventoryItemContext(uint id, uint count, bool isHighQuality)
|
|
||||||
{
|
|
||||||
this.Id = id;
|
|
||||||
this.Count = count;
|
|
||||||
this.IsHighQuality = isHighQuality;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the id of the item.
|
|
||||||
/// </summary>
|
|
||||||
public uint Id { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the count of the item in the stack.
|
|
||||||
/// </summary>
|
|
||||||
public uint Count { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the item is high quality.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsHighQuality { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.ContextMenus.OldStructs;
|
|
||||||
|
|
||||||
// TODO: This is transplanted from client structs before the rework. Need to take some time to sort all of this out soon.
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public unsafe struct OldAgentContext
|
|
||||||
{
|
|
||||||
public static OldAgentContext* Instance() => (OldAgentContext*)FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Context);
|
|
||||||
|
|
||||||
[FieldOffset(0x0)] public AgentInterface AgentInterface;
|
|
||||||
[FieldOffset(0x0)] public OldAgentContextInterface AgentContextInterface;
|
|
||||||
[FieldOffset(0xD18)] public unsafe OldAgentContextMenuItems* Items;
|
|
||||||
[FieldOffset(0xE08)] public Utf8String GameObjectName;
|
|
||||||
[FieldOffset(0xEE0)] public ulong GameObjectContentId;
|
|
||||||
[FieldOffset(0xEF0)] public uint GameObjectId;
|
|
||||||
[FieldOffset(0xF00)] public ushort GameObjectWorldId;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct OldAgentContextMenuItems
|
|
||||||
{
|
|
||||||
[FieldOffset(0x0)] public ushort AtkValueCount;
|
|
||||||
[FieldOffset(0x8)] public AtkValue AtkValues;
|
|
||||||
[FieldOffset(0x428)] public byte Actions;
|
|
||||||
[FieldOffset(0x450)] public ulong UnkFunctionPointers;
|
|
||||||
[FieldOffset(0x598)] public ulong RedButtonActions;
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.ContextMenus.OldStructs;
|
|
||||||
|
|
||||||
// TODO: This is transplanted from client structs before the rework. Need to take some time to sort all of this out soon.
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public unsafe struct OldAgentContextInterface
|
|
||||||
{
|
|
||||||
[FieldOffset(0x0)] public AgentInterface AgentInterface;
|
|
||||||
[FieldOffset(0x670)] public unsafe byte SelectedIndex;
|
|
||||||
[FieldOffset(0x690)] public byte* Unk1;
|
|
||||||
[FieldOffset(0xD08)] public byte* SubContextMenuTitle;
|
|
||||||
[FieldOffset(0x1740)] public bool IsSubContextMenu;
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.ContextMenus.OldStructs;
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public unsafe struct OldAgentInventoryContext
|
|
||||||
{
|
|
||||||
public static OldAgentInventoryContext* Instance() => (OldAgentInventoryContext*) FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.InventoryContext);
|
|
||||||
|
|
||||||
[FieldOffset(0x0)] public AgentInterface AgentInterface;
|
|
||||||
[FieldOffset(0x0)] public OldAgentContextInterface AgentContextInterface;
|
|
||||||
[FieldOffset(0x2C)] public uint FirstContextMenuItemAtkValueIndex;
|
|
||||||
[FieldOffset(0x30)] public uint ContextMenuItemCount;
|
|
||||||
[FieldOffset(0x38)] public AtkValue AtkValues;
|
|
||||||
[FieldOffset(0x558)] public unsafe byte Actions;
|
|
||||||
[FieldOffset(0x5A8)] public uint UnkFlags;
|
|
||||||
[FieldOffset(0x5B0)] public uint PositionX;
|
|
||||||
[FieldOffset(0x5B4)] public uint PositionY;
|
|
||||||
[FieldOffset(0x5F8)] public uint InventoryItemId;
|
|
||||||
[FieldOffset(0x5FC)] public uint InventoryItemCount;
|
|
||||||
[FieldOffset(0x604)] public bool InventoryItemIsHighQuality;
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
using Dalamud.Game.Text;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.ContextMenus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An item in a context menu that can open a sub context menu.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class OpenSubContextMenuItem : ContextMenuItem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="OpenSubContextMenuItem"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name of the item.</param>
|
|
||||||
/// <param name="opened">The action that will be called when the item is selected.</param>
|
|
||||||
internal OpenSubContextMenuItem(SeString name, ContextMenuOpenedDelegate opened)
|
|
||||||
: base(new SeString().Append(new UIForegroundPayload(539)).Append($"{SeIconChar.BoxedLetterD.ToIconString()} ").Append(new UIForegroundPayload(0)).Append(name))
|
|
||||||
{
|
|
||||||
this.Opened = opened;
|
|
||||||
this.Indicator = ContextMenuItemIndicator.Next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the action that will be called when the item is selected.
|
|
||||||
/// </summary>
|
|
||||||
public ContextMenuOpenedDelegate Opened { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
unchecked
|
|
||||||
{
|
|
||||||
int hash = base.GetHashCode();
|
|
||||||
hash = (hash * 23) + this.Opened.GetHashCode();
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.Gui.ContextMenus;
|
//using Dalamud.Game.Gui.ContextMenus;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
|
@ -37,8 +37,8 @@ namespace Dalamud.Game.Internal
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
// [ServiceManager.ServiceDependency]
|
||||||
private readonly ContextMenu contextMenu = Service<ContextMenu>.Get();
|
// private readonly ContextMenu contextMenu = Service<ContextMenu>.Get();
|
||||||
|
|
||||||
private readonly string locDalamudPlugins;
|
private readonly string locDalamudPlugins;
|
||||||
private readonly string locDalamudSettings;
|
private readonly string locDalamudSettings;
|
||||||
|
|
@ -65,7 +65,7 @@ namespace Dalamud.Game.Internal
|
||||||
this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins");
|
this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins");
|
||||||
this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings");
|
this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings");
|
||||||
|
|
||||||
this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened;
|
// this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened;
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
||||||
|
|
@ -86,6 +86,7 @@ namespace Dalamud.Game.Internal
|
||||||
this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
|
this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args)
|
private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args)
|
||||||
{
|
{
|
||||||
var systemText = Service<DataManager>.GetNullable()?.GetExcelSheet<Addon>()?.GetRow(1059)?.Text?.RawString; // "System"
|
var systemText = Service<DataManager>.GetNullable()?.GetExcelSheet<Addon>()?.GetRow(1059)?.Text?.RawString; // "System"
|
||||||
|
|
@ -109,6 +110,7 @@ namespace Dalamud.Game.Internal
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
private IntPtr AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* arg)
|
private IntPtr AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* arg)
|
||||||
{
|
{
|
||||||
|
|
@ -270,7 +272,7 @@ namespace Dalamud.Game.Internal
|
||||||
this.hookUiModuleRequestMainCommand.Dispose();
|
this.hookUiModuleRequestMainCommand.Dispose();
|
||||||
this.hookAtkUnitBaseReceiveGlobalEvent.Dispose();
|
this.hookAtkUnitBaseReceiveGlobalEvent.Dispose();
|
||||||
|
|
||||||
this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
|
// this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disposed = true;
|
this.disposed = true;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.Gui.ContextMenus;
|
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
@ -44,6 +43,7 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public SelfTestStepResult RunStep()
|
public SelfTestStepResult RunStep()
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
var contextMenu = Service<ContextMenu>.Get();
|
var contextMenu = Service<ContextMenu>.Get();
|
||||||
var dataMgr = Service<DataManager>.Get();
|
var dataMgr = Service<DataManager>.Get();
|
||||||
|
|
||||||
|
|
@ -133,13 +133,16 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
|
||||||
}
|
}
|
||||||
|
|
||||||
return SelfTestStepResult.Waiting;
|
return SelfTestStepResult.Waiting;
|
||||||
|
*/
|
||||||
|
|
||||||
|
return SelfTestStepResult.Pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void CleanUp()
|
public void CleanUp()
|
||||||
{
|
{
|
||||||
var contextMenu = Service<ContextMenu>.Get();
|
// var contextMenu = Service<ContextMenu>.Get();
|
||||||
contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
|
// contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
|
||||||
|
|
||||||
this.currentSubStep = SubStep.Start;
|
this.currentSubStep = SubStep.Start;
|
||||||
this.clickedItemId = 0;
|
this.clickedItemId = 0;
|
||||||
|
|
@ -147,6 +150,7 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
|
||||||
this.multipleTriggerOne = this.multipleTriggerTwo = false;
|
this.multipleTriggerOne = this.multipleTriggerTwo = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args)
|
private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args)
|
||||||
{
|
{
|
||||||
Log.Information("Got context menu with parent addon: {ParentAddonName}, title:{Title}, itemcnt:{ItemCount}", args.ParentAddonName, args.Title, args.Items.Count);
|
Log.Information("Got context menu with parent addon: {ParentAddonName}, title:{Title}, itemcnt:{ItemCount}", args.ParentAddonName, args.Title, args.Items.Count);
|
||||||
|
|
@ -239,5 +243,6 @@ namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue