diff --git a/Source/Common/ChatCommands.cs b/Source/Common/ChatCommands.cs index d39ae5ca..02864264 100644 --- a/Source/Common/ChatCommands.cs +++ b/Source/Common/ChatCommands.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace Multiplayer.Common; @@ -18,11 +19,41 @@ public void Handle(IChatSource source, string cmd) } else { - source.SendMsg("Invalid command"); + source.SendMsg("Invalid command. Use help or ? to list available commands."); } } public void AddCommandHandler(string name, ChatCmdHandler handler) => handlers[name] = handler; + + public IEnumerable GetCommandNames() => handlers.Keys.OrderBy(name => name); + + public IEnumerable GetCommandInfos() + { + return handlers + .GroupBy(entry => entry.Value) + .Select(group => new ChatCmdInfo(group.Key, group.Select(entry => entry.Key).ToArray())) + .OrderBy(info => info.PrimaryName); + } + + public bool TryGetCommandInfo(string name, out ChatCmdInfo info) + { + if (handlers.TryGetValue(name, out var handler)) + { + info = new ChatCmdInfo(handler, handlers.Where(entry => entry.Value == handler).Select(entry => entry.Key).ToArray()); + return true; + } + + info = null!; + return false; + } +} + +public sealed class ChatCmdInfo(ChatCmdHandler handler, string[] names) +{ + public ChatCmdHandler Handler { get; } = handler; + public string[] Names { get; } = names; + public string PrimaryName => Names.First(); + public string DisplayNames => string.Join(", ", Names); } public abstract class ChatCmdHandler @@ -31,6 +62,9 @@ public abstract class ChatCmdHandler public MultiplayerServer Server => MultiplayerServer.instance!; + public virtual string Description => string.Empty; + public virtual string Usage => string.Empty; + public abstract void Handle(IChatSource source, string[] args); public void SendNoPermission(ServerPlayer player) @@ -46,6 +80,9 @@ public void SendNoPermission(ServerPlayer player) public class ChatCmdJoinPoint : ChatCmdHandler { + public override string Description => "Create a fresh join point immediately."; + public override string Usage => "joinpoint"; + public ChatCmdJoinPoint() { requiresHost = true; @@ -58,8 +95,54 @@ public override void Handle(IChatSource source, string[] args) } } +public class ChatCmdHelp : ChatCmdHandler +{ + public override string Description => "Show available commands or detailed help for one command."; + public override string Usage => "help [command]"; + + public override void Handle(IChatSource source, string[] args) + { + if (args.Length > 0) + { + if (Server.chatCmdManager.TryGetCommandInfo(args[0], out var command)) + { + source.SendMsg($"Command: {command.DisplayNames}"); + + if (!string.IsNullOrEmpty(command.Handler.Description)) + source.SendMsg($"Description: {command.Handler.Description}"); + + if (!string.IsNullOrEmpty(command.Handler.Usage)) + source.SendMsg($"Usage: {command.Handler.Usage}"); + + if (command.Handler.requiresHost) + source.SendMsg("Requires host permissions."); + + return; + } + + source.SendMsg($"Unknown command '{args[0]}'. Use help to list available commands."); + return; + } + + source.SendMsg("Available commands:"); + foreach (var command in Server.chatCmdManager.GetCommandInfos()) + { + var summary = command.Handler.Description; + if (command.Handler.requiresHost) + summary = string.IsNullOrEmpty(summary) ? "Requires host permissions." : $"{summary} Requires host permissions."; + + source.SendMsg($"- {command.DisplayNames}: {summary}"); + } + + source.SendMsg("Use help for detailed usage."); + } +} + public class ChatCmdKick : ChatCmdHandler { + public override string Description => "Disconnect a player by username."; + public override string Usage => "kick "; + public ChatCmdKick() { requiresHost = true; @@ -92,6 +175,9 @@ public override void Handle(IChatSource source, string[] args) public class ChatCmdStop : ChatCmdHandler { + public override string Description => "Stop the standalone server."; + public override string Usage => "stop"; + public ChatCmdStop() { requiresHost = true; diff --git a/Source/Common/MultiplayerServer.cs b/Source/Common/MultiplayerServer.cs index 27dc3553..1312a2b4 100644 --- a/Source/Common/MultiplayerServer.cs +++ b/Source/Common/MultiplayerServer.cs @@ -86,6 +86,9 @@ public MultiplayerServer(ServerSettings settings) chatCmdManager = new ChatCmdManager(); playerManager = new PlayerManager(this); + var helpCmd = new ChatCmdHelp(); + RegisterChatCmd("help", helpCmd); + RegisterChatCmd("?", helpCmd); RegisterChatCmd("joinpoint", new ChatCmdJoinPoint()); RegisterChatCmd("kick", new ChatCmdKick()); RegisterChatCmd("stop", new ChatCmdStop());