From fed420901cb48feef80120be0713678004d038ee Mon Sep 17 00:00:00 2001 From: Infi Date: Mon, 8 Apr 2024 03:26:49 +0200 Subject: [PATCH] Working tells for eureka and bozja Implemented the same focus requirements that vanilla chat has, so as long as the input retains focus, on focus loss it automatically restore the previous channel --- ChatTwo/GameFunctions/Chat.cs | 251 ++++++++++++++++++++-------------- ChatTwo/PayloadHandler.cs | 45 ++++-- ChatTwo/Ui/ChatLogWindow.cs | 36 +++-- 3 files changed, 204 insertions(+), 128 deletions(-) diff --git a/ChatTwo/GameFunctions/Chat.cs b/ChatTwo/GameFunctions/Chat.cs index ad1badb..33ff0c9 100755 --- a/ChatTwo/GameFunctions/Chat.cs +++ b/ChatTwo/GameFunctions/Chat.cs @@ -25,49 +25,52 @@ internal sealed unsafe class Chat : IDisposable { // Functions [Signature("E8 ?? ?? ?? ?? 0F B7 44 37 ??", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _changeChatChannel = null!; + private readonly delegate* unmanaged ChangeChatChannel = null!; + + [Signature("48 89 5C 24 ?? 55 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 02", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged SetChannelTargetTell = null!; [Signature("4C 8B 81 ?? ?? ?? ?? 4D 85 C0 74 17", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _getContentIdForChatEntry = null!; + private readonly delegate* unmanaged GetContentIdForChatEntry = null!; [Signature("E8 ?? ?? ?? ?? 48 8D 4D A0 8B F8")] - private readonly delegate* unmanaged _getKeybind = null!; + private readonly delegate* unmanaged GetKeybindNative = null!; [Signature("E8 ?? ?? ?? ?? 48 3B F0 74 35")] - private readonly delegate* unmanaged _getFocus = null!; + private readonly delegate* unmanaged GetFocus = null!; [Signature("44 8B 89 ?? ?? ?? ?? 4C 8B C1 45 85 C9")] - private readonly delegate* unmanaged _getTellHistory = null!; + private readonly delegate* unmanaged GetTellHistory = null!; [Signature("E8 ?? ?? ?? ?? 48 8D 4D 50 E8 ?? ?? ?? ?? 48 8B 17")] - private readonly delegate* unmanaged _printTell = null!; + private readonly delegate* unmanaged PrintTell = null!; [Signature("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8C 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01")] - private readonly delegate* unmanaged _sendTell = null!; + private readonly delegate* unmanaged SendTellNative = null!; [Signature("E8 ?? ?? ?? ?? F6 43 0A 40")] - private readonly delegate* unmanaged _getNetworkModule = null!; + private readonly delegate* unmanaged GetNetworkModule = null!; [Signature("E8 ?? ?? ?? ?? 48 8B C8 E8 ?? ?? ?? ?? 45 8D 46 FB")] - private readonly delegate* unmanaged _getCrossLinkshellName = null!; + private readonly delegate* unmanaged GetCrossLinkshellNameNative = null!; [Signature("3B 51 10 73 0F 8B C2 48 83 C0 0B")] - private readonly delegate* unmanaged _getLinkshellInfo = null!; + private readonly delegate* unmanaged GetLinkshellInfo = null!; [Signature("E8 ?? ?? ?? ?? 4C 8B C8 44 8D 47 01")] - private readonly delegate* unmanaged _getLinkshellName = null!; + private readonly delegate* unmanaged GetLinkshellNameNative = null!; [Signature("40 56 41 54 41 55 41 57 48 83 EC 28 48 8B 01", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _rotateLinkshellHistory; + private readonly delegate* unmanaged RotateLinkshellHistoryNative; [Signature("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 20 48 8B 01 44 8B F2", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _rotateCrossLinkshellHistory; + private readonly delegate* unmanaged RotateCrossLinkshellHistoryNative; [Signature("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B F2 48 8D B9")] - private readonly delegate* unmanaged _getColourInfo = null!; + private readonly delegate* unmanaged GetColourInfo = null!; [Signature("E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D")] - private readonly delegate* unmanaged _sanitiseString = null!; + private readonly delegate* unmanaged SanitiseString = null!; // Hooks @@ -79,6 +82,8 @@ internal sealed unsafe class Chat : IDisposable { private delegate byte SetChatLogTellTarget(IntPtr a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort a6, byte a7); + private delegate void EurekaContextMenuTellDelegate(RaptureShellModule* param1, Utf8String* playerName, Utf8String* worldName, ushort world, ulong contentId, ushort param6); + [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) @@ -103,31 +108,37 @@ internal sealed unsafe class Chat : IDisposable { )] private Hook? SetChatLogTellTargetHook { get; init; } + [Signature( + "E8 ?? ?? ?? ?? EB 8A 48 8B 1D", + DetourName = nameof(EurekaContextMenuTell) + )] + private Hook? EurekaContextMenuTellHook { get; init; } + // Offsets #pragma warning disable 0649 [Signature("8B B9 ?? ?? ?? ?? 48 8B D9 83 FF FE 0F 84", Offset = 2)] - private readonly int? _replyChannelOffset; + private readonly int? ReplyChannelOffset; [Signature("89 83 ?? ?? ?? ?? 48 8B 01 83 FE 13 7C 05 41 8B D4 EB 03 83 CA FF FF 90", Offset = 2)] - private readonly int? _shellChannelOffset; + private readonly int? ShellChannelOffset; [Signature("4C 8D B6 ?? ?? ?? ?? 41 8B 1E 45 85 E4 74 7A 33 FF 8B EF 66 0F 1F 44 00", Offset = 3)] - private readonly int? _linkshellCycleOffset; + private readonly int? LinkshellCycleOffset; [Signature("BA ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8B F0 48 85 C0 0F 84 ?? ?? ?? ?? 48 8B 10 33", Offset = 1)] - private readonly uint? _linkshellInfoProxyIdx; + private readonly uint? LinkshellInfoProxyIdx; [Signature("BA ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 89 6C 24 ?? 4C 8B E0 48 89 74 24", Offset = 1)] - private readonly uint? _crossLinkshellInfoProxyIdx; + private readonly uint? CrossLinkshellInfoProxyIdx; #pragma warning restore 0649 // Pointers [Signature("48 8D 15 ?? ?? ?? ?? 0F B6 C8 48 8D 05", ScanType = ScanType.StaticAddress)] - private readonly char* _currentCharacter = null!; + private readonly char* CurrentCharacter = null!; [Signature("48 8D 0D ?? ?? ?? ?? 8B 14 ?? 85 D2 7E ?? 48 8B 0D ?? ?? ?? ?? 48 83 C1 10 E8 ?? ?? ?? ?? 8B 70 ?? 41 8D 4D", ScanType = ScanType.StaticAddress)] private IntPtr ColourLookup { get; init; } @@ -141,76 +152,81 @@ internal sealed unsafe class Chat : IDisposable { private Plugin Plugin { get; } internal (InputChannel channel, List name) Channel { get; private set; } + internal bool UsesTellTempChannel { get; set; } + internal InputChannel? PreviousChannel { get; private set; } + internal Chat(Plugin plugin) { - this.Plugin = plugin; + Plugin = plugin; Plugin.GameInteropProvider.InitializeFromAttributes(this); - this.ChatLogRefreshHook?.Enable(); - this.ChangeChannelNameHook?.Enable(); - this.ReplyInSelectedChatModeHook?.Enable(); - this.SetChatLogTellTargetHook?.Enable(); + ChatLogRefreshHook?.Enable(); + ChangeChannelNameHook?.Enable(); + ReplyInSelectedChatModeHook?.Enable(); + SetChatLogTellTargetHook?.Enable(); + EurekaContextMenuTellHook?.Enable(); - Plugin.Framework.Update += this.InterceptKeybinds; - Plugin.ClientState.Login += this.Login; - this.Login(); + Plugin.Framework.Update += InterceptKeybinds; + Plugin.ClientState.Login += Login; + Login(); } public void Dispose() { - Plugin.ClientState.Login -= this.Login; - Plugin.Framework.Update -= this.InterceptKeybinds; + Plugin.ClientState.Login -= Login; + Plugin.Framework.Update -= InterceptKeybinds; - this.SetChatLogTellTargetHook?.Dispose(); - this.ReplyInSelectedChatModeHook?.Dispose(); - this.ChangeChannelNameHook?.Dispose(); - this.ChatLogRefreshHook?.Dispose(); + SetChatLogTellTargetHook?.Dispose(); + ReplyInSelectedChatModeHook?.Dispose(); + ChangeChannelNameHook?.Dispose(); + ChatLogRefreshHook?.Dispose(); + EurekaContextMenuTellHook?.Dispose(); - this.Activated = null; + Activated = null; } internal string? GetLinkshellName(uint idx) { - if (this._linkshellInfoProxyIdx is not { } proxyIdx) { + if (LinkshellInfoProxyIdx is not { } proxyIdx) { return null; } - var infoProxy = this.Plugin.Functions.GetInfoProxyByIndex(proxyIdx); + var infoProxy = Plugin.Functions.GetInfoProxyByIndex(proxyIdx); if (infoProxy == IntPtr.Zero) { return null; } - var lsInfo = this._getLinkshellInfo(infoProxy, idx); + var lsInfo = GetLinkshellInfo(infoProxy, idx); if (lsInfo == null) { return null; } - var utf = this._getLinkshellName(infoProxy, *lsInfo); + var utf = GetLinkshellNameNative(infoProxy, *lsInfo); return utf == null ? null : MemoryHelper.ReadStringNullTerminated((IntPtr) utf); } internal string? GetCrossLinkshellName(uint idx) { - if (this._crossLinkshellInfoProxyIdx is not { } proxyIdx) { + if (CrossLinkshellInfoProxyIdx is not { } proxyIdx) { return null; } - var infoProxy = this.Plugin.Functions.GetInfoProxyByIndex(proxyIdx); + var infoProxy = Plugin.Functions.GetInfoProxyByIndex(proxyIdx); if (infoProxy == IntPtr.Zero) { return null; } - var utf = this._getCrossLinkshellName(infoProxy, idx); + var utf = GetCrossLinkshellNameNative(infoProxy, idx); return utf == null ? null : utf->ToString(); } internal ulong RotateLinkshellHistory(RotateMode mode) { - if (mode == RotateMode.None && this._linkshellCycleOffset != null) { + if (mode == RotateMode.None && LinkshellCycleOffset != null) { // for the branch at 6.08: 5E1680 var uiModule = (IntPtr) Framework.Instance()->GetUiModule(); - *(int*) (uiModule + this._linkshellCycleOffset.Value) = -1; + *(int*) (uiModule + LinkshellCycleOffset.Value) = -1; } - return RotateLinkshellHistoryInternal(this._rotateLinkshellHistory, mode); + return RotateLinkshellHistoryInternal(RotateLinkshellHistoryNative, mode); } - internal ulong RotateCrossLinkshellHistory(RotateMode mode) => RotateLinkshellHistoryInternal(this._rotateCrossLinkshellHistory, mode); + internal ulong RotateCrossLinkshellHistory(RotateMode mode) => RotateLinkshellHistoryInternal(RotateCrossLinkshellHistoryNative, mode); private static ulong RotateLinkshellHistoryInternal(delegate* unmanaged func, RotateMode mode) { // ReSharper disable once ConditionIsAlwaysTrueOrFalse @@ -232,7 +248,7 @@ internal sealed unsafe class Chat : IDisposable { // // If this function would ever return 0, it returns null instead. internal uint? GetChannelColour(ChatType type) { - if (this._getColourInfo == null || this.ColourLookup == IntPtr.Zero) { + if (GetColourInfo == null || ColourLookup == IntPtr.Zero) { return null; } @@ -252,8 +268,8 @@ internal sealed unsafe class Chat : IDisposable { var framework = (IntPtr) Framework.Instance(); - var lookupResult = *(uint*) (this.ColourLookup + (int) parent * 4); - var info = this._getColourInfo(framework + 16, lookupResult); + var lookupResult = *(uint*) (ColourLookup + (int) parent * 4); + var info = GetColourInfo(framework + 16, lookupResult); var rgb = *(uint*) (info + 32) & 0xFFFFFF; if (rgb == 0) { @@ -264,7 +280,7 @@ internal sealed unsafe class Chat : IDisposable { } private readonly Dictionary _keybinds = new(); - internal IReadOnlyDictionary Keybinds => this._keybinds; + internal IReadOnlyDictionary Keybinds => _keybinds; internal static readonly IReadOnlyDictionary KeybindsToIntercept = new Dictionary { ["CMD_CHAT"] = new(null), @@ -336,10 +352,10 @@ internal sealed unsafe class Chat : IDisposable { private void CheckFocus() { void Decrement() { - if (this._graceFrames > 0) { - this._graceFrames -= 1; + if (_graceFrames > 0) { + _graceFrames -= 1; } else { - this._inputFocused = false; + _inputFocused = false; } } @@ -351,8 +367,8 @@ internal sealed unsafe class Chat : IDisposable { } if (*isTextInputActivePtr) { - this._inputFocused = true; - this._graceFrames = 60; + _inputFocused = true; + _graceFrames = 60; } else { Decrement(); } @@ -360,20 +376,20 @@ internal sealed unsafe class Chat : IDisposable { private void UpdateKeybinds() { foreach (var name in KeybindsToIntercept.Keys) { - var keybind = this.GetKeybind(name); + var keybind = GetKeybind(name); if (keybind is null) { continue; } - this._keybinds[name] = keybind; + _keybinds[name] = keybind; } } private void InterceptKeybinds(IFramework framework1) { - this.CheckFocus(); - this.UpdateKeybinds(); + CheckFocus(); + UpdateKeybinds(); - if (this._inputFocused) { + if (_inputFocused) { return; } @@ -387,7 +403,7 @@ internal sealed unsafe class Chat : IDisposable { var turnedOff = new Dictionary(); foreach (var toIntercept in KeybindsToIntercept.Keys) { - if (!this.Keybinds.TryGetValue(toIntercept, out var keybind)) { + if (!Keybinds.TryGetValue(toIntercept, out var keybind)) { continue; } @@ -396,7 +412,7 @@ internal sealed unsafe class Chat : IDisposable { return; } - var modifierPressed = this.Plugin.Config.KeybindMode switch { + var modifierPressed = Plugin.Config.KeybindMode switch { KeybindMode.Strict => modifier == modifierState, KeybindMode.Flexible => modifierState.HasFlag(modifier), _ => false, @@ -427,7 +443,7 @@ internal sealed unsafe class Chat : IDisposable { } try { - this.Activated?.Invoke(new ChatActivatedArgs(info) { + Activated?.Invoke(new ChatActivatedArgs(info) { TellReason = TellReason.Reply, }); } catch (Exception ex) { @@ -437,7 +453,7 @@ internal sealed unsafe class Chat : IDisposable { } private void Login() { - if (this.ChangeChannelNameHook == null) { + if (ChangeChannelNameHook == null) { return; } @@ -446,19 +462,19 @@ internal sealed unsafe class Chat : IDisposable { return; } - this.ChangeChannelNameDetour((IntPtr) agent); + 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); + return ChatLogRefreshHook!.Original(log, eventId, value); } string? input = null; if (Plugin.GameConfig.TryGet(UiControlOption.DirectChat, out bool option) && option) { - if (this._currentCharacter != null) { + if (CurrentCharacter != null) { // FIXME: this whole system sucks - var c = *this._currentCharacter; + var c = *CurrentCharacter; if (c != '\0' && !char.IsControl(c)) { input = c.ToString(); } @@ -480,7 +496,7 @@ internal sealed unsafe class Chat : IDisposable { AddIfNotPresent = addIfNotPresent, Input = input, }; - this.Activated?.Invoke(args); + Activated?.Invoke(args); } catch (Exception ex) { Plugin.Log.Error(ex, "Error in chat Activated event"); } @@ -494,7 +510,7 @@ internal sealed unsafe class Chat : IDisposable { // +0x40 = chat channel (byte or uint?) // channel is 17 (maybe 18?) for tells // +0x48 = pointer to channel name string - var ret = this.ChangeChannelNameHook!.Original(agent); + var ret = ChangeChannelNameHook!.Original(agent); if (agent == IntPtr.Zero) { return ret; } @@ -507,8 +523,8 @@ internal sealed unsafe class Chat : IDisposable { } var channel = 0u; - if (this._shellChannelOffset != null) { - channel = *(uint*) (shellModule + this._shellChannelOffset.Value); + if (ShellChannelOffset != null) { + channel = *(uint*) (shellModule + ShellChannelOffset.Value); } // var channel = *(uint*) (agent + 0x40); @@ -535,32 +551,32 @@ internal sealed unsafe class Chat : IDisposable { text.Content = text.Content.TrimStart('\uE01E').TrimStart(); } - this.Channel = ((InputChannel) channel, nameChunks); + Channel = ((InputChannel) channel, nameChunks); return ret; } private void ReplyInSelectedChatModeDetour(AgentInterface* agent) { - if (this._replyChannelOffset == null) { + if (ReplyChannelOffset == null) { goto Original; } - var replyMode = *(int*) ((IntPtr) agent + this._replyChannelOffset.Value); + var replyMode = *(int*) ((IntPtr) agent + ReplyChannelOffset.Value); if (replyMode == -2) { goto Original; } - this.SetChannel((InputChannel) replyMode); + SetChannel((InputChannel) replyMode); Original: - this.ReplyInSelectedChatModeHook!.Original(agent); + ReplyInSelectedChatModeHook!.Original(agent); } private byte SetChatLogTellTargetDetour(IntPtr a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort reason, byte a7) { if (name != null) { try { var target = new TellTarget(name->ToString(), world, contentId, (TellReason) reason); - this.Activated?.Invoke(new ChatActivatedArgs(new ChannelSwitchInfo(InputChannel.Tell)) { + Activated?.Invoke(new ChatActivatedArgs(new ChannelSwitchInfo(InputChannel.Tell)) { TellReason = (TellReason) reason, TellTarget = target, }); @@ -569,31 +585,65 @@ internal sealed unsafe class Chat : IDisposable { } } - return this.SetChatLogTellTargetHook!.Original(a1, name, a3, world, contentId, reason, a7); + return SetChatLogTellTargetHook!.Original(a1, name, a3, world, contentId, reason, a7); + } + + private void EurekaContextMenuTell(RaptureShellModule* param1, Utf8String* playerName, Utf8String* worldName, ushort world, ulong id, ushort param6) + { + if (!UsesTellTempChannel) + { + UsesTellTempChannel = true; + PreviousChannel = Channel.channel; + } + + if (SetChannelTargetTell != null) + SetChannelTargetTell(param1, playerName, worldName, world, id, param6, 0); + + EurekaContextMenuTellHook!.Original(param1, playerName, worldName, world, id, param6); } internal ulong? GetContentIdForEntry(uint index) { - if (this._getContentIdForChatEntry == null) { + if (GetContentIdForChatEntry == null) { return null; } - return this._getContentIdForChatEntry(Framework.Instance()->GetUiModule()->GetRaptureLogModule(), index); + return GetContentIdForChatEntry(Framework.Instance()->GetUiModule()->GetRaptureLogModule(), index); } internal void SetChannel(InputChannel channel, string? tellTarget = null) { - if (this._changeChatChannel == null) { + if (ChangeChatChannel == null) return; - } var target = Utf8String.FromString(tellTarget ?? ""); var idx = channel.LinkshellIndex(); - if (idx == uint.MaxValue) { + if (idx == uint.MaxValue) idx = 0; + + ChangeChatChannel(RaptureShellModule.Instance(), (int) channel, idx, target, 1); + target->Dtor(true); + } + + internal void SetEurekaTellChannel(string name, string worldName, ushort worldId, ulong objectId, ushort param6, byte param7) + { + // param6 is 0 for contentId and 1 for objectId + // param7 is always 0 ? + + if (SetChannelTargetTell == null) + return; + + if (!UsesTellTempChannel) + { + UsesTellTempChannel = true; + PreviousChannel = Channel.channel; } - this._changeChatChannel(RaptureShellModule.Instance(), (int) channel, idx, target, 1); - target->Dtor(); - IMemorySpace.Free(target); + var utfName = Utf8String.FromString(name); + var utfWorld = Utf8String.FromString(worldName); + + SetChannelTargetTell(RaptureShellModule.Instance(), utfName, utfWorld, worldId, objectId, param6, param7); + + utfName->Dtor(true); + utfWorld->Dtor(true); } private static VirtualKey GetKeyForModifier(ModifierFlag modifierFlag) => modifierFlag switch { @@ -616,9 +666,8 @@ internal sealed unsafe class Chat : IDisposable { var outData = stackalloc byte[32]; var idString = Utf8String.FromString(id); - this._getKeybind((IntPtr) a1, idString, (IntPtr) outData); - idString->Dtor(); - IMemorySpace.Free(idString); + GetKeybindNative((IntPtr) a1, idString, (IntPtr) outData); + idString->Dtor(true); var key1 = (VirtualKey) outData[0]; if (key1 is VirtualKey.F23) { @@ -644,7 +693,7 @@ internal sealed unsafe class Chat : IDisposable { return null; } - var ptr = this._getTellHistory(acquaintanceModule, index); + var ptr = GetTellHistory(acquaintanceModule, index); if (ptr == IntPtr.Zero) { return null; } @@ -660,28 +709,24 @@ internal sealed unsafe class Chat : IDisposable { var uName = Utf8String.FromString(name); var uMessage = Utf8String.FromString(message); - var networkModule = this._getNetworkModule(Framework.Instance()); + var networkModule = GetNetworkModule(Framework.Instance()); var a1 = *(IntPtr*) (networkModule + 8); var logModule = Framework.Instance()->GetUiModule()->GetRaptureLogModule(); - this._printTell(logModule, 33, uName, uMessage, contentId, homeWorld, 255, 0, 0); - this._sendTell(a1, contentId, homeWorld, uName, uMessage, (byte) reason, homeWorld); + PrintTell(logModule, 33, uName, uMessage, contentId, homeWorld, 255, 0, 0); + SendTellNative(a1, contentId, homeWorld, uName, uMessage, (byte) reason, homeWorld); - uName->Dtor(); - IMemorySpace.Free(uName); - - uMessage->Dtor(); - IMemorySpace.Free(uMessage); + uName->Dtor(true); + uMessage->Dtor(true); } internal bool IsCharValid(char c) { var uC = Utf8String.FromString(c.ToString()); - this._sanitiseString(uC, 0x27F, IntPtr.Zero); + SanitiseString(uC, 0x27F, IntPtr.Zero); var wasValid = uC->ToString().Length > 0; - uC->Dtor(); - IMemorySpace.Free(uC); + uC->Dtor(true); return wasValid; } diff --git a/ChatTwo/PayloadHandler.cs b/ChatTwo/PayloadHandler.cs index 41f68a8..a971615 100755 --- a/ChatTwo/PayloadHandler.cs +++ b/ChatTwo/PayloadHandler.cs @@ -15,7 +15,9 @@ using Dalamud.Interface.Utility; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; +using Lumina.Excel; using Lumina.Excel.GeneratedSheets; + using Action = System.Action; using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload; using ChatTwoPartyFinderPayload = ChatTwo.Util.PartyFinderPayload; @@ -26,7 +28,6 @@ public sealed class PayloadHandler { private const string PopupId = "chat2-context-popup"; private ChatLogWindow LogWindow { get; } - private (Chunk, Payload?)? Popup { get; set; } private bool _handleTooltips; @@ -34,8 +35,18 @@ public sealed class PayloadHandler { private uint _hoverCounter; private uint _lastHoverCounter; + private readonly ExcelSheet ItemSheet; + private readonly ExcelSheet EventItemSheet; + private readonly ExcelSheet TerritorySheet; + private readonly ExcelSheet EventItemHelpSheet; + internal PayloadHandler(ChatLogWindow logWindow) { LogWindow = logWindow; + + ItemSheet = Plugin.DataManager.GetExcelSheet()!; + EventItemSheet = Plugin.DataManager.GetExcelSheet()!; + TerritorySheet = Plugin.DataManager.GetExcelSheet()!; + EventItemHelpSheet = Plugin.DataManager.GetExcelSheet()!; } internal void Draw() { @@ -303,7 +314,7 @@ public sealed class PayloadHandler { } private void HoverEventItem(ItemPayload payload) { - var item = Plugin.DataManager.GetExcelSheet()?.GetRow(payload.RawItemId); + var item = EventItemSheet.GetRow(payload.RawItemId); if (item == null) { return; } @@ -316,7 +327,7 @@ public sealed class PayloadHandler { LogWindow.DrawChunks(name.ToList()); ImGui.Separator(); - var help = Plugin.DataManager.GetExcelSheet()?.GetRow(payload.RawItemId); + var help = EventItemHelpSheet.GetRow(payload.RawItemId); if (help != null) { var desc = ChunkUtil.ToChunks(help.Description.ToDalamudString(), ChunkSource.None, null); LogWindow.DrawChunks(desc.ToList()); @@ -402,7 +413,7 @@ public sealed class PayloadHandler { return; } - var item = Plugin.DataManager.GetExcelSheet()?.GetRow(payload.ItemId); + var item = ItemSheet.GetRow(payload.ItemId); if (item == null) { return; } @@ -459,7 +470,7 @@ public sealed class PayloadHandler { return; } - var item = Plugin.DataManager.GetExcelSheet()?.GetRow(payload.ItemId); + var item = EventItemSheet.GetRow(payload.ItemId); if (item == null) { return; } @@ -506,17 +517,27 @@ public sealed class PayloadHandler { LogWindow.DrawChunks(name, false); ImGui.Separator(); - if (ImGui.Selectable(Language.Context_SendTell)) { - LogWindow.Chat = $"/tell {player.PlayerName}"; - if (world.IsPublic) { - LogWindow.Chat += $"@{world.Name}"; + var validContentId = chunk.Message?.ContentId is not (null or 0); + if (ImGui.Selectable(Language.Context_SendTell)) + { + // Eureka and Bozja need special handling as tells work different + if (TerritorySheet.GetRow(Plugin.ClientState.TerritoryType)?.TerritoryIntendedUse != 41) + { + LogWindow.Chat = $"/tell {player.PlayerName}"; + if (world.IsPublic) { + LogWindow.Chat += $"@{world.Name}"; + } + + LogWindow.Chat += " "; + } + else if (validContentId) + { + LogWindow.Plugin.Functions.Chat.SetEurekaTellChannel(player.PlayerName, world.Name.ToString(), (ushort) world.RowId, chunk.Message!.ContentId, 0, 0); } - LogWindow.Chat += " "; LogWindow.Activate = true; } - var validContentId = chunk.Message?.ContentId is not (null or 0); if (world.IsPublic) { var party = Plugin.PartyList; var leader = (ulong?) party[(int) party.PartyLeaderIndex]?.ContentId; @@ -524,7 +545,7 @@ public sealed class PayloadHandler { var member = party.FirstOrDefault(member => member.Name.TextValue == player.PlayerName && member.World.Id == world.RowId); var isInParty = member != default; var inInstance = LogWindow.Plugin.Functions.IsInInstance(); - var inPartyInstance = Plugin.DataManager.GetExcelSheet()!.GetRow(Plugin.ClientState.TerritoryType)?.TerritoryIntendedUse is (41 or 47 or 48 or 52 or 53); + var inPartyInstance = TerritorySheet.GetRow(Plugin.ClientState.TerritoryType)?.TerritoryIntendedUse is (41 or 47 or 48 or 52 or 53); if (isLeader) { if (!isInParty) { if (inInstance && inPartyInstance) { diff --git a/ChatTwo/Ui/ChatLogWindow.cs b/ChatTwo/Ui/ChatLogWindow.cs index be9851f..c5f73f6 100644 --- a/ChatTwo/Ui/ChatLogWindow.cs +++ b/ChatTwo/Ui/ChatLogWindow.cs @@ -144,10 +144,8 @@ public sealed class ChatLogWindow : Window, IUiComponent { if (info.Channel is InputChannel.Tell) { if (info.Rotate != RotateMode.None) { var idx = prevTemp != InputChannel.Tell - ? 0 - : info.Rotate == RotateMode.Reverse - ? -1 - : 1; + ? 0 : info.Rotate == RotateMode.Reverse + ? -1 : 1; var tellInfo = Plugin.Functions.Chat.GetTellHistoryInfo(idx); if (tellInfo != null && reason != null) { @@ -164,9 +162,7 @@ public sealed class ChatLogWindow : Window, IUiComponent { _tellTarget = null; } - var mode = prevTemp == null - ? RotateMode.None - : info.Rotate; + var mode = prevTemp == null ? RotateMode.None : info.Rotate; if (info.Channel is InputChannel.Linkshell1 && info.Rotate != RotateMode.None) { var idx = Plugin.Functions.Chat.RotateLinkshellHistory(mode); @@ -439,7 +435,6 @@ public sealed class ChatLogWindow : Window, IUiComponent { DrawAutoComplete(); } - /// true if window was rendered private unsafe void DrawChatLog() { var resized = LastWindowSize != ImGui.GetWindowSize(); @@ -595,13 +590,24 @@ public sealed class ChatLogWindow : Window, IUiComponent { if (ImGui.IsItemDeactivated()) { if (ImGui.IsKeyDown(ImGuiKey.Escape)) { Chat = chatCopy; + + if (Plugin.Functions.Chat.UsesTellTempChannel) + { + Plugin.Functions.Chat.UsesTellTempChannel = false; + Plugin.Functions.Chat.SetChannel(Plugin.Functions.Chat.PreviousChannel ?? InputChannel.Say); + } } - var enter = ImGui.IsKeyDown(ImGuiKey.Enter) - || ImGui.IsKeyDown(ImGuiKey.KeypadEnter); + var enter = ImGui.IsKeyDown(ImGuiKey.Enter) || ImGui.IsKeyDown(ImGuiKey.KeypadEnter); if (enter) { Plugin.CommandHelpWindow.IsOpen = false; SendChatBox(activeTab); + + if (Plugin.Functions.Chat.UsesTellTempChannel) + { + Plugin.Functions.Chat.UsesTellTempChannel = false; + Plugin.Functions.Chat.SetChannel(Plugin.Functions.Chat.PreviousChannel ?? InputChannel.Say); + } } } @@ -615,6 +621,11 @@ public sealed class ChatLogWindow : Window, IUiComponent { } _tempChannel = null; + if (Plugin.Functions.Chat.UsesTellTempChannel) + { + Plugin.Functions.Chat.UsesTellTempChannel = false; + Plugin.Functions.Chat.SetChannel(Plugin.Functions.Chat.PreviousChannel ?? InputChannel.Say); + } } if (ImGui.BeginPopupContextItem()) { @@ -677,11 +688,10 @@ public sealed class ChatLogWindow : Window, IUiComponent { } - if (_tempChannel != null) { + if (_tempChannel != null) trimmed = $"{_tempChannel.Value.Prefix()} {trimmed}"; - } else if (activeTab is { Channel: { } channel }) { + else if (activeTab is { Channel: { } channel }) trimmed = $"{channel.Prefix()} {trimmed}"; - } } var bytes = Encoding.UTF8.GetBytes(trimmed);