using Serilog; using System.Reflection; namespace Elisa.GameServer.Commands; [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class CommandHandler(string name, string description, string example) : Attribute { public string Name = name; public string Description = description; public string Example = example; } [AttributeUsage(AttributeTargets.Property)] public class ArgumentAttribute(string key) : Attribute { public string Key = key; } [Flags] public enum CommandUsage { None = 0, Console = 1, User = 2 } public abstract class Command { public virtual CommandUsage Usage { get { var usage = CommandUsage.None; if (GetType().GetMethod(nameof(Execute), [typeof(Dictionary), typeof(Connection)])?.DeclaringType == GetType()) usage |= CommandUsage.User; if (GetType().GetMethod(nameof(Execute), [typeof(Dictionary)])?.DeclaringType == GetType()) usage |= CommandUsage.Console; return usage; } } readonly Dictionary argsProperties; public Command() { argsProperties = GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(x => Attribute.IsDefined(x, typeof(ArgumentAttribute))) .ToDictionary(x => ((ArgumentAttribute)Attribute.GetCustomAttribute(x, typeof(ArgumentAttribute))!).Key, StringComparer.OrdinalIgnoreCase); } public virtual void Execute(Dictionary args) { foreach (var arg in args) { if (argsProperties.TryGetValue(arg.Key, out var prop)) prop.SetValue(this, arg.Value); } } public virtual void Execute(Dictionary args, Connection connection) { Execute(args); } public virtual void NotifySuccess(Connection connection) { // connection.SendSystemMsg($"{GetType().Name} success!"); } protected T Parse(string? value, T fallback = default!) { var tryParseMethod = typeof(T).GetMethod("TryParse", [typeof(string), typeof(T).MakeByRefType()]); if (tryParseMethod != null) { var parameters = new object[] { value!, null! }; bool success = (bool)tryParseMethod.Invoke(null, parameters)!; if (success) return (T)parameters[1]; } return fallback; } } public static class CommandHandlerFactory { public static readonly List Commands = new List(); static readonly Dictionary>> commandFunctions; static readonly Dictionary, Connection>> commandFunctionsUser; private static readonly char[] separator = new[] { ' ' }; static CommandHandlerFactory() { commandFunctions = new Dictionary>>(StringComparer.OrdinalIgnoreCase); commandFunctionsUser = new Dictionary, Connection>>(StringComparer.OrdinalIgnoreCase); } public static void RegisterCommands(Assembly assembly) { var commandTypes = assembly.GetTypes() .Where(t => Attribute.IsDefined(t, typeof(CommandHandler)) && typeof(Command).IsAssignableFrom(t)); foreach (var commandType in commandTypes) { var commandAttribute = (CommandHandler?)Attribute.GetCustomAttribute(commandType, typeof(CommandHandler)); if (commandAttribute != null) { var commandInstance = (Command)Activator.CreateInstance(commandType)!; if (commandInstance.Usage.HasFlag(CommandUsage.Console)) commandFunctions[commandAttribute.Name] = commandInstance.Execute; if (commandInstance.Usage.HasFlag(CommandUsage.User)) commandFunctionsUser[commandAttribute.Name] = commandInstance.Execute; Commands.Add(commandInstance); } } Log.Information($"Registered {Commands.Count} commands"); } public static void HandleCommand(string commandLine, Connection? connection = null) { var parts = commandLine.Split(separator, StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 0) return; var arguments = new Dictionary(StringComparer.OrdinalIgnoreCase); for (var i = 1; i < parts.Length; i++) { var argParts = parts[i].Split('=', 2); if (argParts.Length == 2) arguments[argParts[0]] = argParts[1]; } if (connection is not null) { if (!commandFunctionsUser.TryGetValue(parts[0], out var command)) { Log.Warning($"Unknown command: {parts[0]}"); return; } command(arguments, connection); } else { if (!commandFunctions.TryGetValue(parts[0], out var command)) { Log.Warning($"Unknown command: {parts[0]}"); return; } command(arguments); } } } // This is just a stub for now, probably won't even get used public class Connection { }