From 342cb792b9d54af247c2514252cfaebf855e4da1 Mon Sep 17 00:00:00 2001 From: Pilzinsel64 Date: Thu, 16 Jun 2022 01:18:31 +0200 Subject: [PATCH] use Dalamud's ContextMenu (but doesn't work atm?) --- .../Configuration/PluginConfigurationUI.cs | 8 +- PlayerTags/Data/PluginData.cs | 2 +- .../Features/CustomTagsContextMenuFeature.cs | 126 ++-- .../GameInterface/ContextMenus/ContextMenu.cs | 646 ------------------ .../ContextMenus/ContextMenuItem.cs | 53 -- .../ContextMenus/ContextMenuItemIndicator.cs | 21 - .../ContextMenus/ContextMenuOpenedArgs.cs | 62 -- .../ContextMenus/ContextMenuOpenedDelegate.cs | 8 - .../ContextMenus/ContextMenuReaderWriter.cs | 526 -------------- .../ContextMenus/CustomContextMenuItem.cs | 36 - .../CustomContextMenuItemSelectedArgs.cs | 29 - .../CustomContextMenuItemSelectedDelegate.cs | 8 - .../FFXIV/Client/UI/AddonContextMenu.cs | 14 - .../FFXIV/Client/UI/AddonInterface.cs | 11 - .../FFXIV/Client/UI/Agent/AgentContext.cs | 20 - .../Client/UI/Agent/AgentContextInterface.cs | 15 - .../Client/UI/Agent/AgentContextMenuItems.cs | 15 - .../Client/UI/Agent/AgentInventoryContext.cs | 24 - .../ContextMenus/GameContextMenuItem.cs | 36 - .../ContextMenus/GameObjectContext.cs | 45 -- .../ContextMenus/InventoryItemContext.cs | 36 - .../ContextMenus/OpenSubContextMenuItem.cs | 37 - PlayerTags/PlayerTags.csproj | 2 +- PlayerTags/PluginServices.cs | 1 + 24 files changed, 67 insertions(+), 1714 deletions(-) delete mode 100644 PlayerTags/GameInterface/ContextMenus/ContextMenu.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/ContextMenuItem.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/ContextMenuItemIndicator.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedArgs.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedDelegate.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/ContextMenuReaderWriter.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/CustomContextMenuItem.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/CustomContextMenuItemSelectedArgs.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/CustomContextMenuItemSelectedDelegate.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonContextMenu.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonInterface.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContext.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextInterface.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextMenuItems.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentInventoryContext.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/GameContextMenuItem.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/GameObjectContext.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/InventoryItemContext.cs delete mode 100644 PlayerTags/GameInterface/ContextMenus/OpenSubContextMenuItem.cs diff --git a/PlayerTags/Configuration/PluginConfigurationUI.cs b/PlayerTags/Configuration/PluginConfigurationUI.cs index ac14b1a..48fdab5 100644 --- a/PlayerTags/Configuration/PluginConfigurationUI.cs +++ b/PlayerTags/Configuration/PluginConfigurationUI.cs @@ -41,6 +41,8 @@ namespace PlayerTags.Configuration return; } + ImGui.SetNextWindowSize(new Vector2(400, 500), ImGuiCond.FirstUseEver); + if (ImGui.Begin(Strings.Loc_Static_PluginName, ref m_PluginConfiguration.IsVisible)) { ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1, 0.8f, 0.5f, 1)); @@ -53,9 +55,9 @@ namespace PlayerTags.Configuration { if (ImGui.BeginTabItem(Strings.Loc_Static_General)) { - //ImGui.Spacing(); - //ImGui.Spacing(); - //DrawCheckbox(nameof(m_PluginConfiguration.IsCustomTagsContextMenuEnabled), true, ref m_PluginConfiguration.IsCustomTagsContextMenuEnabled, () => m_PluginConfiguration.Save(m_PluginData)); + ImGui.Spacing(); + ImGui.Spacing(); + DrawCheckbox(nameof(m_PluginConfiguration.IsCustomTagsContextMenuEnabled), true, ref m_PluginConfiguration.IsCustomTagsContextMenuEnabled, () => m_PluginConfiguration.Save(m_PluginData)); ImGui.Spacing(); diff --git a/PlayerTags/Data/PluginData.cs b/PlayerTags/Data/PluginData.cs index 1adac08..62cd204 100644 --- a/PlayerTags/Data/PluginData.cs +++ b/PlayerTags/Data/PluginData.cs @@ -1,9 +1,9 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Party; +using Dalamud.Game.Gui.ContextMenus; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Logging; using PlayerTags.Configuration; -using PlayerTags.GameInterface.ContextMenus; using PlayerTags.PluginStrings; using System; using System.Collections.Generic; diff --git a/PlayerTags/Features/CustomTagsContextMenuFeature.cs b/PlayerTags/Features/CustomTagsContextMenuFeature.cs index 23d832e..0f5db26 100644 --- a/PlayerTags/Features/CustomTagsContextMenuFeature.cs +++ b/PlayerTags/Features/CustomTagsContextMenuFeature.cs @@ -1,7 +1,8 @@ -using Dalamud.Logging; +using Dalamud.Game.Gui.ContextMenus; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Logging; using PlayerTags.Configuration; using PlayerTags.Data; -using PlayerTags.GameInterface.ContextMenus; using PlayerTags.Resources; using System; using System.Collections.Generic; @@ -32,84 +33,75 @@ namespace PlayerTags.Features private PluginConfiguration m_PluginConfiguration; private PluginData m_PluginData; - - //private ContextMenu? m_ContextMenu; + private ContextMenu? m_ContextMenu; public CustomTagsContextMenuFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) { m_PluginConfiguration = pluginConfiguration; m_PluginData = pluginData; - - //m_ContextMenu = new ContextMenu(); - //if (!m_ContextMenu.IsValid) - //{ - // m_ContextMenu = null; - //} - //if (m_ContextMenu != null) - //{ - // m_ContextMenu.ContextMenuOpened += ContextMenuHooks_ContextMenuOpened; - //} + m_ContextMenu = new ContextMenu(); + + if (m_PluginConfiguration.IsCustomTagsContextMenuEnabled) + { + m_ContextMenu.ContextMenuOpened += ContextMenuHooks_ContextMenuOpened; + PluginServices.GameGui.Enable(); + } } public void Dispose() { - //if (m_ContextMenu != null) - //{ - // m_ContextMenu.ContextMenuOpened -= ContextMenuHooks_ContextMenuOpened; - - // m_ContextMenu.Dispose(); - // m_ContextMenu = null; - //} + if (m_ContextMenu != null) + { + m_ContextMenu.ContextMenuOpened -= ContextMenuHooks_ContextMenuOpened; + ((IDisposable)m_ContextMenu).Dispose(); + m_ContextMenu = null; + } } - //private void ContextMenuHooks_ContextMenuOpened(ContextMenuOpenedArgs contextMenuOpenedArgs) - //{ - // if (!m_PluginConfiguration.IsCustomTagsContextMenuEnabled - // || !SupportedAddonNames.Contains(contextMenuOpenedArgs.ParentAddonName)) - // { - // return; - // } + private void ContextMenuHooks_ContextMenuOpened(ContextMenuOpenedArgs contextMenuOpenedArgs) + { + if (!m_PluginConfiguration.IsCustomTagsContextMenuEnabled + || !SupportedAddonNames.Contains(contextMenuOpenedArgs.ParentAddonName)) + { + return; + } - // Identity? identity = m_PluginData.GetIdentity(contextMenuOpenedArgs); - // if (identity != null) - // { - // var notAddedTags = m_PluginData.CustomTags.Where(customTag => !identity.CustomTagIds.Contains(customTag.CustomId.Value)); - // if (notAddedTags.Any()) - // { - // contextMenuOpenedArgs.Items.Add(new OpenSubContextMenuItem(Strings.Loc_Static_ContextMenu_AddTag, (subContextMenuOpenedArgs => - // { - // List newContextMenuItems = new List(); - // foreach (var notAddedTag in notAddedTags) - // { - // newContextMenuItems.Add(new CustomContextMenuItem(notAddedTag.Text.Value, (args => - // { - // m_PluginData.AddCustomTagToIdentity(notAddedTag, identity); - // m_PluginConfiguration.Save(m_PluginData); - // }))); - // } - // subContextMenuOpenedArgs.Items.InsertRange(0, newContextMenuItems); - // }))); - // } + Identity? identity = m_PluginData.GetIdentity(contextMenuOpenedArgs); + if (identity != null) + { + var notAddedTags = m_PluginData.CustomTags.Where(customTag => !identity.CustomTagIds.Contains(customTag.CustomId.Value)); + if (notAddedTags.Any()) + { + contextMenuOpenedArgs.AddCustomSubMenu(Strings.Loc_Static_ContextMenu_AddTag, subContextMenuOpenedArgs => + { + foreach (var notAddedTag in notAddedTags) + { + subContextMenuOpenedArgs.AddCustomItem(notAddedTag.Text.Value, args => + { + m_PluginData.AddCustomTagToIdentity(notAddedTag, identity); + m_PluginConfiguration.Save(m_PluginData); + }); + } + }); + } - // var addedTags = m_PluginData.CustomTags.Where(customTag => identity.CustomTagIds.Contains(customTag.CustomId.Value)); - // if (addedTags.Any()) - // { - // contextMenuOpenedArgs.Items.Add(new OpenSubContextMenuItem(Strings.Loc_Static_ContextMenu_RemoveTag, (subContextMenuOpenedArgs => - // { - // List newContextMenuItems = new List(); - // foreach (var addedTag in addedTags) - // { - // newContextMenuItems.Add(new CustomContextMenuItem(addedTag.Text.Value, (args => - // { - // m_PluginData.RemoveCustomTagFromIdentity(addedTag, identity); - // m_PluginConfiguration.Save(m_PluginData); - // }))); - // } - // subContextMenuOpenedArgs.Items.InsertRange(0, newContextMenuItems); - // }))); - // } - // } - //} + var addedTags = m_PluginData.CustomTags.Where(customTag => identity.CustomTagIds.Contains(customTag.CustomId.Value)); + if (addedTags.Any()) + { + contextMenuOpenedArgs.AddCustomSubMenu(Strings.Loc_Static_ContextMenu_RemoveTag, subContextMenuOpenedArgs => + { + foreach (var addedTag in addedTags) + { + subContextMenuOpenedArgs.AddCustomItem(addedTag.Text.Value, args => + { + m_PluginData.RemoveCustomTagFromIdentity(addedTag, identity); + m_PluginConfiguration.Save(m_PluginData); + }); + } + }); + } + } + } } } diff --git a/PlayerTags/GameInterface/ContextMenus/ContextMenu.cs b/PlayerTags/GameInterface/ContextMenus/ContextMenu.cs deleted file mode 100644 index b32bae1..0000000 --- a/PlayerTags/GameInterface/ContextMenus/ContextMenu.cs +++ /dev/null @@ -1,646 +0,0 @@ -using Dalamud.Game; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Hooking; -using Dalamud.Logging; -using FFXIVClientStructs.FFXIV.Client.Game; -using FFXIVClientStructs.FFXIV.Client.UI; -using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using FFXIVClientStructs.FFXIV.Component.GUI; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; - -namespace PlayerTags.GameInterface.ContextMenus -{ - internal unsafe delegate void AtkValueChangeTypeDelegate_Unmanaged(AtkValue* thisPtr, FFXIVClientStructs.FFXIV.Component.GUI.ValueType type); - internal unsafe delegate void AtkValueSetStringDelegate_Unmanaged(AtkValue* thisPtr, byte* bytes); - - /// - /// Provides an interface to modify context menus. - /// - public class ContextMenu : IDisposable - { - private class PluginAddressResolver : BaseAddressResolver - { - private const string c_AtkValueChangeType = "E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24"; - public IntPtr? AtkValueChangeTypePtr { get; private set; } - - private const string c_AtkValueSetString = "E8 ?? ?? ?? ?? 41 03 ED"; - public IntPtr? AtkValueSetStringPtr { get; private set; } - - private const string c_GetAddonById = "E8 ?? ?? ?? ?? 8B 6B 20"; - public IntPtr? GetAddonByIdPtr { get; private set; } - - private const string c_OpenSubContextMenu = "E8 ?? ?? ?? ?? 44 39 A3 ?? ?? ?? ?? 0F 86"; - public IntPtr? OpenSubContextMenuPtr { get; private set; } - - private const string c_ContextMenuOpening = "E8 ?? ?? ?? ?? 0F B7 C0 48 83 C4 60"; - public IntPtr? ContextMenuOpeningPtr { get; private set; } - - private const string c_ContextMenuOpened = "48 8B C4 57 41 56 41 57 48 81 EC"; - public IntPtr? ContextMenuOpenedPtr { get; private set; } - - private const string c_ContextMenuItemSelected = "48 89 5C 24 ?? 55 57 41 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? 80 B9"; - public IntPtr? ContextMenuItemSelectedPtr { get; private set; } - - private const string c_SubContextMenuOpening = "E8 ?? ?? ?? ?? 44 39 A3 ?? ?? ?? ?? 0F 84"; - public IntPtr? SubContextMenuOpeningPtr { get; private set; } - - private const string c_SubContextMenuOpened = "48 8B C4 57 41 55 41 56 48 81 EC"; - public IntPtr? SubContextMenuOpenedPtr { get; private set; } - - protected override void Setup64Bit(SigScanner scanner) - { - if (scanner.TryScanText(c_AtkValueChangeType, out var atkValueChangeTypePtr)) - { - AtkValueChangeTypePtr = atkValueChangeTypePtr; - } - - if (scanner.TryScanText(c_AtkValueSetString, out var atkValueSetStringPtr)) - { - AtkValueSetStringPtr = atkValueSetStringPtr; - } - - if (scanner.TryScanText(c_GetAddonById, out var getAddonByIdPtr)) - { - GetAddonByIdPtr = getAddonByIdPtr; - } - - if (scanner.TryScanText(c_OpenSubContextMenu, out var openSubContextMenuPtr)) - { - OpenSubContextMenuPtr = openSubContextMenuPtr; - } - - if (scanner.TryScanText(c_ContextMenuOpening, out var someOpenAddonThingPtr)) - { - ContextMenuOpeningPtr = someOpenAddonThingPtr; - } - - if (scanner.TryScanText(c_ContextMenuOpened, out var contextMenuOpenedPtr)) - { - ContextMenuOpenedPtr = contextMenuOpenedPtr; - } - - if (scanner.TryScanText(c_ContextMenuItemSelected, out var contextMenuItemSelectedPtr)) - { - ContextMenuItemSelectedPtr = contextMenuItemSelectedPtr; - } - - if (scanner.TryScanText(c_SubContextMenuOpening, out var subContextMenuOpening)) - { - SubContextMenuOpeningPtr = subContextMenuOpening; - } - - if (scanner.TryScanText(c_SubContextMenuOpened, out var titleScreenContextMenuOpenedPtr)) - { - SubContextMenuOpenedPtr = titleScreenContextMenuOpenedPtr; - } - } - } - - private readonly AtkValueChangeTypeDelegate_Unmanaged? m_AtkValueChangeType; - private readonly AtkValueSetStringDelegate_Unmanaged? m_AtkValueSetString; - - private unsafe delegate AddonInterface* GetAddonByIdDelegate_Unmanaged(RaptureAtkUnitManager* raptureAtkUnitManager, ushort id); - private readonly GetAddonByIdDelegate_Unmanaged? m_GetAddonById; - - private unsafe delegate bool OpenSubContextMenuDelegate_Unmanaged(AgentContext* agentContext); - private readonly OpenSubContextMenuDelegate_Unmanaged? m_OpenSubContextMenu; - - private unsafe delegate IntPtr ContextMenuOpeningDelegate_Unmanaged(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, AgentContextInterface* agentContextInterface, IntPtr a7, ushort a8); - private Hook? m_ContextMenuOpeningHook; - - private unsafe delegate bool ContextMenuOpenedDelegate_Unmanaged(AddonContextMenu* addonContextMenu, int menuSize, AtkValue* atkValueArgs); - private Hook? m_ContextMenuOpenedHook; - private Hook? m_SubContextMenuOpenedHook; - - private unsafe delegate bool ContextMenuItemSelectedDelegate_Unmanaged(AddonContextMenu* addonContextMenu, int selectedIndex, byte a3); - private Hook? m_ContextMenuItemSelectedHook; - - private unsafe delegate bool SubContextMenuOpeningDelegate_Unmanaged(AgentContext* agentContext); - private Hook? m_SubContextMenuOpeningHook; - - private PluginAddressResolver m_PluginAddressResolver; - - private const int MaxContextMenuItemsPerContextMenu = 32; - - private unsafe AgentContextInterface* m_CurrentAgentContextInterface; - private IntPtr m_CurrentSubContextMenuTitle; - - private OpenSubContextMenuItem? m_SelectedOpenSubContextMenuItem; - private ContextMenuOpenedArgs? m_CurrentContextMenuOpenedArgs; - - /// - /// Occurs when a context menu is opened by the game. - /// - public event ContextMenuOpenedDelegate? ContextMenuOpened; - - /// - /// Whether the required hooks are in place and this instance is valid. - /// - public bool IsValid - { - get - { - if (!m_PluginAddressResolver.AtkValueChangeTypePtr.HasValue - || !m_PluginAddressResolver.AtkValueSetStringPtr.HasValue - || !m_PluginAddressResolver.GetAddonByIdPtr.HasValue - || !m_PluginAddressResolver.OpenSubContextMenuPtr.HasValue - || !m_PluginAddressResolver.ContextMenuOpeningPtr.HasValue - || !m_PluginAddressResolver.ContextMenuOpenedPtr.HasValue - || !m_PluginAddressResolver.ContextMenuItemSelectedPtr.HasValue - || !m_PluginAddressResolver.SubContextMenuOpeningPtr.HasValue - || !m_PluginAddressResolver.SubContextMenuOpenedPtr.HasValue - ) - { - return false; - } - - return true; - } - } - - public ContextMenu() - { - m_PluginAddressResolver = new PluginAddressResolver(); - m_PluginAddressResolver.Setup(); - if (!IsValid) - { - return; - } - - if (m_PluginAddressResolver.AtkValueChangeTypePtr.HasValue) - { - m_AtkValueChangeType = Marshal.GetDelegateForFunctionPointer(m_PluginAddressResolver.AtkValueChangeTypePtr.Value); - } - - if (m_PluginAddressResolver.AtkValueSetStringPtr.HasValue) - { - m_AtkValueSetString = Marshal.GetDelegateForFunctionPointer(m_PluginAddressResolver.AtkValueSetStringPtr.Value); - } - - if (m_PluginAddressResolver.GetAddonByIdPtr.HasValue) - { - m_GetAddonById = Marshal.GetDelegateForFunctionPointer(m_PluginAddressResolver.GetAddonByIdPtr.Value); - } - - if (m_PluginAddressResolver.OpenSubContextMenuPtr.HasValue) - { - m_OpenSubContextMenu = Marshal.GetDelegateForFunctionPointer(m_PluginAddressResolver.OpenSubContextMenuPtr.Value); - } - - if (m_PluginAddressResolver.ContextMenuOpeningPtr.HasValue) - { - unsafe - { - m_ContextMenuOpeningHook = new Hook(m_PluginAddressResolver.ContextMenuOpeningPtr.Value, ContextMenuOpeningDetour); - } - m_ContextMenuOpeningHook?.Enable(); - } - - if (m_PluginAddressResolver.ContextMenuOpenedPtr.HasValue) - { - unsafe - { - m_ContextMenuOpenedHook = new Hook(m_PluginAddressResolver.ContextMenuOpenedPtr.Value, ContextMenuOpenedDetour); - } - m_ContextMenuOpenedHook?.Enable(); - } - - if (m_PluginAddressResolver.ContextMenuItemSelectedPtr.HasValue) - { - unsafe - { - m_ContextMenuItemSelectedHook = new Hook(m_PluginAddressResolver.ContextMenuItemSelectedPtr.Value, ContextMenuItemSelectedDetour); - } - m_ContextMenuItemSelectedHook?.Enable(); - } - - if (m_PluginAddressResolver.SubContextMenuOpeningPtr.HasValue) - { - unsafe - { - m_SubContextMenuOpeningHook = new Hook(m_PluginAddressResolver.SubContextMenuOpeningPtr.Value, SubContextMenuOpeningDetour); - } - m_SubContextMenuOpeningHook?.Enable(); - } - - if (m_PluginAddressResolver.SubContextMenuOpenedPtr.HasValue) - { - unsafe - { - m_SubContextMenuOpenedHook = new Hook(m_PluginAddressResolver.SubContextMenuOpenedPtr.Value, SubContextMenuOpenedDetour); - } - m_SubContextMenuOpenedHook?.Enable(); - } - } - - public void Dispose() - { - m_SubContextMenuOpeningHook?.Disable(); - m_ContextMenuItemSelectedHook?.Disable(); - m_SubContextMenuOpenedHook?.Disable(); - m_ContextMenuOpenedHook?.Disable(); - m_ContextMenuOpeningHook?.Disable(); - } - - private unsafe IntPtr ContextMenuOpeningDetour(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, AgentContextInterface* agentContextInterface, IntPtr a7, ushort a8) - { - m_CurrentAgentContextInterface = agentContextInterface; - return m_ContextMenuOpeningHook!.Original(a1, a2, a3, a4, a5, agentContextInterface, a7, a8); - } - - private unsafe bool ContextMenuOpenedDetour(AddonContextMenu* addonContextMenu, int atkValueCount, AtkValue* atkValues) - { - if (m_ContextMenuOpenedHook == null) - { - return false; - } - - try - { - ContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues); - } - catch (Exception ex) - { - PluginLog.Error(ex, "ContextMenuOpenedDetour"); - } - - return m_ContextMenuOpenedHook.Original(addonContextMenu, atkValueCount, atkValues); - } - - private unsafe void ContextMenuOpenedImplementation(AddonContextMenu* addonContextMenu, ref int atkValueCount, ref AtkValue* atkValues) - { - if (m_AtkValueChangeType == null - || m_AtkValueSetString == null - || ContextMenuOpened == null - || m_CurrentAgentContextInterface == null) - { - return; - } - - ContextMenuReaderWriter contextMenuReaderWriter = new ContextMenuReaderWriter(m_CurrentAgentContextInterface, atkValueCount, atkValues); - - // Check for a title. - string? title = null; - if (m_SelectedOpenSubContextMenuItem != null) - { - title = m_SelectedOpenSubContextMenuItem.Name.TextValue; - - // Write the custom title - var titleAtkValue = &atkValues[1]; - fixed (byte* TtlePtr = m_SelectedOpenSubContextMenuItem.Name.Encode().NullTerminate()) - { - m_AtkValueSetString(titleAtkValue, TtlePtr); - } - } - else if (contextMenuReaderWriter.Title != null) - { - title = contextMenuReaderWriter.Title.TextValue; - } - - // Determine which event to raise. - ContextMenuOpenedDelegate contextMenuOpenedDelegate = ContextMenuOpened; - if (m_SelectedOpenSubContextMenuItem is OpenSubContextMenuItem openSubContextMenuItem) - { - contextMenuOpenedDelegate = openSubContextMenuItem.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 (IsInventoryContext(m_CurrentAgentContextInterface) && m_SelectedOpenSubContextMenuItem != null) - { - contextMenuItems = contextMenuItems.TakeLast(1).ToArray(); - } - - int beforeHashCode = GetContextMenuItemsHashCode(contextMenuItems); - - // Raise the event and get the context menu changes. - m_CurrentContextMenuOpenedArgs = NotifyContextMenuOpened(addonContextMenu, m_CurrentAgentContextInterface, title, contextMenuOpenedDelegate, contextMenuItems); - if (m_CurrentContextMenuOpenedArgs == null) - { - return; - } - - int afterHashCode = GetContextMenuItemsHashCode(m_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(m_CurrentContextMenuOpenedArgs.Items, m_AtkValueChangeType, m_AtkValueSetString); - - // Update the addon. - atkValueCount = *(&addonContextMenu->AtkValuesCount) = (ushort)contextMenuReaderWriter.AtkValueCount; - atkValues = *(&addonContextMenu->AtkValues) = contextMenuReaderWriter.AtkValues; - } - } - - private unsafe bool SubContextMenuOpeningDetour(AgentContext* agentContext) - { - if (m_SubContextMenuOpeningHook == null) - { - return false; - } - - if (SubContextMenuOpeningImplementation(agentContext)) - { - return true; - } - - return m_SubContextMenuOpeningHook.Original(agentContext); - } - - private unsafe bool SubContextMenuOpeningImplementation(AgentContext* agentContext) - { - if (m_OpenSubContextMenu == null - || m_AtkValueChangeType == null - || m_AtkValueSetString == null - || !(m_SelectedOpenSubContextMenuItem is OpenSubContextMenuItem)) - { - 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. - - m_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. - GameInterfaceHelper.GameFree(ref m_CurrentSubContextMenuTitle, (ulong)IntPtr.Size); - m_CurrentSubContextMenuTitle = GameInterfaceHelper.GameUIAllocate(1); - *(&(&agentContext->AgentContextInterface)->SubContextMenuTitle) = (byte*)m_CurrentSubContextMenuTitle; - *(byte*)m_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 - m_AtkValueChangeType(&(&agentContext->Items->AtkValues)[1], FFXIVClientStructs.FFXIV.Component.GUI.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) - { - if (m_SubContextMenuOpenedHook == null) - { - return false; - } - - try - { - SubContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues); - } - catch (Exception ex) - { - PluginLog.Error(ex, "SubContextMenuOpenedDetour"); - } - - return m_SubContextMenuOpenedHook.Original(addonContextMenu, atkValueCount, atkValues); - } - - private unsafe void SubContextMenuOpenedImplementation(AddonContextMenu* addonContextMenu, ref int atkValueCount, ref AtkValue* atkValues) - { - ContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues); - } - - private int GetContextMenuItemsHashCode(IEnumerable contextMenuItems) - { - unchecked - { - int hash = 17; - foreach (var item in contextMenuItems) - { - hash = hash * 23 + item.GetHashCode(); - } - return hash; - } - } - - private unsafe ContextMenuOpenedArgs? NotifyContextMenuOpened(AddonContextMenu* addonContextMenu, AgentContextInterface* agentContextInterface, string? title, ContextMenuOpenedDelegate contextMenuOpenedDelegate, IEnumerable initialContextMenuItems) - { - var parentAddonName = GetParentAddonName(&addonContextMenu->AddonInterface); - - InventoryItemContext? inventoryItemContext = null; - GameObjectContext? gameObjectContext = null; - if (IsInventoryContext(agentContextInterface)) - { - var agentInventoryContext = (AgentInventoryContext*)agentContextInterface; - inventoryItemContext = new InventoryItemContext(agentInventoryContext->InventoryItemId, agentInventoryContext->InventoryItemCount, agentInventoryContext->InventoryItemIsHighQuality); - } - else - { - var agentContext = (AgentContext*)agentContextInterface; - - uint? id = agentContext->GameObjectId; - if (id == 0) - { - id = null; - } - - ulong? contentId = agentContext->GameObjectContentId; - if (contentId == 0) - { - contentId = null; - } - - string? name; - unsafe - { - name = GameInterfaceHelper.ReadSeString((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((IntPtr)addonContextMenu, (IntPtr)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) - { - if (m_ContextMenuItemSelectedHook == null) - { - return false; - } - - try - { - ContextMenuItemSelectedImplementation(addonContextMenu, selectedIndex); - } - catch (Exception ex) - { - PluginLog.Error(ex, "ContextMenuItemSelectedDetour"); - } - - return m_ContextMenuItemSelectedHook.Original(addonContextMenu, selectedIndex, a3); - } - - private unsafe void ContextMenuItemSelectedImplementation(AddonContextMenu* addonContextMenu, int selectedIndex) - { - if (m_CurrentContextMenuOpenedArgs == null || selectedIndex == -1) - { - m_CurrentContextMenuOpenedArgs = null; - m_SelectedOpenSubContextMenuItem = null; - return; - } - - // Read the selected item directly from the game - ContextMenuReaderWriter contextMenuReaderWriter = new ContextMenuReaderWriter(m_CurrentAgentContextInterface, addonContextMenu->AtkValuesCount, addonContextMenu->AtkValues); - var gameContextMenuItems = contextMenuReaderWriter.Read(); - var gameSelectedItem = gameContextMenuItems.ElementAtOrDefault(selectedIndex); - - // This should be impossible - if (gameSelectedItem == null) - { - m_CurrentContextMenuOpenedArgs = null; - m_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 = m_CurrentContextMenuOpenedArgs.Items.FirstOrDefault(item => item.Name.Encode().SequenceEqual(gameSelectedItem.Name.Encode())); - - m_SelectedOpenSubContextMenuItem = null; - if (selectedItem is CustomContextMenuItem customContextMenuItem) - { - try - { - var customContextMenuItemSelectedArgs = new CustomContextMenuItemSelectedArgs(m_CurrentContextMenuOpenedArgs, customContextMenuItem); - customContextMenuItem.ItemSelected(customContextMenuItemSelectedArgs); - } - catch (Exception ex) - { - PluginLog.LogError(ex, "ContextMenuItemSelectedImplementation"); - } - } - else if (selectedItem is OpenSubContextMenuItem openSubContextMenuItem) - { - m_SelectedOpenSubContextMenuItem = openSubContextMenuItem; - } - - m_CurrentContextMenuOpenedArgs = null; - } - - private unsafe string? GetParentAddonName(AddonInterface* addonInterface) - { - if (m_GetAddonById == null) - { - return null; - } - - var parentAddonId = addonInterface->ParentAddonId; - if (parentAddonId == 0) - { - return null; - } - - var atkStage = AtkStage.GetSingleton(); - var parentAddon = m_GetAddonById(atkStage->RaptureAtkUnitManager, parentAddonId); - return GameInterfaceHelper.ReadString((IntPtr)(&parentAddon->Name)); - } - - private unsafe bool IsInventoryContext(AgentContextInterface* agentContextInterface) - { - if (agentContextInterface == AgentInventoryContext.Instance()) - { - return true; - } - - return false; - } - - private unsafe AddonInterface* GetAddonFromAgent(AgentInterface* agentInterface) - { - if (m_GetAddonById == null) - { - return null; - } - - if (agentInterface->AddonId == 0) - { - return null; - } - - return m_GetAddonById(AtkStage.GetSingleton()->RaptureAtkUnitManager, (ushort)agentInterface->AddonId); - } - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/ContextMenuItem.cs b/PlayerTags/GameInterface/ContextMenus/ContextMenuItem.cs deleted file mode 100644 index 779e9ac..0000000 --- a/PlayerTags/GameInterface/ContextMenus/ContextMenuItem.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Dalamud.Game.Text.SeStringHandling; -using System; -using System.Numerics; - -namespace PlayerTags.GameInterface.ContextMenus -{ - /// - /// An item in a context menu. - /// - public abstract class ContextMenuItem - { - /// - /// The name of the item. - /// - public SeString Name { get; set; } - - /// - /// Whether the item is enabled. When enabled, an item is selectable. - /// - public bool IsEnabled { get; set; } = true; - - /// - /// The indicator of the item. - /// - public ContextMenuItemIndicator Indicator { get; set; } = ContextMenuItemIndicator.None; - - /// - /// Initializes a new instance of the class. - /// - /// The name of the item. - public ContextMenuItem(SeString name) - { - Name = name; - } - - public override string ToString() - { - return Name.ToString(); - } - - public override int GetHashCode() - { - unchecked - { - int hash = 17; - hash = hash * 23 + new BigInteger(Name.Encode()).GetHashCode(); - hash = hash * 23 + IsEnabled.GetHashCode(); - hash = hash * 23 + ((int)Indicator).GetHashCode(); - return hash; - } - } - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/ContextMenuItemIndicator.cs b/PlayerTags/GameInterface/ContextMenus/ContextMenuItemIndicator.cs deleted file mode 100644 index 4fac0bf..0000000 --- a/PlayerTags/GameInterface/ContextMenus/ContextMenuItemIndicator.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace PlayerTags.GameInterface.ContextMenus -{ - /// - /// An indicator displayed on a context menu item. - /// - public enum ContextMenuItemIndicator - { - /// - /// The item has no indicator. - /// - None, - /// - /// The item has a previous indicator. - /// - Previous, - /// - /// The item has a next indicator. - /// - Next - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedArgs.cs b/PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedArgs.cs deleted file mode 100644 index 51abe36..0000000 --- a/PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedArgs.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Dalamud.Game.Text.SeStringHandling; -using System; -using System.Collections.Generic; - -namespace PlayerTags.GameInterface.ContextMenus -{ - /// - /// Provides data for methods. - /// - public class ContextMenuOpenedArgs - { - /// - /// The addon associated with the context menu. - /// - public IntPtr Addon { get; } - - /// - /// The agent associated with the context menu. - /// - public IntPtr Agent { get; } - - /// - /// The the name of the parent addon associated with the context menu. - /// - public string? ParentAddonName { get; } - - /// - /// The title of the context menu. - /// - public string? Title { get; init; } - - /// - /// The items in the context menu. - /// - public List Items { get; } - - /// - /// The game object context associated with the context menu. - /// - public GameObjectContext? GameObjectContext { get; init; } - - /// - /// The item context associated with the context menu. - /// - public InventoryItemContext? InventoryItemContext { get; init; } - - /// - /// Initializes a new instance of the class. - /// - /// The addon associated with the context menu. - /// The agent associated with the context menu. - /// The the name of the parent addon associated with the context menu. - /// The items in the context menu. - public ContextMenuOpenedArgs(IntPtr addon, IntPtr agent, string? parentAddonName, IEnumerable items) - { - Addon = addon; - Agent = agent; - ParentAddonName = parentAddonName; - Items = new List(items); - } - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedDelegate.cs b/PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedDelegate.cs deleted file mode 100644 index c6a22de..0000000 --- a/PlayerTags/GameInterface/ContextMenus/ContextMenuOpenedDelegate.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace PlayerTags.GameInterface.ContextMenus -{ - /// - /// Represents the method the event. - /// - /// The data associated with the event. - public delegate void ContextMenuOpenedDelegate(ContextMenuOpenedArgs args); -} diff --git a/PlayerTags/GameInterface/ContextMenus/ContextMenuReaderWriter.cs b/PlayerTags/GameInterface/ContextMenus/ContextMenuReaderWriter.cs deleted file mode 100644 index cd0b200..0000000 --- a/PlayerTags/GameInterface/ContextMenus/ContextMenuReaderWriter.cs +++ /dev/null @@ -1,526 +0,0 @@ -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Logging; -using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using FFXIVClientStructs.FFXIV.Component.GUI; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Runtime.InteropServices; - -namespace PlayerTags.GameInterface.ContextMenus -{ - internal unsafe class ContextMenuReaderWriter - { - private enum SubContextMenuStructLayout - { - Main, - Alternate - } - - private AgentContextInterface* m_AgentContextInterface; - - private int m_AtkValueCount; - public int AtkValueCount - { - get { return m_AtkValueCount; } - } - - private AtkValue* m_AtkValues; - public AtkValue* AtkValues - { - get { return m_AtkValues; } - } - - public int ContextMenuItemCount - { - get { return m_AtkValues[0].Int; } - } - - public bool HasTitle - { - get - { - bool isStringType = - (int)m_AtkValues[1].Type == 8 - || (int)m_AtkValues[1].Type == 38 - || m_AtkValues[1].Type == FFXIVClientStructs.FFXIV.Component.GUI.ValueType.String; - - return isStringType; - } - } - - public SeString? Title - { - get - { - if (HasTitle) - { - GameInterfaceHelper.TryReadSeString((IntPtr)(&m_AtkValues[1])->String, out var str); - return str; - } - - return null; - } - } - - public int HasPreviousIndicatorFlagsIndex - { - get - { - if (HasTitle) - { - return 6; - } - - return 2; - } - } - - public int HasNextIndicatorFlagsIndex - { - get - { - if (HasTitle) - { - return 5; - } - - return 3; - } - } - - public int FirstContextMenuItemIndex - { - get - { - if (HasTitle) - { - return 8; - } - - return 7; - } - } - - public int NameIndexOffset - { - get - { - if (HasTitle && StructLayout == SubContextMenuStructLayout.Alternate) - { - return 1; - } - - return 0; - } - } - - public int IsDisabledIndexOffset - { - get - { - if (HasTitle && StructLayout == SubContextMenuStructLayout.Alternate) - { - return 2; - } - - return ContextMenuItemCount; - } - } - - /// - /// 0x14000000 | action - /// - public int? MaskedActionIndexOffset - { - get - { - if (HasTitle && StructLayout == SubContextMenuStructLayout.Alternate) - { - return 3; - } - - return null; - } - } - - public int SequentialAtkValuesPerContextMenuItem - { - get - { - if (HasTitle && StructLayout == SubContextMenuStructLayout.Alternate) - { - return 4; - } - - return 1; - } - } - - public int TotalDesiredAtkValuesPerContextMenuItem - { - get - { - if (HasTitle && StructLayout == SubContextMenuStructLayout.Alternate) - { - return 4; - } - - return 2; - } - } - - public Vector2? Position - { - get - { - if (HasTitle) - { - return new Vector2(m_AtkValues[2].Int, m_AtkValues[3].Int); - } - - return null; - } - } - - public unsafe bool IsInventoryContext - { - get - { - if ((IntPtr)m_AgentContextInterface == (IntPtr)AgentInventoryContext.Instance()) - { - return true; - } - - return false; - } - } - - private SubContextMenuStructLayout? StructLayout - { - get - { - if (HasTitle) - { - if (m_AtkValues[7].Int == 8) - { - return SubContextMenuStructLayout.Alternate; - } - else if (m_AtkValues[7].Int == 1) - { - return SubContextMenuStructLayout.Main; - } - } - - return null; - } - } - - public byte NoopAction - { - get - { - if (IsInventoryContext) - { - return 0xff; - } - else - { - return 0x67; - } - } - } - - public byte OpenSubContextMenuAction - { - get - { - if (IsInventoryContext) - { - // This is actually the action to open the Second Tier context menu and we just hack around it - return 0x31; - } - else - { - return 0x66; - } - } - } - - public byte? FirstUnhandledAction - { - get - { - if (StructLayout != null && StructLayout == SubContextMenuStructLayout.Alternate) - { - return 0x68; - } - - return null; - } - } - - public ContextMenuReaderWriter(AgentContextInterface* agentContextInterface, int atkValueCount, AtkValue* atkValues) - { - PluginLog.Warning($"{(IntPtr)atkValues:X}"); - - m_AgentContextInterface = agentContextInterface; - m_AtkValueCount = atkValueCount; - m_AtkValues = atkValues; - } - - public GameContextMenuItem[] Read() - { - List gameContextMenuItems = new List(); - for (var contextMenuItemIndex = 0; contextMenuItemIndex < ContextMenuItemCount; contextMenuItemIndex++) - { - var contextMenuItemAtkValueBaseIndex = FirstContextMenuItemIndex + (contextMenuItemIndex * SequentialAtkValuesPerContextMenuItem); - - // Get the name - var nameAtkValue = &m_AtkValues[contextMenuItemAtkValueBaseIndex + NameIndexOffset]; - if (nameAtkValue->Type == 0) - { - continue; - } - var name = GameInterfaceHelper.ReadSeString((IntPtr)nameAtkValue->String); - - // Get the enabled state. Note that SE stores this as IsDisabled, NOT IsEnabled (those heathens) - var isEnabled = true; - bool isDisabledDefined = FirstContextMenuItemIndex + ContextMenuItemCount < AtkValueCount; - if (isDisabledDefined) - { - var isDisabledAtkValue = &m_AtkValues[contextMenuItemAtkValueBaseIndex + IsDisabledIndexOffset]; - isEnabled = isDisabledAtkValue->Int == 0; - } - - // Get the action - byte action = 0; - if (IsInventoryContext) - { - var actions = &((AgentInventoryContext*)m_AgentContextInterface)->Actions; - action = *(actions + contextMenuItemAtkValueBaseIndex); - } - else if (StructLayout != null && StructLayout.Value == SubContextMenuStructLayout.Alternate) - { - var redButtonActions = &((AgentContext*)m_AgentContextInterface)->Items->RedButtonActions; - action = (byte)*(redButtonActions + contextMenuItemIndex); - } - else - { - var actions = &((AgentContext*)m_AgentContextInterface)->Items->Actions; - action = *(actions + contextMenuItemAtkValueBaseIndex); - } - - // Get the has previous indicator flag - var hasPreviousIndicatorFlagsAtkValue = &m_AtkValues[HasPreviousIndicatorFlagsIndex]; - var hasPreviousIndicator = HasFlag(hasPreviousIndicatorFlagsAtkValue->UInt, contextMenuItemIndex); - - // Get the has next indicator flag - var hasNextIndicatorlagsAtkValue = &m_AtkValues[HasNextIndicatorFlagsIndex]; - var hasNextIndicator = HasFlag(hasNextIndicatorlagsAtkValue->UInt, contextMenuItemIndex); - - ContextMenuItemIndicator 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(); - } - - public unsafe void Write(IEnumerable contextMenuItems, AtkValueChangeTypeDelegate_Unmanaged atkValueChangeType, AtkValueSetStringDelegate_Unmanaged atkValueSetString, bool allowReallocate = true) - { - if (allowReallocate) - { - var newAtkValuesCount = FirstContextMenuItemIndex + (contextMenuItems.Count() * 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() * newAtkValuesCount; - var newAtkValuesArray = GameInterfaceHelper.GameUIAllocate((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(m_AtkValues, newAtkValues, newAtkValuesArraySize - arrayCountSize, (long)sizeof(AtkValue) * FirstContextMenuItemIndex); - - // Free the old array - IntPtr oldArray = (IntPtr)m_AtkValues - arrayCountSize; - ulong oldArrayCount = *(ulong*)oldArray; - ulong oldArraySize = arrayCountSize + ((ulong)sizeof(AtkValue) * oldArrayCount); - GameInterfaceHelper.GameFree(ref oldArray, oldArraySize); - - // Set the array count - *(ulong*)newAtkValuesArray = (ulong)newAtkValuesCount; - - m_AtkValueCount = newAtkValuesCount; - m_AtkValues = newAtkValues; - } - - // Set the context menu item count - const int contextMenuItemCountAtkValueIndex = 0; - var contextMenuItemCountAtkValue = &m_AtkValues[contextMenuItemCountAtkValueIndex]; - contextMenuItemCountAtkValue->UInt = (uint)contextMenuItems.Count(); - - // Clear the previous arrow flags - var hasPreviousIndicatorAtkValue = &m_AtkValues[HasPreviousIndicatorFlagsIndex]; - hasPreviousIndicatorAtkValue->UInt = 0; - - // Clear the next arrow flags - var hasNextIndiactorFlagsAtkValue = &m_AtkValues[HasNextIndicatorFlagsIndex]; - hasNextIndiactorFlagsAtkValue->UInt = 0; - - for (int contextMenuItemIndex = 0; contextMenuItemIndex < contextMenuItems.Count(); ++contextMenuItemIndex) - { - var contextMenuItem = contextMenuItems.ElementAt(contextMenuItemIndex); - - var contextMenuItemAtkValueBaseIndex = FirstContextMenuItemIndex + (contextMenuItemIndex * SequentialAtkValuesPerContextMenuItem); - - // Set the name - var nameAtkValue = &m_AtkValues[contextMenuItemAtkValueBaseIndex + NameIndexOffset]; - atkValueChangeType(nameAtkValue, FFXIVClientStructs.FFXIV.Component.GUI.ValueType.String); - fixed (byte* nameBytesPtr = contextMenuItem.Name.Encode().NullTerminate()) - { - atkValueSetString(nameAtkValue, nameBytesPtr); - } - - // Set the enabled state. Note that SE stores this as IsDisabled, NOT IsEnabled (those heathens) - var disabledAtkValue = &m_AtkValues[contextMenuItemAtkValueBaseIndex + IsDisabledIndexOffset]; - atkValueChangeType(disabledAtkValue, FFXIVClientStructs.FFXIV.Component.GUI.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 = NoopAction; - } - else if (contextMenuItem is OpenSubContextMenuItem openSubContextMenuItem) - { - action = OpenSubContextMenuAction; - } - - if (IsInventoryContext) - { - var actions = &((AgentInventoryContext*)m_AgentContextInterface)->Actions; - *(actions + FirstContextMenuItemIndex + contextMenuItemIndex) = action; - } - else if (StructLayout != null && StructLayout.Value == SubContextMenuStructLayout.Alternate && FirstUnhandledAction != null) - { - // Some weird placeholder goes here - var actions = &((AgentContext*)m_AgentContextInterface)->Items->Actions; - *(actions + FirstContextMenuItemIndex + contextMenuItemIndex) = (byte)(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 = &((AgentContext*)m_AgentContextInterface)->Items->UnkFunctionPointers; - *(unkFunctionPointers + FirstContextMenuItemIndex + contextMenuItemIndex) = *(unkFunctionPointers + FirstContextMenuItemIndex); - - // The real action goes here - var redButtonActions = &((AgentContext*)m_AgentContextInterface)->Items->RedButtonActions; - *(redButtonActions + contextMenuItemIndex) = action; - } - else - { - var actions = &((AgentContext*)m_AgentContextInterface)->Items->Actions; - *(actions + FirstContextMenuItemIndex + contextMenuItemIndex) = action; - } - - if (contextMenuItem.Indicator == ContextMenuItemIndicator.Previous) - { - SetFlag(ref hasPreviousIndicatorAtkValue->UInt, contextMenuItemIndex, true); - } - else if (contextMenuItem.Indicator == ContextMenuItemIndicator.Next) - { - 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 &= ~((uint)1 << itemIndex); - - if (value) - { - mask |= (uint)(1 << itemIndex); - } - } - - public void Log() - { - Log(m_AtkValueCount, m_AtkValues); - } - - public static void Log(int atkValueCount, AtkValue* atkValues) - { - PluginLog.Debug($"ContextMenuReader.Log"); - - for (int atkValueIndex = 0; atkValueIndex < atkValueCount; ++atkValueIndex) - { - var atkValue = &atkValues[atkValueIndex]; - - object? value = null; - if (atkValue->Type == FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int) - { - value = atkValue->Int; - } - else if (atkValue->Type == FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Bool) - { - value = atkValue->Byte; - } - else if (atkValue->Type == FFXIVClientStructs.FFXIV.Component.GUI.ValueType.UInt) - { - value = atkValue->UInt; - } - else if (atkValue->Type == FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Float) - { - value = atkValue->Float; - } - else if (atkValue->Type == FFXIVClientStructs.FFXIV.Component.GUI.ValueType.String - || (int)atkValue->Type == 38 - || (int)atkValue->Type == 8) - { - if (GameInterfaceHelper.TryReadSeString((IntPtr)atkValue->String, out var str)) - { - value = str; - } - } - else - { - value = $"{(IntPtr)atkValue->String:X}"; - } - - PluginLog.Debug($"atkValues[{atkValueIndex}]={(IntPtr)atkValue:X} {atkValue->Type}={value}"); - } - } - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/CustomContextMenuItem.cs b/PlayerTags/GameInterface/ContextMenus/CustomContextMenuItem.cs deleted file mode 100644 index f7cf303..0000000 --- a/PlayerTags/GameInterface/ContextMenus/CustomContextMenuItem.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Dalamud.Game.Text.SeStringHandling; - -namespace PlayerTags.GameInterface.ContextMenus -{ - /// - /// An item in a context menu with a user defined action. - /// - public class CustomContextMenuItem : ContextMenuItem - { - /// - /// The action that will be called when the item is selected. - /// - public CustomContextMenuItemSelectedDelegate ItemSelected { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the item. - /// The action that will be called when the item is selected. - internal CustomContextMenuItem(SeString name, CustomContextMenuItemSelectedDelegate itemSelected) - : base(name) - { - ItemSelected = itemSelected; - } - - public override int GetHashCode() - { - unchecked - { - int hash = base.GetHashCode(); - hash = hash * 23 + ItemSelected.GetHashCode(); - return hash; - } - } - } -} \ No newline at end of file diff --git a/PlayerTags/GameInterface/ContextMenus/CustomContextMenuItemSelectedArgs.cs b/PlayerTags/GameInterface/ContextMenus/CustomContextMenuItemSelectedArgs.cs deleted file mode 100644 index 75ba640..0000000 --- a/PlayerTags/GameInterface/ContextMenus/CustomContextMenuItemSelectedArgs.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace PlayerTags.GameInterface.ContextMenus -{ - /// - /// Provides data for methods. - /// - public class CustomContextMenuItemSelectedArgs - { - /// - /// The currently opened context menu. - /// - public ContextMenuOpenedArgs ContextMenuOpenedArgs { get; init; } - - /// - /// The selected item within the currently opened context menu. - /// - public CustomContextMenuItem SelectedItem { get; init; } - - /// - /// Initializes a new instance of the class. - /// - /// The currently opened context menu. - /// The selected item within the currently opened context menu. - public CustomContextMenuItemSelectedArgs(ContextMenuOpenedArgs contextMenuOpenedArgs, CustomContextMenuItem selectedItem) - { - ContextMenuOpenedArgs = contextMenuOpenedArgs; - SelectedItem = selectedItem; - } - } -} \ No newline at end of file diff --git a/PlayerTags/GameInterface/ContextMenus/CustomContextMenuItemSelectedDelegate.cs b/PlayerTags/GameInterface/ContextMenus/CustomContextMenuItemSelectedDelegate.cs deleted file mode 100644 index 53fc6c3..0000000 --- a/PlayerTags/GameInterface/ContextMenus/CustomContextMenuItemSelectedDelegate.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace PlayerTags.GameInterface.ContextMenus -{ - /// - /// Represents the method that handles when a is selected. - /// - /// The data associated with the selected . - public delegate void CustomContextMenuItemSelectedDelegate(CustomContextMenuItemSelectedArgs args); -} diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonContextMenu.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonContextMenu.cs deleted file mode 100644 index 96787b8..0000000 --- a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonContextMenu.cs +++ /dev/null @@ -1,14 +0,0 @@ -using FFXIVClientStructs.FFXIV.Component.GUI; -using System.Runtime.InteropServices; - -namespace FFXIVClientStructs.FFXIV.Client.UI -{ - [StructLayout(LayoutKind.Explicit)] - public struct AddonContextMenu - { - [FieldOffset(0x0)] public unsafe AddonInterface AddonInterface; - [FieldOffset(0x160)] public unsafe AtkValue* AtkValues; - [FieldOffset(0x1CA)] public ushort AtkValuesCount; - [FieldOffset(0x690)] public bool IsInitialMenu; - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonInterface.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonInterface.cs deleted file mode 100644 index 44e5e1e..0000000 --- a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/AddonInterface.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Runtime.InteropServices; - -namespace FFXIVClientStructs.FFXIV.Client.UI -{ - [StructLayout(LayoutKind.Explicit)] - public unsafe struct AddonInterface - { - [FieldOffset(0x8)] public byte Name; - [FieldOffset(0x1D2)] public ushort ParentAddonId; - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContext.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContext.cs deleted file mode 100644 index a34c8ca..0000000 --- a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContext.cs +++ /dev/null @@ -1,20 +0,0 @@ -using FFXIVClientStructs.FFXIV.Client.System.String; -using FFXIVClientStructs.FFXIV.Component.GUI; -using System.Runtime.InteropServices; - -namespace FFXIVClientStructs.FFXIV.Client.UI.Agent -{ - [StructLayout(LayoutKind.Explicit)] - public unsafe struct AgentContext - { - public static AgentContext* Instance() => (AgentContext*)System.Framework.Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Context); - - [FieldOffset(0x0)] public AgentInterface AgentInterface; - [FieldOffset(0x0)] public AgentContextInterface AgentContextInterface; - [FieldOffset(0xD18)] public unsafe AgentContextMenuItems* Items; - [FieldOffset(0xE08)] public Utf8String GameObjectName; - [FieldOffset(0xEE0)] public ulong GameObjectContentId; - [FieldOffset(0xEF0)] public uint GameObjectId; - [FieldOffset(0xF00)] public ushort GameObjectWorldId; - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextInterface.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextInterface.cs deleted file mode 100644 index 29895a6..0000000 --- a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextInterface.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FFXIVClientStructs.FFXIV.Component.GUI; -using System.Runtime.InteropServices; - -namespace FFXIVClientStructs.FFXIV.Client.UI.Agent -{ - [StructLayout(LayoutKind.Explicit)] - public unsafe struct AgentContextInterface - { - [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; - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextMenuItems.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextMenuItems.cs deleted file mode 100644 index 6b2e10b..0000000 --- a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentContextMenuItems.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FFXIVClientStructs.FFXIV.Component.GUI; -using System.Runtime.InteropServices; - -namespace FFXIVClientStructs.FFXIV.Client.UI.Agent -{ - [StructLayout(LayoutKind.Explicit)] - public struct AgentContextMenuItems - { - [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; - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentInventoryContext.cs b/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentInventoryContext.cs deleted file mode 100644 index a1134cf..0000000 --- a/PlayerTags/GameInterface/ContextMenus/FFXIVClientStructs/FFXIV/Client/UI/Agent/AgentInventoryContext.cs +++ /dev/null @@ -1,24 +0,0 @@ -using FFXIVClientStructs.FFXIV.Component.GUI; -using System.Runtime.InteropServices; - -namespace FFXIVClientStructs.FFXIV.Client.UI.Agent -{ - [StructLayout(LayoutKind.Explicit)] - public unsafe struct AgentInventoryContext - { - public static AgentInventoryContext* Instance() => (AgentInventoryContext*)System.Framework.Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.InventoryContext); - - [FieldOffset(0x0)] public AgentInterface AgentInterface; - [FieldOffset(0x0)] public AgentContextInterface 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; - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/GameContextMenuItem.cs b/PlayerTags/GameInterface/ContextMenus/GameContextMenuItem.cs deleted file mode 100644 index 49805d3..0000000 --- a/PlayerTags/GameInterface/ContextMenus/GameContextMenuItem.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Dalamud.Game.Text.SeStringHandling; - -namespace PlayerTags.GameInterface.ContextMenus -{ - /// - /// An item in a context menu that with a specific game action. - /// - public class GameContextMenuItem : ContextMenuItem - { - /// - /// The game action that will be handled when the item is selected. - /// - public byte SelectedAction { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the item. - /// The game action that will be handled when the item is selected. - public GameContextMenuItem(SeString name, byte selectedAction) - : base(name) - { - SelectedAction = selectedAction; - } - - public override int GetHashCode() - { - unchecked - { - int hash = base.GetHashCode(); - hash = hash * 23 + SelectedAction; - return hash; - } - } - } -} \ No newline at end of file diff --git a/PlayerTags/GameInterface/ContextMenus/GameObjectContext.cs b/PlayerTags/GameInterface/ContextMenus/GameObjectContext.cs deleted file mode 100644 index c7e7318..0000000 --- a/PlayerTags/GameInterface/ContextMenus/GameObjectContext.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Dalamud.Game.Text.SeStringHandling; - -namespace PlayerTags.GameInterface.ContextMenus -{ - /// - /// Provides game object context to a context menu. - /// - public class GameObjectContext - { - /// - /// The id of the game object. - /// - public uint? Id { get; } - - /// - /// The content id of the game object. - /// - public ulong? ContentId { get; } - - /// - /// The name of the game object. - /// - public string? Name { get; } - - /// - /// The world id of the game object. - /// - public ushort? WorldId { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The id of the game object. - /// The lower content id of the game object. - /// The name of the game object. - /// The world id of the game object. - public GameObjectContext(uint? id, ulong? contentId, string? name, ushort? worldId) - { - Id = id; - ContentId = contentId; - Name = name; - WorldId = worldId; - } - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/InventoryItemContext.cs b/PlayerTags/GameInterface/ContextMenus/InventoryItemContext.cs deleted file mode 100644 index f9d6ce2..0000000 --- a/PlayerTags/GameInterface/ContextMenus/InventoryItemContext.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace PlayerTags.GameInterface.ContextMenus -{ - /// - /// Provides inventory item context to a context menu. - /// - public class InventoryItemContext - { - /// - /// The id of the item. - /// - public uint Id { get; } - - /// - /// The count of the item in the stack. - /// - public uint Count { get; } - - /// - /// Whether the item is high quality. - /// - public bool IsHighQuality { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The id of the item. - /// The count of the item in the stack. - /// Whether the item is high quality. - public InventoryItemContext(uint id, uint count, bool isHighQuality) - { - Id = id; - Count = count; - IsHighQuality = isHighQuality; - } - } -} diff --git a/PlayerTags/GameInterface/ContextMenus/OpenSubContextMenuItem.cs b/PlayerTags/GameInterface/ContextMenus/OpenSubContextMenuItem.cs deleted file mode 100644 index a3549e6..0000000 --- a/PlayerTags/GameInterface/ContextMenus/OpenSubContextMenuItem.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Dalamud.Game.Text.SeStringHandling; - -namespace PlayerTags.GameInterface.ContextMenus -{ - /// - /// An item in a context menu that can open a sub context menu. - /// - public class OpenSubContextMenuItem : ContextMenuItem - { - /// - /// The action that will be called when the item is selected. - /// - public ContextMenuOpenedDelegate Opened { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The name of the item. - /// The action that will be called when the item is selected. - internal OpenSubContextMenuItem(SeString name, ContextMenuOpenedDelegate opened) - : base(name) - { - Opened = opened; - Indicator = ContextMenuItemIndicator.Next; - } - - public override int GetHashCode() - { - unchecked - { - int hash = base.GetHashCode(); - hash = hash * 23 + Opened.GetHashCode(); - return hash; - } - } - } -} \ No newline at end of file diff --git a/PlayerTags/PlayerTags.csproj b/PlayerTags/PlayerTags.csproj index e5dc305..dd3985a 100644 --- a/PlayerTags/PlayerTags.csproj +++ b/PlayerTags/PlayerTags.csproj @@ -5,7 +5,7 @@ - net5.0-windows + net5.0-windows7 x64 enable latest diff --git a/PlayerTags/PluginServices.cs b/PlayerTags/PluginServices.cs index 7728766..36902ee 100644 --- a/PlayerTags/PluginServices.cs +++ b/PlayerTags/PluginServices.cs @@ -5,6 +5,7 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Party; using Dalamud.Game.Command; using Dalamud.Game.Gui; +using Dalamud.Game.Gui.ContextMenus; using Dalamud.IoC; using Dalamud.Plugin;