Context menu tweaks
- Don't write items to memory unless they change after the event call - Don't pass the Return item through to the event call; enforce that it must always appear last
This commit is contained in:
@@ -287,6 +287,13 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
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)
|
||||
{
|
||||
@@ -301,7 +308,7 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
}
|
||||
|
||||
// Get the existing items from the game.
|
||||
// For inventory sub context menus, we take only the last item -- the return item.
|
||||
// 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)
|
||||
@@ -309,6 +316,8 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
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)
|
||||
@@ -316,12 +325,20 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the new changes.
|
||||
contextMenuReaderWriter.Write(m_CurrentContextMenuOpenedArgs, m_SelectedOpenSubContextMenuItem, m_AtkValueChangeType, m_AtkValueSetString);
|
||||
int afterHashCode = GetContextMenuItemsHashCode(m_CurrentContextMenuOpenedArgs.Items);
|
||||
|
||||
// Update the addon.
|
||||
atkValueCount = *(&addonContextMenu->AtkValuesCount) = (ushort)contextMenuReaderWriter.AtkValueCount;
|
||||
atkValues = *(&addonContextMenu->AtkValues) = contextMenuReaderWriter.AtkValues;
|
||||
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)
|
||||
@@ -403,6 +420,19 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
ContextMenuOpenedImplementation(addonContextMenu, ref atkValueCount, ref atkValues);
|
||||
}
|
||||
|
||||
private int GetContextMenuItemsHashCode(IEnumerable<ContextMenuItem> 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<ContextMenuItem> initialContextMenuItems)
|
||||
{
|
||||
var parentAddonName = GetParentAddonName(&addonContextMenu->AddonInterface);
|
||||
@@ -455,6 +485,13 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
@@ -472,10 +509,14 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
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())
|
||||
{
|
||||
contextMenuItem.Agent = (IntPtr)agentContextInterface;
|
||||
|
||||
// TODO: Game doesn't support nested sub context menus, but we might be able to.
|
||||
if (contextMenuItem is OpenSubContextMenuItem && contextMenuOpenedArgs.Title != null)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace PlayerTags.GameInterface.ContextMenus
|
||||
{
|
||||
@@ -23,11 +24,6 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
/// </summary>
|
||||
public ContextMenuItemIndicator Indicator { get; set; } = ContextMenuItemIndicator.None;
|
||||
|
||||
/// <summary>
|
||||
/// The agent of the item.
|
||||
/// </summary>
|
||||
internal IntPtr Agent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContextMenuItem"/> class.
|
||||
/// </summary>
|
||||
@@ -41,5 +37,17 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +263,8 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
|
||||
public ContextMenuReaderWriter(AgentContextInterface* agentContextInterface, int atkValueCount, AtkValue* atkValues)
|
||||
{
|
||||
PluginLog.Warning($"{(IntPtr)atkValues:X}");
|
||||
|
||||
m_AgentContextInterface = agentContextInterface;
|
||||
m_AtkValueCount = atkValueCount;
|
||||
m_AtkValues = atkValues;
|
||||
@@ -330,7 +332,6 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
|
||||
var gameContextMenuItem = new GameContextMenuItem(name, action)
|
||||
{
|
||||
Agent = (IntPtr)m_AgentContextInterface,
|
||||
IsEnabled = isEnabled,
|
||||
Indicator = indicator
|
||||
};
|
||||
@@ -341,11 +342,11 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
return gameContextMenuItems.ToArray();
|
||||
}
|
||||
|
||||
public unsafe void Write(ContextMenuOpenedArgs contextMenuOpenedArgs, ContextMenuItem? selectedContextMenuItem, AtkValueChangeTypeDelegate_Unmanaged atkValueChangeType, AtkValueSetStringDelegate_Unmanaged atkValueSetString, bool allowReallocate = true)
|
||||
public unsafe void Write(IEnumerable<ContextMenuItem> contextMenuItems, AtkValueChangeTypeDelegate_Unmanaged atkValueChangeType, AtkValueSetStringDelegate_Unmanaged atkValueSetString, bool allowReallocate = true)
|
||||
{
|
||||
if (allowReallocate)
|
||||
{
|
||||
var newAtkValuesCount = FirstContextMenuItemIndex + (contextMenuOpenedArgs.Items.Count() * TotalDesiredAtkValuesPerContextMenuItem);
|
||||
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;
|
||||
@@ -375,20 +376,10 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
m_AtkValues = newAtkValues;
|
||||
}
|
||||
|
||||
// Set the custom title if appropriate
|
||||
if (selectedContextMenuItem is OpenSubContextMenuItem)
|
||||
{
|
||||
var titleAtkValue = &m_AtkValues[1];
|
||||
fixed (byte* TtlePtr = selectedContextMenuItem.Name.Encode().NullTerminate())
|
||||
{
|
||||
atkValueSetString(titleAtkValue, TtlePtr);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the context menu item count
|
||||
const int contextMenuItemCountAtkValueIndex = 0;
|
||||
var contextMenuItemCountAtkValue = &m_AtkValues[contextMenuItemCountAtkValueIndex];
|
||||
contextMenuItemCountAtkValue->UInt = (uint)contextMenuOpenedArgs.Items.Count();
|
||||
contextMenuItemCountAtkValue->UInt = (uint)contextMenuItems.Count();
|
||||
|
||||
// Clear the previous arrow flags
|
||||
var hasPreviousIndicatorAtkValue = &m_AtkValues[HasPreviousIndicatorFlagsIndex];
|
||||
@@ -398,9 +389,9 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
var hasNextIndiactorFlagsAtkValue = &m_AtkValues[HasNextIndicatorFlagsIndex];
|
||||
hasNextIndiactorFlagsAtkValue->UInt = 0;
|
||||
|
||||
for (int contextMenuItemIndex = 0; contextMenuItemIndex < contextMenuOpenedArgs.Items.Count(); ++contextMenuItemIndex)
|
||||
for (int contextMenuItemIndex = 0; contextMenuItemIndex < contextMenuItems.Count(); ++contextMenuItemIndex)
|
||||
{
|
||||
var contextMenuItem = contextMenuOpenedArgs.Items.ElementAt(contextMenuItemIndex);
|
||||
var contextMenuItem = contextMenuItems.ElementAt(contextMenuItemIndex);
|
||||
|
||||
var contextMenuItemAtkValueBaseIndex = FirstContextMenuItemIndex + (contextMenuItemIndex * SequentialAtkValuesPerContextMenuItem);
|
||||
|
||||
@@ -500,7 +491,7 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
object? value = null;
|
||||
if (atkValue->Type == FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int)
|
||||
{
|
||||
value = $"{atkValue->Int:X}";
|
||||
value = atkValue->Int;
|
||||
}
|
||||
else if (atkValue->Type == FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Bool)
|
||||
{
|
||||
|
||||
@@ -22,5 +22,15 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
{
|
||||
ItemSelected = itemSelected;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = base.GetHashCode();
|
||||
hash = hash * 23 + ItemSelected.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,5 +22,15 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
{
|
||||
SelectedAction = selectedAction;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = base.GetHashCode();
|
||||
hash = hash * 23 + SelectedAction;
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,5 +23,15 @@ namespace PlayerTags.GameInterface.ContextMenus
|
||||
Opened = opened;
|
||||
Indicator = ContextMenuItemIndicator.Next;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = base.GetHashCode();
|
||||
hash = hash * 23 + Opened.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user