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 _commandHistory = []; private int _historyIndex = -1; public static CommandManager? Instance { get; private set; } public Dictionary Commands { get; } = []; public Dictionary CommandInfo { get; } = []; public Dictionary 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(); 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(); 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 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(); 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(); 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")); } } }