Elisa/Elisa.GameServer/Commands/Command.cs

168 lines
5.3 KiB
C#
Raw Normal View History

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<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)
{
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
{
}