2024-05-25 04:43:43 +00:00
|
|
|
|
using Serilog;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
|
2024-05-27 12:29:01 +00:00
|
|
|
|
namespace Elisa.GameServer.Commands;
|
2024-05-25 04:43:43 +00:00
|
|
|
|
|
|
|
|
|
[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<string, string>), typeof(Connection)])?.DeclaringType == GetType())
|
|
|
|
|
usage |= CommandUsage.User;
|
|
|
|
|
if (GetType().GetMethod(nameof(Execute), [typeof(Dictionary<string, string>)])?.DeclaringType == GetType())
|
|
|
|
|
usage |= CommandUsage.Console;
|
|
|
|
|
|
|
|
|
|
return usage;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
readonly Dictionary<string, PropertyInfo> 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<string, string> args)
|
|
|
|
|
{
|
|
|
|
|
foreach (var arg in args)
|
|
|
|
|
{
|
|
|
|
|
if (argsProperties.TryGetValue(arg.Key, out var prop))
|
|
|
|
|
prop.SetValue(this, arg.Value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public virtual void Execute(Dictionary<string, string> args, Connection connection)
|
|
|
|
|
{
|
|
|
|
|
Execute(args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public virtual void NotifySuccess(Connection connection)
|
|
|
|
|
{
|
|
|
|
|
// connection.SendSystemMsg($"{GetType().Name} success!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected T Parse<T>(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<Command> Commands = new List<Command>();
|
|
|
|
|
|
|
|
|
|
static readonly Dictionary<string, Action<Dictionary<string, string>>> commandFunctions;
|
|
|
|
|
static readonly Dictionary<string, Action<Dictionary<string, string>, Connection>> commandFunctionsUser;
|
|
|
|
|
private static readonly char[] separator = new[] { ' ' };
|
|
|
|
|
|
|
|
|
|
static CommandHandlerFactory()
|
|
|
|
|
{
|
|
|
|
|
commandFunctions = new Dictionary<string, Action<Dictionary<string, string>>>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
commandFunctionsUser = new Dictionary<string, Action<Dictionary<string, string>, 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<string, string>(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)
|
|
|
|
|
{
|
2024-05-27 12:29:01 +00:00
|
|
|
|
if (!commandFunctionsUser.TryGetValue(parts[0], out var command))
|
2024-05-25 04:43:43 +00:00
|
|
|
|
{
|
|
|
|
|
Log.Warning($"Unknown command: {parts[0]}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
command(arguments, connection);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-05-27 12:29:01 +00:00
|
|
|
|
if (!commandFunctions.TryGetValue(parts[0], out var command))
|
2024-05-25 04:43:43 +00:00
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
}
|