From 3b353328b8dd04f0d2c69f352ced4184194ffe5a Mon Sep 17 00:00:00 2001 From: Miha Krajnc Date: Thu, 12 Dec 2024 17:52:44 +0100 Subject: [PATCH] feat: Add /help command (#2984) * feat: implement help command and refac commands creation * fix: tweak command descriptions, formatting, and hide internal ones * feat: print help intro command on start --- .../Assets/DCL/Chat/ChatCommandsHandler.cs | 18 ++++----- Explorer/Assets/DCL/Chat/ChatController.cs | 3 ++ .../Assets/DCL/Chat/Commands/IChatCommand.cs | 2 + .../CommandsHandleChatMessageBus.cs | 4 +- .../DCL/Chat/MessageBus/IChatMessagesBus.cs | 4 +- .../ChatCommands/ChangeRealmChatCommand.cs | 4 +- .../Dynamic/ChatCommands/ClearChatCommand.cs | 3 +- .../ChatCommands/DebugPanelChatCommand.cs | 3 +- .../Dynamic/ChatCommands/GoToChatCommand.cs | 4 +- .../Dynamic/ChatCommands/HelpChatCommand.cs | 38 +++++++++++++++++++ .../ChatCommands/HelpChatCommand.cs.meta | 3 ++ .../KillPortableExperienceChatCommand.cs | 4 +- .../LoadPortableExperienceChatCommand.cs | 3 +- .../ChatCommands/ReloadSceneChatCommand.cs | 6 +-- .../ChatCommands/ShowEntityInfoChatCommand.cs | 3 +- .../Global/Dynamic/DynamicWorldContainer.cs | 37 +++++++----------- 16 files changed, 90 insertions(+), 49 deletions(-) create mode 100644 Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/HelpChatCommand.cs create mode 100644 Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/HelpChatCommand.cs.meta diff --git a/Explorer/Assets/DCL/Chat/ChatCommandsHandler.cs b/Explorer/Assets/DCL/Chat/ChatCommandsHandler.cs index e164bad5f4..9633ff9d07 100644 --- a/Explorer/Assets/DCL/Chat/ChatCommandsHandler.cs +++ b/Explorer/Assets/DCL/Chat/ChatCommandsHandler.cs @@ -9,26 +9,22 @@ public class ChatCommandsHandler { private const string CHAT_COMMAND_CHAR = "/"; - private readonly Dictionary commandsCache = new (); - private readonly IReadOnlyDictionary> commandsFactory; + private readonly IReadOnlyList commands; - public ChatCommandsHandler(IReadOnlyDictionary> commandsFactory) + public ChatCommandsHandler(IReadOnlyList commands) { - this.commandsFactory = commandsFactory; + this.commands = commands; } public bool TryGetChatCommand(in string message, ref (IChatCommand command, Match match) commandTuple) { - foreach (Regex? commandRegex in commandsFactory.Keys) + foreach (IChatCommand cmd in commands) { - commandTuple.match = commandRegex.Match(message); + commandTuple.match = cmd.Regex.Match(message); + if (!commandTuple.match.Success) continue; - if (!commandsCache.TryGetValue(commandRegex, out commandTuple.command)) - { - commandTuple.command = commandsFactory[commandRegex](); - commandsCache[commandRegex] = commandTuple.command; - } + commandTuple.command = cmd; return true; } diff --git a/Explorer/Assets/DCL/Chat/ChatController.cs b/Explorer/Assets/DCL/Chat/ChatController.cs index 5f29f06f51..8df1c318d8 100644 --- a/Explorer/Assets/DCL/Chat/ChatController.cs +++ b/Explorer/Assets/DCL/Chat/ChatController.cs @@ -136,6 +136,9 @@ protected override void OnViewInstantiated() viewInstance.ChatBubblesToggle.Toggle.SetIsOnWithoutNotify(nametagsData.showChatBubbles); OnToggleChatBubblesValueChanged(nametagsData.showChatBubbles); OnFocus(); + + // Intro message + chatHistory.AddMessage(ChatMessage.NewFromSystem("Type /help for available commands.")); } protected override void OnViewShow() diff --git a/Explorer/Assets/DCL/Chat/Commands/IChatCommand.cs b/Explorer/Assets/DCL/Chat/Commands/IChatCommand.cs index 04135cdf4f..3704256d3d 100644 --- a/Explorer/Assets/DCL/Chat/Commands/IChatCommand.cs +++ b/Explorer/Assets/DCL/Chat/Commands/IChatCommand.cs @@ -6,7 +6,9 @@ namespace DCL.Chat.Commands { public interface IChatCommand { + Regex Regex { get; } + string Description { get; } UniTask ExecuteAsync(Match match, CancellationToken ct); } diff --git a/Explorer/Assets/DCL/Chat/MessageBus/CommandsHandleChatMessageBus.cs b/Explorer/Assets/DCL/Chat/MessageBus/CommandsHandleChatMessageBus.cs index c983847437..4927a2def6 100644 --- a/Explorer/Assets/DCL/Chat/MessageBus/CommandsHandleChatMessageBus.cs +++ b/Explorer/Assets/DCL/Chat/MessageBus/CommandsHandleChatMessageBus.cs @@ -17,10 +17,10 @@ public class CommandsHandleChatMessageBus : IChatMessagesBus public event Action? MessageAdded; - public CommandsHandleChatMessageBus(IChatMessagesBus origin, IReadOnlyDictionary> commandsFactory) + public CommandsHandleChatMessageBus(IChatMessagesBus origin, IReadOnlyList commands) { this.origin = origin; - this.chatCommandsHandler = new ChatCommandsHandler(commandsFactory); + this.chatCommandsHandler = new ChatCommandsHandler(commands); origin.MessageAdded += OriginOnOnMessageAdded; } diff --git a/Explorer/Assets/DCL/Chat/MessageBus/IChatMessagesBus.cs b/Explorer/Assets/DCL/Chat/MessageBus/IChatMessagesBus.cs index db4b5e7829..111552c0e1 100644 --- a/Explorer/Assets/DCL/Chat/MessageBus/IChatMessagesBus.cs +++ b/Explorer/Assets/DCL/Chat/MessageBus/IChatMessagesBus.cs @@ -32,8 +32,8 @@ void CreateTestChatEntry() return messagesBus; } - public static IChatMessagesBus WithCommands(this IChatMessagesBus messagesBus, IReadOnlyDictionary> commandsFactory) => - new CommandsHandleChatMessageBus(messagesBus, commandsFactory); + public static IChatMessagesBus WithCommands(this IChatMessagesBus messagesBus, IReadOnlyList commands) => + new CommandsHandleChatMessageBus(messagesBus, commands); public static IChatMessagesBus WithIgnoreSymbols(this IChatMessagesBus messagesBus) => new IgnoreWithSymbolsChatMessageBus(messagesBus); diff --git a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ChangeRealmChatCommand.cs b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ChangeRealmChatCommand.cs index c984bd53ee..7b57a75201 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ChangeRealmChatCommand.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ChangeRealmChatCommand.cs @@ -27,10 +27,12 @@ public class ChangeRealmChatCommand : IChatCommand // Parameters to URL mapping private readonly Dictionary paramUrls; - public static readonly Regex REGEX = + public Regex Regex { get; } = new ( $@"^/({COMMAND_WORLD}|{ChatCommandsUtils.COMMAND_GOTO})\s+((?!-?\d+\s*,\s*-?\d+$).+?)(?:\s+(-?\d+)\s*,\s*(-?\d+))?$", RegexOptions.Compiled); + public string Description => "/world - Teleport to a different realm"; + private readonly URLDomain worldDomain = URLDomain.FromString(IRealmNavigator.WORLDS_DOMAIN); private readonly Dictionary worldAddressesCaches = new (); diff --git a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ClearChatCommand.cs b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ClearChatCommand.cs index 234ccf40a5..e30868624d 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ClearChatCommand.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ClearChatCommand.cs @@ -8,7 +8,8 @@ namespace Global.Dynamic.ChatCommands { public class ClearChatCommand : IChatCommand { - public static readonly Regex REGEX = new ($@"^/(clear).*", RegexOptions.Compiled); + public Regex Regex { get; } = new ("^/(clear).*", RegexOptions.Compiled); + public string Description => "/clear - Clear the chat"; private readonly IChatHistory chatHistory; diff --git a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/DebugPanelChatCommand.cs b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/DebugPanelChatCommand.cs index a770fe2dca..9513e4fd57 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/DebugPanelChatCommand.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/DebugPanelChatCommand.cs @@ -10,7 +10,8 @@ namespace Global.Dynamic.ChatCommands { public class DebugPanelChatCommand : IChatCommand { - public static readonly Regex REGEX = new (@"^/debug(?:\s+(\w+))?$", RegexOptions.Compiled); + public Regex Regex { get; } = new (@"^/debug(?:\s+(\w+))?$", RegexOptions.Compiled); + public string Description => "/debug - Toggle debug panel"; private readonly IDebugContainerBuilder debugContainerBuilder; private readonly ConnectionStatusPanelPlugin connectionStatusPanelPlugin; diff --git a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/GoToChatCommand.cs b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/GoToChatCommand.cs index d513ca59be..c570634e36 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/GoToChatCommand.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/GoToChatCommand.cs @@ -15,10 +15,12 @@ public class GoToChatCommand : IChatCommand private const string COMMAND_GOTO_LOCAL = "goto-local"; private const string PARAMETER_RANDOM = "random"; - public static readonly Regex REGEX = + public Regex Regex { get; } = new ( $@"^/({ChatCommandsUtils.COMMAND_GOTO}|{COMMAND_GOTO_LOCAL})\s+(?:(-?\d+)\s*,\s*(-?\d+)|{PARAMETER_RANDOM})$", RegexOptions.Compiled); + public string Description => "/goto - Teleport to a specific location."; + private readonly IRealmNavigator realmNavigator; private int x; diff --git a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/HelpChatCommand.cs b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/HelpChatCommand.cs new file mode 100644 index 0000000000..1fbca47cb1 --- /dev/null +++ b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/HelpChatCommand.cs @@ -0,0 +1,38 @@ +using Cysharp.Threading.Tasks; +using DCL.Chat.Commands; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; + +namespace Global.Dynamic.ChatCommands +{ + public class HelpChatCommand : IChatCommand + { + public Regex Regex { get; } = new ("^/(help).*", RegexOptions.Compiled); + public string Description => "/help - Shows this help message"; + + private readonly List commands; + + public HelpChatCommand(List commands) + { + this.commands = commands; + } + + public UniTask ExecuteAsync(Match match, CancellationToken ct) + { + var sb = new StringBuilder(); + + sb.AppendLine("Available commands:\n"); + + foreach (IChatCommand cmd in commands) + { + if (string.IsNullOrEmpty(cmd.Description)) continue; + + sb.AppendLine(cmd.Description); + } + + return UniTask.FromResult(sb.ToString()); + } + } +} diff --git a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/HelpChatCommand.cs.meta b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/HelpChatCommand.cs.meta new file mode 100644 index 0000000000..30f73fefe2 --- /dev/null +++ b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/HelpChatCommand.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 501dc56fc0dc4d6c86e76215411ace59 +timeCreated: 1733916874 \ No newline at end of file diff --git a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/KillPortableExperienceChatCommand.cs b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/KillPortableExperienceChatCommand.cs index af4b7f9900..2ae78e4b1f 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/KillPortableExperienceChatCommand.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/KillPortableExperienceChatCommand.cs @@ -3,6 +3,7 @@ using DCL.Chat.Commands; using DCL.FeatureFlags; using PortableExperiences.Controller; +using System; using System.Text.RegularExpressions; using System.Threading; @@ -17,7 +18,8 @@ public class KillPortableExperienceChatCommand : IChatCommand private static readonly string COMMAND_PATTERN = $"^/(?{Regex.Escape(COMMAND_PX)})"; private static readonly string OPTIONAL_SUFFIX_PATTERN = $"(?{Regex.Escape(ENS_SUFFIX)})?"; - public static readonly Regex REGEX = new($"{COMMAND_PATTERN}{NAME_PATTERN}{OPTIONAL_SUFFIX_PATTERN}$", RegexOptions.Compiled); + public Regex Regex { get; } = new($"{COMMAND_PATTERN}{NAME_PATTERN}{OPTIONAL_SUFFIX_PATTERN}$", RegexOptions.Compiled); + public string Description => string.Empty; // Internal command, don't advertise it in /help private readonly IPortableExperiencesController portableExperiencesController; private readonly FeatureFlagsCache featureFlagsCache; diff --git a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/LoadPortableExperienceChatCommand.cs b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/LoadPortableExperienceChatCommand.cs index 1dd6ef058f..822fc01ab5 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/LoadPortableExperienceChatCommand.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/LoadPortableExperienceChatCommand.cs @@ -27,7 +27,8 @@ public class LoadPortableExperienceChatCommand : IChatCommand private static readonly string COMMAND_PATTERN = $"^/(?{Regex.Escape(COMMAND_PX)})"; private static readonly string OPTIONAL_SUFFIX_PATTERN = $"(?{Regex.Escape(ENS_SUFFIX)})?"; - public static readonly Regex REGEX = new($"{COMMAND_PATTERN}{NAME_PATTERN}{OPTIONAL_SUFFIX_PATTERN}$", RegexOptions.Compiled); + public Regex Regex { get; } = new($"{COMMAND_PATTERN}{NAME_PATTERN}{OPTIONAL_SUFFIX_PATTERN}$", RegexOptions.Compiled); + public string Description => string.Empty; // Internal command, don't advertise it in /help private readonly IPortableExperiencesController portableExperiencesController; private readonly FeatureFlagsCache featureFlagsCache; diff --git a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ReloadSceneChatCommand.cs b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ReloadSceneChatCommand.cs index 58c8c1d9e2..593f272c5c 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ReloadSceneChatCommand.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ReloadSceneChatCommand.cs @@ -3,14 +3,13 @@ using Cysharp.Threading.Tasks; using DCL.Chat.Commands; using ECS.SceneLifeCycle; -using ECS.SceneLifeCycle.Systems; -using System; namespace Global.Dynamic.ChatCommands { public class ReloadSceneChatCommand : IChatCommand { - public static readonly Regex REGEX = new (@"^/reload(?:\s+(\w+))?$", RegexOptions.Compiled); + public Regex Regex { get; } = new (@"^/reload(?:\s+(\w+))?$", RegexOptions.Compiled); + public string Description => "/reload - Reload the current scene"; private readonly ECSReloadScene reloadScene; @@ -23,6 +22,7 @@ public async UniTask ExecuteAsync(Match match, CancellationToken ct) { if (await reloadScene.TryReloadSceneAsync(ct)) return "🟢 Current scene has been reloaded"; + return "🔴 You need to be in a SDK7 scene to reload it."; } } diff --git a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ShowEntityInfoChatCommand.cs b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ShowEntityInfoChatCommand.cs index f8989f5703..f496ec0d1e 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ShowEntityInfoChatCommand.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/ChatCommands/ShowEntityInfoChatCommand.cs @@ -12,7 +12,8 @@ public class ShowEntityInfoChatCommand : IChatCommand private const string COMMAND_SHOW = "show-entity-components"; private const string EXAMPLE = "/show-entity-components scene_name entity_id"; - public static readonly Regex REGEX = new ($@"^/({COMMAND_SHOW}).*", RegexOptions.Compiled); + public Regex Regex { get; } = new ($"^/({COMMAND_SHOW}).*", RegexOptions.Compiled); + public string Description => "/show - Show entity components info"; private readonly IWorldInfoHub worldInfoHub; diff --git a/Explorer/Assets/Scripts/Global/Dynamic/DynamicWorldContainer.cs b/Explorer/Assets/Scripts/Global/Dynamic/DynamicWorldContainer.cs index fcc08563ad..c5508bb800 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/DynamicWorldContainer.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/DynamicWorldContainer.cs @@ -288,7 +288,6 @@ IMultiPool MultiPoolFactory() => staticContainer.WebRequestsContainer.WebRequestController ); - bool localSceneDevelopment = !string.IsNullOrEmpty(dynamicWorldParams.LocalSceneDevelopmentRealm); container.RealmController = new RealmController( @@ -419,34 +418,24 @@ IMultiPool MultiPoolFactory() => var currentSceneInfo = new CurrentSceneInfo(); var connectionStatusPanelPlugin = new ConnectionStatusPanelPlugin(container.UserInAppInAppInitializationFlow, container.MvcManager, mainUIView, roomsStatus, currentSceneInfo, container.reloadSceneController, globalWorld, playerEntity, debugBuilder); - var chatCommandsFactory = new Dictionary> + var chatCommands = new List { - { GoToChatCommand.REGEX, () => new GoToChatCommand(realmNavigator) }, - { - ChangeRealmChatCommand.REGEX, - () => new ChangeRealmChatCommand(realmNavigator, bootstrapContainer.DecentralandUrlsSource, - new EnvironmentValidator(bootstrapContainer.Environment)) - }, - { DebugPanelChatCommand.REGEX, () => new DebugPanelChatCommand(debugBuilder, connectionStatusPanelPlugin) }, - { ShowEntityInfoChatCommand.REGEX, () => new ShowEntityInfoChatCommand(worldInfoHub) }, - { ClearChatCommand.REGEX, () => new ClearChatCommand(chatHistory) }, - { ReloadSceneChatCommand.REGEX, () => new ReloadSceneChatCommand(container.reloadSceneController) }, - { - LoadPortableExperienceChatCommand.REGEX, - () => new LoadPortableExperienceChatCommand(staticContainer.PortableExperiencesController, - staticContainer.FeatureFlagsCache) - }, - { - KillPortableExperienceChatCommand.REGEX, - () => new KillPortableExperienceChatCommand(staticContainer.PortableExperiencesController, - staticContainer.FeatureFlagsCache) - } + new GoToChatCommand(realmNavigator), + new ChangeRealmChatCommand(realmNavigator, bootstrapContainer.DecentralandUrlsSource, new EnvironmentValidator(bootstrapContainer.Environment)), + new DebugPanelChatCommand(debugBuilder, connectionStatusPanelPlugin), + new ShowEntityInfoChatCommand(worldInfoHub), + new ClearChatCommand(chatHistory), + new ReloadSceneChatCommand(container.reloadSceneController), + new LoadPortableExperienceChatCommand(staticContainer.PortableExperiencesController, staticContainer.FeatureFlagsCache), + new KillPortableExperienceChatCommand(staticContainer.PortableExperiencesController, staticContainer.FeatureFlagsCache), }; + chatCommands.Add(new HelpChatCommand(chatCommands)); + IChatMessagesBus coreChatMessageBus = new MultiplayerChatMessagesBus(container.MessagePipesHub, container.ProfileRepository, new MessageDeduplication()) .WithSelfResend(identityCache, container.ProfileRepository) .WithIgnoreSymbols() - .WithCommands(chatCommandsFactory) + .WithCommands(chatCommands) .WithDebugPanel(debugBuilder); container.ChatMessagesBus = dynamicWorldParams.EnableAnalytics @@ -627,7 +616,7 @@ IMultiPool MultiPoolFactory() => assetsProvisioner, container.MvcManager, dclCursor, - realmUrl => container.ChatMessagesBus.Send($"/{ChatCommandsUtils.COMMAND_GOTO} {realmUrl}", "RestrictedActionAPI")), + realmUrl => container.ChatMessagesBus.Send($"/{ChatCommandsUtils.COMMAND_GOTO} {realmUrl}", "RestrictedActionAPI")), new NftPromptPlugin(assetsProvisioner, webBrowser, container.MvcManager, nftInfoAPIClient, staticContainer.WebRequestsContainer.WebRequestController, dclCursor), staticContainer.CharacterContainer.CreateGlobalPlugin(), staticContainer.QualityContainer.CreatePlugin(),