Add context menu support.

This commit is contained in:
Ottermandias 2023-07-24 14:23:36 +02:00
parent ec27c3e931
commit 662ec617dc
5 changed files with 180 additions and 7 deletions

View file

@ -27,6 +27,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool HideApplyCheckmarks { get; set; } = false;
public bool SmallEquip { get; set; } = false;
public bool UnlockedItemMode { get; set; } = false;
public bool EnableGameContextMenu { get; set; } = true;
public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings;
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);

View file

@ -88,6 +88,7 @@
<ProjectReference Include="..\..\Penumbra\Penumbra.Api\Penumbra.Api.csproj" />
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Dalamud.ContextMenu" Version="1.2.3" />
</ItemGroup>
<ItemGroup>

View file

@ -2,6 +2,7 @@
using System.Runtime.CompilerServices;
using Dalamud.Interface;
using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
using Glamourer.State;
@ -19,15 +20,17 @@ public class SettingsTab : ITab
private readonly StateListener _stateListener;
private readonly CodeService _codeService;
private readonly PenumbraAutoRedraw _autoRedraw;
private readonly ContextMenuService _contextMenuService;
public SettingsTab(Configuration config, DesignFileSystemSelector selector, StateListener stateListener,
CodeService codeService, PenumbraAutoRedraw autoRedraw)
CodeService codeService, PenumbraAutoRedraw autoRedraw, ContextMenuService contextMenuService)
{
_config = config;
_selector = selector;
_stateListener = stateListener;
_codeService = codeService;
_autoRedraw = autoRedraw;
_contextMenuService = contextMenuService;
}
public ReadOnlySpan<byte> Label
@ -59,6 +62,15 @@ public class SettingsTab : ITab
Checkbox("Hide Application Checkboxes",
"Hide the application checkboxes in the Customization and Equipment panels of the design tab, and only show them under Application Rules.",
_config.HideApplyCheckmarks, v => _config.HideApplyCheckmarks = v);
Checkbox("Enable Game Context Menus", "Whether to show a Try On via Glamourer button on context menus for equippable items.",
_config.EnableGameContextMenu, v =>
{
_config.EnableGameContextMenu = v;
if (v)
_contextMenuService.Enable();
else
_contextMenuService.Disable();
});
if (Widget.DoubleModifierSelector("Design Deletion Modifier",
"A modifier you need to hold while clicking the Delete Design button for it to take effect.", 100 * ImGuiHelpers.GlobalScale,
_config.DeleteDesignModifier, v => _config.DeleteDesignModifier = v))

View file

@ -0,0 +1,158 @@
using System;
using Dalamud.ContextMenu;
using Dalamud.Game.Gui;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Glamourer.Events;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop;
public class ContextMenuService : IDisposable
{
public const int ItemSearchContextItemId = 0x1738;
public const int ChatLogContextItemId = 0x948;
private readonly ItemManager _items;
private readonly DalamudContextMenu _contextMenu = new();
private readonly StateManager _state;
private readonly ObjectManager _objects;
private readonly GameGui _gameGui;
private readonly Configuration _config;
public ContextMenuService(ItemManager items, StateManager state, ObjectManager objects, GameGui gameGui, Configuration config)
{
_items = items;
_state = state;
_objects = objects;
_gameGui = gameGui;
_config = config;
Enable();
}
public void Enable()
{
_contextMenu.OnOpenGameObjectContextMenu += AddGameObjectItem;
_contextMenu.OnOpenInventoryContextMenu += AddInventoryItem;
}
public void Disable()
{
_contextMenu.OnOpenGameObjectContextMenu -= AddGameObjectItem;
_contextMenu.OnOpenInventoryContextMenu -= AddInventoryItem;
}
public void Dispose()
{
Disable();
_contextMenu.Dispose();
}
private static readonly SeString TryOnString = new SeStringBuilder().AddUiForeground(SeIconChar.BoxedLetterG.ToIconString(), 541)
.AddText(" Try On").AddUiForegroundOff().BuiltString;
private void AddInventoryItem(InventoryContextMenuOpenArgs args)
{
var item = CheckInventoryItem(args.ItemId);
if (item != null)
args.AddCustomItem(item);
}
private InventoryContextMenuItem? CheckInventoryItem(uint itemId)
{
if (itemId > 500000)
itemId -= 500000;
if (!_items.ItemService.AwaitedService.TryGetValue(itemId, EquipSlot.MainHand, out var item))
return null;
return new InventoryContextMenuItem(TryOnString, GetInventoryAction(item));
}
private GameObjectContextMenuItem? CheckGameObjectItem(uint itemId)
{
if (itemId > 500000)
itemId -= 500000;
if (!_items.ItemService.AwaitedService.TryGetValue(itemId, EquipSlot.MainHand, out var item))
return null;
return new GameObjectContextMenuItem(TryOnString, GetGameObjectAction(item));
}
private unsafe GameObjectContextMenuItem? CheckGameObjectItem(IntPtr agent, int offset, Func<nint, bool> validate)
=> agent != IntPtr.Zero && validate(agent) ? CheckGameObjectItem(*(uint*)(agent + offset)) : null;
private unsafe GameObjectContextMenuItem? CheckGameObjectItem(IntPtr agent, int offset)
=> agent != IntPtr.Zero ? CheckGameObjectItem(*(uint*)(agent + offset)) : null;
private GameObjectContextMenuItem? CheckGameObjectItem(string name, int offset, Func<nint, bool> validate)
=> CheckGameObjectItem(_gameGui.FindAgentInterface(name), offset, validate);
private void AddGameObjectItem(GameObjectContextMenuOpenArgs args)
{
var item = args.ParentAddonName switch
{
"ItemSearch" => CheckGameObjectItem(args.Agent, ItemSearchContextItemId),
"ChatLog" => CheckGameObjectItem("ChatLog", ChatLogContextItemId, ValidateChatLogContext),
_ => null,
};
if (item != null)
args.AddCustomItem(item);
}
private DalamudContextMenu.InventoryContextMenuItemSelectedDelegate GetInventoryAction(EquipItem item)
{
return _ =>
{
var (id, playerData) = _objects.PlayerData;
if (!playerData.Valid)
return;
if (!_state.GetOrCreate(id, playerData.Objects[0], out var state))
return;
var slot = item.Type.ToSlot();
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual);
if (item.Type.ValidOffhand().IsOffhandType())
{
if (item.ModelId.Value is > 1600 and < 1651
&& _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual);
}
};
}
private DalamudContextMenu.GameObjectContextMenuItemSelectedDelegate GetGameObjectAction(EquipItem item)
{
return _ =>
{
var (id, playerData) = _objects.PlayerData;
if (!playerData.Valid)
return;
if (!_state.GetOrCreate(id, playerData.Objects[0], out var state))
return;
var slot = item.Type.ToSlot();
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual);
if (item.Type.ValidOffhand().IsOffhandType())
{
if (item.ModelId.Value is > 1600 and < 1651
&& _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual);
}
};
}
private static unsafe bool ValidateChatLogContext(nint agent)
=> *(uint*)(agent + ChatLogContextItemId + 8) == 3;
}

View file

@ -92,7 +92,8 @@ public static class ServiceManager
.AddSingleton<CustomizeUnlockManager>()
.AddSingleton<ItemUnlockManager>()
.AddSingleton<DatFileService>()
.AddSingleton<InventoryService>();
.AddSingleton<InventoryService>()
.AddSingleton<ContextMenuService>();
private static IServiceCollection AddDesigns(this IServiceCollection services)
=> services.AddSingleton<DesignManager>()