feat: start adding our own shortcut support

This commit is contained in:
Anna 2022-01-28 23:01:10 -05:00
parent 60d1771c95
commit d844b000e5
11 changed files with 458 additions and 58 deletions

View File

@ -1,6 +1,8 @@
using System.Text;
using ChatTwo.Code;
using ChatTwo.GameFunctions.Types;
using ChatTwo.Util;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Hooking;
using Dalamud.Logging;
@ -25,6 +27,15 @@ internal sealed unsafe class Chat : IDisposable {
[Signature("4C 8B 81 ?? ?? ?? ?? 4D 85 C0 74 17", Fallibility = Fallibility.Fallible)]
private readonly delegate* unmanaged<RaptureLogModule*, uint, ulong> _getContentIdForChatEntry = null!;
[Signature("E8 ?? ?? ?? ?? 48 8D 4D A0 8B F8")]
private readonly delegate* unmanaged<IntPtr, Utf8String*, IntPtr, uint> _getKeybind = null!;
[Signature("E8 ?? ?? ?? ?? 48 3B F0 74 35")]
private readonly delegate* unmanaged<AtkStage*, IntPtr> _getFocus = null!;
[Signature("44 8B 89 ?? ?? ?? ?? 4C 8B C1")]
private readonly delegate* unmanaged<void*, int, IntPtr> _getTellHistory = null!;
// Hooks
private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value);
@ -33,6 +44,8 @@ internal sealed unsafe class Chat : IDisposable {
private delegate void ReplyInSelectedChatModeDelegate(AgentInterface* agent);
private delegate byte SetChatLogTellTarget(IntPtr a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort a6, byte a7);
[Signature(
"40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B F0 8B FA",
DetourName = nameof(ChatLogRefreshDetour)
@ -51,6 +64,12 @@ internal sealed unsafe class Chat : IDisposable {
)]
private Hook<ReplyInSelectedChatModeDelegate>? ReplyInSelectedChatModeHook { get; init; }
[Signature(
"E8 ?? ?? ?? ?? 4C 8B 7C 24 ?? EB 34",
DetourName = nameof(SetChatLogTellTargetDetour)
)]
private Hook<SetChatLogTellTarget>? SetChatLogTellTargetHook { get; init; }
// Offsets
#pragma warning disable 0649
@ -65,7 +84,7 @@ internal sealed unsafe class Chat : IDisposable {
// Events
internal delegate void ChatActivatedEventDelegate(string? input, InputChannel? channel, (string, string)? tellTarget);
internal delegate void ChatActivatedEventDelegate(string? input, ChannelSwitchInfo info);
internal event ChatActivatedEventDelegate? Activated;
@ -79,14 +98,18 @@ internal sealed unsafe class Chat : IDisposable {
this.ChatLogRefreshHook?.Enable();
this.ChangeChannelNameHook?.Enable();
this.ReplyInSelectedChatModeHook?.Enable();
this.SetChatLogTellTargetHook?.Enable();
this.Plugin.Framework.Update += this.InterceptKeybinds;
this.Plugin.ClientState.Login += this.Login;
this.Login(null, null);
}
public void Dispose() {
this.Plugin.ClientState.Login -= this.Login;
this.Plugin.Framework.Update -= this.InterceptKeybinds;
this.SetChatLogTellTargetHook?.Dispose();
this.ReplyInSelectedChatModeHook?.Dispose();
this.ChangeChannelNameHook?.Dispose();
this.ChatLogRefreshHook?.Dispose();
@ -94,6 +117,175 @@ internal sealed unsafe class Chat : IDisposable {
this.Activated = null;
}
private readonly Dictionary<string, Keybind> _keybinds = new();
internal IReadOnlyDictionary<string, Keybind> Keybinds => this._keybinds;
internal static readonly IReadOnlyDictionary<string, ChannelSwitchInfo> KeybindsToIntercept = new Dictionary<string, ChannelSwitchInfo> {
["CMD_CHAT"] = new(null),
["CMD_COMMAND"] = new(null, text: "/"),
["CMD_REPLY"] = new(InputChannel.Tell, rotate: RotateMode.Forward),
["CMD_REPLY_REV"] = new(InputChannel.Tell, rotate: RotateMode.Reverse),
["CMD_SAY"] = new(InputChannel.Say),
["CMD_YELL"] = new(InputChannel.Yell),
["CMD_SHOUT"] = new(InputChannel.Shout),
["CMD_PARTY"] = new(InputChannel.Party),
["CMD_ALLIANCE"] = new(InputChannel.Alliance),
["CMD_FREECOM"] = new(InputChannel.FreeCompany),
["PVPTEAM_CHAT"] = new(InputChannel.PvpTeam),
["CMD_CWLINKSHELL"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Forward),
["CMD_CWLINKSHELL_REV"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Reverse),
["CMD_CWLINKSHELL_1"] = new(InputChannel.CrossLinkshell1),
["CMD_CWLINKSHELL_2"] = new(InputChannel.CrossLinkshell2),
["CMD_CWLINKSHELL_3"] = new(InputChannel.CrossLinkshell3),
["CMD_CWLINKSHELL_4"] = new(InputChannel.CrossLinkshell4),
["CMD_CWLINKSHELL_5"] = new(InputChannel.CrossLinkshell5),
["CMD_CWLINKSHELL_6"] = new(InputChannel.CrossLinkshell6),
["CMD_CWLINKSHELL_7"] = new(InputChannel.CrossLinkshell7),
["CMD_CWLINKSHELL_8"] = new(InputChannel.CrossLinkshell8),
["CMD_LINKSHELL"] = new(InputChannel.Linkshell1, rotate: RotateMode.Forward),
["CMD_LINKSHELL_REV"] = new(InputChannel.Linkshell1, rotate: RotateMode.Reverse),
["CMD_LINKSHELL_1"] = new(InputChannel.Linkshell1),
["CMD_LINKSHELL_2"] = new(InputChannel.Linkshell2),
["CMD_LINKSHELL_3"] = new(InputChannel.Linkshell3),
["CMD_LINKSHELL_4"] = new(InputChannel.Linkshell4),
["CMD_LINKSHELL_5"] = new(InputChannel.Linkshell5),
["CMD_LINKSHELL_6"] = new(InputChannel.Linkshell6),
["CMD_LINKSHELL_7"] = new(InputChannel.Linkshell7),
["CMD_LINKSHELL_8"] = new(InputChannel.Linkshell8),
["CMD_BEGINNER"] = new(InputChannel.NoviceNetwork),
["CMD_REPLY_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Forward),
["CMD_REPLY_REV_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Reverse),
["CMD_SAY_ALWAYS"] = new(InputChannel.Say, true),
["CMD_YELL_ALWAYS"] = new(InputChannel.Yell, true),
["CMD_PARTY_ALWAYS"] = new(InputChannel.Party, true),
["CMD_ALLIANCE_ALWAYS"] = new(InputChannel.Alliance, true),
["CMD_FREECOM_ALWAYS"] = new(InputChannel.FreeCompany, true),
["PVPTEAM_CHAT_ALWAYS"] = new(InputChannel.PvpTeam, true),
["CMD_CWLINKSHELL_ALWAYS"] = new(InputChannel.CrossLinkshell1, true, RotateMode.Forward),
["CMD_CWLINKSHELL_ALWAYS_REV"] = new(InputChannel.CrossLinkshell1, true, RotateMode.Reverse),
["CMD_CWLINKSHELL_1_ALWAYS"] = new(InputChannel.CrossLinkshell1, true),
["CMD_CWLINKSHELL_2_ALWAYS"] = new(InputChannel.CrossLinkshell2, true),
["CMD_CWLINKSHELL_3_ALWAYS"] = new(InputChannel.CrossLinkshell3, true),
["CMD_CWLINKSHELL_4_ALWAYS"] = new(InputChannel.CrossLinkshell4, true),
["CMD_CWLINKSHELL_5_ALWAYS"] = new(InputChannel.CrossLinkshell5, true),
["CMD_CWLINKSHELL_6_ALWAYS"] = new(InputChannel.CrossLinkshell6, true),
["CMD_CWLINKSHELL_7_ALWAYS"] = new(InputChannel.CrossLinkshell7, true),
["CMD_CWLINKSHELL_8_ALWAYS"] = new(InputChannel.CrossLinkshell8, true),
["CMD_LINKSHELL_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Forward),
["CMD_LINKSHELL_REV_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Reverse),
["CMD_LINKSHELL_1_ALWAYS"] = new(InputChannel.Linkshell1, true),
["CMD_LINKSHELL_2_ALWAYS"] = new(InputChannel.Linkshell2, true),
["CMD_LINKSHELL_3_ALWAYS"] = new(InputChannel.Linkshell3, true),
["CMD_LINKSHELL_4_ALWAYS"] = new(InputChannel.Linkshell4, true),
["CMD_LINKSHELL_5_ALWAYS"] = new(InputChannel.Linkshell5, true),
["CMD_LINKSHELL_6_ALWAYS"] = new(InputChannel.Linkshell6, true),
["CMD_LINKSHELL_7_ALWAYS"] = new(InputChannel.Linkshell7, true),
["CMD_LINKSHELL_8_ALWAYS"] = new(InputChannel.Linkshell8, true),
["CMD_BEGINNER_ALWAYS"] = new(InputChannel.NoviceNetwork, true),
};
private bool _inputFocused;
private int _graceFrames;
private void CheckFocus() {
void Decrement() {
if (this._graceFrames > 0) {
this._graceFrames -= 1;
} else {
this._inputFocused = false;
}
}
var focus = this._getFocus(AtkStage.GetSingleton());
if (focus == IntPtr.Zero) {
Decrement();
return;
}
var node = (AtkResNode*) focus;
var parent = node->ParentNode;
if (parent == null || (uint) parent->Type is not (1007 or 1011)) {
Decrement();
return;
}
this._inputFocused = true;
this._graceFrames = 60;
}
private void UpdateKeybinds() {
foreach (var name in KeybindsToIntercept.Keys) {
var keybind = this.GetKeybind(name);
if (keybind is null) {
continue;
}
this._keybinds[name] = keybind;
}
}
private void InterceptKeybinds(Dalamud.Game.Framework framework) {
this.CheckFocus();
this.UpdateKeybinds();
if (this._inputFocused) {
return;
}
var modifierState = (ModifierFlag) 0;
foreach (var modifier in Enum.GetValues<ModifierFlag>()) {
var modifierKey = GetKeyForModifier(modifier);
if (modifierKey != VirtualKey.NO_KEY && this.Plugin.KeyState[modifierKey]) {
modifierState |= modifier;
}
}
var turnedOff = new Dictionary<VirtualKey, (uint, string)>();
foreach (var toIntercept in KeybindsToIntercept.Keys) {
if (!this.Keybinds.TryGetValue(toIntercept, out var keybind)) {
continue;
}
void Intercept(VirtualKey key, ModifierFlag modifier) {
if (!this.Plugin.KeyState.IsVirtualKeyValid(key)) {
return;
}
if (!modifierState.HasFlag(modifier)) {
return;
}
if (!this.Plugin.KeyState[key]) {
return;
}
var bits = NumUtil.NumberOfSetBits((uint) modifier);
if (!turnedOff.TryGetValue(key, out var previousBits) || previousBits.Item1 < bits) {
turnedOff[key] = (bits, toIntercept);
}
}
Intercept(keybind.Key1, keybind.Modifier1);
Intercept(keybind.Key2, keybind.Modifier2);
}
foreach (var (key, (_, keybind)) in turnedOff) {
PluginLog.Log($"intercepting {keybind}");
this.Plugin.KeyState[key] = false;
if (!KeybindsToIntercept.TryGetValue(keybind, out var info)) {
continue;
}
try {
this.Activated?.Invoke(null, info);
} catch (Exception ex) {
PluginLog.LogError(ex, "Error in chat Activated event");
}
}
}
private void Login(object? sender, EventArgs? e) {
if (this.ChangeChannelNameHook == null) {
return;
@ -107,6 +299,31 @@ internal sealed unsafe class Chat : IDisposable {
this.ChangeChannelNameDetour((IntPtr) agent);
}
private byte ChatLogRefreshDetour(IntPtr log, ushort eventId, AtkValue* value) {
if (eventId != 0x31 || value == null || value->UInt is not (0x05 or 0x0C)) {
return this.ChatLogRefreshHook!.Original(log, eventId, value);
}
string? eventInput = null;
var str = value + 2;
if (str != null && ((int) str->Type & 0xF) == (int) ValueType.String && str->String != null) {
var input = MemoryHelper.ReadStringNullTerminated((IntPtr) str->String);
if (input.Length > 0) {
eventInput = input;
}
}
try {
this.Activated?.Invoke(eventInput, new ChannelSwitchInfo(null));
} catch (Exception ex) {
PluginLog.LogError(ex, "Error in chat Activated event");
}
// prevent the game from focusing the chat log
return 1;
}
private IntPtr ChangeChannelNameDetour(IntPtr agent) {
// Last ShB patch
// +0x40 = chat channel (byte or uint?)
@ -158,56 +375,6 @@ internal sealed unsafe class Chat : IDisposable {
return ret;
}
private byte ChatLogRefreshDetour(IntPtr log, ushort eventId, AtkValue* value) {
if (eventId != 0x31 || value == null || value->UInt is not (0x05 or 0x0C)) {
return this.ChatLogRefreshHook!.Original(log, eventId, value);
}
InputChannel? channel = null;
(string, string)? tellTarget = null;
// if (this._shellChannelOffset != null) {
// var shell = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureShellModule();
//
// channel = (InputChannel) (*(uint*) (shell + this._shellChannelOffset.Value));
// if ((int) channel is 17 or 18) {
// channel = InputChannel.Tell;
//
// var targetPtr = *(byte**) (shell + 0xFD8);
// var targetWorldPtr = *(byte**) (shell + 0x1040);
//
// var target = MemoryHelper.ReadStringNullTerminated((IntPtr) targetPtr);
// var targetWorld = MemoryHelper.ReadStringNullTerminated((IntPtr) targetWorldPtr);
// tellTarget = (target, targetWorld);
// }
// }
string? eventInput = null;
var str = value + 2;
if (str != null && ((int) str->Type & 0xF) == (int) ValueType.String && str->String != null) {
var input = MemoryHelper.ReadStringNullTerminated((IntPtr) str->String);
if (input.Length > 0) {
eventInput = input;
}
}
try {
this.Activated?.Invoke(eventInput, channel, tellTarget);
} catch (Exception ex) {
PluginLog.LogError(ex, "Error in ChatActivated event");
}
// var ret = this.ChatLogRefreshHook!.Original(log, eventId, value);
// if (this._clearFocus != null) {
// this._clearFocus(AtkStage.GetSingleton());
// }
// return ret;
// return this.ChatLogRefreshHook!.Original(log, eventId, value);
return 1;
}
private void ReplyInSelectedChatModeDetour(AgentInterface* agent) {
if (this._replyChannelOffset == null) {
goto Original;
@ -224,6 +391,14 @@ internal sealed unsafe class Chat : IDisposable {
this.ReplyInSelectedChatModeHook!.Original(agent);
}
private byte SetChatLogTellTargetDetour(IntPtr a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort reason, byte a7) {
if (name != null) {
PluginLog.Log($"{name->ToString()}@{world} ({contentId}) {(TellReason) reason}");
}
return this.SetChatLogTellTargetHook!.Original(a1, name, a3, world, contentId, reason, a7);
}
internal ulong? GetContentIdForEntry(uint index) {
if (this._getContentIdForChatEntry == null) {
return null;
@ -254,4 +429,62 @@ internal sealed unsafe class Chat : IDisposable {
this._changeChatChannel(RaptureShellModule.Instance, (int) channel, idx, &target, 1);
}
}
private static VirtualKey GetKeyForModifier(ModifierFlag modifierFlag) => modifierFlag switch {
ModifierFlag.Shift => VirtualKey.SHIFT,
ModifierFlag.Ctrl => VirtualKey.CONTROL,
ModifierFlag.Alt => VirtualKey.MENU,
_ => VirtualKey.NO_KEY,
};
private Keybind? GetKeybind(string id) {
var agent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Configkey);
if (agent == IntPtr.Zero) {
return null;
}
var a1 = *(void**) (agent + 0x78);
if (a1 == null) {
return null;
}
var outData = stackalloc byte[32];
var idString = Utf8String.FromString(id);
this._getKeybind((IntPtr) a1, idString, (IntPtr) outData);
var key1 = (VirtualKey) outData[0];
if (key1 is VirtualKey.F23) {
key1 = VirtualKey.OEM_2;
}
var key2 = (VirtualKey) outData[2];
if (key2 is VirtualKey.F23) {
key2 = VirtualKey.OEM_2;
}
return new Keybind {
Key1 = key1,
Modifier1 = (ModifierFlag) outData[1],
Key2 = key2,
Modifier2 = (ModifierFlag) outData[3],
};
}
internal TellHistoryInfo? GetTellHistoryInfo(int index) {
var acquaintanceModule = Framework.Instance()->GetUiModule()->GetAcquaintanceModule();
if (acquaintanceModule == null) {
return null;
}
var ptr = this._getTellHistory(acquaintanceModule, index);
if (ptr == IntPtr.Zero) {
return null;
}
var name = MemoryHelper.ReadStringNullTerminated(*(IntPtr*) ptr);
var world = *(ushort*) (ptr + 0xD0);
var contentId = *(ulong*) (ptr + 0xD8);
return new TellHistoryInfo(name, world, contentId);
}
}

View File

@ -0,0 +1,17 @@
using ChatTwo.Code;
namespace ChatTwo.GameFunctions.Types;
internal class ChannelSwitchInfo {
internal InputChannel? Channel { get; }
internal bool Permanent { get; }
internal RotateMode Rotate { get; }
internal string? Text { get; }
internal ChannelSwitchInfo(InputChannel? channel, bool permanent = false, RotateMode rotate = RotateMode.None, string? text = null) {
this.Channel = channel;
this.Permanent = permanent;
this.Rotate = rotate;
this.Text = text;
}
}

View File

@ -0,0 +1,11 @@
using Dalamud.Game.ClientState.Keys;
namespace ChatTwo.GameFunctions.Types;
internal class Keybind {
internal VirtualKey Key1 { get; init; }
internal ModifierFlag Modifier1 { get; init; }
internal VirtualKey Key2 { get; init; }
internal ModifierFlag Modifier2 { get; init; }
}

View File

@ -0,0 +1,8 @@
namespace ChatTwo.GameFunctions.Types;
[Flags]
internal enum ModifierFlag {
Shift = 1 << 0,
Ctrl = 1 << 1,
Alt = 1 << 2,
}

View File

@ -0,0 +1,7 @@
namespace ChatTwo.GameFunctions.Types;
internal enum RotateMode {
None,
Forward,
Reverse,
}

View File

@ -0,0 +1,13 @@
namespace ChatTwo.GameFunctions.Types;
internal sealed class TellHistoryInfo {
internal string Name { get; }
internal uint World { get; }
internal ulong ContentId { get; }
internal TellHistoryInfo(string name, uint world, ulong contentId) {
this.Name = name;
this.World = world;
this.ContentId = contentId;
}
}

View File

@ -0,0 +1,7 @@
namespace ChatTwo.GameFunctions.Types;
internal enum TellReason {
Direct = 0,
PartyFinder = 1,
Friend = 3,
}

View File

@ -154,6 +154,10 @@ internal sealed class PayloadHandler {
}
private void HoverItem(ItemPayload item) {
if (item.Item == null) {
return;
}
if (this.Ui.Plugin.TextureCache.GetItem(item.Item, item.IsHQ) is { } icon) {
InlineIcon(icon);
}
@ -232,6 +236,10 @@ internal sealed class PayloadHandler {
}
private void DrawItemPopup(ItemPayload item) {
if (item.Item == null) {
return;
}
if (this.Ui.Plugin.TextureCache.GetItem(item.Item, item.IsHQ) is { } icon) {
InlineIcon(icon);
}

View File

@ -1,6 +1,7 @@
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Party;
using Dalamud.Game.Command;
@ -35,6 +36,9 @@ public sealed class Plugin : IDalamudPlugin {
[PluginService]
internal GameGui GameGui { get; init; }
[PluginService]
internal KeyState KeyState { get; init; }
[PluginService]
internal ObjectTable ObjectTable { get; init; }

View File

@ -1,11 +1,12 @@
using System.Numerics;
using ChatTwo.Code;
using ChatTwo.GameFunctions.Types;
using ChatTwo.Util;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Command;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface;
using Dalamud.Memory;
using Dalamud.Utility;
using Dalamud.Logging;
using ImGuiNET;
using ImGuiScene;
using Lumina.Excel.GeneratedSheets;
@ -24,7 +25,8 @@ internal sealed class ChatLog : IUiComponent {
private int _inputBacklogIdx = -1;
private int _lastTab;
private InputChannel? _tempChannel;
private (string, string)? _tellTarget;
private (string, string, ulong)? _tellTarget;
private int _tellIdx;
private PayloadHandler PayloadHandler { get; }
private Dictionary<string, ChatType> TextCommandChannels { get; } = new();
@ -50,17 +52,44 @@ internal sealed class ChatLog : IUiComponent {
this.Ui.Plugin.CommandManager.RemoveHandler("/clearlog2");
}
private void Activated(string? input, InputChannel? channel, (string, string)? tellTarget) {
private void Activated(string? input, ChannelSwitchInfo info) {
this.Activate = true;
if (input != null && !this.Chat.Contains(input)) {
this.Chat += input;
}
if (channel != null && this.Ui.Plugin.Functions.Chat.Channel.channel != channel) {
this._tempChannel = channel;
if (info.Channel != null) {
var prevTemp = this._tempChannel;
if (info.Permanent) {
this.Ui.Plugin.Functions.Chat.SetChannel(info.Channel.Value);
} else {
this._tempChannel = info.Channel.Value;
}
if (info.Channel is InputChannel.Tell && info.Rotate != RotateMode.None) {
this._tellIdx = prevTemp != InputChannel.Tell
? 0
: info.Rotate == RotateMode.Reverse
? -1
: 1;
var tellInfo = this.Ui.Plugin.Functions.Chat.GetTellHistoryInfo(this._tellIdx);
if (tellInfo != null) {
var world = this.Ui.Plugin.DataManager.GetExcelSheet<World>()?.GetRow(tellInfo.World);
if (world != null) {
this._tellTarget = (tellInfo.Name, world.Name, tellInfo.ContentId);
}
}
} else {
this._tellIdx = 0;
this._tellTarget = null;
}
}
this._tellTarget = tellTarget;
if (info.Text != null && this.Chat.Length == 0) {
this.Chat = info.Text;
}
}
private void ClearLog(string command, string arguments) {
@ -140,6 +169,55 @@ internal sealed class ChatLog : IUiComponent {
private unsafe ImGuiViewport* _lastViewport;
private void HandleKeybinds() {
var modifierState = (ModifierFlag) 0;
if (ImGui.GetIO().KeyAlt) {
modifierState |= ModifierFlag.Alt;
}
if (ImGui.GetIO().KeyCtrl) {
modifierState |= ModifierFlag.Ctrl;
}
if (ImGui.GetIO().KeyShift) {
modifierState |= ModifierFlag.Shift;
}
var turnedOff = new Dictionary<VirtualKey, (uint, string)>();
foreach (var (toIntercept, keybind) in this.Ui.Plugin.Functions.Chat.Keybinds) {
if (toIntercept is "CMD_CHAT" or "CMD_COMMAND") {
continue;
}
void Intercept(VirtualKey key, ModifierFlag modifier) {
if (!ImGui.IsKeyPressed((int) key) || !modifierState.HasFlag(modifier)) {
return;
}
var bits = NumUtil.NumberOfSetBits((uint) modifier);
if (!turnedOff.TryGetValue(key, out var previousBits) || previousBits.Item1 < bits) {
turnedOff[key] = (bits, toIntercept);
}
}
Intercept(keybind.Key1, keybind.Modifier1);
Intercept(keybind.Key2, keybind.Modifier2);
}
foreach (var (_, (_, keybind)) in turnedOff) {
PluginLog.Log($"intercepting {keybind}");
if (!GameFunctions.Chat.KeybindsToIntercept.TryGetValue(keybind, out var info)) {
continue;
}
try {
this.Activated(null, info);
} catch (Exception ex) {
PluginLog.LogError(ex, "Error in chat Activated event");
}
}
}
public unsafe void Draw() {
var flags = ImGuiWindowFlags.None;
if (!this.Ui.Plugin.Config.CanMove) {
@ -271,8 +349,13 @@ internal sealed class ChatLog : IUiComponent {
this.Chat = string.Empty;
}
if (ImGui.IsItemActive()) {
this.HandleKeybinds();
}
if (!this.Activate && !ImGui.IsItemActive()) {
this._tempChannel = null;
this._tellIdx = 0;
}
if (inputColour != null) {

9
ChatTwo/Util/NumUtil.cs Executable file
View File

@ -0,0 +1,9 @@
namespace ChatTwo.Util;
internal static class NumUtil {
internal static uint NumberOfSetBits(uint i) {
i -= (i >> 1) & 0x55555555;
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}
}