From f90b20aefefa634f3feafc616d89c7577c3886c9 Mon Sep 17 00:00:00 2001 From: baltdev Date: Sun, 21 Jul 2024 11:47:22 -0500 Subject: [PATCH 01/15] /locate command --- .../CMDs/CmdLocate.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs diff --git a/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs b/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs new file mode 100644 index 00000000..070ee622 --- /dev/null +++ b/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using Celeste.Mod.CelesteNet.DataTypes; +using MonoMod.Utils; + +namespace Celeste.Mod.CelesteNet.Server.Chat.Cmd; + +public class CmdLocate : ChatCmd { + + public override CompletionType Completion => CompletionType.Player; + + public override string Info => "Find where a player is."; + + private CelesteNetPlayerSession? Other; + private DataPlayerInfo? OtherPlayer; + private DataPlayerState? OtherState; + + public override void Init(ChatModule chat) { + Chat = chat; + + ArgParser parser = new(chat, this); + parser.AddParameter(new ParamPlayerSession(chat, ValidatePlayerSession)); + ArgParsers.Add(parser); + } + + private void ValidatePlayerSession(string raw, CmdEnv env, ICmdArg arg) { + if (arg is not CmdArgPlayerSession sessionArg) + throw new CommandRunException("Invalid username or ID."); + + CelesteNetPlayerSession? other = sessionArg.Session; + DataPlayerInfo otherPlayer = other?.PlayerInfo ?? throw new CommandRunException("Invalid username or ID."); + + Other = other; + OtherPlayer = otherPlayer; + + if (!env.Server.Data.TryGetBoundRef(otherPlayer, out DataPlayerState? otherState) || + otherState == null || + otherState.SID.IsNullOrEmpty()) + return; + + OtherState = otherState; + } + + + public override void Run(CmdEnv env, List? args) { + if (Other == null || OtherPlayer == null) + throw new InvalidOperationException("This should never happen if ValidatePlayerSession returns without error."); + + if (OtherState == null) { + env.Send($"{OtherPlayer.FullName} isn't in game."); + return; + } + + // TODO: + // Ideally, part of CelesteNetPlayerListComponent should be factored out for this. + // I don't have the time or energy to *do* that, though. + + string chapter = OtherState.SID; + string Side = ((char) ('A' + (int) OtherState.Mode)).ToString(); + string Level = OtherState.Level; + + env.Send($"{OtherPlayer.FullName} is in room {Level} of {chapter}, side {Side}."); + } + +} From 7f42447f7da7a96b54d676ed853deacbd6203c79 Mon Sep 17 00:00:00 2001 From: baltdev Date: Sun, 21 Jul 2024 17:40:02 -0500 Subject: [PATCH 02/15] Rework /locate to resolve the message client-side --- .../Components/CelesteNetChatComponent.cs | 51 +++++++++++++++++++ .../CelesteNetPlayerListComponent.cs | 2 +- .../CMDs/CmdLocate.cs | 29 +++-------- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetChatComponent.cs b/CelesteNet.Client/Components/CelesteNetChatComponent.cs index 80f1b70a..38d19a1d 100644 --- a/CelesteNet.Client/Components/CelesteNetChatComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetChatComponent.cs @@ -1,12 +1,16 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; using System.Text.RegularExpressions; using Celeste.Mod.CelesteNet.Client.Entities; using Celeste.Mod.CelesteNet.DataTypes; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using Monocle; +using static Celeste.Mod.CelesteNet.Client.Components.CelesteNetPlayerListComponent; namespace Celeste.Mod.CelesteNet.Client.Components { public class CelesteNetChatComponent : CelesteNetGameComponent { @@ -349,9 +353,56 @@ public void Handle(CelesteNetConnection con, DataChannelList channelList) { CurrentChannelName = tmp; } + #nullable enable + public void HandleLocate(DataChat chat) { + // For some reason, using the actual target field always came up as null for me. + // Instead, I'm opting to use the Player field, Because It Works :TM: + var target = chat.Player ?? throw new InvalidOperationException("Target of /locate should not be null"); + + string iconEmoji = ""; + + if (Client?.Data?.TryGetBoundRef(target, out DataPlayerState? state) == true) { // This can be true, false, or null + // Abuse the player list representation to get a formatted version + var fakeBlob = new BlobPlayer(); + CelesteNetPlayerListComponent playerlist = (CelesteNetPlayerListComponent) Context.Components[typeof(CelesteNetPlayerListComponent)]; + playerlist.GetState(fakeBlob, state); + var location = fakeBlob.Location; + + if (location.Icon != null) { + string emojiId = $"celestenet_SID_{location.SID}_"; + if (!Emoji.Registered.Contains(emojiId)) { + // We need to downscale the icon to fit in chat + MTexture? icon = + (fakeBlob.LocationMode & LocationModes.Icons) != 0 && GFX.Gui.Has(location.Icon) + ? GFX.Gui[location.Icon] + : null; + if (icon == null) { goto Skip; } + float scale = 64f / icon.Height; + + var tex = new MTexture(new MTexture(icon.Texture), icon.ClipRect) { ScaleFix = scale }; + Emoji.Register(emojiId, tex); + Emoji.Fill(CelesteNetClientFont.Font); + } + iconEmoji = $":{emojiId}:"; + } + Skip: + chat.Text = $"{target.FullName} is in room '{location.Level}' of {iconEmoji} {location.Name}{(location.Side.Length != 0 ? $" {location.Side}" : "")}."; + } else { + chat.Text = $"{target.FullName} isn't in game or is in another channel."; + } + chat.Player = null; + chat.Tag = ""; + } + #nullable restore + public void Handle(CelesteNetConnection con, DataChat msg) { if (Client == null) return; + + if (msg.Tag == "locate") { + Logger.Log("!", "Found /locate! Handling..."); + HandleLocate(msg); + } if (Settings.PlayerListUI.HideOwnChannelName) { // don't get too eager, only replace text in ACK'd commands and server responses diff --git a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs index 76de7fe1..0e3c4d5a 100644 --- a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs @@ -528,7 +528,7 @@ private DataPlayerInfo ListPlayerUnderChannel(BlobPlayer blob, DataPlayerInfo pl } } - private void GetState(BlobPlayer blob, DataPlayerState state) { + public void GetState(BlobPlayer blob, DataPlayerState state) { if (!string.IsNullOrWhiteSpace(state.SID)) { AreaData area = AreaData.Get(state.SID); string chapter = area?.Name?.DialogCleanOrNull(Dialog.Languages["english"]) ?? state.SID; diff --git a/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs b/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs index 070ee622..e334739b 100644 --- a/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs +++ b/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs @@ -13,7 +13,6 @@ public class CmdLocate : ChatCmd { private CelesteNetPlayerSession? Other; private DataPlayerInfo? OtherPlayer; - private DataPlayerState? OtherState; public override void Init(ChatModule chat) { Chat = chat; @@ -32,13 +31,6 @@ private void ValidatePlayerSession(string raw, CmdEnv env, ICmdArg arg) { Other = other; OtherPlayer = otherPlayer; - - if (!env.Server.Data.TryGetBoundRef(otherPlayer, out DataPlayerState? otherState) || - otherState == null || - otherState.SID.IsNullOrEmpty()) - return; - - OtherState = otherState; } @@ -46,20 +38,15 @@ public override void Run(CmdEnv env, List? args) { if (Other == null || OtherPlayer == null) throw new InvalidOperationException("This should never happen if ValidatePlayerSession returns without error."); - if (OtherState == null) { - env.Send($"{OtherPlayer.FullName} isn't in game."); - return; - } - - // TODO: - // Ideally, part of CelesteNetPlayerListComponent should be factored out for this. - // I don't have the time or energy to *do* that, though. - - string chapter = OtherState.SID; - string Side = ((char) ('A' + (int) OtherState.Mode)).ToString(); - string Level = OtherState.Level; + CelesteNetPlayerSession? self = env.Session ?? throw new CommandRunException("Cannot locate as the server."); - env.Send($"{OtherPlayer.FullName} is in room {Level} of {chapter}, side {Side}."); + var chat = new DataChat { + Player = OtherPlayer, + Tag = "locate", + Text = "If you see this, either your client does not properly handle /locate or this is a bug. Please report!", + Color = Chat.Settings.ColorCommandReply + }; + self.Con.Send(Chat.PrepareAndLog(self, chat)); } } From 18e292b609888a7ffb505b134ab4b6e3a4a4b666 Mon Sep 17 00:00:00 2001 From: baltdev Date: Sun, 21 Jul 2024 17:45:41 -0500 Subject: [PATCH 03/15] Fix spacing on icon --- CelesteNet.Client/Components/CelesteNetChatComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CelesteNet.Client/Components/CelesteNetChatComponent.cs b/CelesteNet.Client/Components/CelesteNetChatComponent.cs index 38d19a1d..59c94a1d 100644 --- a/CelesteNet.Client/Components/CelesteNetChatComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetChatComponent.cs @@ -386,7 +386,7 @@ public void HandleLocate(DataChat chat) { iconEmoji = $":{emojiId}:"; } Skip: - chat.Text = $"{target.FullName} is in room '{location.Level}' of {iconEmoji} {location.Name}{(location.Side.Length != 0 ? $" {location.Side}" : "")}."; + chat.Text = $"{target.FullName} is in room '{location.Level}' of {(iconEmoji.Length != 0 ? $"{iconEmoji} " : "")}{location.Name}{(location.Side.Length != 0 ? $" {location.Side}" : "")}."; } else { chat.Text = $"{target.FullName} isn't in game or is in another channel."; } From ecb78f3050fd29665b8d5fac9a8c8c301d09098b Mon Sep 17 00:00:00 2001 From: baltdev Date: Sun, 21 Jul 2024 17:52:55 -0500 Subject: [PATCH 04/15] Properly handle when SID is empty --- .../Components/CelesteNetChatComponent.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetChatComponent.cs b/CelesteNet.Client/Components/CelesteNetChatComponent.cs index 59c94a1d..1271924f 100644 --- a/CelesteNet.Client/Components/CelesteNetChatComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetChatComponent.cs @@ -360,13 +360,17 @@ public void HandleLocate(DataChat chat) { var target = chat.Player ?? throw new InvalidOperationException("Target of /locate should not be null"); string iconEmoji = ""; - + + chat.Text = $"{target.FullName} isn't in game or is in another channel."; if (Client?.Data?.TryGetBoundRef(target, out DataPlayerState? state) == true) { // This can be true, false, or null // Abuse the player list representation to get a formatted version var fakeBlob = new BlobPlayer(); - CelesteNetPlayerListComponent playerlist = (CelesteNetPlayerListComponent) Context.Components[typeof(CelesteNetPlayerListComponent)]; + var playerlist = Context.Get(); playerlist.GetState(fakeBlob, state); var location = fakeBlob.Location; + if (location.SID.Length == 0) { + goto Done; + } if (location.Icon != null) { string emojiId = $"celestenet_SID_{location.SID}_"; @@ -376,7 +380,7 @@ public void HandleLocate(DataChat chat) { (fakeBlob.LocationMode & LocationModes.Icons) != 0 && GFX.Gui.Has(location.Icon) ? GFX.Gui[location.Icon] : null; - if (icon == null) { goto Skip; } + if (icon == null) { goto MakeMessage; } float scale = 64f / icon.Height; var tex = new MTexture(new MTexture(icon.Texture), icon.ClipRect) { ScaleFix = scale }; @@ -385,11 +389,10 @@ public void HandleLocate(DataChat chat) { } iconEmoji = $":{emojiId}:"; } - Skip: + MakeMessage: chat.Text = $"{target.FullName} is in room '{location.Level}' of {(iconEmoji.Length != 0 ? $"{iconEmoji} " : "")}{location.Name}{(location.Side.Length != 0 ? $" {location.Side}" : "")}."; - } else { - chat.Text = $"{target.FullName} isn't in game or is in another channel."; } + Done: chat.Player = null; chat.Tag = ""; } From 857faceb953e36f7f3720c201d90bd18884c4f93 Mon Sep 17 00:00:00 2001 From: Balt <59123926+balt-dev@users.noreply.github.com> Date: Sun, 21 Jul 2024 17:55:49 -0500 Subject: [PATCH 05/15] Remove stray logging --- CelesteNet.Client/Components/CelesteNetChatComponent.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/CelesteNet.Client/Components/CelesteNetChatComponent.cs b/CelesteNet.Client/Components/CelesteNetChatComponent.cs index 1271924f..68bfb551 100644 --- a/CelesteNet.Client/Components/CelesteNetChatComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetChatComponent.cs @@ -403,7 +403,6 @@ public void Handle(CelesteNetConnection con, DataChat msg) { return; if (msg.Tag == "locate") { - Logger.Log("!", "Found /locate! Handling..."); HandleLocate(msg); } From 5a74bd2dba067579d3dbdf43994730872c6e8fd9 Mon Sep 17 00:00:00 2001 From: baltdev Date: Sun, 21 Jul 2024 18:52:16 -0500 Subject: [PATCH 06/15] Reformat HandleLocate --- .../Components/CelesteNetChatComponent.cs | 72 ++++++++++++------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetChatComponent.cs b/CelesteNet.Client/Components/CelesteNetChatComponent.cs index 68bfb551..b2761cca 100644 --- a/CelesteNet.Client/Components/CelesteNetChatComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetChatComponent.cs @@ -357,44 +357,62 @@ public void Handle(CelesteNetConnection con, DataChannelList channelList) { public void HandleLocate(DataChat chat) { // For some reason, using the actual target field always came up as null for me. // Instead, I'm opting to use the Player field, Because It Works :TM: - var target = chat.Player ?? throw new InvalidOperationException("Target of /locate should not be null"); + var target = chat.Player; + + chat.Player = null; + chat.Tag = ""; + + if (target == null) { + chat.Text = $"Target of /locate not found."; + return; + } - string iconEmoji = ""; - chat.Text = $"{target.FullName} isn't in game or is in another channel."; - if (Client?.Data?.TryGetBoundRef(target, out DataPlayerState? state) == true) { // This can be true, false, or null - // Abuse the player list representation to get a formatted version - var fakeBlob = new BlobPlayer(); - var playerlist = Context.Get(); - playerlist.GetState(fakeBlob, state); - var location = fakeBlob.Location; - if (location.SID.Length == 0) { - goto Done; - } - if (location.Icon != null) { - string emojiId = $"celestenet_SID_{location.SID}_"; - if (!Emoji.Registered.Contains(emojiId)) { - // We need to downscale the icon to fit in chat - MTexture? icon = - (fakeBlob.LocationMode & LocationModes.Icons) != 0 && GFX.Gui.Has(location.Icon) - ? GFX.Gui[location.Icon] - : null; - if (icon == null) { goto MakeMessage; } + DataPlayerState? state = null; + if (Client?.Data?.TryGetBoundRef(target, out state) == false || state == null) return; + + // Abuse the player list representation to get a formatted version + var fakeBlob = new BlobPlayer(); + Context.Get().GetState(fakeBlob, state); + var location = fakeBlob.Location; + + if (string.IsNullOrWhiteSpace(location.SID)) { + // We fall back to the above assignment + return; + } + + string iconEmoji = ""; + + if (location.Icon != null) { + string emojiId = $"celestenet_SID_{location.SID}_"; + if (!Emoji.Registered.Contains(emojiId)) { + // We need to downscale the icon to fit in chat + MTexture? icon = + (fakeBlob.LocationMode & LocationModes.Icons) != 0 && GFX.Gui.Has(location.Icon) + ? GFX.Gui[location.Icon] + : null; + if (icon != null) { float scale = 64f / icon.Height; var tex = new MTexture(new MTexture(icon.Texture), icon.ClipRect) { ScaleFix = scale }; Emoji.Register(emojiId, tex); Emoji.Fill(CelesteNetClientFont.Font); } - iconEmoji = $":{emojiId}:"; } - MakeMessage: - chat.Text = $"{target.FullName} is in room '{location.Level}' of {(iconEmoji.Length != 0 ? $"{iconEmoji} " : "")}{location.Name}{(location.Side.Length != 0 ? $" {location.Side}" : "")}."; + if (Emoji.Registered.Contains(emojiId)) + iconEmoji = $":{emojiId}:"; } - Done: - chat.Player = null; - chat.Tag = ""; + + string locationName = location.Name; + + if (!string.IsNullOrEmpty(iconEmoji)) + locationName = $"{iconEmoji} {locationName}"; + + if (!string.IsNullOrEmpty(location.Side)) + locationName += $" {location.Side}"; + + chat.Text = $"{target.FullName} is in room '{location.Level}' of {locationName}."; } #nullable restore From df2274532ad02d361b0035ed7b6fd1e77d639638 Mon Sep 17 00:00:00 2001 From: baltdev Date: Tue, 23 Jul 2024 10:36:00 -0500 Subject: [PATCH 07/15] Remove static import and nullable annotation --- .../Components/CelesteNetChatComponent.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetChatComponent.cs b/CelesteNet.Client/Components/CelesteNetChatComponent.cs index b2761cca..7f454266 100644 --- a/CelesteNet.Client/Components/CelesteNetChatComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetChatComponent.cs @@ -10,7 +10,6 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using Monocle; -using static Celeste.Mod.CelesteNet.Client.Components.CelesteNetPlayerListComponent; namespace Celeste.Mod.CelesteNet.Client.Components { public class CelesteNetChatComponent : CelesteNetGameComponent { @@ -353,7 +352,6 @@ public void Handle(CelesteNetConnection con, DataChannelList channelList) { CurrentChannelName = tmp; } - #nullable enable public void HandleLocate(DataChat chat) { // For some reason, using the actual target field always came up as null for me. // Instead, I'm opting to use the Player field, Because It Works :TM: @@ -369,11 +367,12 @@ public void HandleLocate(DataChat chat) { chat.Text = $"{target.FullName} isn't in game or is in another channel."; - DataPlayerState? state = null; + // This has to be set, or we will return early + DataPlayerState state = null; if (Client?.Data?.TryGetBoundRef(target, out state) == false || state == null) return; // Abuse the player list representation to get a formatted version - var fakeBlob = new BlobPlayer(); + var fakeBlob = new CelesteNetPlayerListComponent.BlobPlayer(); Context.Get().GetState(fakeBlob, state); var location = fakeBlob.Location; @@ -384,24 +383,26 @@ public void HandleLocate(DataChat chat) { string iconEmoji = ""; - if (location.Icon != null) { + if ( + // Icon exists + location.Icon != null && + // Location should have an icon + (fakeBlob.LocationMode & CelesteNetPlayerListComponent.LocationModes.Icons) != 0 && + // Icon is loaded + GFX.Gui.Has(location.Icon) + ) { string emojiId = $"celestenet_SID_{location.SID}_"; if (!Emoji.Registered.Contains(emojiId)) { // We need to downscale the icon to fit in chat - MTexture? icon = - (fakeBlob.LocationMode & LocationModes.Icons) != 0 && GFX.Gui.Has(location.Icon) - ? GFX.Gui[location.Icon] - : null; - if (icon != null) { - float scale = 64f / icon.Height; - - var tex = new MTexture(new MTexture(icon.Texture), icon.ClipRect) { ScaleFix = scale }; - Emoji.Register(emojiId, tex); - Emoji.Fill(CelesteNetClientFont.Font); - } + // Due to our previous checks, this is never null + MTexture icon = GFX.Gui[location.Icon]; + float scale = 64f / icon.Height; + + var tex = new MTexture(new MTexture(icon.Texture), icon.ClipRect) { ScaleFix = scale }; + Emoji.Register(emojiId, tex); + Emoji.Fill(CelesteNetClientFont.Font); } - if (Emoji.Registered.Contains(emojiId)) - iconEmoji = $":{emojiId}:"; + iconEmoji = $":{emojiId}:"; } string locationName = location.Name; @@ -414,7 +415,6 @@ public void HandleLocate(DataChat chat) { chat.Text = $"{target.FullName} is in room '{location.Level}' of {locationName}."; } - #nullable restore public void Handle(CelesteNetConnection con, DataChat msg) { if (Client == null) From 4fcca1179ae3bf00bd4ed9a6e50f1c6e377e8e5e Mon Sep 17 00:00:00 2001 From: baltdev Date: Tue, 23 Jul 2024 11:46:49 -0500 Subject: [PATCH 08/15] Change placeholder message --- CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs b/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs index e334739b..85da6d20 100644 --- a/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs +++ b/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs @@ -43,7 +43,8 @@ public override void Run(CmdEnv env, List? args) { var chat = new DataChat { Player = OtherPlayer, Tag = "locate", - Text = "If you see this, either your client does not properly handle /locate or this is a bug. Please report!", + // TODO: This really, REALLY should be a handshake. + Text = "Your client is out of date and does not support /locate. Please update and try again.", Color = Chat.Settings.ColorCommandReply }; self.Con.Send(Chat.PrepareAndLog(self, chat)); From a164deaf4dab6812d94d368bc094e2e92b32b33f Mon Sep 17 00:00:00 2001 From: baltdev Date: Tue, 23 Jul 2024 19:51:49 -0500 Subject: [PATCH 09/15] Add client feature gateway to /locate --- CelesteNet.Client/CelesteNetClient.cs | 2 ++ CelesteNet.Client/Components/CelesteNetChatComponent.cs | 4 +++- CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs | 7 +++++-- CelesteNet.Server.ChatModule/CMDs/CommandsContext.cs | 9 ++++++++- CelesteNet.Server.ChatModule/ChatModule.cs | 9 ++++++++- CelesteNet.Server/CelesteNetPlayerSession.cs | 2 +- CelesteNet.Shared/CelesteNetClientOptions.cs | 7 +++++-- CelesteNet.Shared/CommandInfo.cs | 4 ++++ 8 files changed, 36 insertions(+), 8 deletions(-) diff --git a/CelesteNet.Client/CelesteNetClient.cs b/CelesteNet.Client/CelesteNetClient.cs index 9fae7bea..5ee0dffe 100644 --- a/CelesteNet.Client/CelesteNetClient.cs +++ b/CelesteNet.Client/CelesteNetClient.cs @@ -54,6 +54,8 @@ public CelesteNetClient(CelesteNetClientSettings settings, CelesteNetClientOptio Options.ClientID = Settings.ClientID; Options.InstanceID = Settings.InstanceID; + Options.SupportedClientFeatures |= (ulong) CelesteNetSupportedClientFeatures.LocateCommand; + bool isServerLocalhost = Settings.Host == "localhost" || Settings.Host == "127.0.0.1"; if (Settings.ClientIDSending == cIDSendMode.Off || (Settings.ClientIDSending == cIDSendMode.NotOnLocalhost && isServerLocalhost)) diff --git a/CelesteNet.Client/Components/CelesteNetChatComponent.cs b/CelesteNet.Client/Components/CelesteNetChatComponent.cs index 7f454266..8f89fcd3 100644 --- a/CelesteNet.Client/Components/CelesteNetChatComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetChatComponent.cs @@ -627,7 +627,9 @@ public override void Update(GameTime gameTime) { // completions for commands or their first parameter if (Typing.StartsWith("/")) { int firstSpace = Typing.IndexOf(" "); - CommandInfo cmd = firstSpace == -1 ? null : CommandList.FirstOrDefault(c => c.ID == Typing.Substring(1, firstSpace - 1)); + CommandInfo cmd = firstSpace == -1 ? null : CommandList.FirstOrDefault(cmd => + cmd.ID == Typing.Substring(1, firstSpace - 1) + ); if (Typing.Substring(0, _CursorIndex).Equals(completable)) { UpdateCompletion(CompletionType.Command, completable.Substring(1).ToLowerInvariant()); diff --git a/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs b/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs index 85da6d20..d4be999e 100644 --- a/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs +++ b/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs @@ -11,6 +11,9 @@ public class CmdLocate : ChatCmd { public override string Info => "Find where a player is."; + public override CelesteNetSupportedClientFeatures RequiredFeatures => + CelesteNetSupportedClientFeatures.LocateCommand; + private CelesteNetPlayerSession? Other; private DataPlayerInfo? OtherPlayer; @@ -43,8 +46,8 @@ public override void Run(CmdEnv env, List? args) { var chat = new DataChat { Player = OtherPlayer, Tag = "locate", - // TODO: This really, REALLY should be a handshake. - Text = "Your client is out of date and does not support /locate. Please update and try again.", + // On older clients, we never get here. On newer clients, this is replaced. + Text = "{YOU SHOULD NEVER SEE THIS, PLEASE REPORT}", Color = Chat.Settings.ColorCommandReply }; self.Con.Send(Chat.PrepareAndLog(self, chat)); diff --git a/CelesteNet.Server.ChatModule/CMDs/CommandsContext.cs b/CelesteNet.Server.ChatModule/CMDs/CommandsContext.cs index 56e538c9..dd4edb5f 100644 --- a/CelesteNet.Server.ChatModule/CMDs/CommandsContext.cs +++ b/CelesteNet.Server.ChatModule/CMDs/CommandsContext.cs @@ -49,7 +49,8 @@ public CommandsContext(ChatModule chat) { Auth = cmd.MustAuth, AuthExec = cmd.MustAuthExec, FirstArg = cmd.Completion, - AliasTo = cmd.InternalAliasing ? "" : aliasTo?.ID ?? "" + AliasTo = cmd.InternalAliasing ? "" : aliasTo?.ID ?? "", + RequiredFeatures = cmd.RequiredFeatures }; } @@ -78,6 +79,7 @@ public abstract class ChatCmd { #pragma warning restore CS8618 public List ArgParsers = new(); + public virtual CelesteNetSupportedClientFeatures RequiredFeatures => 0; public virtual string ID => GetType().Name.Substring(3).ToLowerInvariant(); @@ -104,6 +106,11 @@ public virtual void ParseAndRun(CmdEnv env) { return; } + if (env.Session != null && !env.Session.CheckClientFeatureSupport(RequiredFeatures)) { + env.Error(new CommandRunException("Command is unsupported by your client! Try updating CelesteNet.")); + return; + } + List caught = new(); if (ArgParsers.Count == 0) { diff --git a/CelesteNet.Server.ChatModule/ChatModule.cs b/CelesteNet.Server.ChatModule/ChatModule.cs index c07ebde4..da4d2720 100644 --- a/CelesteNet.Server.ChatModule/ChatModule.cs +++ b/CelesteNet.Server.ChatModule/ChatModule.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; @@ -223,7 +224,13 @@ private void OnSessionStart(CelesteNetPlayerSession session) { Broadcast(Settings.MessageGreeting.InjectSingleValue("player", session.PlayerInfo?.FullName ?? "???")); SendTo(session, Settings.MessageMOTD); } - session.SendCommandList(Commands.DataAll); + + var commandList = Commands.DataAll + .List + .Where(cmd => session.CheckClientFeatureSupport(cmd.RequiredFeatures)) + .ToArray(); + var commands = new DataCommandList { List = commandList }; + session.SendCommandList(commands); SpamContext spam = session.Set(this, new SpamContext(this)); spam.OnSpam += (msg, timeout) => { diff --git a/CelesteNet.Server/CelesteNetPlayerSession.cs b/CelesteNet.Server/CelesteNetPlayerSession.cs index 2e7a559d..a889a89c 100644 --- a/CelesteNet.Server/CelesteNetPlayerSession.cs +++ b/CelesteNet.Server/CelesteNetPlayerSession.cs @@ -85,7 +85,7 @@ internal CelesteNetPlayerSession(CelesteNetServer server, CelesteNetConnection c } public bool CheckClientFeatureSupport(CelesteNetSupportedClientFeatures features) { - return (ClientOptions.SupportedClientFeatures & features) == features; + return (ClientOptions.SupportedClientFeatures & (ulong)features) == (ulong)features; } public T? Get(object ctx) where T : class { diff --git a/CelesteNet.Shared/CelesteNetClientOptions.cs b/CelesteNet.Shared/CelesteNetClientOptions.cs index 42c2ec10..560fe585 100644 --- a/CelesteNet.Shared/CelesteNetClientOptions.cs +++ b/CelesteNet.Shared/CelesteNetClientOptions.cs @@ -6,9 +6,12 @@ public class CelesteNetClientOptions { public bool AvatarsDisabled = false; public ulong ClientID; public uint InstanceID; - public CelesteNetSupportedClientFeatures SupportedClientFeatures; + // Due to how these are sent as packets, this needs to be a ulong. + public ulong SupportedClientFeatures = 0; } [Flags] - public enum CelesteNetSupportedClientFeatures : ulong {} + public enum CelesteNetSupportedClientFeatures : ulong { + LocateCommand = 1 >> 0 + } } \ No newline at end of file diff --git a/CelesteNet.Shared/CommandInfo.cs b/CelesteNet.Shared/CommandInfo.cs index 97d537b8..60a19648 100644 --- a/CelesteNet.Shared/CommandInfo.cs +++ b/CelesteNet.Shared/CommandInfo.cs @@ -6,6 +6,10 @@ public class CommandInfo { public bool Auth = false; public bool AuthExec = false; public CompletionType FirstArg = CompletionType.None; + + // NOTE: This is not sent to the client/server, for legacy compatibility reasons! Be careful. + // - 23 July, 2024 + public CelesteNetSupportedClientFeatures RequiredFeatures = 0; } public enum CompletionType : byte { From 7c543a55f3ec28b349800ae53d61defa32891e1d Mon Sep 17 00:00:00 2001 From: RedFlames Date: Mon, 5 Aug 2024 19:05:15 +0200 Subject: [PATCH 10/15] Refactor AreaData related logic of CelesteNetPlayerListComponent.GetState into helper class CelesteNetLocationInfo which /locate client logic can make use of. --- .../Components/CelesteNetChatComponent.cs | 46 +++++-------------- .../CelesteNetPlayerListComponent.cs | 29 ++++-------- 2 files changed, 20 insertions(+), 55 deletions(-) diff --git a/CelesteNet.Client/Components/CelesteNetChatComponent.cs b/CelesteNet.Client/Components/CelesteNetChatComponent.cs index 8f89fcd3..3b33bb42 100644 --- a/CelesteNet.Client/Components/CelesteNetChatComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetChatComponent.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Data; using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; using System.Text.RegularExpressions; using Celeste.Mod.CelesteNet.Client.Entities; using Celeste.Mod.CelesteNet.DataTypes; @@ -371,49 +369,27 @@ public void HandleLocate(DataChat chat) { DataPlayerState state = null; if (Client?.Data?.TryGetBoundRef(target, out state) == false || state == null) return; - // Abuse the player list representation to get a formatted version - var fakeBlob = new CelesteNetPlayerListComponent.BlobPlayer(); - Context.Get().GetState(fakeBlob, state); - var location = fakeBlob.Location; - - if (string.IsNullOrWhiteSpace(location.SID)) { + if (string.IsNullOrWhiteSpace(state.SID)) { // We fall back to the above assignment return; } + CelesteNetLocationInfo location = new(state); + + string locationTitle = location.Name; string iconEmoji = ""; - if ( - // Icon exists - location.Icon != null && - // Location should have an icon - (fakeBlob.LocationMode & CelesteNetPlayerListComponent.LocationModes.Icons) != 0 && - // Icon is loaded - GFX.Gui.Has(location.Icon) - ) { - string emojiId = $"celestenet_SID_{location.SID}_"; - if (!Emoji.Registered.Contains(emojiId)) { - // We need to downscale the icon to fit in chat - // Due to our previous checks, this is never null - MTexture icon = GFX.Gui[location.Icon]; - float scale = 64f / icon.Height; - - var tex = new MTexture(new MTexture(icon.Texture), icon.ClipRect) { ScaleFix = scale }; - Emoji.Register(emojiId, tex); - Emoji.Fill(CelesteNetClientFont.Font); - } - iconEmoji = $":{emojiId}:"; + if ((Settings.PlayerListUI.ShowPlayerListLocations & CelesteNetPlayerListComponent.LocationModes.Icons) != 0) { + iconEmoji = location.Emote; } - - string locationName = location.Name; - + if (!string.IsNullOrEmpty(iconEmoji)) - locationName = $"{iconEmoji} {locationName}"; + locationTitle = $"{iconEmoji} {locationTitle}"; if (!string.IsNullOrEmpty(location.Side)) - locationName += $" {location.Side}"; + locationTitle += $" {location.Side}"; - chat.Text = $"{target.FullName} is in room '{location.Level}' of {locationName}."; + chat.Text = $"{target.FullName} is in room '{location.Level}' of {locationTitle}."; } public void Handle(CelesteNetConnection con, DataChat msg) { @@ -830,7 +806,7 @@ public void UpdateCompletion(CompletionType type, string partial = "") { break; case CompletionType.Emoji: - IEnumerable filter_emotes = Emoji.Registered.Where(name => !name.StartsWith("celestenet_avatar_")); + IEnumerable filter_emotes = Emoji.Registered.Where(name => !name.StartsWith("celestenet_avatar_") && !name.StartsWith("celestenet_SID_")); if (string.IsNullOrEmpty(partial)) { Completion = filter_emotes.ToList(); diff --git a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs index 0e3c4d5a..d77a786e 100644 --- a/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetPlayerListComponent.cs @@ -530,30 +530,19 @@ private DataPlayerInfo ListPlayerUnderChannel(BlobPlayer blob, DataPlayerInfo pl public void GetState(BlobPlayer blob, DataPlayerState state) { if (!string.IsNullOrWhiteSpace(state.SID)) { - AreaData area = AreaData.Get(state.SID); - string chapter = area?.Name?.DialogCleanOrNull(Dialog.Languages["english"]) ?? state.SID; + CelesteNetLocationInfo location = new(state); blob.Location.Color = DefaultLevelColor; - blob.Location.TitleColor = Color.Lerp(area?.TitleBaseColor ?? Color.White, DefaultLevelColor, 0.5f); - blob.Location.AccentColor = Color.Lerp(area?.TitleAccentColor ?? Color.White, DefaultLevelColor, 0.8f); + blob.Location.TitleColor = Color.Lerp(location.Area?.TitleBaseColor ?? Color.White, DefaultLevelColor, 0.5f); + blob.Location.AccentColor = Color.Lerp(location.Area?.TitleAccentColor ?? Color.White, DefaultLevelColor, 0.8f); - blob.Location.SID = state.SID; - blob.Location.Name = chapter; - blob.Location.Side = ((char) ('A' + (int) state.Mode)).ToString(); - blob.Location.Level = state.Level; + blob.Location.SID = location.SID; + blob.Location.Name = location.Name; + blob.Location.Side = location.Side; + blob.Location.Level = location.Level; - blob.Location.IsRandomizer = chapter.StartsWith("randomizer/"); - - if (blob.Location.IsRandomizer || area == null) { - blob.Location.Icon = ""; - } else { - blob.Location.Icon = area?.Icon ?? ""; - - string lobbySID = area?.Meta?.Parent; - AreaData lobby = string.IsNullOrEmpty(lobbySID) ? null : AreaData.Get(lobbySID); - if (lobby?.Icon != null) - blob.Location.Icon = lobby.Icon; - } + blob.Location.IsRandomizer = location.IsRandomizer; + blob.Location.Icon = location.Icon; ShortenRandomizerLocation(ref blob.Location); } From e4841fc635b4621281f786a658d2a76527c2cfc0 Mon Sep 17 00:00:00 2001 From: RedFlames Date: Mon, 5 Aug 2024 19:17:04 +0200 Subject: [PATCH 11/15] Forgot to add CelesteNet.Client/CelesteNetLocationInfo.cs to repo...! --- CelesteNet.Client/CelesteNetLocationInfo.cs | 83 +++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 CelesteNet.Client/CelesteNetLocationInfo.cs diff --git a/CelesteNet.Client/CelesteNetLocationInfo.cs b/CelesteNet.Client/CelesteNetLocationInfo.cs new file mode 100644 index 00000000..80dcbc4d --- /dev/null +++ b/CelesteNet.Client/CelesteNetLocationInfo.cs @@ -0,0 +1,83 @@ +using Celeste.Mod.CelesteNet.DataTypes; + +namespace Celeste.Mod.CelesteNet.Client { + public class CelesteNetLocationInfo { + + public string SID { get; set; } + public AreaData Area { get; set; } + public string Name { get; set; } + public string Side { get; set; } + public string Level { get; set; } + public string Icon { get; set; } + public string EmoteID => string.IsNullOrWhiteSpace(SID) ? "" : $"celestenet_SID_{SID}_"; + + private bool emoteLoaded = false; + public string Emote => LoadIconEmote() ? $":{EmoteID}:" : ""; + public bool IsRandomizer => Name.StartsWith("randomizer/"); + + public CelesteNetLocationInfo() { } + + public CelesteNetLocationInfo(string sid) { + SID = sid; + Area = AreaData.Get(SID); + + Name = Area?.Name?.DialogCleanOrNull(Dialog.Languages["english"]) ?? SID; + Side = "A"; + Level = ""; + Icon = ""; + + if (!IsRandomizer && Area != null) { + Icon = Area.Icon; + + string lobbySID = Area?.Meta?.Parent; + AreaData lobby = string.IsNullOrEmpty(lobbySID) ? null : AreaData.Get(lobbySID); + if (lobby?.Icon != null) + Icon = lobby.Icon; + } + } + + public CelesteNetLocationInfo(DataPlayerState state) : this(state?.SID) { + + if (state != null) { + Side = ((char)('A' + (int)state.Mode)).ToString(); + Level = state.Level; + } + + if (!IsRandomizer && Area != null) { + Icon = Area.Icon; + + string lobbySID = Area?.Meta?.Parent; + AreaData lobby = string.IsNullOrEmpty(lobbySID) ? null : AreaData.Get(lobbySID); + if (lobby?.Icon != null) + Icon = lobby.Icon; + } + } + + public bool LoadIconEmote() { + if (emoteLoaded) + return true; + + if ( + // Icon exists + !string.IsNullOrWhiteSpace(Icon) && + // Can construct Emoji ID + !string.IsNullOrWhiteSpace(EmoteID) && + // Icon is loaded + GFX.Gui.Has(Icon) + ) { + if (!Emoji.Registered.Contains(EmoteID)) { + // We need to downscale the icon to fit in chat + // Due to our previous checks, this is never null + Monocle.MTexture icon = GFX.Gui[Icon]; + float scale = 64f / icon.Height; + + Monocle.MTexture tex = new(new(icon.Texture), icon.ClipRect) { ScaleFix = scale }; + Emoji.Register(EmoteID, tex); + Emoji.Fill(CelesteNetClientFont.Font); + } + emoteLoaded = Emoji.Registered.Contains(EmoteID); + } + return emoteLoaded; + } + } +} From 76db0190602f3d91324067e9650fded0ce40f803 Mon Sep 17 00:00:00 2001 From: RedFlames Date: Mon, 5 Aug 2024 19:22:20 +0200 Subject: [PATCH 12/15] Remove duplicated code in CelesteNetLocationInfo ctor --- CelesteNet.Client/CelesteNetLocationInfo.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/CelesteNet.Client/CelesteNetLocationInfo.cs b/CelesteNet.Client/CelesteNetLocationInfo.cs index 80dcbc4d..0661dff9 100644 --- a/CelesteNet.Client/CelesteNetLocationInfo.cs +++ b/CelesteNet.Client/CelesteNetLocationInfo.cs @@ -42,15 +42,6 @@ public CelesteNetLocationInfo(DataPlayerState state) : this(state?.SID) { Side = ((char)('A' + (int)state.Mode)).ToString(); Level = state.Level; } - - if (!IsRandomizer && Area != null) { - Icon = Area.Icon; - - string lobbySID = Area?.Meta?.Parent; - AreaData lobby = string.IsNullOrEmpty(lobbySID) ? null : AreaData.Get(lobbySID); - if (lobby?.Icon != null) - Icon = lobby.Icon; - } } public bool LoadIconEmote() { From 4cd4cbc5c1291992b5687808947feee1750453eb Mon Sep 17 00:00:00 2001 From: RedFlames Date: Mon, 5 Aug 2024 19:27:30 +0200 Subject: [PATCH 13/15] No idea why `using System.Data;` was in here. --- CelesteNet.Client/Components/CelesteNetChatComponent.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/CelesteNet.Client/Components/CelesteNetChatComponent.cs b/CelesteNet.Client/Components/CelesteNetChatComponent.cs index 3b33bb42..1ec40259 100644 --- a/CelesteNet.Client/Components/CelesteNetChatComponent.cs +++ b/CelesteNet.Client/Components/CelesteNetChatComponent.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Data; using System.Linq; using System.Text.RegularExpressions; using Celeste.Mod.CelesteNet.Client.Entities; From b7f86495706391b0aa08f5e648a1392872d805ba Mon Sep 17 00:00:00 2001 From: RedFlames Date: Sun, 18 Aug 2024 05:55:25 +0200 Subject: [PATCH 14/15] When sending CelesteNetClientOptions as header strings during the handshake, enum types would ToString to the names of the values rather than the underlying numeric type. This changes it so that the value gets sent as the underlying type and then the server parses it by its TypeCode as normal. --- CelesteNet.Client/CelesteNetClient.cs | 2 +- CelesteNet.Client/Handshake.cs | 8 +++++++- CelesteNet.Server.ChatModule/CMDs/CommandsContext.cs | 2 +- CelesteNet.Server/CelesteNetPlayerSession.cs | 2 +- CelesteNet.Shared/CelesteNetClientOptions.cs | 6 +++--- CelesteNet.Shared/CommandInfo.cs | 2 +- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CelesteNet.Client/CelesteNetClient.cs b/CelesteNet.Client/CelesteNetClient.cs index 5ee0dffe..8ddb7be0 100644 --- a/CelesteNet.Client/CelesteNetClient.cs +++ b/CelesteNet.Client/CelesteNetClient.cs @@ -54,7 +54,7 @@ public CelesteNetClient(CelesteNetClientSettings settings, CelesteNetClientOptio Options.ClientID = Settings.ClientID; Options.InstanceID = Settings.InstanceID; - Options.SupportedClientFeatures |= (ulong) CelesteNetSupportedClientFeatures.LocateCommand; + Options.SupportedClientFeatures |= CelesteNetSupportedClientFeatures.LocateCommand; bool isServerLocalhost = Settings.Host == "localhost" || Settings.Host == "127.0.0.1"; diff --git a/CelesteNet.Client/Handshake.cs b/CelesteNet.Client/Handshake.cs index 0a7b808e..d7f72eaa 100644 --- a/CelesteNet.Client/Handshake.cs +++ b/CelesteNet.Client/Handshake.cs @@ -28,6 +28,12 @@ public static class Handshake { "); foreach (FieldInfo field in typeof(CelesteNetClientOptions).GetFields(BindingFlags.Public | BindingFlags.Instance)) { + object optionValue = field.GetValue(options); + + if (field.FieldType.IsEnum) { + optionValue = Convert.ChangeType(optionValue, Enum.GetUnderlyingType(field.FieldType)); + } + switch (Type.GetTypeCode(field.FieldType)) { case TypeCode.Boolean: case TypeCode.Int16: @@ -38,7 +44,7 @@ public static class Handshake { case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: { - reqBuilder.AppendLine($"CelesteNet-ClientOptions-{field.Name}: {field.GetValue(options)}"); + reqBuilder.AppendLine($"CelesteNet-ClientOptions-{field.Name}: {optionValue}"); } break; } } diff --git a/CelesteNet.Server.ChatModule/CMDs/CommandsContext.cs b/CelesteNet.Server.ChatModule/CMDs/CommandsContext.cs index dd4edb5f..0ade897a 100644 --- a/CelesteNet.Server.ChatModule/CMDs/CommandsContext.cs +++ b/CelesteNet.Server.ChatModule/CMDs/CommandsContext.cs @@ -79,7 +79,7 @@ public abstract class ChatCmd { #pragma warning restore CS8618 public List ArgParsers = new(); - public virtual CelesteNetSupportedClientFeatures RequiredFeatures => 0; + public virtual CelesteNetSupportedClientFeatures RequiredFeatures => CelesteNetSupportedClientFeatures.None; public virtual string ID => GetType().Name.Substring(3).ToLowerInvariant(); diff --git a/CelesteNet.Server/CelesteNetPlayerSession.cs b/CelesteNet.Server/CelesteNetPlayerSession.cs index a889a89c..97b3eeac 100644 --- a/CelesteNet.Server/CelesteNetPlayerSession.cs +++ b/CelesteNet.Server/CelesteNetPlayerSession.cs @@ -85,7 +85,7 @@ internal CelesteNetPlayerSession(CelesteNetServer server, CelesteNetConnection c } public bool CheckClientFeatureSupport(CelesteNetSupportedClientFeatures features) { - return (ClientOptions.SupportedClientFeatures & (ulong)features) == (ulong)features; + return ClientOptions.SupportedClientFeatures.HasFlag(features); } public T? Get(object ctx) where T : class { diff --git a/CelesteNet.Shared/CelesteNetClientOptions.cs b/CelesteNet.Shared/CelesteNetClientOptions.cs index 560fe585..f4411881 100644 --- a/CelesteNet.Shared/CelesteNetClientOptions.cs +++ b/CelesteNet.Shared/CelesteNetClientOptions.cs @@ -6,12 +6,12 @@ public class CelesteNetClientOptions { public bool AvatarsDisabled = false; public ulong ClientID; public uint InstanceID; - // Due to how these are sent as packets, this needs to be a ulong. - public ulong SupportedClientFeatures = 0; + public CelesteNetSupportedClientFeatures SupportedClientFeatures = CelesteNetSupportedClientFeatures.None; } [Flags] public enum CelesteNetSupportedClientFeatures : ulong { - LocateCommand = 1 >> 0 + None = 0, + LocateCommand = 1 << 0 } } \ No newline at end of file diff --git a/CelesteNet.Shared/CommandInfo.cs b/CelesteNet.Shared/CommandInfo.cs index 60a19648..65d46d17 100644 --- a/CelesteNet.Shared/CommandInfo.cs +++ b/CelesteNet.Shared/CommandInfo.cs @@ -9,7 +9,7 @@ public class CommandInfo { // NOTE: This is not sent to the client/server, for legacy compatibility reasons! Be careful. // - 23 July, 2024 - public CelesteNetSupportedClientFeatures RequiredFeatures = 0; + public CelesteNetSupportedClientFeatures RequiredFeatures = CelesteNetSupportedClientFeatures.None; } public enum CompletionType : byte { From 6c5611bb54d4ba4f7a712e7831acc71a1d62b1a0 Mon Sep 17 00:00:00 2001 From: RedFlames Date: Sun, 18 Aug 2024 06:32:11 +0200 Subject: [PATCH 15/15] PrepareAndLog shouldn't be called for the locate response, especially since the Player field of the DataChat is being (intentionally) mis-used. Also, filtering the command list down before calling SendCommandList was wrong, since that function already does some filtering and can check for the required features as well. --- CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs | 3 +-- CelesteNet.Server.ChatModule/ChatModule.cs | 8 +------- CelesteNet.Server/CelesteNetPlayerSession.cs | 6 +++++- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs b/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs index d4be999e..85d8d26b 100644 --- a/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs +++ b/CelesteNet.Server.ChatModule/CMDs/CmdLocate.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Celeste.Mod.CelesteNet.DataTypes; -using MonoMod.Utils; namespace Celeste.Mod.CelesteNet.Server.Chat.Cmd; @@ -50,7 +49,7 @@ public override void Run(CmdEnv env, List? args) { Text = "{YOU SHOULD NEVER SEE THIS, PLEASE REPORT}", Color = Chat.Settings.ColorCommandReply }; - self.Con.Send(Chat.PrepareAndLog(self, chat)); + self.Con.Send(chat); } } diff --git a/CelesteNet.Server.ChatModule/ChatModule.cs b/CelesteNet.Server.ChatModule/ChatModule.cs index da4d2720..5de60884 100644 --- a/CelesteNet.Server.ChatModule/ChatModule.cs +++ b/CelesteNet.Server.ChatModule/ChatModule.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; @@ -225,12 +224,7 @@ private void OnSessionStart(CelesteNetPlayerSession session) { SendTo(session, Settings.MessageMOTD); } - var commandList = Commands.DataAll - .List - .Where(cmd => session.CheckClientFeatureSupport(cmd.RequiredFeatures)) - .ToArray(); - var commands = new DataCommandList { List = commandList }; - session.SendCommandList(commands); + session.SendCommandList(Commands.DataAll); SpamContext spam = session.Set(this, new SpamContext(this)); spam.OnSpam += (msg, timeout) => { diff --git a/CelesteNet.Server/CelesteNetPlayerSession.cs b/CelesteNet.Server/CelesteNetPlayerSession.cs index 97b3eeac..5eddeafb 100644 --- a/CelesteNet.Server/CelesteNetPlayerSession.cs +++ b/CelesteNet.Server/CelesteNetPlayerSession.cs @@ -430,7 +430,11 @@ public void SendCommandList(DataCommandList commands) { authExec = info.Tags.Contains(BasicUserInfo.TAG_AUTH_EXEC); } - filteredCommands.List = commands.List.Where(cmd => (!cmd.Auth || auth) && (!cmd.AuthExec || authExec)).ToArray(); + filteredCommands.List = commands.List.Where(cmd => { + return (!cmd.Auth || auth) + && (!cmd.AuthExec || authExec) + && CheckClientFeatureSupport(cmd.RequiredFeatures); + }).ToArray(); Con.Send(filteredCommands); }