From c22bde296daa26be7362f7abb690336e6fd2d34a Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Sat, 27 Jul 2024 03:31:19 +1000 Subject: [PATCH] fix: various keybind fixes (modifiers and linkshells) - Avoids overriding modifiers after leaving a vanilla text box. This would prevent you from holding e.g. Ctrl between multiple vanilla text boxes. - Reworks linkshell rotation code to fix issues with LS number gaps causing linkshell rotation to not function. --- ChatTwo/GameFunctions/Chat.cs | 49 ++++++++++++++++++++++ ChatTwo/GameFunctions/KeybindManager.cs | 8 ++-- ChatTwo/Ui/ChatLogWindow.cs | 56 ++++++++++++++----------- 3 files changed, 85 insertions(+), 28 deletions(-) diff --git a/ChatTwo/GameFunctions/Chat.cs b/ChatTwo/GameFunctions/Chat.cs index d6a5c78..715aea9 100755 --- a/ChatTwo/GameFunctions/Chat.cs +++ b/ChatTwo/GameFunctions/Chat.cs @@ -354,6 +354,55 @@ internal sealed unsafe class Chat : IDisposable return InfoProxyCrossWorldLinkshell.Instance()->CrossWorldLinkshells[(int) idx].Name.Length > 0; } + private static uint? RotateLinkshell(uint currentIndex, RotateMode rotate, Func validFn) + { + if (rotate == RotateMode.None) + return null; + var delta = rotate switch + { + RotateMode.Forward => 1, + RotateMode.Reverse => -1, + }; + + // Iterate up to 8 times to find a valid linkshell. + for (var i = 0; i < 8; i++) + { + currentIndex = (uint) ((8 + currentIndex + delta) % 8); + if (validFn(currentIndex)) + return currentIndex; + } + + return null; + } + + internal static InputChannel? ResolveTempInputChannel(InputChannel? currentTempChannel, InputChannel channel, RotateMode rotate) + { + switch (channel) + { + case InputChannel.Linkshell1 or InputChannel.CrossLinkshell1 when rotate != RotateMode.None: + { + // If we're activating for the first time, start at the beginning + // or end of the linkshell list depending on the rotate mode. + var currentIndex = rotate == RotateMode.Forward ? 7u : 0u; + if (currentTempChannel != null) + { + switch (channel) + { + case InputChannel.Linkshell1 when currentTempChannel.Value.IsLinkshell(): + case InputChannel.CrossLinkshell1 when currentTempChannel.Value.IsCrossLinkshell(): + currentIndex = currentTempChannel.Value.LinkshellIndex(); + break; + } + } + + var idx = RotateLinkshell(currentIndex, rotate, channel == InputChannel.Linkshell1 ? ValidLinkshell : ValidCrossLinkshell); + return channel + idx; + } + default: + return channel; + } + } + internal static void SetChannel(InputChannel channel, string? tellTarget = null) { // ExtraChat linkshells aren't supported in game so we never want to diff --git a/ChatTwo/GameFunctions/KeybindManager.cs b/ChatTwo/GameFunctions/KeybindManager.cs index ecc3a95..d0d174e 100644 --- a/ChatTwo/GameFunctions/KeybindManager.cs +++ b/ChatTwo/GameFunctions/KeybindManager.cs @@ -396,11 +396,13 @@ internal unsafe class KeybindManager : IDisposable { return; } - // If the vanilla text input has just lost focus, clear all keys so we - // don't try to process them immediately. + // If the vanilla text input has just lost focus, clear all non-modifier + // keys so we don't try to process them immediately on the next frame. if (VanillaTextInputHasFocus) { - Plugin.KeyState.ClearAll(); + foreach (var key in Plugin.KeyState.GetValidVirtualKeys()) + if (key is not VirtualKey.CONTROL and not VirtualKey.SHIFT and not VirtualKey.MENU) + Plugin.KeyState[key] = false; VanillaTextInputHasFocus = false; return; } diff --git a/ChatTwo/Ui/ChatLogWindow.cs b/ChatTwo/Ui/ChatLogWindow.cs index b68a113..6168a93 100644 --- a/ChatTwo/Ui/ChatLogWindow.cs +++ b/ChatTwo/Ui/ChatLogWindow.cs @@ -172,20 +172,12 @@ public sealed class ChatLogWindow : Window if (info.Channel != null) { - if (!GameFunctions.Chat.ValidAnyLinkshell(info.Channel.Value)) - return; - - var prevTemp = TempChannel; - if (info.Permanent) - SetChannel(info.Channel.Value); - else - TempChannel = info.Channel.Value; - + var targetChannel = info.Channel; if (info.Channel is InputChannel.Tell) { if (info.Rotate != RotateMode.None) { - var idx = prevTemp != InputChannel.Tell + var idx = TempChannel != InputChannel.Tell ? 0 : info.Rotate == RotateMode.Reverse ? -1 : 1; @@ -205,27 +197,41 @@ public sealed class ChatLogWindow : Window TellTarget = null; } - var mode = prevTemp == null ? RotateMode.None : info.Rotate; - if (info.Channel is InputChannel.Linkshell1 && info.Rotate != RotateMode.None) + if (info.Channel is InputChannel.Linkshell1 or InputChannel.CrossLinkshell1 && info.Rotate != RotateMode.None) { - var idx = GameFunctions.Chat.RotateLinkshellHistory(mode); - if (idx < 0 || !GameFunctions.Chat.ValidLinkshell((uint)idx)) + // If any of these operations fail, do nothing. + targetChannel = null; + if (info.Permanent) { - TempChannel = 0; - return; + // Rotate using the game's code. + if (info.Channel == InputChannel.Linkshell1) + { + var idx = GameFunctions.Chat.RotateLinkshellHistory(info.Rotate); + if (idx >= 0 && GameFunctions.Chat.ValidLinkshell((uint)idx)) + targetChannel = info.Channel + (uint)idx; + } + else + { + var idx = GameFunctions.Chat.RotateCrossLinkshellHistory(info.Rotate); + if (idx >= 0 && GameFunctions.Chat.ValidCrossLinkshell((uint)idx)) + targetChannel = info.Channel + (uint)idx; + } + } + else + { + targetChannel = GameFunctions.Chat.ResolveTempInputChannel(TempChannel, info.Channel.Value, info.Rotate); } - TempChannel = info.Channel.Value + (uint) idx; } - else if (info.Channel is InputChannel.CrossLinkshell1 && info.Rotate != RotateMode.None) + + if (targetChannel == null || !GameFunctions.Chat.ValidAnyLinkshell(targetChannel.Value)) { - var idx = GameFunctions.Chat.RotateCrossLinkshellHistory(mode); - if (idx < 0 || !GameFunctions.Chat.ValidCrossLinkshell((uint)idx)) - { - TempChannel = 0; - return; - } - TempChannel = info.Channel.Value + (uint) idx; + Plugin.Log.Warning($"Channel was set to an invalid value '{targetChannel}', ignoring"); + return; } + if (info.Permanent) + SetChannel(targetChannel); + else + TempChannel = targetChannel; } if (info.Text != null && Chat.Length == 0)