Files
DanhengServer-OpenSource/Command/Command/CommandManager.cs
2025-05-25 12:13:57 +08:00

271 lines
9.3 KiB
C#

using System.Reflection;
using EggLink.DanhengServer.GameServer.Server;
using EggLink.DanhengServer.Internationalization;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Util;
using Spectre.Console;
using static EggLink.DanhengServer.GameServer.Plugin.Event.PluginEvent;
namespace EggLink.DanhengServer.Command.Command;
public class CommandManager
{
private const int MaxCommandHistory = 100;
private readonly List<string> _commandHistory = [];
private int _historyIndex = -1;
public static CommandManager? Instance { get; private set; }
public Dictionary<string, ICommand> Commands { get; } = [];
public Dictionary<string, CommandInfoAttribute> CommandInfo { get; } = [];
public Dictionary<string, string> CommandAlias { get; } = []; // alias -> command
public Logger Logger { get; } = new("CommandManager");
public Connection? Target { get; set; }
public void RegisterCommands()
{
Instance = this;
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
if (typeof(ICommand).IsAssignableFrom(type) && !type.IsAbstract)
RegisterCommand(type);
Logger.Info(I18NManager.Translate("Server.ServerInfo.RegisterItem", Commands.Count.ToString(),
I18NManager.Translate("Word.Command")));
}
public void RegisterCommand(Type type)
{
var attr = type.GetCustomAttribute<CommandInfoAttribute>();
if (attr == null) return;
var instance = Activator.CreateInstance(type);
if (instance is not ICommand command) return;
Commands.Add(attr.Name, command);
CommandInfo.Add(attr.Name, attr);
// register alias
foreach (var alias in attr.Alias) // add alias
CommandAlias.Add(alias, attr.Name);
}
public void Start()
{
while (true)
try
{
var input = ReadCommand();
if (string.IsNullOrEmpty(input)) continue;
if (input.StartsWith("/")) input = input[1..];
if (_commandHistory.Count >= MaxCommandHistory) _commandHistory.RemoveAt(0);
if (_commandHistory.Count == 0 || _commandHistory.Last() != input) _commandHistory.Add(input);
_historyIndex = _commandHistory.Count;
HandleCommand(input, new ConsoleCommandSender(Logger));
}
catch
{
Logger.Error(I18NManager.Translate("Game.Command.Notice.InternalError"));
}
// ReSharper disable once FunctionNeverReturns
}
private string ReadCommand()
{
var input = new List<char>();
AnsiConsole.Markup("[yellow]> [/]");
while (true)
{
var keyInfo = Console.ReadKey(true);
if (keyInfo.Key == ConsoleKey.Enter)
{
Console.WriteLine();
break;
}
switch (keyInfo.Key)
{
case ConsoleKey.Backspace:
{
if (input.Count > 0)
{
input.RemoveAt(input.Count - 1);
Console.Write("\b \b");
}
break;
}
case ConsoleKey.UpArrow:
{
if (_historyIndex > 0)
{
_historyIndex--;
ReplaceInput(input, _commandHistory[_historyIndex]);
}
break;
}
case ConsoleKey.DownArrow when _historyIndex < _commandHistory.Count - 1:
_historyIndex++;
ReplaceInput(input, _commandHistory[_historyIndex]);
break;
case ConsoleKey.DownArrow:
{
if (_historyIndex == _commandHistory.Count - 1)
{
_historyIndex++;
ReplaceInput(input, string.Empty);
}
break;
}
// known issue: Ctrl + (Any Key but C) or other control key will cause display error
default:
input.Add(keyInfo.KeyChar);
Console.Write(keyInfo.KeyChar);
break;
}
}
return new string(input.ToArray());
}
private static void ReplaceInput(List<char> input, string newText)
{
while (input.Count > 0)
{
input.RemoveAt(input.Count - 1);
Console.Write("\b \b");
}
input.AddRange(newText.ToCharArray());
Console.Write(newText);
}
public void HandleCommand(string input, ICommandSender sender)
{
try
{
var cmd = input.Split(' ')[0];
var tempTarget = Target;
if (sender is ConsoleCommandSender)
{
if (cmd.StartsWith('@'))
{
var target = cmd[1..];
if (DanhengListener.Connections.Values.ToList()
.Find(item => (item as Connection)?.Player?.Uid.ToString() == target) is Connection con)
{
Target = con;
sender.SendMsg(I18NManager.Translate("Game.Command.Notice.TargetFound", target,
con.Player!.Data.Name!));
}
else
{
// offline or not exist
sender.SendMsg(I18NManager.Translate("Game.Command.Notice.TargetNotFound", target));
}
return;
}
}
else
{
InvokeOnPlayerUseCommand(sender, input);
// player
tempTarget = Listener.GetActiveConnection(sender.GetSender());
if (tempTarget == null)
{
sender.SendMsg(I18NManager.Translate("Game.Command.Notice.TargetNotFound",
sender.GetSender().ToString()));
return;
}
}
if (tempTarget is { IsOnline: false })
{
sender.SendMsg(I18NManager.Translate("Game.Command.Notice.TargetOffline",
tempTarget.Player!.Uid.ToString(), tempTarget.Player!.Data.Name!));
tempTarget = null;
}
// find the command
if (CommandAlias.TryGetValue(cmd, out var realCmd)) cmd = realCmd;
if (Commands.TryGetValue(cmd, out var command))
{
var split = input.Split(' ').ToList();
split.RemoveAt(0);
var arg = new CommandArg(split.JoinFormat(" ", ""), sender, tempTarget);
// judge permission
if (arg.Target?.Player?.Uid != sender.GetSender() && !sender.HasPermission("command.others"))
{
sender.SendMsg(I18NManager.Translate("Game.Command.Notice.NoPermission"));
return;
}
// find the proper method with attribute CommandMethodAttribute
var isFound = false;
var info = CommandInfo[cmd];
if (!sender.HasPermission(info.Permission))
{
sender.SendMsg(I18NManager.Translate("Game.Command.Notice.NoPermission"));
return;
}
foreach (var method in command.GetType().GetMethods())
{
var attr = method.GetCustomAttribute<CommandMethodAttribute>();
if (attr == null) continue;
var canRun = true;
foreach (var condition in attr.Conditions)
{
if (split.Count <= condition.Index)
{
canRun = false;
break;
}
if (split[condition.Index].Equals(condition.ShouldBe)) continue;
canRun = false;
break;
}
if (!canRun) continue;
isFound = true;
method.Invoke(command, [arg]);
break;
}
if (isFound) return;
// find the default method with attribute CommandDefaultAttribute
foreach (var method in command.GetType().GetMethods())
{
var attr = method.GetCustomAttribute<CommandDefaultAttribute>();
if (attr == null) continue;
isFound = true;
method.Invoke(command, [arg]);
break;
}
if (isFound) return;
sender.SendMsg(I18NManager.Translate(info.Usage));
}
else
{
sender.SendMsg(I18NManager.Translate("Game.Command.Notice.CommandNotFound"));
}
}
catch (Exception e)
{
Logger.Error(I18NManager.Translate("Game.Command.Notice.InternalError"), e);
sender.SendMsg(I18NManager.Translate("Game.Command.Notice.InternalError"));
}
}
}