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:
r00telement
2022-01-23 23:39:32 +00:00
parent d9012eb6ec
commit 6031310aa8
6 changed files with 100 additions and 30 deletions

View File

@@ -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)
{

View File

@@ -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;
}
}
}
}

View File

@@ -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)
{

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}
}