From bf27d3e85340bacbd58640f3765f94d5b23a2525 Mon Sep 17 00:00:00 2001 From: Infi Date: Sat, 16 Nov 2024 21:06:35 +0100 Subject: [PATCH] Final API11 updates and new channel switcher --- ChatTwo/ChatTwo.csproj | 3 +- ChatTwo/Code/InputChannel.cs | 2 + ChatTwo/Code/InputChannelExt.cs | 8 + ChatTwo/Configuration.cs | 16 +- ChatTwo/GameFunctions/Chat.cs | 49 ++--- ChatTwo/GameFunctions/Party.cs | 4 +- ChatTwo/GameFunctions/Types/TellTarget.cs | 5 + ChatTwo/Http/Processing.cs | 4 +- ChatTwo/Http/RouteController.cs | 8 +- ChatTwo/Http/ServerCore.cs | 1 + ChatTwo/MessageManager.cs | 6 +- ChatTwo/PayloadHandler.cs | 2 +- ChatTwo/Plugin.cs | 12 ++ ChatTwo/Ui/ChatLogWindow.cs | 241 +++++++++++----------- ChatTwo/packages.lock.json | 22 -- 15 files changed, 193 insertions(+), 190 deletions(-) diff --git a/ChatTwo/ChatTwo.csproj b/ChatTwo/ChatTwo.csproj index f16b5eb..9a6779a 100755 --- a/ChatTwo/ChatTwo.csproj +++ b/ChatTwo/ChatTwo.csproj @@ -1,6 +1,6 @@ - 1.29.5 + 1.29.6 net8.0-windows enable enable @@ -54,7 +54,6 @@ - diff --git a/ChatTwo/Code/InputChannel.cs b/ChatTwo/Code/InputChannel.cs index bdf856d..969af63 100755 --- a/ChatTwo/Code/InputChannel.cs +++ b/ChatTwo/Code/InputChannel.cs @@ -40,4 +40,6 @@ internal enum InputChannel : uint ExtraChatLinkshell6 = 1006, ExtraChatLinkshell7 = 1007, ExtraChatLinkshell8 = 1008, + + Invalid = 9999, } diff --git a/ChatTwo/Code/InputChannelExt.cs b/ChatTwo/Code/InputChannelExt.cs index 537dfef..0f69560 100755 --- a/ChatTwo/Code/InputChannelExt.cs +++ b/ChatTwo/Code/InputChannelExt.cs @@ -40,6 +40,7 @@ internal static class InputChannelExt InputChannel.ExtraChatLinkshell6 => ChatType.ExtraChatLinkshell6, InputChannel.ExtraChatLinkshell7 => ChatType.ExtraChatLinkshell7, InputChannel.ExtraChatLinkshell8 => ChatType.ExtraChatLinkshell8, + InputChannel.Invalid => ChatType.Echo, _ => throw new ArgumentOutOfRangeException(nameof(input), input, null), }; @@ -107,6 +108,7 @@ internal static class InputChannelExt InputChannel.ExtraChatLinkshell6 => "/ecl6", InputChannel.ExtraChatLinkshell7 => "/ecl7", InputChannel.ExtraChatLinkshell8 => "/ecl8", + InputChannel.Invalid => "/e", _ => "", }; @@ -187,4 +189,10 @@ internal static class InputChannelExt InputChannel.ExtraChatLinkshell8 => true, _ => false, }; + + internal static bool IsValid(this InputChannel channel) => channel switch + { + InputChannel.Invalid => false, + _ => true, + }; } diff --git a/ChatTwo/Configuration.cs b/ChatTwo/Configuration.cs index 8c5d23c..992e02b 100755 --- a/ChatTwo/Configuration.cs +++ b/ChatTwo/Configuration.cs @@ -241,8 +241,7 @@ internal class Tab [NonSerialized] public long LastActivity; [NonSerialized] public MessageList Messages = new(); - [NonSerialized] public TellTarget? TellTarget; - [NonSerialized] public InputChannel? PreviousChannel; + [NonSerialized] public UsedChannel CurrentChannel = new(); [NonSerialized] public Guid Identifier = Guid.NewGuid(); @@ -271,6 +270,8 @@ internal class Tab ExtraChatChannels = ExtraChatChannels.ToHashSet(), UnreadMode = UnreadMode, UnhideOnActivity = UnhideOnActivity, + Unread = Unread, + LastActivity = LastActivity, DisplayTimestamp = DisplayTimestamp, Channel = Channel, PopOut = PopOut, @@ -278,6 +279,7 @@ internal class Tab Opacity = Opacity, Identifier = Identifier, InputDisabled = InputDisabled, + CurrentChannel = CurrentChannel, }; } @@ -422,6 +424,16 @@ internal class Tab } } +internal class UsedChannel +{ + internal InputChannel Channel = InputChannel.Invalid; + internal List Name = []; + internal TellTarget? TellTarget; + + internal bool UseTempChannel; + internal InputChannel TempChannel = InputChannel.Invalid; +} + [Serializable] internal enum PreviewPosition { diff --git a/ChatTwo/GameFunctions/Chat.cs b/ChatTwo/GameFunctions/Chat.cs index 045d927..d1308d8 100755 --- a/ChatTwo/GameFunctions/Chat.cs +++ b/ChatTwo/GameFunctions/Chat.cs @@ -52,15 +52,6 @@ internal sealed unsafe class Chat : IDisposable private Plugin Plugin { get; } - /// - /// Holds the current game channel details. - /// `TellPlayerName` and `TellWorldId` are only set when the channel is `InputChannel.Tell`. - /// - internal (InputChannel Channel, List Name, string? TellPlayerName, ushort TellWorldId) Channel { get; private set; } - - internal bool UsesTellTempChannel { get; set; } - internal InputChannel? PreviousChannel { get; private set; } - private enum PlayerNameDisplayType : uint { FullName = 0, @@ -171,7 +162,7 @@ internal sealed unsafe class Chat : IDisposable private byte ChatLogRefreshDetour(nint log, ushort eventId, AtkValue* value) { - if (Plugin is { ChatLogWindow.CurrentTab.InputDisabled: true }) + if (Plugin.CurrentTab.InputDisabled) return ChatLogRefreshHook!.Original(log, eventId, value); if (eventId != 0x31 || value == null || value->UInt is not (0x05 or 0x0C)) @@ -263,7 +254,12 @@ internal sealed unsafe class Chat : IDisposable Plugin.Log.Debug($"Detected tell target '{playerName}'@{worldId}"); } - Channel = ((InputChannel) channel, nameChunks, playerName, worldId); + Plugin.CurrentTab.CurrentChannel = new UsedChannel + { + Channel = (InputChannel) channel, + Name = nameChunks, + TellTarget = playerName != null ? new TellTarget(playerName, worldId, 0, 0) : null, + }; return ret; } @@ -305,11 +301,8 @@ internal sealed unsafe class Chat : IDisposable 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 (!Plugin.CurrentTab.CurrentChannel.UseTempChannel) + Plugin.CurrentTab.CurrentChannel.UseTempChannel = true; if (playerName != null) { @@ -411,7 +404,7 @@ internal sealed unsafe class Chat : IDisposable } } - internal static void SetChannel(InputChannel channel, string? tellTarget = null) + internal void SetChannel(InputChannel channel, TellTarget? tellTarget = null) { // ExtraChat linkshells aren't supported in game so we never want to // call the ChangeChatChannel function with them. @@ -421,14 +414,15 @@ internal sealed unsafe class Chat : IDisposable if (channel.IsExtraChatLinkshell()) return; - var target = Utf8String.FromString(tellTarget ?? ""); + var target = Utf8String.FromString(tellTarget?.ToTargetString() ?? ""); var idx = channel.LinkshellIndex(); if (idx == uint.MaxValue) idx = 0; + if (!ValidAnyLinkshell(channel)) return; - RaptureShellModule.Instance()->ChangeChatChannel((int) channel, idx, target, true); + RaptureShellModule.Instance()->ChangeChatChannel(tellTarget != null ? 17 : (int)channel, idx, target, true); target->Dtor(true); } @@ -437,11 +431,8 @@ internal sealed unsafe class Chat : IDisposable // param6 is 0 for contentId and 1 for objectId // param7 is always 0 ? - if (!UsesTellTempChannel) - { - UsesTellTempChannel = true; - PreviousChannel = Channel.Channel; - } + if (!Plugin.CurrentTab.CurrentChannel.UseTempChannel) + Plugin.CurrentTab.CurrentChannel.UseTempChannel = true; var utfName = Utf8String.FromString(name); var utfWorld = Utf8String.FromString(worldName); @@ -492,6 +483,16 @@ internal sealed unsafe class Chat : IDisposable if (reason == TellReason.Direct) reason = TellReason.Friend; + if (contentId == 0) + { + encoded->Dtor(true); + uName->Dtor(true); + uMessage->Dtor(true); + + Plugin.Log.Warning("Tried to send a tell with content id being 0"); + return; + } + var ok = SendTellNative(networkModule, contentId, homeWorld, uName, encoded, (ushort) reason, homeWorld); if (ok == 1) { diff --git a/ChatTwo/GameFunctions/Party.cs b/ChatTwo/GameFunctions/Party.cs index 6325a56..17556d6 100755 --- a/ChatTwo/GameFunctions/Party.cs +++ b/ChatTwo/GameFunctions/Party.cs @@ -16,7 +16,7 @@ internal static unsafe class Party } } - internal static void InviteOtherWorld(ulong contentId) + internal static void InviteOtherWorld(ulong contentId, ushort worldId = 0) { // third param is world, but it requires a specific world // if they're not on that world, it will fail @@ -28,7 +28,7 @@ internal static unsafe class Party return; } - InfoProxyPartyInvite.Instance()->InviteToPartyContentId(contentId, 0); + InfoProxyPartyInvite.Instance()->InviteToPartyContentId(contentId, worldId); } internal static void InviteInInstance(ulong contentId) diff --git a/ChatTwo/GameFunctions/Types/TellTarget.cs b/ChatTwo/GameFunctions/Types/TellTarget.cs index 000d8a0..9d03fb4 100755 --- a/ChatTwo/GameFunctions/Types/TellTarget.cs +++ b/ChatTwo/GameFunctions/Types/TellTarget.cs @@ -14,4 +14,9 @@ internal sealed class TellTarget ContentId = contentId; Reason = reason; } + + public bool IsSet() => Name.Length > 0 && World > 0; + + public string ToWorldString() => Sheets.WorldSheet.TryGetRow(World, out var worldRow) ? worldRow.Name.ExtractText() : string.Empty; + public string ToTargetString() => $"{Name}@{ToWorldString()}"; } diff --git a/ChatTwo/Http/Processing.cs b/ChatTwo/Http/Processing.cs index aac8300..872bf5d 100644 --- a/ChatTwo/Http/Processing.cs +++ b/ChatTwo/Http/Processing.cs @@ -17,13 +17,13 @@ public class Processing internal (MessageTemplate[] ChannelName, bool Locked) ReadChannelName(Chunk[] channelName) { - var locked = Plugin.ChatLogWindow.CurrentTab is not { Channel: null }; + var locked = Plugin.CurrentTab is not { Channel: null }; return (channelName.Select(ProcessChunk).ToArray(), locked); } internal async Task ReadMessageList() { - var tabMessages = await Plugin.ChatLogWindow.CurrentTab!.Messages.GetCopy(); + var tabMessages = await Plugin.CurrentTab!.Messages.GetCopy(); return tabMessages.TakeLast(Plugin.Config.WebinterfaceMaxLinesToSend).Select(ReadMessageContent).ToArray(); } diff --git a/ChatTwo/Http/RouteController.cs b/ChatTwo/Http/RouteController.cs index 3dd9e88..63bf9af 100644 --- a/ChatTwo/Http/RouteController.cs +++ b/ChatTwo/Http/RouteController.cs @@ -154,12 +154,6 @@ public class RouteController #region PostAuthRoutes private async Task ChatBoxRoute(HttpContextBase ctx) { - if (Plugin.ChatLogWindow.CurrentTab == null) - { - await ctx.Response.Send("No valid chat tab!"); - return; - } - await ctx.Response.Send(ChatBoxTemplate); } @@ -183,7 +177,7 @@ public class RouteController await Plugin.Framework.RunOnFrameworkThread(() => { Plugin.ChatLogWindow.Chat = content.Message; - Plugin.ChatLogWindow.SendChatBox(Plugin.ChatLogWindow.CurrentTab); + Plugin.ChatLogWindow.SendChatBox(Plugin.CurrentTab); }); ctx.Response.StatusCode = 201; diff --git a/ChatTwo/Http/ServerCore.cs b/ChatTwo/Http/ServerCore.cs index 89a4d2b..64c412e 100644 --- a/ChatTwo/Http/ServerCore.cs +++ b/ChatTwo/Http/ServerCore.cs @@ -1,4 +1,5 @@ using ChatTwo.Http.MessageProtocol; + namespace ChatTwo.Http; public class ServerCore : IAsyncDisposable diff --git a/ChatTwo/MessageManager.cs b/ChatTwo/MessageManager.cs index 4986a84..ea277fe 100644 --- a/ChatTwo/MessageManager.cs +++ b/ChatTwo/MessageManager.cs @@ -256,11 +256,11 @@ internal class MessageManager : IAsyncDisposable if (Plugin.Config.DatabaseBattleMessages || !message.Code.IsBattle()) Store.UpsertMessage(message); - var currentTabId = Plugin.ChatLogWindow.CurrentTab?.Identifier ?? Guid.Empty; - var currentMatches = Plugin.ChatLogWindow.CurrentTab?.Matches(message) ?? false; + var currentTabId = Plugin.CurrentTab.Identifier; + var currentMatches = Plugin.CurrentTab.Matches(message); foreach (var tab in Plugin.Config.Tabs) { - var unread = !(tab.UnreadMode == UnreadMode.Unseen && Plugin.ChatLogWindow.CurrentTab != tab && currentMatches); + var unread = !(tab.UnreadMode == UnreadMode.Unseen && Plugin.CurrentTab != tab && currentMatches); if (tab.Matches(message)) { diff --git a/ChatTwo/PayloadHandler.cs b/ChatTwo/PayloadHandler.cs index 98165fc..a481300 100755 --- a/ChatTwo/PayloadHandler.cs +++ b/ChatTwo/PayloadHandler.cs @@ -585,7 +585,7 @@ public sealed class PayloadHandler { GameFunctions.Party.InviteSameWorld(player.PlayerName, (ushort) world.RowId, chunk.Message?.ContentId ?? 0); if (validContentId && ImGui.Selectable(Language.Context_InviteToParty_DifferentWorld)) - GameFunctions.Party.InviteOtherWorld(chunk.Message!.ContentId); + GameFunctions.Party.InviteOtherWorld(chunk.Message!.ContentId, (ushort) world.RowId); ImGui.EndMenu(); } diff --git a/ChatTwo/Plugin.cs b/ChatTwo/Plugin.cs index 69ffc93..f63ddee 100755 --- a/ChatTwo/Plugin.cs +++ b/ChatTwo/Plugin.cs @@ -66,6 +66,18 @@ public sealed class Plugin : IDalamudPlugin internal DateTime GameStarted { get; } + // Tab managed needs to happen outside the chatlog window class for access reasons + internal int LastTab { get; set; } + internal int? WantedTab { get; set; } + internal Tab CurrentTab + { + get + { + var i = LastTab; + return i > -1 && i < Config.Tabs.Count ? Config.Tabs[i] : new Tab(); + } + } + public Plugin() { try diff --git a/ChatTwo/Ui/ChatLogWindow.cs b/ChatTwo/Ui/ChatLogWindow.cs index 870964b..67fb1b2 100644 --- a/ChatTwo/Ui/ChatLogWindow.cs +++ b/ChatTwo/Ui/ChatLogWindow.cs @@ -20,7 +20,6 @@ using Dalamud.Interface.Windowing; using Dalamud.Memory; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using FFXIVClientStructs.FFXIV.Client.UI.Info; using ImGuiNET; using Lumina.Excel.Sheets; @@ -41,24 +40,12 @@ public sealed class ChatLogWindow : Window internal Vector4 DefaultText { get; set; } - internal int? WantedTab { get; set; } - internal Tab? CurrentTab - { - get - { - var i = LastTab; - return i > -1 && i < Plugin.Config.Tabs.Count ? Plugin.Config.Tabs[i] : null; - } - } - internal bool FocusedPreview; internal bool Activate; private int ActivatePos = -1; internal string Chat = string.Empty; private readonly List InputBacklog = []; private int InputBacklogIdx = -1; - private int LastTab { get; set; } - private InputChannel? TempChannel; public bool TellSpecial; private readonly Stopwatch LastResize = new(); private AutoCompleteInfo? AutoCompleteInfo; @@ -141,10 +128,6 @@ public sealed class ChatLogWindow : Window internal unsafe void Activated(ChatActivatedArgs args) { - // Return if we don't have a tab selected - if (CurrentTab == null) - return; - TellSpecial = args.TellSpecial; Activate = true; @@ -153,7 +136,7 @@ public sealed class ChatLogWindow : Window UIModule.PlaySound(ChatOpenSfx); // Don't set the channel or text content when activating a disabled tab. - if (CurrentTab?.InputDisabled == true) + if (Plugin.CurrentTab.InputDisabled) { // The closing sound would've been immediately played in this case. PlayedClosingSound = true; @@ -175,24 +158,26 @@ public sealed class ChatLogWindow : Window { if (info.Rotate != RotateMode.None) { - var idx = TempChannel != InputChannel.Tell + var idx = Plugin.CurrentTab.CurrentChannel.TempChannel != InputChannel.Tell ? 0 : info.Rotate == RotateMode.Reverse ? -1 : 1; var tellInfo = Plugin.Functions.Chat.GetTellHistoryInfo(idx); if (tellInfo != null && reason != null) - CurrentTab!.TellTarget = new TellTarget(tellInfo.Name, (ushort) tellInfo.World, tellInfo.ContentId, reason.Value); + { + Plugin.CurrentTab.CurrentChannel.TellTarget = new TellTarget(tellInfo.Name, (ushort) tellInfo.World, tellInfo.ContentId, reason.Value); + } } else { - CurrentTab!.TellTarget = null; + Plugin.CurrentTab.CurrentChannel.TellTarget = null; if (target != null) - CurrentTab!.TellTarget = target; + Plugin.CurrentTab.CurrentChannel.TellTarget = target; } } else { - CurrentTab!.TellTarget = null; + Plugin.CurrentTab.CurrentChannel.TellTarget = null; } if (info.Channel is InputChannel.Linkshell1 or InputChannel.CrossLinkshell1 && info.Rotate != RotateMode.None) @@ -215,7 +200,7 @@ public sealed class ChatLogWindow : Window } else { - targetChannel = GameFunctions.Chat.ResolveTempInputChannel(TempChannel, info.Channel.Value, info.Rotate); + targetChannel = GameFunctions.Chat.ResolveTempInputChannel(Plugin.CurrentTab.CurrentChannel.TempChannel, info.Channel.Value, info.Rotate); } } @@ -226,9 +211,11 @@ public sealed class ChatLogWindow : Window } if (info.Permanent) + { SetChannel(targetChannel); + } else - TempChannel = targetChannel; + Plugin.CurrentTab.CurrentChannel.TempChannel = targetChannel.Value; } if (info.Text != null && Chat.Length == 0) @@ -253,8 +240,8 @@ public sealed class ChatLogWindow : Window Plugin.ChatGui.Print("- /clearlog2 help: shows this help"); break; default: - if (LastTab > -1 && LastTab < Plugin.Config.Tabs.Count) - Plugin.Config.Tabs[LastTab].Clear(); + if (Plugin.LastTab > -1 && Plugin.LastTab < Plugin.Config.Tabs.Count) + Plugin.Config.Tabs[Plugin.LastTab].Clear(); break; } } @@ -354,31 +341,27 @@ public sealed class ChatLogWindow : Window } internal void ChangeTab(int index) { - WantedTab = index; + Plugin.WantedTab = index; LastActivityTime = FrameTime; } internal void ChangeTabDelta(int offset) { - var newIndex = (LastTab + offset) % Plugin.Config.Tabs.Count; + var newIndex = (Plugin.LastTab + offset) % Plugin.Config.Tabs.Count; while (newIndex < 0) newIndex += Plugin.Config.Tabs.Count; ChangeTab(newIndex); } - private void TabChannelSwitch(Tab tab) + private void TabChannelSwitch(Tab newTab, Tab previousTab) { - // Save the previous channel to restore it later - var current = CurrentTab; - if (current is { Channel: null }) - current.PreviousChannel = Plugin.Functions.Chat.Channel.Channel; + // Use the fixed channel if set by the user, or set it to the current tabs channel if this tab wasn't accessed before + if (newTab.Channel is not null) + newTab.CurrentChannel.Channel = newTab.Channel.Value; + else if (newTab.CurrentChannel.Channel is InputChannel.Invalid) + newTab.CurrentChannel = previousTab.CurrentChannel; - // Channel will be null if PreviousChannel is used - var channel = tab.Channel ?? tab.PreviousChannel; - - // If channel is null it doesn't have a default, and we never selected this channel before - if (channel != null) - SetChannel(tab.Channel ?? tab.PreviousChannel); + SetChannel(newTab.CurrentChannel.Channel); // Inform the webinterface about tab switch // TODO implement tabs in the webinterface @@ -475,7 +458,7 @@ public sealed class ChatLogWindow : Window return true; } - var currentTab = CurrentTab; // local to avoid calling the getter repeatedly + var currentTab = Plugin.CurrentTab; // local to avoid calling the getter repeatedly var lastActivityTime = Plugin.Config.Tabs .Where(tab => !tab.PopOut && (tab.UnhideOnActivity || tab == currentTab)) .Select(tab => tab.LastActivity) @@ -499,7 +482,7 @@ public sealed class ChatLogWindow : Window // the text input in a tab with input disabled. The usual way that // Activate gets disabled is via the text input callback, but that // doesn't get called if the input is disabled. - if (CurrentTab?.InputDisabled == true) + if (Plugin.CurrentTab.InputDisabled == true) Activate = false; if (Plugin.Config is { OverrideStyle: true, ChosenStyle: not null }) StyleModel.GetConfiguredStyles()?.FirstOrDefault(style => style.Name == Plugin.Config.ChosenStyle)?.Pop(); @@ -548,12 +531,12 @@ public sealed class ChatLogWindow : Window if (IsChatMode && Plugin.InputPreview.IsDrawable) Plugin.InputPreview.CalculatePreview(); - var currentTab = Plugin.Config.SidebarTabView ? DrawTabSidebar() : DrawTabBar(); - - Tab? activeTab = null; - if (currentTab > -1 && currentTab < Plugin.Config.Tabs.Count) - activeTab = Plugin.Config.Tabs[currentTab]; + if (Plugin.Config.SidebarTabView) + DrawTabSidebar(); + else + DrawTabBar(); + var activeTab = Plugin.CurrentTab; if (Plugin.Config.PreviewPosition is PreviewPosition.Inside && Plugin.InputPreview.IsDrawable) Plugin.InputPreview.DrawPreview(); @@ -563,10 +546,10 @@ public sealed class ChatLogWindow : Window } var beforeIcon = ImGui.GetCursorPos(); - if (ImGuiUtil.IconButton(FontAwesomeIcon.Comment) && activeTab is not { Channel: not null }) + if (ImGuiUtil.IconButton(FontAwesomeIcon.Comment) && activeTab.Channel is null) ImGui.OpenPopup(ChatChannelPicker); - if (activeTab is { Channel: not null } && ImGui.IsItemHovered()) + if (activeTab.Channel is not null && ImGui.IsItemHovered()) ImGui.SetTooltip(Language.ChatLog_SwitcherDisabled); using (var popup = ImRaii.Popup(ChatChannelPicker)) @@ -588,7 +571,7 @@ public sealed class ChatLogWindow : Window var buttonsRight = (showNovice ? 1 : 0) + (Plugin.Config.ShowHideButton ? 1 : 0); var inputWidth = ImGui.GetContentRegionAvail().X - buttonWidth * (1 + buttonsRight); - var inputType = TempChannel?.ToChatType() ?? activeTab?.Channel?.ToChatType() ?? Plugin.Functions.Chat.Channel.Channel.ToChatType(); + var inputType = activeTab.CurrentChannel.UseTempChannel ? activeTab.CurrentChannel.TempChannel.ToChatType() : activeTab.CurrentChannel.Channel.ToChatType(); var isCommand = Chat.Trim().StartsWith('/'); if (isCommand) { @@ -642,10 +625,10 @@ public sealed class ChatLogWindow : Window { Chat = chatCopy; - if (Plugin.Functions.Chat.UsesTellTempChannel) + if (activeTab.CurrentChannel.UseTempChannel) { - Plugin.Functions.Chat.UsesTellTempChannel = false; - SetChannel(Plugin.Functions.Chat.PreviousChannel); + activeTab.CurrentChannel.UseTempChannel = false; + SetChannel(activeTab.CurrentChannel.Channel); } } @@ -654,10 +637,10 @@ public sealed class ChatLogWindow : Window Plugin.CommandHelpWindow.IsOpen = false; SendChatBox(activeTab); - if (Plugin.Functions.Chat.UsesTellTempChannel) + if (activeTab.CurrentChannel.UseTempChannel) { - Plugin.Functions.Chat.UsesTellTempChannel = false; - SetChannel(Plugin.Functions.Chat.PreviousChannel); + activeTab.CurrentChannel.UseTempChannel = false; + SetChannel(activeTab.CurrentChannel.Channel); } } } @@ -678,14 +661,10 @@ public sealed class ChatLogWindow : Window UIModule.PlaySound(ChatCloseSfx); } - if (TempChannel is InputChannel.Tell && CurrentTab != null) - CurrentTab.TellTarget = null; - - TempChannel = null; - if (Plugin.Functions.Chat.UsesTellTempChannel) + if (Plugin.CurrentTab.CurrentChannel.UseTempChannel) { - Plugin.Functions.Chat.UsesTellTempChannel = false; - SetChannel(Plugin.Functions.Chat.PreviousChannel); + Plugin.CurrentTab.CurrentChannel.UseTempChannel = false; + SetChannel(Plugin.CurrentTab.CurrentChannel.Channel); } } @@ -729,6 +708,9 @@ public sealed class ChatLogWindow : Window var channels = new Dictionary(); foreach (var channel in Enum.GetValues()) { + if (!channel.IsValid()) + continue; + var name = Sheets.LogFilterSheet.FirstOrNull(row => row.LogKind == (byte) channel.ToChatType())?.Name.ExtractText() ?? channel.ToChatType().Name(); if (channel.IsLinkshell()) { @@ -763,32 +745,31 @@ public sealed class ChatLogWindow : Window return channels; } - private void DrawChannelName(Tab? activeTab) + private void DrawChannelName(Tab activeTab) { var currentChannel = ReadChannelName(activeTab); - if (!currentChannel.SequenceEqual(PreviousChannel)) { PreviousChannel = currentChannel; Plugin.ServerCore?.SendChannelSwitch(currentChannel); } - DrawChunks(ReadChannelName(activeTab)); + DrawChunks(currentChannel); } - internal Chunk[] ReadChannelName(Tab? activeTab) + internal Chunk[] ReadChannelName(Tab activeTab) { Chunk[] channelNameChunks; - if (activeTab?.TellTarget != null) + if (activeTab.CurrentChannel.TellTarget != null && activeTab.CurrentChannel.TellTarget.IsSet()) { - var playerName = activeTab.TellTarget.Name; + var playerName = activeTab.CurrentChannel.TellTarget.Name; if (ScreenshotMode) // Note: don't use HidePlayerInString here because // abbreviation settings do not affect this. - playerName = HashPlayer(activeTab.TellTarget.Name, activeTab.TellTarget.World); + playerName = HashPlayer(activeTab.CurrentChannel.TellTarget.Name, activeTab.CurrentChannel.TellTarget.World); - var world = Sheets.WorldSheet.HasRow(activeTab.TellTarget.World) - ? Sheets.WorldSheet.GetRow(activeTab.TellTarget.World).Name.ExtractText() + var world = Sheets.WorldSheet.TryGetRow(activeTab.CurrentChannel.TellTarget.World, out var worldRow) + ? worldRow.Name.ExtractText() : "???"; channelNameChunks = @@ -799,24 +780,24 @@ public sealed class ChatLogWindow : Window new TextChunk(ChunkSource.None, null, world) ]; } - else if (TempChannel != null) + else if (activeTab.CurrentChannel.UseTempChannel) { string name; - if (TempChannel.Value.IsLinkshell()) + if (activeTab.CurrentChannel.TempChannel.IsLinkshell()) { - var idx = (uint) TempChannel.Value - (uint) InputChannel.Linkshell1; + var idx = (uint) activeTab.CurrentChannel.TempChannel - (uint) InputChannel.Linkshell1; var lsName = Plugin.Functions.Chat.GetLinkshellName(idx); name = $"LS #{idx + 1}: {lsName}"; } - else if (TempChannel.Value.IsCrossLinkshell()) + else if (activeTab.CurrentChannel.TempChannel.IsCrossLinkshell()) { - var idx = (uint) TempChannel.Value - (uint) InputChannel.CrossLinkshell1; + var idx = (uint) activeTab.CurrentChannel.TempChannel - (uint) InputChannel.CrossLinkshell1; var cwlsName = Plugin.Functions.Chat.GetCrossLinkshellName(idx); name = $"CWLS [{idx + 1}]: {cwlsName}"; } else { - name = TempChannel.Value.ToChatType().Name(); + name = activeTab.CurrentChannel.TempChannel.ToChatType().Name(); } channelNameChunks = [new TextChunk(ChunkSource.None, null, name)]; @@ -835,15 +816,15 @@ public sealed class ChatLogWindow : Window { channelNameChunks = [new TextChunk(ChunkSource.None, null, overrideName)]; } - else if (ScreenshotMode && Plugin.Functions.Chat.Channel is (InputChannel.Tell, _, var tellPlayerName, var tellWorldId)) + else if (ScreenshotMode && activeTab.CurrentChannel.Channel is InputChannel.Tell && activeTab.CurrentChannel.TellTarget != null) { - if (!string.IsNullOrWhiteSpace(tellPlayerName) && tellWorldId != 0) + if (!string.IsNullOrWhiteSpace(activeTab.CurrentChannel.TellTarget.Name) && activeTab.CurrentChannel.TellTarget.World != 0) { // Note: don't use HidePlayerInString here because // abbreviation settings do not affect this. - var playerName = HashPlayer(tellPlayerName, tellWorldId); - var world = Sheets.WorldSheet.HasRow(tellWorldId) - ? Sheets.WorldSheet.GetRow(tellWorldId).Name.ExtractText() + var playerName = HashPlayer(activeTab.CurrentChannel.TellTarget.Name, activeTab.CurrentChannel.TellTarget.World); + var world = Sheets.WorldSheet.TryGetRow(activeTab.CurrentChannel.TellTarget.World, out var worldRow) + ? worldRow.Name.ExtractText() : "???"; channelNameChunks = @@ -863,7 +844,7 @@ public sealed class ChatLogWindow : Window } else { - channelNameChunks = Plugin.Functions.Chat.Channel.Name.ToArray(); + channelNameChunks = activeTab.CurrentChannel.Name.ToArray(); } return channelNameChunks; @@ -872,8 +853,9 @@ public sealed class ChatLogWindow : Window internal void SetChannel(InputChannel? channel) { channel ??= InputChannel.Say; - if (CurrentTab != null) - CurrentTab.TellTarget = null; + + if (channel != InputChannel.Tell) + Plugin.CurrentTab.CurrentChannel.TellTarget = null; // Instead of calling SetChannel(), we ask the ExtraChat plugin to set a // channel override by just calling the command directly. @@ -893,10 +875,10 @@ public sealed class ChatLogWindow : Window return; } - GameFunctions.Chat.SetChannel(channel.Value); + Plugin.Functions.Chat.SetChannel(channel.Value, Plugin.CurrentTab.CurrentChannel.TellTarget); } - internal void SendChatBox(Tab? activeTab) + internal void SendChatBox(Tab activeTab) { if (!string.IsNullOrWhiteSpace(Chat)) { @@ -912,8 +894,9 @@ public sealed class ChatLogWindow : Window Plugin.Functions.Chat.SendTellUsingCommandInner(tellBytes); TellSpecial = false; - if (TempChannel is InputChannel.Tell && CurrentTab != null) - CurrentTab.TellTarget = null; + activeTab.CurrentChannel.UseTempChannel = false; + if (activeTab.CurrentChannel.TempChannel is InputChannel.Tell) + activeTab.CurrentChannel.TellTarget = null; Chat = string.Empty; return; @@ -921,9 +904,22 @@ public sealed class ChatLogWindow : Window if (!trimmed.StartsWith('/')) { - if (CurrentTab?.TellTarget != null) + var target = activeTab.CurrentChannel.TellTarget; + if (target != null) { - var target = CurrentTab.TellTarget; + // ContentId 0 is a case where we can't directly send messages, so we send a /tell formatted message and let the game handle it + if (target.ContentId == 0) + { + trimmed = $"/tell {target.ToTargetString()} {trimmed}"; + var tellBytes = Encoding.UTF8.GetBytes(trimmed); + AutoTranslate.ReplaceWithPayload(ref tellBytes); + + Plugin.Common.SendMessageUnsafe(tellBytes); + + Chat = string.Empty; + return; + } + var reason = target.Reason; var world = Sheets.WorldSheet.GetRow(target.World); if (world is { IsPublic: true }) @@ -937,18 +933,17 @@ public sealed class ChatLogWindow : Window Plugin.Functions.Chat.SendTell(reason, target.ContentId, target.Name, (ushort) world.RowId, tellBytes, trimmed); } - if (TempChannel is InputChannel.Tell) - CurrentTab.TellTarget = null; + if (activeTab.CurrentChannel.TempChannel is InputChannel.Tell) + activeTab.CurrentChannel.TellTarget = null; Chat = string.Empty; return; } - - if (TempChannel != null) - trimmed = $"{TempChannel.Value.Prefix()} {trimmed}"; - else if (activeTab is { Channel: { } channel }) - trimmed = $"{channel.Prefix()} {trimmed}"; + if (activeTab.CurrentChannel.UseTempChannel) + trimmed = $"{activeTab.CurrentChannel.TempChannel.Prefix()} {trimmed}"; + else // (activeTab is { Channel: { } channel }) TODO Check this channel selection + trimmed = $"{activeTab.CurrentChannel.Channel.Prefix()} {trimmed}"; } var bytes = Encoding.UTF8.GetBytes(trimmed); @@ -1196,23 +1191,22 @@ public sealed class ChatLogWindow : Window } } - private int DrawTabBar() + private void DrawTabBar() { - var currentTab = -1; - using var tabBar = ImRaii.TabBar("##chat2-tabs"); if (!tabBar.Success) - return currentTab; + return; + var previousTab = Plugin.CurrentTab; for (var tabI = 0; tabI < Plugin.Config.Tabs.Count; tabI++) { var tab = Plugin.Config.Tabs[tabI]; if (tab.PopOut) continue; - var unread = tabI == LastTab || tab.UnreadMode == UnreadMode.None || tab.Unread == 0 ? "" : $" ({tab.Unread})"; + var unread = tabI == Plugin.LastTab || tab.UnreadMode == UnreadMode.None || tab.Unread == 0 ? "" : $" ({tab.Unread})"; var flags = ImGuiTabItemFlags.None; - if (WantedTab == tabI) + if (Plugin.WantedTab == tabI) flags |= ImGuiTabItemFlags.SetSelected; using var tabItem = ImRaii.TabItem($"{tab.Name}{unread}###log-tab-{tabI}", flags); DrawTabContextMenu(tab, tabI); @@ -1220,28 +1214,25 @@ public sealed class ChatLogWindow : Window if (!tabItem.Success) continue; - currentTab = tabI; - var switchedTab = LastTab != tabI; + var switchedTab = Plugin.LastTab != tabI; + + Plugin.LastTab = tabI; if (switchedTab) - TabChannelSwitch(tab); + TabChannelSwitch(tab, previousTab); - LastTab = tabI; tab.Unread = 0; - DrawMessageLog(tab, PayloadHandler, GetRemainingHeightForMessageLog(), switchedTab); } - WantedTab = null; - return currentTab; + Plugin.WantedTab = null; } - private int DrawTabSidebar() + private void DrawTabSidebar() { var currentTab = -1; - using var tabTable = ImRaii.Table("tabs-table", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.Resizable); if (!tabTable.Success) - return currentTab; + return; ImGui.TableSetupColumn("tabs", ImGuiTableColumnFlags.None, 1); ImGui.TableSetupColumn("chat", ImGuiTableColumnFlags.None, 4); @@ -1254,41 +1245,41 @@ public sealed class ChatLogWindow : Window { if (child) { + var previousTab = Plugin.CurrentTab; for (var tabI = 0; tabI < Plugin.Config.Tabs.Count; tabI++) { var tab = Plugin.Config.Tabs[tabI]; if (tab.PopOut) continue; - var unread = tabI == LastTab || tab.UnreadMode == UnreadMode.None || tab.Unread == 0 ? "" : $" ({tab.Unread})"; - var clicked = ImGui.Selectable($"{tab.Name}{unread}###log-tab-{tabI}", LastTab == tabI || WantedTab == tabI); + var unread = tabI == Plugin.LastTab || tab.UnreadMode == UnreadMode.None || tab.Unread == 0 ? "" : $" ({tab.Unread})"; + var clicked = ImGui.Selectable($"{tab.Name}{unread}###log-tab-{tabI}", Plugin.LastTab == tabI || Plugin.WantedTab == tabI); DrawTabContextMenu(tab, tabI); - if (!clicked && WantedTab != tabI) + if (!clicked && Plugin.WantedTab != tabI) continue; currentTab = tabI; - switchedTab = LastTab != tabI; + switchedTab = Plugin.LastTab != tabI; + Plugin.LastTab = tabI; if (switchedTab) - TabChannelSwitch(tab); - LastTab = tabI; + TabChannelSwitch(tab, previousTab); } } } ImGui.TableNextColumn(); - if (currentTab == -1 && LastTab < Plugin.Config.Tabs.Count) + if (currentTab == -1 && Plugin.LastTab < Plugin.Config.Tabs.Count) { - currentTab = LastTab; + currentTab = Plugin.LastTab; Plugin.Config.Tabs[currentTab].Unread = 0; } if (currentTab > -1) DrawMessageLog(Plugin.Config.Tabs[currentTab], PayloadHandler, childHeight, switchedTab); - WantedTab = null; - return currentTab; + Plugin.WantedTab = null; } private void DrawTabContextMenu(Tab tab, int i) diff --git a/ChatTwo/packages.lock.json b/ChatTwo/packages.lock.json index d6fdde7..30d1237 100644 --- a/ChatTwo/packages.lock.json +++ b/ChatTwo/packages.lock.json @@ -8,15 +8,6 @@ "resolved": "11.0.0", "contentHash": "bjT7XUlhIJSmsE/O76b7weUX+evvGQctbQB8aKXt94o+oPWxHpCepxAGMs7Thow3AzCyqWs7cOpp9/2wcgRRQA==" }, - "EmbedIO": { - "type": "Direct", - "requested": "[3.5.2, )", - "resolved": "3.5.2", - "contentHash": "YU4j+3XvuO8/VPkNf7KWOF1TpMhnyVhXnPsG1mvnDhTJ9D5BZOFXVDvCpE/SkQ1AJ0Aa+dXOVSW3ntgmLL7aJg==", - "dependencies": { - "Unosquare.Swan.Lite": "3.1.0" - } - }, "MessagePack": { "type": "Direct", "requested": "[3.0.238-rc.1, )", @@ -139,24 +130,11 @@ "resolved": "8.0.5", "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==" }, - "System.ValueTuple": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" - }, "Timestamps": { "type": "Transitive", "resolved": "1.0.9", "contentHash": "xdCVp+T4VFXOT+Ube2Cz527fnFXFUxQK24uPMGcCmICIpIcGCXPtI7vKLnWsJ6Nfc1B92tNBK9e/I8NDq2ee6g==" }, - "Unosquare.Swan.Lite": { - "type": "Transitive", - "resolved": "3.1.0", - "contentHash": "X3s5QE/KMj3WAPFqFve7St+Ds10BB50u8kW8PmKIn7FVkn7yEXe9Yxr2htt1WV85DRqfFR0MN/BUNHkGHtL4OQ==", - "dependencies": { - "System.ValueTuple": "4.5.0" - } - }, "UrlMatcher": { "type": "Transitive", "resolved": "3.0.0",