From 117d9fc45c7d5bd0f98c3da0f8549eaf2c1b363e Mon Sep 17 00:00:00 2001 From: Infi Date: Tue, 20 Aug 2024 23:14:52 +0200 Subject: [PATCH] fix #81 --- ChatTwo/ChatTwo.yaml | 11 ++- ChatTwo/GameFunctions/Chat.cs | 95 +++++++++++-------- ChatTwo/GameFunctions/Party.cs | 22 ++++- .../GameFunctions/Types/ChatActivatedArgs.cs | 1 + ChatTwo/Resources/Language.Designer.cs | 9 ++ ChatTwo/Resources/Language.resx | 3 + ChatTwo/Ui/ChatLogWindow.cs | 18 ++++ 7 files changed, 114 insertions(+), 45 deletions(-) diff --git a/ChatTwo/ChatTwo.yaml b/ChatTwo/ChatTwo.yaml index 1509b29..e77595a 100755 --- a/ChatTwo/ChatTwo.yaml +++ b/ChatTwo/ChatTwo.yaml @@ -22,7 +22,14 @@ tags: - Chat - Replacement changelog: |- - **Misc** + **Added** - Implement 24-hour clock timestamp option [default false] - - Fix hide activity channels not being saved across sessions + + **Fixes** + - Bozja/Eureka tells should work again + - Invites in Bozja/Eureka should work again + - Added a notification that warns about fails duo missing player id + - Hide activity channels are now saved across sessions + + **Misc** - Loc updates diff --git a/ChatTwo/GameFunctions/Chat.cs b/ChatTwo/GameFunctions/Chat.cs index 715aea9..4bb4136 100755 --- a/ChatTwo/GameFunctions/Chat.cs +++ b/ChatTwo/GameFunctions/Chat.cs @@ -30,20 +30,23 @@ internal sealed unsafe class Chat : IDisposable private readonly delegate* unmanaged PrintTellNative = null!; [Signature("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8C 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01")] - private readonly delegate* unmanaged SendTellNative = null!; + private readonly delegate* unmanaged SendTellNative = null!; // Client::UI::AddonChatLog.OnRefresh [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))] private Hook? ChatLogRefreshHook { get; init; } private delegate byte ChatLogRefreshDelegate(nint log, ushort eventId, AtkValue* value); + // Replace with CS version later + [Signature("E8 ?? ?? ?? ?? EB 81 48 8B 1D", DetourName = nameof(ContextMenuTellInForayDetour))] + private Hook? ContextMenuTellInForayHook { get; set; } + private delegate void ContextMenuTellInForayDelegate(RaptureShellModule* module, Utf8String* playerName, Utf8String* worldName, ushort worldId, ulong accountId, ulong contentId, ushort reason); + private Hook ChangeChannelNameHook { get; init; } private Hook? ReplyInSelectedChatModeHook { get; init; } private Hook? SetChatLogTellTargetHook { get; init; } - private Hook? EurekaContextMenuTellHook { get; init; } // Pointers - [Signature("48 8D 35 ?? ?? ?? ?? 8B 05", ScanType = ScanType.StaticAddress)] private readonly char* CurrentCharacter = null!; @@ -75,6 +78,7 @@ internal sealed unsafe class Chat : IDisposable Plugin.GameInteropProvider.InitializeFromAttributes(this); ChatLogRefreshHook?.Enable(); + ContextMenuTellInForayHook?.Enable(); ChangeChannelNameHook = Plugin.GameInteropProvider.HookFromAddress(AgentChatLog.MemberFunctionPointers.ChangeChannelName, ChangeChannelNameDetour); ChangeChannelNameHook.Enable(); @@ -85,9 +89,6 @@ internal sealed unsafe class Chat : IDisposable SetChatLogTellTargetHook = Plugin.GameInteropProvider.HookFromAddress(RaptureShellModule.MemberFunctionPointers.SetContextTellTarget, SetContextTellTarget); SetChatLogTellTargetHook.Enable(); - // EurekaContextMenuTellHook = Plugin.GameInteropProvider.HookFromAddress(RaptureShellModule.MemberFunctionPointers.SetContextTellTargetInForay, SetContextTellTargetInForay); - // EurekaContextMenuTellHook.Enable(); - Plugin.ClientState.Login += Login; Login(); } @@ -100,7 +101,7 @@ internal sealed unsafe class Chat : IDisposable ReplyInSelectedChatModeHook?.Dispose(); ChangeChannelNameHook?.Dispose(); ChatLogRefreshHook?.Dispose(); - EurekaContextMenuTellHook?.Dispose(); + ContextMenuTellInForayHook?.Dispose(); } internal string? GetLinkshellName(uint idx) @@ -213,6 +214,11 @@ internal sealed unsafe class Chat : IDisposable try { + // We already called this function once, so we skip the duplicated call + // Also return the original value here so that vanilla chat receives all information + if (Plugin.ChatLogWindow.TellSpecial) + return ChatLogRefreshHook!.Original(log, eventId, value); + Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(new ChannelSwitchInfo(null)) { AddIfNotPresent = addIfNotPresent, }); } catch (Exception ex) @@ -294,35 +300,34 @@ internal sealed unsafe class Chat : IDisposable return SetChatLogTellTargetHook!.Original(a1, playerName, worldName, worldId, accountId, contentId, reason, setChatType); } - // private void SetContextTellTargetInForay(RaptureShellModule* a1, Utf8String* playerName, Utf8String* worldName, ushort worldId, ulong accountId, ulong contentId, ushort reason) - // { - // Plugin.Log.Information($"SetContextTellTargetInForay"); - // if (!UsesTellTempChannel) - // { - // UsesTellTempChannel = true; - // PreviousChannel = Channel.Channel; - // } - // - // if (playerName != null) - // { - // try - // { - // Plugin.Log.Information($"Name {playerName->ToString()} World {worldName->ToString()} WorldId {worldId} accountId {accountId} ContentId {contentId} Reason {reason} rapture reason {a1->TellReason}"); - // var target = new TellTarget(playerName->ToString(), worldId, contentId, (TellReason) reason); - // Activated?.Invoke(new ChatActivatedArgs(new ChannelSwitchInfo(InputChannel.Tell)) - // { - // TellReason = (TellReason) reason, - // TellTarget = target, - // }); - // } - // catch (Exception ex) - // { - // Plugin.Log.Error(ex, "Error in chat Activated event"); - // } - // } - // - // EurekaContextMenuTellHook!.Original(a1, playerName, worldName, worldId, accountId, contentId, reason); - // } + private void ContextMenuTellInForayDetour(RaptureShellModule* a1, Utf8String* playerName, Utf8String* worldName, ushort worldId, ulong accountId, ulong contentId, ushort reason) + { + if (!UsesTellTempChannel) + { + UsesTellTempChannel = true; + PreviousChannel = Channel.Channel; + } + + if (playerName != null) + { + try + { + var target = new TellTarget(playerName->ToString(), worldId, contentId, (TellReason) reason); + Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(new ChannelSwitchInfo(InputChannel.Tell)) + { + TellReason = (TellReason) reason, + TellTarget = target, + TellSpecial = true, + }); + } + catch (Exception ex) + { + Plugin.Log.Error(ex, "Error in chat Activated event"); + } + } + + ContextMenuTellInForayHook!.Original(a1, playerName, worldName, worldId, accountId, contentId, reason); + } /// /// Returns true if the channel is any non-linkshell channel, or if the @@ -457,6 +462,16 @@ internal sealed unsafe class Chat : IDisposable return new TellHistoryInfo(name, world, contentId); } + internal void SendTellUsingCommandInner(byte[] message) + { + var mes = new Utf8String(message); + + RaptureShellModule.Instance()->ExecuteCommandInner(&mes, UIModule.Instance()); + RaptureAtkModule.Instance()->ClearFocus(); // Clear the focus of vanilla chat that was still active + + mes.Dtor(true); + } + internal void SendTell(TellReason reason, ulong contentId, string name, ushort homeWorld, byte[] message, string rawText) { var uName = Utf8String.FromString(name); @@ -470,15 +485,19 @@ internal sealed unsafe class Chat : IDisposable var logModule = RaptureLogModule.Instance(); var networkModule = Framework.Instance()->GetNetworkModuleProxy()->NetworkModule; - // TODO: Remap TellReasons + // // TODO: Remap TellReasons if (reason == TellReason.Direct) reason = TellReason.Friend; var ok = SendTellNative(networkModule, contentId, homeWorld, uName, encoded, (ushort) reason, homeWorld); - if (ok) + if (ok == 1) + { PrintTellNative(logModule, 33, uName, &decodedUtf8String, 0, contentId, homeWorld, 255, 0, 0); + } else + { Plugin.ChatGui.PrintError(Language.Chat_SendTell_Error); + } encoded->Dtor(true); uName->Dtor(true); diff --git a/ChatTwo/GameFunctions/Party.cs b/ChatTwo/GameFunctions/Party.cs index cc56211..6325a56 100755 --- a/ChatTwo/GameFunctions/Party.cs +++ b/ChatTwo/GameFunctions/Party.cs @@ -1,10 +1,12 @@ +using ChatTwo.Resources; using ChatTwo.Util; +using Dalamud.Interface.ImGuiNotification; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Info; namespace ChatTwo.GameFunctions; -internal sealed unsafe class Party +internal static unsafe class Party { internal static void InviteSameWorld(string name, ushort world, ulong contentId) { @@ -20,14 +22,24 @@ internal sealed unsafe class Party // if they're not on that world, it will fail // pass 0 and it will work on any world EXCEPT for the world the // current player is on - if (contentId != 0) - InfoProxyPartyInvite.Instance()->InviteToPartyContentId(contentId, 0); + if (contentId == 0) + { + WrapperUtil.AddNotification(Language.PartyInvite_NoId, NotificationType.Warning); + return; + } + + InfoProxyPartyInvite.Instance()->InviteToPartyContentId(contentId, 0); } internal static void InviteInInstance(ulong contentId) { - if (contentId != 0) - InfoProxyPartyInvite.Instance()->InviteToPartyInInstance(contentId); + if (contentId == 0) + { + WrapperUtil.AddNotification(Language.PartyInvite_NoId, NotificationType.Warning); + return; + } + + InfoProxyPartyInvite.Instance()->InviteToPartyInInstance(contentId); } internal static void Kick(string name, ulong contentId) diff --git a/ChatTwo/GameFunctions/Types/ChatActivatedArgs.cs b/ChatTwo/GameFunctions/Types/ChatActivatedArgs.cs index f929b8a..ba4a377 100755 --- a/ChatTwo/GameFunctions/Types/ChatActivatedArgs.cs +++ b/ChatTwo/GameFunctions/Types/ChatActivatedArgs.cs @@ -7,6 +7,7 @@ internal sealed class ChatActivatedArgs internal ChannelSwitchInfo ChannelSwitchInfo { get; } internal TellReason? TellReason { get; init; } internal TellTarget? TellTarget { get; init; } + internal bool TellSpecial { get; init; } // specific to Eureka/Bozja/Zadnor internal ChatActivatedArgs(ChannelSwitchInfo channelSwitchInfo) { diff --git a/ChatTwo/Resources/Language.Designer.cs b/ChatTwo/Resources/Language.Designer.cs index 398cba5..b97b620 100755 --- a/ChatTwo/Resources/Language.Designer.cs +++ b/ChatTwo/Resources/Language.Designer.cs @@ -3416,6 +3416,15 @@ namespace ChatTwo.Resources { } } + /// + /// Looks up a localized string similar to Unable to find ID for this message, please try another one.. + /// + internal static string PartyInvite_NoId { + get { + return ResourceManager.GetString("PartyInvite_NoId", resourceCulture); + } + } + /// /// Looks up a localized string similar to Discard. /// diff --git a/ChatTwo/Resources/Language.resx b/ChatTwo/Resources/Language.resx index 985171c..d5daa0a 100644 --- a/ChatTwo/Resources/Language.resx +++ b/ChatTwo/Resources/Language.resx @@ -964,6 +964,9 @@ Copied message to clipboard + + Unable to find ID for this message, please try another one. + Copy link to clipboard diff --git a/ChatTwo/Ui/ChatLogWindow.cs b/ChatTwo/Ui/ChatLogWindow.cs index 6ea271a..5c614fa 100644 --- a/ChatTwo/Ui/ChatLogWindow.cs +++ b/ChatTwo/Ui/ChatLogWindow.cs @@ -59,6 +59,7 @@ public sealed class ChatLogWindow : Window private int LastTab { get; set; } private InputChannel? TempChannel; private TellTarget? TellTarget; + public bool TellSpecial; private readonly Stopwatch LastResize = new(); private AutoCompleteInfo? AutoCompleteInfo; private bool AutoCompleteOpen; @@ -145,6 +146,8 @@ public sealed class ChatLogWindow : Window internal void Activated(ChatActivatedArgs args) { + TellSpecial = args.TellSpecial; + Activate = true; PlayedClosingSound = false; if (Plugin.Config.PlaySounds) @@ -857,6 +860,21 @@ public sealed class ChatLogWindow : Window AddBacklog(trimmed); InputBacklogIdx = -1; + if (TellSpecial) + { + var tellBytes = Encoding.UTF8.GetBytes(trimmed); + AutoTranslate.ReplaceWithPayload(ref tellBytes); + + Plugin.Functions.Chat.SendTellUsingCommandInner(tellBytes); + + TellSpecial = false; + if (TempChannel is InputChannel.Tell) + TellTarget = null; + + Chat = string.Empty; + return; + } + if (!trimmed.StartsWith('/')) { if (TellTarget != null)