mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
* Saving work * Move shit to engine * lord * merg * awa * bql is kill * forgot the fucking bike rack * bql is kill for real * pjb will kill me * aughfhbdj * yo ho here we go on my way to the MINES * a * adgddf * gdsgvfvxshngfgh * b * hfsjhghj * hbfdjjh * tf you mean i have to document it * follow C# standards * Assorted cleanup and documentation pass, minor bugfix in ValueRefParser. * Start porting old commands, remove that pesky prefix in favor of integrating with the shell. * Fix valueref up a bit, improve autocomplete for it. * e * Toolshed type system adventure. * a log * a * a e i o u * awa * fix tests * Arithmetic commands. * a * parse improvements --------- Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
355 lines
12 KiB
C#
355 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Robust.Client.Log;
|
|
using Robust.Client.Player;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Console;
|
|
using Robust.Shared.Enums;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Log;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Network.Messages;
|
|
using Robust.Shared.Players;
|
|
using Robust.Shared.Reflection;
|
|
using Robust.Shared.Utility;
|
|
using Robust.Shared.ViewVariables;
|
|
|
|
namespace Robust.Client.Console
|
|
{
|
|
public sealed class AddStringArgs : EventArgs
|
|
{
|
|
public FormattedMessage Text { get; }
|
|
|
|
public bool Local { get; }
|
|
|
|
public bool Error { get; }
|
|
|
|
public AddStringArgs(FormattedMessage text, bool local, bool error)
|
|
{
|
|
Text = text;
|
|
Local = local;
|
|
Error = error;
|
|
}
|
|
}
|
|
|
|
public sealed class AddFormattedMessageArgs : EventArgs
|
|
{
|
|
public readonly FormattedMessage Message;
|
|
|
|
public AddFormattedMessageArgs(FormattedMessage message)
|
|
{
|
|
Message = message;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc cref="IClientConsoleHost" />
|
|
internal sealed partial class ClientConsoleHost : ConsoleHost, IClientConsoleHost, IConsoleHostInternal, IPostInjectInit
|
|
{
|
|
[Dependency] private readonly IClientConGroupController _conGroup = default!;
|
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
[Dependency] private readonly IPlayerManager _player = default!;
|
|
[Dependency] private readonly IBaseClient _client = default!;
|
|
[Dependency] private readonly ILogManager _logMan = default!;
|
|
|
|
[ViewVariables] private readonly Dictionary<string, IConsoleCommand> _availableServerCommands = new();
|
|
|
|
private bool _requestedCommands;
|
|
private ISawmill _logger = default!;
|
|
private ISawmill _conLogger = default!;
|
|
|
|
public ClientConsoleHost() : base(isServer: false) {}
|
|
|
|
/// <inheritdoc />
|
|
public void Initialize()
|
|
{
|
|
NetManager.RegisterNetMessage<MsgConCmdReg>(HandleConCmdReg);
|
|
NetManager.RegisterNetMessage<MsgConCmdAck>(HandleConCmdAck);
|
|
NetManager.RegisterNetMessage<MsgConCmd>(ProcessCommand);
|
|
NetManager.RegisterNetMessage<MsgConCompletion>();
|
|
NetManager.RegisterNetMessage<MsgConCompletionResp>(ProcessCompletionResp);
|
|
|
|
_requestedCommands = false;
|
|
NetManager.Connected += OnNetworkConnected;
|
|
NetManager.Disconnect += OnNetworkDisconnected;
|
|
_client.RunLevelChanged += OnLevelChanged;
|
|
|
|
LoadConsoleCommands();
|
|
SendServerCommandRequest();
|
|
LogManager.RootSawmill.AddHandler(new DebugConsoleLogHandler(this));
|
|
}
|
|
|
|
private readonly Dictionary<string, IConsoleCommand> _availableCommands = new();
|
|
public override IReadOnlyDictionary<string, IConsoleCommand> AvailableCommands => _availableCommands;
|
|
|
|
private void OnLevelChanged(object? sender, RunLevelChangedEventArgs e)
|
|
{
|
|
UpdateAvailableCommands();
|
|
}
|
|
|
|
private void OnNetworkDisconnected(object? sender, NetDisconnectedArgs e)
|
|
{
|
|
_availableServerCommands.Clear();
|
|
_requestedCommands = false;
|
|
UpdateAvailableCommands();
|
|
}
|
|
|
|
protected override void UpdateAvailableCommands()
|
|
{
|
|
_availableCommands.Clear();
|
|
|
|
foreach (var (name, cmd) in RegisteredCommands)
|
|
{
|
|
if (!cmd.RequireServerOrSingleplayer || (_client.RunLevel is ClientRunLevel.Initialize or ClientRunLevel.SinglePlayerGame))
|
|
_availableCommands.Add(name, cmd);
|
|
}
|
|
|
|
foreach (var (name, cmd) in _availableServerCommands)
|
|
{
|
|
_availableCommands.TryAdd(name, cmd);
|
|
}
|
|
}
|
|
|
|
private void ProcessCommand(MsgConCmd message)
|
|
{
|
|
string? text = message.Text;
|
|
LogManager.GetSawmill(SawmillName).Info($"SERVER:{text}");
|
|
|
|
ExecuteCommand(null, text);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public event EventHandler<AddStringArgs>? AddString;
|
|
|
|
/// <inheritdoc />
|
|
public event EventHandler<AddFormattedMessageArgs>? AddFormatted;
|
|
|
|
/// <inheritdoc />
|
|
public void AddFormattedLine(FormattedMessage message)
|
|
{
|
|
AddFormatted?.Invoke(this, new AddFormattedMessageArgs(message));
|
|
}
|
|
|
|
public override void WriteLine(ICommonSession? session, FormattedMessage msg)
|
|
{
|
|
AddFormattedLine(msg);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void WriteError(ICommonSession? session, string text)
|
|
{
|
|
var msg = new FormattedMessage();
|
|
msg.AddText(text);
|
|
OutputText(msg, true, true);
|
|
}
|
|
|
|
public bool IsCmdServer(IConsoleCommand cmd)
|
|
{
|
|
return cmd is ServerDummyCommand;
|
|
}
|
|
|
|
public override event ConAnyCommandCallback? AnyCommandExecuted;
|
|
|
|
/// <inheritdoc />
|
|
public override void ExecuteCommand(ICommonSession? session, string command)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(command))
|
|
return;
|
|
|
|
WriteLine(null, "");
|
|
var msg = new FormattedMessage();
|
|
msg.PushColor(Color.Gold);
|
|
msg.AddText("> " + command);
|
|
msg.Pop();
|
|
// echo the command locally
|
|
OutputText(msg, true, false);
|
|
|
|
//Commands are processed locally and then sent to the server to be processed there again.
|
|
var args = new List<string>();
|
|
|
|
CommandParsing.ParseArguments(command, args);
|
|
|
|
var commandName = args[0];
|
|
|
|
if (!AvailableCommands.TryGetValue(commandName, out var cmd))
|
|
{
|
|
WriteError(null, "Unknown command: " + commandName);
|
|
return;
|
|
}
|
|
|
|
if (!CanExecute(commandName))
|
|
{
|
|
WriteError(null, $"Insufficient perms for command: {commandName}");
|
|
return;
|
|
}
|
|
|
|
args.RemoveAt(0);
|
|
var shell = new ConsoleShell(this, session ?? _player.LocalPlayer?.Session, session == null);
|
|
var cmdArgs = args.ToArray();
|
|
|
|
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
|
|
cmd.Execute(shell, command, cmdArgs);
|
|
}
|
|
|
|
private bool CanExecute(string cmdName)
|
|
{
|
|
// When not connected to a server, you can run all local commands.
|
|
// When connected to a server, you can only run commands according to the con group controller.
|
|
|
|
return _player.LocalPlayer == null
|
|
|| _player.LocalPlayer.Session.Status <= SessionStatus.Connecting
|
|
|| _conGroup.CanCommand(cmdName);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void RemoteExecuteCommand(ICommonSession? session, string command)
|
|
{
|
|
if (!NetManager.IsConnected) // we don't care about session on client
|
|
return;
|
|
|
|
var msg = new MsgConCmd();
|
|
msg.Text = command;
|
|
NetManager.ClientSendMessage(msg);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void WriteLine(ICommonSession? session, string text)
|
|
{
|
|
var msg = new FormattedMessage();
|
|
msg.AddText(text);
|
|
OutputText(msg, true, false);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose()
|
|
{
|
|
// We don't have anything to dispose.
|
|
}
|
|
|
|
private void OutputText(FormattedMessage text, bool local, bool error)
|
|
{
|
|
AddString?.Invoke(this, new AddStringArgs(text, local, error));
|
|
|
|
var level = error ? LogLevel.Warning : LogLevel.Info;
|
|
_conLogger.Log(level, text.ToString());
|
|
}
|
|
|
|
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
|
|
{
|
|
SendServerCommandRequest();
|
|
}
|
|
|
|
private void HandleConCmdAck(MsgConCmdAck msg)
|
|
{
|
|
OutputText(msg.Text, false, msg.Error);
|
|
}
|
|
|
|
private void HandleConCmdReg(MsgConCmdReg msg)
|
|
{
|
|
foreach (var cmd in msg.Commands)
|
|
{
|
|
string? commandName = cmd.Name;
|
|
|
|
// Do not do duplicate commands.
|
|
if (_availableServerCommands.ContainsKey(commandName))
|
|
{
|
|
_logger.Error($"Server sent duplicate console command {commandName}");
|
|
continue;
|
|
}
|
|
|
|
var command = new ServerDummyCommand(commandName, cmd.Help, cmd.Description);
|
|
_availableServerCommands[commandName] = command;
|
|
}
|
|
|
|
UpdateAvailableCommands();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requests remote commands from server.
|
|
/// </summary>
|
|
private void SendServerCommandRequest()
|
|
{
|
|
if (_requestedCommands)
|
|
return;
|
|
|
|
if (!NetManager.IsConnected)
|
|
return;
|
|
|
|
var msg = new MsgConCmdReg();
|
|
NetManager.ClientSendMessage(msg);
|
|
|
|
_requestedCommands = true;
|
|
}
|
|
|
|
void IPostInjectInit.PostInject()
|
|
{
|
|
_logger = _logMan.GetSawmill("console");
|
|
_conLogger = _logMan.GetSawmill("CON");
|
|
}
|
|
|
|
/// <summary>
|
|
/// These dummies are made purely so list and help can list server-side commands.
|
|
/// </summary>
|
|
[Reflect(false)]
|
|
private sealed class ServerDummyCommand : IConsoleCommand
|
|
{
|
|
internal ServerDummyCommand(string command, string help, string description)
|
|
{
|
|
Command = command;
|
|
Help = help;
|
|
Description = description;
|
|
}
|
|
|
|
public string Command { get; }
|
|
|
|
public string Description { get; }
|
|
|
|
public string Help { get; }
|
|
|
|
// Always forward to server.
|
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
{
|
|
shell.RemoteExecuteCommand(argStr);
|
|
}
|
|
|
|
public async ValueTask<CompletionResult> GetCompletionAsync(
|
|
IConsoleShell shell,
|
|
string[] args,
|
|
string argStr,
|
|
CancellationToken cancel)
|
|
{
|
|
var host = (ClientConsoleHost)shell.ConsoleHost;
|
|
var argsList = args.ToList();
|
|
argsList.Insert(0, Command);
|
|
|
|
return await host.DoServerCompletions(argsList, argStr, cancel);
|
|
}
|
|
}
|
|
|
|
private sealed class RemoteExecCommand : LocalizedCommands
|
|
{
|
|
public override string Command => ">";
|
|
public override string Description => LocalizationManager.GetString("cmd-remoteexec-desc");
|
|
public override string Help => LocalizationManager.GetString("cmd-remoteexec-help");
|
|
|
|
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
{
|
|
shell.RemoteExecuteCommand(argStr[">".Length..]);
|
|
}
|
|
|
|
public override async ValueTask<CompletionResult> GetCompletionAsync(
|
|
IConsoleShell shell,
|
|
string[] args,
|
|
string argStr,
|
|
CancellationToken cancel)
|
|
{
|
|
var host = (ClientConsoleHost)shell.ConsoleHost;
|
|
return await host.DoServerCompletions(args.ToList(), argStr[">".Length..], cancel);
|
|
}
|
|
}
|
|
}
|
|
}
|