mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Toolshed (#4197)
* 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>
This commit is contained in:
@@ -39,7 +39,10 @@ END TEMPLATE-->
|
||||
|
||||
### New features
|
||||
|
||||
*None yet*
|
||||
|
||||
- Toolshed, a tacit shell language, has been introduced.
|
||||
- Use Robust.Shared.ToolshedManager to invoke commands, with optional input and output.
|
||||
- Implement IInvocationContext for custom invocation contexts i.e. scripting systems.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
|
||||
BIN
Resources/EngineFonts/NotoSans/NotoSansMono-Regular.ttf
Normal file
BIN
Resources/EngineFonts/NotoSans/NotoSansMono-Regular.ttf
Normal file
Binary file not shown.
@@ -17,15 +17,15 @@ cmd-error-dir-not-found = Could not find directory: {$dir}.
|
||||
cmd-failure-no-attached-entity = There is no entity attached to this shell.
|
||||
|
||||
## 'help' command
|
||||
cmd-help-desc = Display general help or help text for a specific command
|
||||
cmd-help-help = Usage: help [command name]
|
||||
cmd-oldhelp-desc = Display general help or help text for a specific command
|
||||
cmd-oldhelp-help = Usage: help [command name]
|
||||
When no command name is provided, displays general-purpose help text. If a command name is provided, displays help text for that command.
|
||||
|
||||
cmd-help-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
|
||||
cmd-help-unknown = Unknown command: { $command }
|
||||
cmd-help-top = { $command } - { $description }
|
||||
cmd-help-invalid-args = Invalid amount of arguments.
|
||||
cmd-help-arg-cmdname = [command name]
|
||||
cmd-oldhelp-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
|
||||
cmd-oldhelp-unknown = Unknown command: { $command }
|
||||
cmd-oldhelp-top = { $command } - { $description }
|
||||
cmd-oldhelp-invalid-args = Invalid amount of arguments.
|
||||
cmd-oldhelp-arg-cmdname = [command name]
|
||||
|
||||
## 'cvar' command
|
||||
cmd-cvar-desc = Gets or sets a CVar.
|
||||
|
||||
163
Resources/Locale/en-US/toolshed-commands.ftl
Normal file
163
Resources/Locale/en-US/toolshed-commands.ftl
Normal file
@@ -0,0 +1,163 @@
|
||||
command-description-tpto =
|
||||
Teleport the given entities to some target entity.
|
||||
command-description-player-list =
|
||||
Returns a list of all player sessions.
|
||||
command-description-player-self =
|
||||
Returns the current player session.
|
||||
command-description-player-imm =
|
||||
Returns the session associated with the player given as argument.
|
||||
command-description-player-entity =
|
||||
Returns the entities of the input sessions.
|
||||
command-description-self =
|
||||
Returns the current attached entity.
|
||||
command-description-physics-velocity =
|
||||
Returns the velocity of the input entities.
|
||||
command-description-physics-angular-velocity =
|
||||
Returns the angular velocity of the input entities.
|
||||
command-description-buildinfo =
|
||||
Provides information about the build of the game.
|
||||
command-description-cmd-list =
|
||||
Returns a list of all commands, for this side.
|
||||
command-description-explain =
|
||||
Explains the given expression, providing command descriptions and signatures.
|
||||
command-description-search =
|
||||
Searches through the input for the provided value.
|
||||
command-description-stopwatch =
|
||||
Measures the execution time of the given expression.
|
||||
command-description-types-consumers =
|
||||
Provides all commands that can consume the given type.
|
||||
command-description-types-tree =
|
||||
Debug tool to return all types the command interpreter can downcast the input to.
|
||||
command-description-types-gettype =
|
||||
Returns the type of the input.
|
||||
command-description-types-fullname =
|
||||
Returns the full name of the input type according to CoreCLR.
|
||||
command-description-as =
|
||||
Casts the input to the given type.
|
||||
Effectively a type hint if you know the type but the interpreter does not.
|
||||
command-description-count =
|
||||
Counts the amount of entries in it's input, returning an integer.
|
||||
command-description-map =
|
||||
Maps the input over the given block, with the provided expected return type.
|
||||
This command may be modified to not need an explicit return type in the future.
|
||||
command-description-select =
|
||||
Selects N objects or N% of objects from the input.
|
||||
One can additionally invert this command with not to make it select everything except N objects instead.
|
||||
command-description-comp =
|
||||
Returns the given component from the input entities, discarding entities without that component.
|
||||
command-description-delete =
|
||||
Deletes the input entities.
|
||||
command-description-ent =
|
||||
Returns the provided entity ID.
|
||||
command-description-entities =
|
||||
Returns all entities on the server.
|
||||
command-description-paused =
|
||||
Filters the input entities by whether or not they are paused.
|
||||
This command can be inverted with not.
|
||||
command-description-with =
|
||||
Filters the input entities by whether or not they have the given component.
|
||||
This command can be inverted with not.
|
||||
command-description-fuck =
|
||||
Throws an exception.
|
||||
command-description-ecscomp-listty =
|
||||
Lists every type of component registered.
|
||||
command-description-cd =
|
||||
Changes the session's current directory to the given relative or absolute path.
|
||||
command-description-ls-here =
|
||||
Lists the contents of the current directory.
|
||||
command-description-ls-in =
|
||||
Lists the contents of the given relative or absolute path.
|
||||
command-description-methods-get =
|
||||
Returns all methods associated with the input type.
|
||||
command-description-methods-overrides =
|
||||
Returns all methods overriden on the input type.
|
||||
command-description-methods-overridesfrom =
|
||||
Returns all methods overriden from the given type on the input type.
|
||||
command-description-cmd-moo =
|
||||
Asks the important questions.
|
||||
command-description-cmd-descloc =
|
||||
Returns the localization string for a command's description.
|
||||
command-description-cmd-getshim =
|
||||
Returns a command's execution shim.
|
||||
command-description-help =
|
||||
Provides a quick rundown of how to use toolshed.
|
||||
command-description-ioc-registered =
|
||||
Returns all the types registered with IoCManager on the current thread (usually the game thread)
|
||||
command-description-ioc-get =
|
||||
Gets an instance of an IoC registration.
|
||||
command-description-loc-tryloc =
|
||||
Tries to get a localization string, returning null if unable.
|
||||
command-description-loc-loc =
|
||||
Gets a localization string, returning the unlocalized string if unable.
|
||||
command-description-physics-angular_velocity =
|
||||
Returns the angular velocity of the given entities.
|
||||
command-description-vars =
|
||||
Provides a list of all variables set in this session.
|
||||
command-description-any =
|
||||
Returns true if there's any values in the input, otherwise false.
|
||||
command-description-ArrowCommand =
|
||||
Assigns the input to a variable.
|
||||
command-description-isempty =
|
||||
Returns true if the input is empty, otherwise false.
|
||||
command-description-isnull =
|
||||
Returns true if the input is null, otherwise false.
|
||||
command-description-unique =
|
||||
Filters the input sequence for uniqueness, removing duplicate values.
|
||||
command-description-where =
|
||||
Given some input sequence IEnumerable<T>, takes a block of signature T -> bool that decides if each input value should be included in the output sequence.
|
||||
command-description-do =
|
||||
Backwards compatibility with BQL, applies the given old commands over the input sequence.
|
||||
command-description-named =
|
||||
Filters the input entities by their name, with the regex $selector^.
|
||||
command-description-prototyped =
|
||||
Filters the input entities by their prototype.
|
||||
command-description-nearby =
|
||||
Creates a new list of all entities nearby the inputs within the given range.
|
||||
command-description-first =
|
||||
Returns the first entry of the given enumerable.
|
||||
command-description-splat =
|
||||
"Splats" a block, value, or variable, creating N copies of it in a list.
|
||||
command-description-val =
|
||||
Casts the given value, block, or variable to the given type. This is mostly a workaround for current limitations of variables.
|
||||
command-description-actor-controlled =
|
||||
Filters entities by whether or not they're actively controlled.
|
||||
command-description-actor-session =
|
||||
Returns the sessions associated with the input entities.
|
||||
command-description-physics-parent =
|
||||
Returns the parent(s) of the input entities.
|
||||
command-description-emplace =
|
||||
Runs the given block over it's inputs, with the input value placed into the variable $value within the block.
|
||||
Additionally breaks out $wx, $wy, $proto, $desc, $name, and $paused for entities.
|
||||
Can also have breakout values for other types, consult the documentation for that type for further info.
|
||||
command-description-AddCommand =
|
||||
Performs numeric addition.
|
||||
command-description-SubtractCommand =
|
||||
Performs numeric subtraction.
|
||||
command-description-MultiplyCommand =
|
||||
Performs numeric multiplication.
|
||||
command-description-DivideCommand =
|
||||
Performs numeric division.
|
||||
command-description-min =
|
||||
Returns the minimum of two values.
|
||||
command-description-max =
|
||||
Returns the maximum of two values.
|
||||
command-description-BitAndCommand =
|
||||
Performs bitwise AND.
|
||||
command-description-BitOrCommand =
|
||||
Performs bitwise OR.
|
||||
command-description-BitXorCommand =
|
||||
Performs bitwise XOR.
|
||||
command-description-neg =
|
||||
Negates the input.
|
||||
command-description-GreaterThanCommand =
|
||||
Performs a greater-than comparison, x > y.
|
||||
command-description-LessThanCommand =
|
||||
Performs a less-than comparison, x < y.
|
||||
command-description-GreaterThanOrEqualCommand =
|
||||
Performs a greater-than-or-equal comparison, x >= y.
|
||||
command-description-LessThanOrEqualCommand =
|
||||
Performs a less-than-or-equal comparison, x <= y.
|
||||
command-description-EqualCommand =
|
||||
Performs an equality comparison, returning true if the inputs are equal.
|
||||
command-description-NotEqualCommand =
|
||||
Performs an equality comparison, returning true if the inputs are not equal.
|
||||
@@ -14,7 +14,7 @@ internal sealed partial class ClientConsoleHost
|
||||
private int _completionSeq;
|
||||
|
||||
|
||||
public async Task<CompletionResult> GetCompletions(List<string> args, CancellationToken cancel)
|
||||
public async Task<CompletionResult> GetCompletions(List<string> args, string argStr, CancellationToken cancel)
|
||||
{
|
||||
// Last element is the command currently being typed. May be empty.
|
||||
|
||||
@@ -24,10 +24,10 @@ internal sealed partial class ClientConsoleHost
|
||||
if (delay > 0)
|
||||
await Task.Delay((int)(delay * 1000), cancel);
|
||||
|
||||
return await CalcCompletions(args, cancel);
|
||||
return await CalcCompletions(args, argStr, cancel);
|
||||
}
|
||||
|
||||
private Task<CompletionResult> CalcCompletions(List<string> args, CancellationToken cancel)
|
||||
private Task<CompletionResult> CalcCompletions(List<string> args, string argStr, CancellationToken cancel)
|
||||
{
|
||||
if (args.Count == 1)
|
||||
{
|
||||
@@ -44,10 +44,10 @@ internal sealed partial class ClientConsoleHost
|
||||
if (!AvailableCommands.TryGetValue(args[0], out var cmd))
|
||||
return Task.FromResult(CompletionResult.Empty);
|
||||
|
||||
return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], cancel).AsTask();
|
||||
return cmd.GetCompletionAsync(LocalShell, args.ToArray()[1..], argStr, cancel).AsTask();
|
||||
}
|
||||
|
||||
private Task<CompletionResult> DoServerCompletions(List<string> args, CancellationToken cancel)
|
||||
private Task<CompletionResult> DoServerCompletions(List<string> args, string argStr, CancellationToken cancel)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<CompletionResult>();
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancel);
|
||||
@@ -62,6 +62,7 @@ internal sealed partial class ClientConsoleHost
|
||||
var msg = new MsgConCompletion
|
||||
{
|
||||
Args = args.ToArray(),
|
||||
ArgString = argStr,
|
||||
Seq = seq
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ 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;
|
||||
@@ -21,13 +22,13 @@ namespace Robust.Client.Console
|
||||
{
|
||||
public sealed class AddStringArgs : EventArgs
|
||||
{
|
||||
public string Text { get; }
|
||||
public FormattedMessage Text { get; }
|
||||
|
||||
public bool Local { get; }
|
||||
|
||||
public bool Error { get; }
|
||||
|
||||
public AddStringArgs(string text, bool local, bool error)
|
||||
public AddStringArgs(FormattedMessage text, bool local, bool error)
|
||||
{
|
||||
Text = text;
|
||||
Local = local;
|
||||
@@ -132,10 +133,17 @@ namespace Robust.Client.Console
|
||||
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)
|
||||
{
|
||||
OutputText(text, true, true);
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
OutputText(msg, true, true);
|
||||
}
|
||||
|
||||
public bool IsCmdServer(IConsoleCommand cmd)
|
||||
@@ -151,8 +159,13 @@ namespace Robust.Client.Console
|
||||
if (string.IsNullOrWhiteSpace(command))
|
||||
return;
|
||||
|
||||
WriteLine(null, "");
|
||||
var msg = new FormattedMessage();
|
||||
msg.PushColor(Color.Gold);
|
||||
msg.AddText("> " + command);
|
||||
msg.Pop();
|
||||
// echo the command locally
|
||||
WriteLine(null, "> " + command);
|
||||
OutputText(msg, true, false);
|
||||
|
||||
//Commands are processed locally and then sent to the server to be processed there again.
|
||||
var args = new List<string>();
|
||||
@@ -205,7 +218,9 @@ namespace Robust.Client.Console
|
||||
/// <inheritdoc />
|
||||
public override void WriteLine(ICommonSession? session, string text)
|
||||
{
|
||||
OutputText(text, true, false);
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
OutputText(msg, true, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -214,12 +229,12 @@ namespace Robust.Client.Console
|
||||
// We don't have anything to dispose.
|
||||
}
|
||||
|
||||
private void OutputText(string text, bool local, bool error)
|
||||
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);
|
||||
_conLogger.Log(level, text.ToString());
|
||||
}
|
||||
|
||||
private void OnNetworkConnected(object? sender, NetChannelArgs netChannelArgs)
|
||||
@@ -229,7 +244,7 @@ namespace Robust.Client.Console
|
||||
|
||||
private void HandleConCmdAck(MsgConCmdAck msg)
|
||||
{
|
||||
OutputText("< " + msg.Text, false, msg.Error);
|
||||
OutputText(msg.Text, false, msg.Error);
|
||||
}
|
||||
|
||||
private void HandleConCmdReg(MsgConCmdReg msg)
|
||||
@@ -303,13 +318,14 @@ namespace Robust.Client.Console
|
||||
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, cancel);
|
||||
return await host.DoServerCompletions(argsList, argStr, cancel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,10 +343,11 @@ namespace Robust.Client.Console
|
||||
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(), cancel);
|
||||
return await host.DoServerCompletions(args.ToList(), argStr[">".Length..], cancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,6 @@ namespace Robust.Client.Console
|
||||
|
||||
void AddFormattedLine(FormattedMessage message);
|
||||
|
||||
Task<CompletionResult> GetCompletions(List<string> args, CancellationToken cancel);
|
||||
Task<CompletionResult> GetCompletions(List<string> args, string argStr, CancellationToken cancel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<OutputPanel Name="Output" VerticalExpand="True">
|
||||
<OutputPanel Name="Output" VerticalExpand="True" StyleClasses="monospace">
|
||||
<OutputPanel.StyleBoxOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252add"
|
||||
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
|
||||
|
||||
@@ -90,7 +90,7 @@ public sealed partial class DebugConsole
|
||||
|
||||
private async void TypeUpdateCompletions(bool fullUpdate)
|
||||
{
|
||||
var (args, _, _) = CalcTypingArgs();
|
||||
var (args, _, _, str) = CalcTypingArgs();
|
||||
|
||||
if (args.Count != _compParamCount)
|
||||
{
|
||||
@@ -101,7 +101,7 @@ public sealed partial class DebugConsole
|
||||
if (fullUpdate)
|
||||
{
|
||||
var seq = ++_compSeqSend;
|
||||
var task = _consoleHost.GetCompletions(args, _compCancel.Token);
|
||||
var task = _consoleHost.GetCompletions(args, str, _compCancel.Token);
|
||||
|
||||
if (!task.IsCompleted)
|
||||
{
|
||||
@@ -140,7 +140,7 @@ public sealed partial class DebugConsole
|
||||
if (_compCurResult == null)
|
||||
return;
|
||||
|
||||
var (_, curTyping, _) = CalcTypingArgs();
|
||||
var (_, curTyping, _, _) = CalcTypingArgs();
|
||||
|
||||
var curSelected = _compFiltered?.Length > 0 ? _compFiltered[_compSelected] : default;
|
||||
_compFiltered = FilterCompletions(_compCurResult.Options, curTyping);
|
||||
@@ -168,7 +168,7 @@ public sealed partial class DebugConsole
|
||||
|
||||
DebugTools.AssertNotNull(_compCurResult);
|
||||
|
||||
var (_, _, endRange) = CalcTypingArgs();
|
||||
var (_, _, endRange, _) = CalcTypingArgs();
|
||||
|
||||
var offset = CommandBar.GetOffsetAtIndex(endRange.start);
|
||||
// Logger.Debug($"Offset: {offset}");
|
||||
@@ -231,7 +231,7 @@ public sealed partial class DebugConsole
|
||||
}
|
||||
}
|
||||
|
||||
private (List<string> args, string curTyping, (int start, int end) lastRange) CalcTypingArgs()
|
||||
private (List<string> args, string curTyping, (int start, int end) lastRange, string argStr) CalcTypingArgs()
|
||||
{
|
||||
var cursor = CommandBar.CursorPosition;
|
||||
// Don't consider text after the cursor.
|
||||
@@ -252,7 +252,7 @@ public sealed partial class DebugConsole
|
||||
else
|
||||
lastRange = (cursor, cursor);
|
||||
|
||||
return (args, args[^1], lastRange);
|
||||
return (args, args[^1], lastRange, text.ToString());
|
||||
}
|
||||
|
||||
private CompletionOption[] FilterCompletions(IEnumerable<CompletionOption> completions, string curTyping)
|
||||
@@ -270,7 +270,7 @@ public sealed partial class DebugConsole
|
||||
{
|
||||
// Figure out typing word so we know how much to replace.
|
||||
var (completion, _, completionFlags) = _compFiltered[_compSelected];
|
||||
var (_, _, lastRange) = CalcTypingArgs();
|
||||
var (_, _, lastRange, _) = CalcTypingArgs();
|
||||
|
||||
// Replace the full word from the start.
|
||||
// This means that letter casing will match the completion suggestion.
|
||||
|
||||
@@ -23,9 +23,9 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
/// <summary>
|
||||
/// Write a line with a specific color to the console window.
|
||||
/// </summary>
|
||||
void AddLine(string text, Color color);
|
||||
void AddLine(FormattedMessage text, Color color);
|
||||
|
||||
void AddLine(string text);
|
||||
void AddLine(FormattedMessage text);
|
||||
|
||||
void AddFormattedLine(FormattedMessage message);
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
private Color DetermineColor(bool local, bool error)
|
||||
{
|
||||
return Color.White;
|
||||
return error ? Color.Red : Color.White;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -136,16 +136,16 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
_flushHistoryToDisk();
|
||||
}
|
||||
|
||||
public void AddLine(string text, Color color)
|
||||
public void AddLine(FormattedMessage text, Color color)
|
||||
{
|
||||
var formatted = new FormattedMessage(3);
|
||||
formatted.PushColor(color);
|
||||
formatted.AddText(text);
|
||||
formatted.AddMessage(text);
|
||||
formatted.Pop();
|
||||
AddFormattedLine(formatted);
|
||||
}
|
||||
|
||||
public void AddLine(string text)
|
||||
public void AddLine(FormattedMessage text)
|
||||
{
|
||||
AddLine(text, Color.White);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ public sealed class DefaultStylesheet
|
||||
{
|
||||
var notoSansFont = res.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf");
|
||||
var notoSansFont12 = new VectorFont(notoSansFont, 12);
|
||||
var notoSansMonoFont = res.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSansMono-Regular.ttf");
|
||||
var notoSansMono12 = new VectorFont(notoSansMonoFont, 12);
|
||||
|
||||
|
||||
var theme = userInterfaceManager.CurrentTheme;
|
||||
|
||||
@@ -38,6 +41,13 @@ public sealed class DefaultStylesheet
|
||||
|
||||
Stylesheet = new Stylesheet(new StyleRule[]
|
||||
{
|
||||
/*
|
||||
* Debug console and other monospace things.
|
||||
*/
|
||||
|
||||
Element().Class("monospace")
|
||||
.Prop("font", notoSansMono12),
|
||||
|
||||
/*
|
||||
* OS Window defaults
|
||||
*/
|
||||
|
||||
@@ -32,6 +32,7 @@ using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Threading;
|
||||
@@ -371,6 +372,7 @@ namespace Robust.Server
|
||||
_prototype.ResolveResults();
|
||||
_refMan.Initialize();
|
||||
|
||||
IoCManager.Resolve<ToolshedManager>().Initialize();
|
||||
_consoleHost.Initialize();
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Pidgin;
|
||||
using Robust.Shared.GameObjects;
|
||||
using static Pidgin.Parser;
|
||||
using static Pidgin.Parser<char>;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public partial class BqlQueryManager
|
||||
{
|
||||
private readonly Dictionary<Type, Parser<char, BqlQuerySelectorParsed>> _parsers = new();
|
||||
private Parser<char, BqlQuerySelectorParsed> _allQuerySelectors = default!;
|
||||
private Parser<char, (IEnumerable<BqlQuerySelectorParsed>, string)> SimpleQuery => Parser.Map((en, _, rest) => (en, rest), SkipWhitespaces.Then(_allQuerySelectors).Many(), String("do").Then(SkipWhitespaces), Any.ManyString());
|
||||
|
||||
private static Parser<char, string> Word => OneOf(LetterOrDigit, Char('_')).ManyString();
|
||||
|
||||
private static Parser<char, object> Objectify<T>(Parser<char, T> inp)
|
||||
{
|
||||
return Parser.Map(x => (object) x!, inp);
|
||||
}
|
||||
|
||||
private struct SubstitutionData
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public SubstitutionData(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private static Parser<char, SubstitutionData> Substitution =>
|
||||
Try(Char('$').Then(OneOf(Uppercase, Char('_')).ManyString()))
|
||||
.MapWithInput((x, _) => new SubstitutionData(x.ToString()));
|
||||
|
||||
private static Parser<char, int> Integer =>
|
||||
Try(Int(10));
|
||||
|
||||
private static Parser<char, object> SubstitutableInteger =>
|
||||
Objectify(Integer).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, double> Float =>
|
||||
Try(Real);
|
||||
|
||||
private static Parser<char, object> SubstitutableFloat =>
|
||||
Objectify(Float).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, double> Percentage =>
|
||||
Try(Real).Before(Char('%'));
|
||||
|
||||
private static Parser<char, object> SubstitutablePercentage =>
|
||||
Objectify(Percentage).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, EntityUid> EntityId =>
|
||||
Try(Parser.Map(x => new EntityUid(x), Int(10)));
|
||||
|
||||
private static Parser<char, object> SubstitutableEntityId =>
|
||||
Objectify(EntityId).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private Parser<char, Type> Component =>
|
||||
Try(Parser.Map(t => _componentFactory.GetRegistration(t).Type, Word));
|
||||
|
||||
private Parser<char, object> SubstitutableComponent =>
|
||||
Objectify(Component).Or(Objectify(Try(Substitution)));
|
||||
|
||||
private static Parser<char, string> QuotedString =>
|
||||
OneOf(Try(Char('"').Then(OneOf(new []
|
||||
{
|
||||
AnyCharExcept("\"")
|
||||
}).ManyString().Before(Char('"')))), Try(Word));
|
||||
|
||||
private static Parser<char, object> SubstitutableString =>
|
||||
Objectify(QuotedString).Or(Objectify(Try(Substitution)));
|
||||
|
||||
// thing to make sure it all compiles.
|
||||
[UsedImplicitly]
|
||||
private Parser<char, object> TypeSystemCheck =>
|
||||
OneOf(new[]
|
||||
{
|
||||
Objectify(Integer),
|
||||
Objectify(Percentage),
|
||||
Objectify(EntityId),
|
||||
Objectify(Component),
|
||||
Objectify(Float),
|
||||
Objectify(QuotedString)
|
||||
});
|
||||
|
||||
private Parser<char, BqlQuerySelectorParsed> BuildBqlQueryParser(BqlQuerySelector inst)
|
||||
{
|
||||
if (inst.Arguments.Length == 0)
|
||||
{
|
||||
return Parser.Map(_ => new BqlQuerySelectorParsed(new List<object>(), inst.Token, false), SkipWhitespaces);
|
||||
}
|
||||
|
||||
List<Parser<char, object>> argsParsers = new();
|
||||
|
||||
foreach (var (arg, _) in inst.Arguments.Select((x, i) => (x, i)))
|
||||
{
|
||||
List<Parser<char, object>> choices = new();
|
||||
if ((arg & QuerySelectorArgument.String) == QuerySelectorArgument.String)
|
||||
{
|
||||
choices.Add(Try(SubstitutableString.Before(SkipWhitespaces).Labelled("string argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Component) == QuerySelectorArgument.Component)
|
||||
{
|
||||
choices.Add(Try(SubstitutableComponent.Before(SkipWhitespaces).Labelled("component argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.EntityId) == QuerySelectorArgument.EntityId)
|
||||
{
|
||||
choices.Add(Try(SubstitutableEntityId.Before(SkipWhitespaces).Labelled("entity ID argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Integer) == QuerySelectorArgument.Integer)
|
||||
{
|
||||
choices.Add(Try(SubstitutableInteger.Before(SkipWhitespaces).Labelled("integer argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Percentage) == QuerySelectorArgument.Percentage)
|
||||
{
|
||||
choices.Add(Try(SubstitutablePercentage.Before(SkipWhitespaces).Labelled("percentage argument")));
|
||||
}
|
||||
if ((arg & QuerySelectorArgument.Float) == QuerySelectorArgument.Float)
|
||||
{
|
||||
choices.Add(Try(SubstitutableFloat.Before(SkipWhitespaces).Labelled("float argument")));
|
||||
}
|
||||
|
||||
argsParsers.Add(OneOf(choices));
|
||||
}
|
||||
|
||||
Parser<char, List<object>> finalParser = argsParsers[0].Map(x => new List<object> { x });
|
||||
|
||||
for (var i = 1; i < argsParsers.Count; i++)
|
||||
{
|
||||
finalParser = finalParser.Then(argsParsers[i], (list, o) =>
|
||||
{
|
||||
list.Add(o);
|
||||
return list;
|
||||
}).Labelled("arguments");
|
||||
}
|
||||
|
||||
return Parser.Map(args => new BqlQuerySelectorParsed(args, inst.Token, false), finalParser);
|
||||
}
|
||||
|
||||
private void DoParserSetup()
|
||||
{
|
||||
foreach (var inst in _instances)
|
||||
{
|
||||
_parsers.Add(inst.GetType(), BuildBqlQueryParser(inst));
|
||||
}
|
||||
|
||||
_allQuerySelectors = Parser.Map((a,b) => (a,b), Try(String("not").Before(Char(' '))).Optional(), OneOf(_instances.Select(x =>
|
||||
Try(String(x.Token).Before(Char(' '))))).Then(tok =>
|
||||
_parsers[_queriesByToken[tok].GetType()])
|
||||
).Map(pair =>
|
||||
{
|
||||
pair.b.Inverted = pair.a.HasValue;
|
||||
return pair.b;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Pidgin;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public partial class BqlQueryManager
|
||||
{
|
||||
public (IEnumerable<EntityUid>, string) SimpleParseAndExecute(string query)
|
||||
{
|
||||
var parsed = SimpleQuery.Parse(query);
|
||||
if (parsed.Success)
|
||||
{
|
||||
var selectors = parsed.Value.Item1.ToArray();
|
||||
if (selectors.Length == 0)
|
||||
{
|
||||
return (_entityManager.GetEntities(), parsed.Value.Item2);
|
||||
}
|
||||
|
||||
var entities = _queriesByToken[selectors[0].Token]
|
||||
.DoInitialSelection(selectors[0].Arguments, selectors[0].Inverted, _entityManager);
|
||||
|
||||
foreach (var sel in selectors[1..])
|
||||
{
|
||||
entities = _queriesByToken[sel.Token].DoSelection(entities, sel.Arguments, sel.Inverted, _entityManager);
|
||||
}
|
||||
|
||||
return (entities, parsed.Value.Item2);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(parsed.Error!.RenderErrorMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public sealed partial class BqlQueryManager : IBqlQueryManager
|
||||
{
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private readonly List<BqlQuerySelector> _instances = new();
|
||||
private readonly Dictionary<string, BqlQuerySelector> _queriesByToken = new();
|
||||
|
||||
/// <summary>
|
||||
/// Automatically registers all query selectors with the parser/executor.
|
||||
/// </summary>
|
||||
public void DoAutoRegistrations()
|
||||
{
|
||||
foreach (var type in _reflectionManager.FindTypesWithAttribute<RegisterBqlQuerySelectorAttribute>())
|
||||
{
|
||||
RegisterClass(type);
|
||||
}
|
||||
|
||||
DoParserSetup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internally registers the given <see cref="BqlQuerySelector"/>.
|
||||
/// </summary>
|
||||
/// <param name="bqlQuerySelector">The selector to register</param>
|
||||
private void RegisterClass(Type bqlQuerySelector)
|
||||
{
|
||||
DebugTools.Assert(bqlQuerySelector.BaseType == typeof(BqlQuerySelector));
|
||||
var inst = (BqlQuerySelector)Activator.CreateInstance(bqlQuerySelector)!;
|
||||
_instances.Add(inst);
|
||||
_queriesByToken.Add(inst.Token, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,368 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class WithQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "with";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Component };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var comp = (Type) arguments[0];
|
||||
return input.Where(x => entityManager.HasComponent(x, comp) ^ isInverted);
|
||||
}
|
||||
|
||||
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
if (isInverted)
|
||||
{
|
||||
return base.DoInitialSelection(arguments, isInverted, entityManager);
|
||||
}
|
||||
|
||||
return entityManager.GetAllComponents((Type) arguments[0], includePaused: true)
|
||||
.Select(x => x.Uid);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class NamedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "named";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var r = new Regex("^" + (string) arguments[0] + "$");
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (entityManager.TryGetComponent<MetaDataComponent>(e, out var metaDataComponent))
|
||||
return r.IsMatch(metaDataComponent.EntityName) ^ isInverted;
|
||||
return isInverted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class ParentedToQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "parentedto";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var uid = (EntityUid) arguments[0];
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) &&
|
||||
transform.ParentUid == uid) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class RecursiveParentedToQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rparentedto";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.EntityId };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var uid = (EntityUid) arguments[0];
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<TransformComponent>(e, out var transform))
|
||||
return isInverted;
|
||||
while (transform.ParentUid != EntityUid.Invalid)
|
||||
{
|
||||
if ((transform.ParentUid == uid) ^ isInverted)
|
||||
return true;
|
||||
if (!entityManager.TryGetComponent(transform.ParentUid, out transform))
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class ChildrenQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "children";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
foreach (var uid in input)
|
||||
{
|
||||
if (!entityManager.TryGetComponent(uid, out TransformComponent? xform)) continue;
|
||||
|
||||
foreach (var child in xform.ChildEntities)
|
||||
{
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class RecursiveChildrenQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rchildren";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments,
|
||||
bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
IEnumerable<EntityUid> toSearch = input;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// TODO: Reduce LINQ chaining
|
||||
var doing = toSearch.Where(entityManager.HasComponent<TransformComponent>).Select(entityManager.GetComponent<TransformComponent>).ToArray();
|
||||
var search = doing.SelectMany(x => x.ChildEntities);
|
||||
if (!search.Any())
|
||||
break;
|
||||
toSearch = doing.SelectMany(x => x.ChildEntities).Where(x => x != EntityUid.Invalid);
|
||||
|
||||
foreach (var xform in doing)
|
||||
{
|
||||
foreach (var uid in xform.ChildEntities)
|
||||
{
|
||||
// This should never happen anyway
|
||||
if (uid == EntityUid.Invalid) continue;
|
||||
yield return uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class ParentQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "parent";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(entityManager.HasComponent<TransformComponent>)
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return DoSelection(
|
||||
Query(entityManager.AllEntityQueryEnumerator<TransformComponent>()),
|
||||
arguments,
|
||||
isInverted, entityManager);
|
||||
|
||||
IEnumerable<EntityUid> Query(AllEntityQueryEnumerator<TransformComponent> enumerator)
|
||||
{
|
||||
while (enumerator.MoveNext(out var entityUid, out _))
|
||||
{
|
||||
yield return entityUid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class AboveQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "above";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
|
||||
var tileTy = tileDefinitionManager[(string) arguments[0]];
|
||||
|
||||
var map = IoCManager.Resolve<IMapManager>();
|
||||
if (tileTy.TileId == 0)
|
||||
{
|
||||
return input.Where(e => entityManager.TryGetComponent<TransformComponent>(e, out var transform) && (transform.GridUid is null) ^ isInverted);
|
||||
}
|
||||
else
|
||||
{
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<TransformComponent>(e, out var transform)) return isInverted;
|
||||
|
||||
if (!map.TryGetGrid(transform.GridUid, out var grid))
|
||||
return isInverted;
|
||||
|
||||
return (grid.GetTileRef(transform.Coordinates).Tile.TypeId == tileTy.TileId) ^ isInverted;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public sealed class OnGridQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "ongrid";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
// TODO: Probably easier and significantly faster to just iterate the grid's children.
|
||||
var grid = new EntityUid((int) arguments[0]);
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.GridUid == grid) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public sealed class OnMapQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "onmap";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.Integer };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
// TODO: Just use EntityLookup GetEntitiesInMap
|
||||
var map = new MapId((int) arguments[0]);
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.MapID == map) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class PrototypedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "prototyped";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var name = (string) arguments[0];
|
||||
return input.Where(e => (entityManager.TryGetComponent<MetaDataComponent>(e, out var metaData) && metaData.EntityPrototype?.ID == name) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class RecursivePrototypedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "rprototyped";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.String };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var name = (string) arguments[0];
|
||||
return input.Where(e =>
|
||||
{
|
||||
if (!entityManager.TryGetComponent<MetaDataComponent>(e, out var metaData))
|
||||
return isInverted;
|
||||
if ((metaData.EntityPrototype?.ID == name) ^ isInverted)
|
||||
return true;
|
||||
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
return metaData.EntityPrototype != null && prototypeManager.EnumerateParents<EntityPrototype>(metaData.EntityPrototype.ID).Any(x => x.Name == name) ^ isInverted;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class SelectQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "select";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Integer | QuerySelectorArgument.Percentage };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
if (arguments[0] is int)
|
||||
{
|
||||
var inp = input.OrderBy(_ => Guid.NewGuid()).ToArray();
|
||||
var taken = (int) arguments[0];
|
||||
|
||||
if (isInverted)
|
||||
taken = Math.Max(0, inp.Length - taken);
|
||||
|
||||
return inp.Take(taken);
|
||||
}
|
||||
|
||||
var enumerable = input.OrderBy(_ => Guid.NewGuid()).ToArray();
|
||||
var amount = isInverted
|
||||
? (int) Math.Floor(enumerable.Length * Math.Clamp(1 - (double) arguments[0], 0, 1))
|
||||
: (int) Math.Floor(enumerable.Length * Math.Clamp((double) arguments[0], 0, 1));
|
||||
return enumerable.Take(amount);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class NearQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "near";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => new []{ QuerySelectorArgument.Float };
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
var radius = (float)(double)arguments[0];
|
||||
var entityLookup = entityManager.System<EntityLookupSystem>();
|
||||
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
|
||||
var distinct = new HashSet<EntityUid>();
|
||||
|
||||
foreach (var uid in input)
|
||||
{
|
||||
foreach (var near in entityLookup.GetEntitiesInRange(xformQuery.GetComponent(uid).Coordinates,
|
||||
radius))
|
||||
{
|
||||
if (!distinct.Add(near)) continue;
|
||||
yield return near;
|
||||
}
|
||||
}
|
||||
|
||||
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
// ReSharper disable once InconsistentNaming the name is correct shut up
|
||||
public sealed class AnchoredQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "anchored";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(e => (entityManager.TryGetComponent<TransformComponent>(e, out var transform) && transform.Anchored) ^ isInverted);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterBqlQuerySelector]
|
||||
public sealed class PausedQuerySelector : BqlQuerySelector
|
||||
{
|
||||
public override string Token => "paused";
|
||||
|
||||
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||
|
||||
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return input.Where(e => entityManager.GetComponent<MetaDataComponent>(e).EntityPaused ^ isInverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[Flags]
|
||||
[PublicAPI]
|
||||
public enum QuerySelectorArgument
|
||||
{
|
||||
Integer = 0b00000001,
|
||||
Float = 0b00000010,
|
||||
String = 0b00000100,
|
||||
Percentage = 0b00001000,
|
||||
Component = 0b00010000,
|
||||
//SubQuery = 0b00100000,
|
||||
EntityId = 0b01000000,
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public abstract class BqlQuerySelector
|
||||
{
|
||||
/// <summary>
|
||||
/// The token name for the given QuerySelector, for example `when`.
|
||||
/// </summary>
|
||||
public virtual string Token => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Arguments for the given QuerySelector, presented as "what arguments are permitted in what spot".
|
||||
/// </summary>
|
||||
public virtual QuerySelectorArgument[] Arguments => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Performs a transform over it's input entity list, whether that be filtering (selecting) or expanding the
|
||||
/// input on some criteria like what entities are nearby.
|
||||
/// </summary>
|
||||
/// <param name="input">Input entity list.</param>
|
||||
/// <param name="arguments">Parsed selector arguments.</param>
|
||||
/// <param name="isInverted">Whether the query is inverted.</param>
|
||||
/// <param name="entityManager">The entity manager.</param>
|
||||
/// <returns>New list of entities</returns>
|
||||
/// <exception cref="NotImplementedException">someone is a moron if this happens.</exception>
|
||||
public abstract IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input,
|
||||
IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager);
|
||||
|
||||
/// <summary>
|
||||
/// Performs selection as the first selector in the query. Allows for optimizing when you can be more efficient
|
||||
/// than just querying every entity.
|
||||
/// </summary>
|
||||
/// <param name="arguments"></param>
|
||||
/// <param name="isInverted"></param>
|
||||
/// <param name="entityManager"></param>
|
||||
/// <returns></returns>
|
||||
public virtual IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||
{
|
||||
return DoSelection(entityManager.GetEntities(), arguments, isInverted, entityManager);
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
protected BqlQuerySelector() {}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public struct BqlQuerySelectorParsed
|
||||
{
|
||||
public List<object> Arguments;
|
||||
public string Token;
|
||||
public bool Inverted;
|
||||
|
||||
public BqlQuerySelectorParsed(List<object> arguments, string token, bool inverted)
|
||||
{
|
||||
Arguments = arguments;
|
||||
Token = token;
|
||||
Inverted = inverted;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public sealed class ForAllCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IBqlQueryManager _bql = default!;
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
|
||||
public override string Command => "forall";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
var transformSystem = _entities.System<SharedTransformSystem>();
|
||||
|
||||
var (entities, rest) = _bql.SimpleParseAndExecute(argStr[6..]);
|
||||
|
||||
foreach (var ent in entities.ToList())
|
||||
{
|
||||
var cmds = SubstituteEntityDetails(_entities, transformSystem, shell, ent, rest).Split(";");
|
||||
foreach (var cmd in cmds)
|
||||
{
|
||||
shell.ExecuteCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will be refactored out soon.
|
||||
private static string SubstituteEntityDetails(
|
||||
IEntityManager entMan,
|
||||
SharedTransformSystem transformSystem,
|
||||
IConsoleShell shell,
|
||||
EntityUid ent,
|
||||
string ruleString)
|
||||
{
|
||||
var transform = entMan.GetComponent<TransformComponent>(ent);
|
||||
var metadata = entMan.GetComponent<MetaDataComponent>(ent);
|
||||
|
||||
var worldPos = transformSystem.GetWorldPosition(transform);
|
||||
var localPos = transform.LocalPosition;
|
||||
|
||||
// gross, is there a better way to do this?
|
||||
ruleString = ruleString.Replace("$ID", ent.ToString());
|
||||
ruleString = ruleString.Replace("$WX", worldPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$WY", worldPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$LX", localPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$LY", localPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$NAME", metadata.EntityName);
|
||||
|
||||
if (shell.Player is { AttachedEntity: { } pEntity})
|
||||
{
|
||||
var pTransform = entMan.GetComponent<TransformComponent>(pEntity);
|
||||
|
||||
var pWorldPos = transformSystem.GetWorldPosition(pTransform);
|
||||
var pLocalPos = pTransform.LocalPosition;
|
||||
|
||||
ruleString = ruleString.Replace("$PID", pEntity.ToString());
|
||||
ruleString = ruleString.Replace("$PWX", pWorldPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PWY", pWorldPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PLX", pLocalPos.X.ToString(CultureInfo.InvariantCulture));
|
||||
ruleString = ruleString.Replace("$PLY", pLocalPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return ruleString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
public interface IBqlQueryManager
|
||||
{
|
||||
public (IEnumerable<EntityUid>, string) SimpleParseAndExecute(string query);
|
||||
void DoAutoRegistrations();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Server.Bql
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
[BaseTypeRequired(typeof(BqlQuerySelector))]
|
||||
[MeansImplicitUse]
|
||||
[PublicAPI]
|
||||
public sealed class RegisterBqlQuerySelectorAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
|
||||
namespace Robust.Server.Console
|
||||
{
|
||||
internal sealed class ConGroupController : IConGroupController
|
||||
internal sealed class ConGroupController : IConGroupController, IPermissionController
|
||||
{
|
||||
public IConGroupControllerImplementation? Implementation { get; set; }
|
||||
|
||||
@@ -30,5 +34,11 @@ namespace Robust.Server.Console
|
||||
{
|
||||
return Implementation?.CanAdminReloadPrototypes(session) ?? false;
|
||||
}
|
||||
|
||||
public bool CheckInvokable(CommandSpec command, ICommonSession? user, out IConError? error)
|
||||
{
|
||||
error = null;
|
||||
return Implementation?.CheckInvokable(command, user, out error) ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Robust.Server.Console
|
||||
{
|
||||
public interface IConGroupControllerImplementation
|
||||
public interface IConGroupControllerImplementation : IPermissionController
|
||||
{
|
||||
bool CanCommand(IPlayerSession session, string cmdName);
|
||||
bool CanAdminPlace(IPlayerSession session);
|
||||
|
||||
@@ -9,6 +9,8 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Console
|
||||
@@ -19,6 +21,7 @@ namespace Robust.Server.Console
|
||||
[Dependency] private readonly IConGroupController _groupController = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
|
||||
[Dependency] private readonly ToolshedManager _toolshed = default!;
|
||||
|
||||
public ServerConsoleHost() : base(isServer: true) {}
|
||||
|
||||
@@ -45,19 +48,31 @@ namespace Robust.Server.Console
|
||||
/// <inheritdoc />
|
||||
public override void WriteLine(ICommonSession? session, string text)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
if (session is IPlayerSession playerSession)
|
||||
OutputText(playerSession, text, false);
|
||||
OutputText(playerSession, msg, false);
|
||||
else
|
||||
OutputText(null, text, false);
|
||||
OutputText(null, msg, false);
|
||||
}
|
||||
|
||||
public override void WriteLine(ICommonSession? session, FormattedMessage msg)
|
||||
{
|
||||
if (session is IPlayerSession playerSession)
|
||||
OutputText(playerSession, msg, false);
|
||||
else
|
||||
OutputText(null, msg, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void WriteError(ICommonSession? session, string text)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(text);
|
||||
if (session is IPlayerSession playerSession)
|
||||
OutputText(playerSession, text, true);
|
||||
OutputText(playerSession, msg, true);
|
||||
else
|
||||
OutputText(null, text, true);
|
||||
OutputText(null, msg, true);
|
||||
}
|
||||
|
||||
public bool IsCmdServer(IConsoleCommand cmd) => true;
|
||||
@@ -70,13 +85,13 @@ namespace Robust.Server.Console
|
||||
var localShell = shell.ConsoleHost.LocalShell;
|
||||
var sudoShell = new SudoShell(this, localShell, shell);
|
||||
ExecuteInShell(sudoShell, argStr["sudo ".Length..]);
|
||||
}, (shell, args) =>
|
||||
}, (shell, args, argStr) =>
|
||||
{
|
||||
var localShell = shell.ConsoleHost.LocalShell;
|
||||
var sudoShell = new SudoShell(this, localShell, shell);
|
||||
|
||||
#pragma warning disable CA2012
|
||||
return CalcCompletions(sudoShell, args);
|
||||
return CalcCompletions(sudoShell, args, argStr);
|
||||
#pragma warning restore CA2012
|
||||
});
|
||||
|
||||
@@ -117,6 +132,18 @@ namespace Robust.Server.Console
|
||||
AnyCommandExecuted?.Invoke(shell, cmdName, command, cmdArgs);
|
||||
conCmd.Execute(shell, command, cmdArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// toolshed time
|
||||
_toolshed.InvokeCommand(shell, command, null, out var res, out var ctx);
|
||||
|
||||
foreach (var err in ctx.GetErrors())
|
||||
{
|
||||
ctx.WriteLine(err.Describe());
|
||||
}
|
||||
|
||||
shell.WriteLine(_toolshed.PrettyPrintType(res));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -162,7 +189,7 @@ namespace Robust.Server.Console
|
||||
ExecuteCommand(session, text);
|
||||
}
|
||||
|
||||
private void OutputText(IPlayerSession? session, string text, bool error)
|
||||
private void OutputText(IPlayerSession? session, FormattedMessage text, bool error)
|
||||
{
|
||||
if (session != null)
|
||||
{
|
||||
@@ -183,10 +210,25 @@ namespace Robust.Server.Console
|
||||
private async void HandleConCompletions(MsgConCompletion message)
|
||||
{
|
||||
var session = _players.GetSessionByChannel(message.MsgChannel);
|
||||
|
||||
var shell = new ConsoleShell(this, session, false);
|
||||
|
||||
var result = await CalcCompletions(shell, message.Args);
|
||||
var result = await CalcCompletions(shell, message.Args, message.ArgString);
|
||||
|
||||
if ((result.Options.Length == 0 && result.Hint is null) || message.Args.Length <= 1)
|
||||
{
|
||||
var parser = new ForwardParser(message.ArgString, _toolshed);
|
||||
CommandRun.TryParse(false, true, parser, null, null, false, out _, out var completions, out _);
|
||||
if (completions == null)
|
||||
{
|
||||
goto done;
|
||||
}
|
||||
var (shedRes, _) = await completions.Value;
|
||||
shedRes ??= CompletionResult.Empty;
|
||||
result = new CompletionResult(shedRes.Options.Concat(result.Options).ToArray(), shedRes.Hint ?? result.Hint);
|
||||
}
|
||||
|
||||
done:
|
||||
var msg = new MsgConCompletionResp
|
||||
{
|
||||
Result = result,
|
||||
@@ -199,7 +241,7 @@ namespace Robust.Server.Console
|
||||
NetManager.ServerSendMessage(msg, message.MsgChannel);
|
||||
}
|
||||
|
||||
private ValueTask<CompletionResult> CalcCompletions(IConsoleShell shell, string[] args)
|
||||
private ValueTask<CompletionResult> CalcCompletions(IConsoleShell shell, string[] args, string argStr)
|
||||
{
|
||||
// Logger.Debug(string.Join(", ", args));
|
||||
|
||||
@@ -217,7 +259,7 @@ namespace Robust.Server.Console
|
||||
if (!ShellCanExecute(shell, cmdName))
|
||||
return ValueTask.FromResult(CompletionResult.Empty);
|
||||
|
||||
return cmd.GetCompletionAsync(shell, args[1..], default);
|
||||
return cmd.GetCompletionAsync(shell, args[1..], argStr, default);
|
||||
}
|
||||
|
||||
private sealed class SudoShell : IConsoleShell
|
||||
@@ -254,6 +296,12 @@ namespace Robust.Server.Console
|
||||
_sudoer.WriteLine(text);
|
||||
}
|
||||
|
||||
public void WriteLine(FormattedMessage message)
|
||||
{
|
||||
_owner.WriteLine(message);
|
||||
_sudoer.WriteLine(message);
|
||||
}
|
||||
|
||||
public void WriteError(string text)
|
||||
{
|
||||
_owner.WriteError(text);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Robust.Server.Bql;
|
||||
using Robust.Server.Configuration;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.DataMetrics;
|
||||
@@ -82,7 +81,6 @@ namespace Robust.Server
|
||||
deps.Register<IScriptHost, ScriptHost>();
|
||||
deps.Register<IMetricsManager, MetricsManager>();
|
||||
deps.Register<IAuthManager, AuthManager>();
|
||||
deps.Register<IBqlQueryManager, BqlQueryManager>();
|
||||
deps.Register<HubManager, HubManager>();
|
||||
deps.Register<IRobustSerializer, ServerRobustSerializer>();
|
||||
deps.Register<IRobustSerializerInternal, ServerRobustSerializer>();
|
||||
|
||||
24
Robust.Server/Toolshed/Commands/Players/ActorCommand.cs
Normal file
24
Robust.Server/Toolshed/Commands/Players/ActorCommand.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Robust.Server.Toolshed.Commands.Players;
|
||||
|
||||
[ToolshedCommand]
|
||||
public sealed class ActorCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation("controlled")]
|
||||
public IEnumerable<EntityUid> Controlled([PipedArgument] IEnumerable<EntityUid> input)
|
||||
{
|
||||
return input.Where(HasComp<ActorComponent>);
|
||||
}
|
||||
|
||||
[CommandImplementation("session")]
|
||||
public IEnumerable<IPlayerSession> Session([PipedArgument] IEnumerable<EntityUid> input)
|
||||
{
|
||||
return input.Where(HasComp<ActorComponent>).Select(x => Comp<ActorComponent>(x).PlayerSession);
|
||||
}
|
||||
}
|
||||
84
Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs
Normal file
84
Robust.Server/Toolshed/Commands/Players/PlayersCommand.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Toolshed.Commands.Players;
|
||||
|
||||
[ToolshedCommand]
|
||||
public sealed class PlayerCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
[CommandImplementation("list")]
|
||||
public IEnumerable<IPlayerSession> Players()
|
||||
=> _playerManager.ServerSessions;
|
||||
|
||||
[CommandImplementation("self")]
|
||||
public IPlayerSession Self([CommandInvocationContext] IInvocationContext ctx)
|
||||
{
|
||||
if (ctx.Session is null)
|
||||
{
|
||||
ctx.ReportError(new NotForServerConsoleError());
|
||||
}
|
||||
|
||||
return (IPlayerSession)ctx.Session!;
|
||||
}
|
||||
|
||||
[CommandImplementation("imm")]
|
||||
public ICommonSession Immediate(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[CommandArgument] string username
|
||||
)
|
||||
{
|
||||
_playerManager.TryGetSessionByUsername(username, out var session);
|
||||
|
||||
if (session is null)
|
||||
{
|
||||
if (Guid.TryParse(username, out var guid))
|
||||
{
|
||||
_playerManager.TryGetSessionById(new NetUserId(guid), out session);
|
||||
}
|
||||
}
|
||||
|
||||
if (session is null)
|
||||
{
|
||||
ctx.ReportError(new NoSuchPlayerError(username));
|
||||
}
|
||||
|
||||
return session!;
|
||||
}
|
||||
|
||||
[CommandImplementation("entity")]
|
||||
public IEnumerable<EntityUid> GetPlayerEntity([PipedArgument] IEnumerable<IPlayerSession> sessions)
|
||||
{
|
||||
return sessions.Select(x => x.AttachedEntity).Where(x => x is not null).Cast<EntityUid>();
|
||||
}
|
||||
|
||||
[CommandImplementation("entity")]
|
||||
public EntityUid GetPlayerEntity([PipedArgument] IPlayerSession sessions)
|
||||
{
|
||||
return sessions.AttachedEntity ?? default;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct NoSuchPlayerError(string Username) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromMarkup($"No player with the username/GUID {Username} could be found.");
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
35
Robust.Shared.Scripting/ILCommand.cs
Normal file
35
Robust.Shared.Scripting/ILCommand.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using ILReader;
|
||||
using ILReader.Readers;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Robust.Shared.Scripting;
|
||||
|
||||
[ToolshedCommand]
|
||||
public sealed class ILCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation("dumpil")]
|
||||
public void DumpIL(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] MethodInfo info
|
||||
)
|
||||
{
|
||||
var reader = GetReader(info);
|
||||
|
||||
foreach (var instruction in reader)
|
||||
{
|
||||
if (instruction is null)
|
||||
break;
|
||||
|
||||
ctx.WriteLine(instruction.ToString()!);
|
||||
}
|
||||
}
|
||||
|
||||
private IILReader GetReader(MethodBase method)
|
||||
{
|
||||
IILReaderConfiguration cfg = ILReader.Configuration.Resolve(method);
|
||||
return cfg.GetReader(method);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ILReader.Core" Version="1.0.0.4" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
|
||||
|
||||
@@ -10,7 +10,10 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Scripting
|
||||
@@ -19,7 +22,7 @@ namespace Robust.Shared.Scripting
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo")]
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
[SuppressMessage("ReSharper", "CA1822")]
|
||||
public abstract class ScriptGlobalsShared
|
||||
public abstract class ScriptGlobalsShared : IInvocationContext
|
||||
{
|
||||
private const BindingFlags DefaultHelpFlags =
|
||||
BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
|
||||
@@ -30,6 +33,8 @@ namespace Robust.Shared.Scripting
|
||||
[field: Dependency] public IMapManager map { get; } = default!;
|
||||
[field: Dependency] public IDependencyCollection dependencies { get; } = default!;
|
||||
|
||||
[field: Dependency] public ToolshedManager shed { get; } = default!;
|
||||
|
||||
protected ScriptGlobalsShared(IDependencyCollection dependencies)
|
||||
{
|
||||
dependencies.InjectDependencies(this);
|
||||
@@ -161,6 +166,24 @@ namespace Robust.Shared.Scripting
|
||||
public abstract void write(object toString);
|
||||
public abstract void show(object obj);
|
||||
|
||||
public object? tsh(string toolshedCommand)
|
||||
{
|
||||
shed.InvokeCommand(this, toolshedCommand, null, out var res);
|
||||
return res;
|
||||
}
|
||||
|
||||
public T tsh<T>(string toolshedCommand)
|
||||
{
|
||||
shed.InvokeCommand(this, toolshedCommand, null, out var res);
|
||||
return (T)res!;
|
||||
}
|
||||
|
||||
public TOut tsh<TIn, TOut>(TIn value, string toolshedCommand)
|
||||
{
|
||||
shed.InvokeCommand(this, toolshedCommand, value, out var res);
|
||||
return (TOut)res!;
|
||||
}
|
||||
|
||||
#region EntityManager proxy methods
|
||||
public T Comp<T>(EntityUid uid) where T : Component
|
||||
=> ent.GetComponent<T>(uid);
|
||||
@@ -227,5 +250,34 @@ namespace Robust.Shared.Scripting
|
||||
return ent.EntityQuery<TComp1, TComp2, TComp3>(includePaused);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public bool CheckInvokable(CommandSpec command, out IConError? error)
|
||||
{
|
||||
error = null;
|
||||
return true; // Do as I say!
|
||||
}
|
||||
|
||||
public ICommonSession? Session => null;
|
||||
|
||||
public void WriteLine(string line)
|
||||
{
|
||||
write(line);
|
||||
}
|
||||
|
||||
public void ReportError(IConError err)
|
||||
{
|
||||
write(err);
|
||||
}
|
||||
|
||||
public IEnumerable<IConError> GetErrors()
|
||||
{
|
||||
return Array.Empty<IConError>();
|
||||
}
|
||||
|
||||
public void ClearErrors()
|
||||
{
|
||||
}
|
||||
|
||||
public Dictionary<string, object?> Variables { get; } = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,31 +5,31 @@ namespace Robust.Shared.Console.Commands;
|
||||
|
||||
internal sealed class HelpCommand : LocalizedCommands
|
||||
{
|
||||
public override string Command => "help";
|
||||
public override string Command => "oldhelp";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
shell.WriteLine(Loc.GetString("cmd-help-no-args"));
|
||||
shell.WriteLine(Loc.GetString("cmd-oldhelp-no-args"));
|
||||
break;
|
||||
|
||||
case 1:
|
||||
var commandName = args[0];
|
||||
if (!shell.ConsoleHost.AvailableCommands.TryGetValue(commandName, out var cmd))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-help-unknown", ("command", commandName)));
|
||||
shell.WriteError(Loc.GetString("cmd-oldhelp-unknown", ("command", commandName)));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine(Loc.GetString("cmd-help-top", ("command", cmd.Command),
|
||||
shell.WriteLine(Loc.GetString("cmd-oldhelp-top", ("command", cmd.Command),
|
||||
("description", cmd.Description)));
|
||||
shell.WriteLine(cmd.Help);
|
||||
break;
|
||||
|
||||
default:
|
||||
shell.WriteError(Loc.GetString("cmd-help-invalid-args"));
|
||||
shell.WriteError(Loc.GetString("cmd-oldhelp-invalid-args"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ internal sealed class HelpCommand : LocalizedCommands
|
||||
var host = shell.ConsoleHost;
|
||||
return CompletionResult.FromHintOptions(
|
||||
host.AvailableCommands.Values.OrderBy(c => c.Command).Select(c => new CompletionOption(c.Command, c.Description)).ToArray(),
|
||||
Loc.GetString("cmd-help-arg-cmdname"));
|
||||
Loc.GetString("cmd-oldhelp-arg-cmdname"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
@@ -173,6 +174,8 @@ namespace Robust.Shared.Console
|
||||
|
||||
//TODO: IConsoleOutput for [e#1225]
|
||||
public abstract void WriteLine(ICommonSession? session, string text);
|
||||
public abstract void WriteLine(ICommonSession? session, FormattedMessage msg);
|
||||
|
||||
public abstract void WriteError(ICommonSession? session, string text);
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -324,10 +327,11 @@ namespace Robust.Shared.Console
|
||||
public ValueTask<CompletionResult> GetCompletionAsync(
|
||||
IConsoleShell shell,
|
||||
string[] args,
|
||||
string argStr,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
if (CompletionCallbackAsync != null)
|
||||
return CompletionCallbackAsync(shell, args);
|
||||
return CompletionCallbackAsync(shell, args, argStr);
|
||||
|
||||
if (CompletionCallback != null)
|
||||
return ValueTask.FromResult(CompletionCallback(shell, args));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
{
|
||||
@@ -48,6 +49,11 @@ namespace Robust.Shared.Console
|
||||
ConsoleHost.WriteLine(Player, text);
|
||||
}
|
||||
|
||||
public void WriteLine(FormattedMessage message)
|
||||
{
|
||||
ConsoleHost.WriteLine(Player, message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteError(string text)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
@@ -8,7 +9,7 @@ namespace Robust.Shared.Console
|
||||
/// Basic interface to handle console commands. Any class implementing this will be
|
||||
/// registered with the console system through reflection.
|
||||
/// </summary>
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors), Obsolete("New commands should utilize RtShellCommand.")]
|
||||
public interface IConsoleCommand
|
||||
{
|
||||
/// <summary>
|
||||
@@ -78,7 +79,7 @@ namespace Robust.Shared.Console
|
||||
/// <remarks>
|
||||
/// If this method is implemented, <see cref="GetCompletion"/> will not be automatically called.
|
||||
/// </remarks>
|
||||
ValueTask<CompletionResult> GetCompletionAsync(IConsoleShell shell, string[] args, CancellationToken cancel)
|
||||
ValueTask<CompletionResult> GetCompletionAsync(IConsoleShell shell, string[] args, string argStr, CancellationToken cancel)
|
||||
{
|
||||
return ValueTask.FromResult(GetCompletion(shell, args));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
{
|
||||
@@ -21,7 +22,7 @@ namespace Robust.Shared.Console
|
||||
/// <summary>
|
||||
/// Called to fetch completions for a console command (async). See <see cref="IConsoleCommand.GetCompletionAsync"/> for details.
|
||||
/// </summary>
|
||||
public delegate ValueTask<CompletionResult> ConCommandCompletionAsyncCallback(IConsoleShell shell, string[] args);
|
||||
public delegate ValueTask<CompletionResult> ConCommandCompletionAsyncCallback(IConsoleShell shell, string[] args, string argStr);
|
||||
|
||||
public delegate void ConAnyCommandCallback(IConsoleShell shell, string commandName, string argStr, string[] args);
|
||||
|
||||
@@ -249,6 +250,8 @@ namespace Robust.Shared.Console
|
||||
/// <param name="text">Text message to send.</param>
|
||||
void WriteLine(ICommonSession? session, string text);
|
||||
|
||||
void WriteLine(ICommonSession? session, FormattedMessage msg);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a foreground colored text string to the remote session.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
{
|
||||
@@ -54,6 +55,8 @@ namespace Robust.Shared.Console
|
||||
/// <param name="text">Line of text to write.</param>
|
||||
void WriteLine(string text);
|
||||
|
||||
void WriteLine(FormattedMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// Write an error line to the console window.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -5,6 +6,7 @@ using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Shared.Console;
|
||||
|
||||
[Obsolete("You should use ToolshedCommand instead.")]
|
||||
public abstract class LocalizedCommands : IConsoleCommand
|
||||
{
|
||||
[Dependency] protected readonly ILocalizationManager LocalizationManager = default!;
|
||||
@@ -23,12 +25,12 @@ public abstract class LocalizedCommands : IConsoleCommand
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void Execute(IConsoleShell shell, string argStr, string[] args);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual CompletionResult GetCompletion(IConsoleShell shell, string[] args) => CompletionResult.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ValueTask<CompletionResult> GetCompletionAsync(IConsoleShell shell, string[] args,
|
||||
public virtual ValueTask<CompletionResult> GetCompletionAsync(IConsoleShell shell, string[] args, string argStr,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
return ValueTask.FromResult(GetCompletion(shell, args));
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
#nullable disable
|
||||
|
||||
@@ -9,17 +11,24 @@ namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.String;
|
||||
|
||||
public string Text { get; set; }
|
||||
public FormattedMessage Text { get; set; }
|
||||
public bool Error { get; set; }
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
Text = buffer.ReadString();
|
||||
int length = buffer.ReadVariableInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
Text = serializer.Deserialize<FormattedMessage>(stream);
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
buffer.Write(Text);
|
||||
var stream = new MemoryStream();
|
||||
|
||||
serializer.Serialize(stream, Text);
|
||||
|
||||
buffer.WriteVariableInt32((int)stream.Length);
|
||||
buffer.Write(stream.AsSpan());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ public sealed class MsgConCompletion : NetMessage
|
||||
public int Seq { get; set; }
|
||||
public string[] Args { get; set; }
|
||||
|
||||
public string ArgString { get; set; }
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
Seq = buffer.ReadInt32();
|
||||
@@ -22,6 +24,8 @@ public sealed class MsgConCompletion : NetMessage
|
||||
{
|
||||
Args[i] = buffer.ReadString();
|
||||
}
|
||||
|
||||
ArgString = buffer.ReadString();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
@@ -33,5 +37,7 @@ public sealed class MsgConCompletion : NetMessage
|
||||
{
|
||||
buffer.Write(arg);
|
||||
}
|
||||
|
||||
buffer.Write(ArgString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +44,5 @@ namespace Robust.Shared.Players
|
||||
/// on the Client only the LocalPlayer has a network channel.
|
||||
/// </remarks>
|
||||
INetChannel ConnectedClient { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Porting convenience for admin commands which use such logic as "at the player's feet", etc: the transform component of the attached entity.
|
||||
/// </summary>
|
||||
[Obsolete("Query manually from the EntityUid")]
|
||||
TransformComponent? AttachedEntityTransform => IoCManager.Resolve<IEntityManager>().GetComponentOrNull<TransformComponent>(AttachedEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Toolshed;
|
||||
|
||||
namespace Robust.Shared
|
||||
{
|
||||
@@ -46,6 +47,7 @@ namespace Robust.Shared
|
||||
deps.Register<IVerticesSimplifier, RamerDouglasPeuckerSimplifier>();
|
||||
deps.Register<IParallelManager, ParallelManager>();
|
||||
deps.Register<IParallelManagerInternal, ParallelManager>();
|
||||
deps.Register<ToolshedManager>();
|
||||
deps.Register<HttpClientHolder>();
|
||||
}
|
||||
}
|
||||
|
||||
90
Robust.Shared/Toolshed/Attributes.cs
Normal file
90
Robust.Shared/Toolshed/Attributes.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Shared.Toolshed;
|
||||
|
||||
/// <summary>
|
||||
/// Used to mark a class so that <see cref="ToolshedManager"/> automatically discovers and registers it.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
[MeansImplicitUse]
|
||||
public sealed class ToolshedCommandAttribute : Attribute
|
||||
{
|
||||
public string? Name = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a function in a <see cref="ToolshedCommand"/> as being an implementation of that command, so that Toolshed will use it's signature for parsing/etc.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
[MeansImplicitUse]
|
||||
public sealed class CommandImplementationAttribute : Attribute
|
||||
{
|
||||
public readonly string? SubCommand = null;
|
||||
|
||||
public CommandImplementationAttribute(string? subCommand = null)
|
||||
{
|
||||
SubCommand = subCommand;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks an argument in a function in a <see cref="ToolshedCommand"/> as being the "piped" argument, the return value of the prior command in the chain.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
[MeansImplicitUse]
|
||||
public sealed class PipedArgumentAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks an argument in a function as being an argument of a <see cref="ToolshedCommand"/>.
|
||||
/// This will make it so the argument will get parsed.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
[MeansImplicitUse]
|
||||
public sealed class CommandArgumentAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks an argument in a function as specifying whether or not this call to a <see cref="ToolshedCommand"/> is inverted.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
[MeansImplicitUse]
|
||||
public sealed class CommandInvertedAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks an argument in a function as being where the invocation context should be provided in a <see cref="ToolshedCommand"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IInvocationContext"/>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
[MeansImplicitUse]
|
||||
public sealed class CommandInvocationContextAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a command implementation as taking the type of the previous command in sequence as a generic argument.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the argument marked with <see cref="PipedArgumentAttribute"/> is not <c>T</c> but instead a pattern like <c>IEnumerable<T></c>, Toolshed will account for this.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class TakesPipedTypeAsGenericAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
// Internal because this is just a hack at the moment and should be replaced with proper inference later!
|
||||
// Overrides type argument parsing to parse a block and then use it's return type as the sole type argument.
|
||||
internal sealed class MapLikeCommandAttribute : Attribute
|
||||
{
|
||||
public bool TakesPipedType;
|
||||
|
||||
public MapLikeCommandAttribute(bool takesPipedType = true)
|
||||
{
|
||||
TakesPipedType = takesPipedType;
|
||||
}
|
||||
}
|
||||
13
Robust.Shared/Toolshed/Commands/Debug/FuckCommand.cs
Normal file
13
Robust.Shared/Toolshed/Commands/Debug/FuckCommand.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Debug;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class FuckCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public object? Fuck([PipedArgument] object? value)
|
||||
{
|
||||
throw new Exception("fuck!");
|
||||
}
|
||||
}
|
||||
18
Robust.Shared/Toolshed/Commands/EcsCompCommand.cs
Normal file
18
Robust.Shared/Toolshed/Commands/EcsCompCommand.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class EcsCompCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
|
||||
[CommandImplementation("listty")]
|
||||
public IEnumerable<Type> ListTy()
|
||||
{
|
||||
return _factory.AllRegisteredTypes;
|
||||
}
|
||||
}
|
||||
28
Robust.Shared/Toolshed/Commands/Entities/CompCommand.cs
Normal file
28
Robust.Shared/Toolshed/Commands/Entities/CompCommand.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Toolshed.TypeParsers;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Entities;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class CompCommand : ToolshedCommand
|
||||
{
|
||||
public override Type[] TypeParameterParsers => new[] {typeof(ComponentType)};
|
||||
|
||||
[CommandImplementation]
|
||||
public IEnumerable<T> CompEnumerable<T>([PipedArgument] IEnumerable<EntityUid> input)
|
||||
where T: IComponent
|
||||
{
|
||||
return input.Where(HasComp<T>).Select(Comp<T>);
|
||||
}
|
||||
|
||||
[CommandImplementation]
|
||||
public T? CompDirect<T>([PipedArgument] EntityUid input)
|
||||
where T : IComponent
|
||||
{
|
||||
TryComp(input, out T? res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
20
Robust.Shared/Toolshed/Commands/Entities/DeleteCommand.cs
Normal file
20
Robust.Shared/Toolshed/Commands/Entities/DeleteCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Entities;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class DeleteCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
|
||||
[CommandImplementation]
|
||||
public void Delete([PipedArgument] IEnumerable<EntityUid> entities)
|
||||
{
|
||||
foreach (var ent in entities)
|
||||
{
|
||||
Del(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Robust.Shared/Toolshed/Commands/Entities/DoCommand.cs
Normal file
44
Robust.Shared/Toolshed/Commands/Entities/DoCommand.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Toolshed.Invocation;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Entities;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class DoCommand : ToolshedCommand
|
||||
{
|
||||
private SharedTransformSystem? _xformSys;
|
||||
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public void Do<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] IEnumerable<T> input,
|
||||
[CommandArgument] string command)
|
||||
{
|
||||
if (ctx is not OldShellInvocationContext { } reqCtx)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
_xformSys ??= GetSys<SharedTransformSystem>();
|
||||
var xformQ = GetEntityQuery<TransformComponent>();
|
||||
var shell = reqCtx.Shell;
|
||||
foreach (var i in input)
|
||||
{
|
||||
var cmdStr = command;
|
||||
if (i is EntityUid id)
|
||||
{
|
||||
var worldPos = _xformSys.GetWorldPosition(id, xformQ);
|
||||
cmdStr = cmdStr
|
||||
.Replace("$ID", id.ToString())
|
||||
.Replace("$WX", worldPos.X.ToString(CultureInfo.InvariantCulture))
|
||||
.Replace("$WY", worldPos.Y.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
cmdStr = cmdStr.Replace("$SELF", i!.ToString() ?? "");
|
||||
shell.ExecuteCommand(cmdStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Robust.Shared/Toolshed/Commands/Entities/EntCommand.cs
Normal file
11
Robust.Shared/Toolshed/Commands/Entities/EntCommand.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Entities;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class EntCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public EntityUid Ent([CommandArgument] EntityUid ent) => ent;
|
||||
}
|
||||
|
||||
16
Robust.Shared/Toolshed/Commands/Entities/EntitiesCommand.cs
Normal file
16
Robust.Shared/Toolshed/Commands/Entities/EntitiesCommand.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Entities;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class EntitiesCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public IEnumerable<EntityUid> Entities()
|
||||
{
|
||||
// NOTE: Makes a copy due to the fact chained on commands might modify this list.
|
||||
return EntityManager.GetEntities().ToHashSet();
|
||||
}
|
||||
}
|
||||
17
Robust.Shared/Toolshed/Commands/Entities/NamedCommand.cs
Normal file
17
Robust.Shared/Toolshed/Commands/Entities/NamedCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Entities;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class NamedCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public IEnumerable<EntityUid> Named([PipedArgument] IEnumerable<EntityUid> input, [CommandArgument] string regex, [CommandInverted] bool inverted)
|
||||
{
|
||||
var compiled = new Regex($"${regex}^");
|
||||
return input.Where(x => compiled.IsMatch(EntName(x)) ^ inverted);
|
||||
}
|
||||
}
|
||||
18
Robust.Shared/Toolshed/Commands/Entities/NearbyCommand.cs
Normal file
18
Robust.Shared/Toolshed/Commands/Entities/NearbyCommand.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Entities;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class NearbyCommand : ToolshedCommand
|
||||
{
|
||||
private EntityLookupSystem? _lookup;
|
||||
|
||||
[CommandImplementation]
|
||||
public IEnumerable<EntityUid> Nearby([PipedArgument] IEnumerable<EntityUid> input, [CommandArgument] float range)
|
||||
{
|
||||
_lookup ??= GetSys<EntityLookupSystem>();
|
||||
return input.SelectMany(x => _lookup.GetEntitiesInRange(x, range)).Distinct();
|
||||
}
|
||||
}
|
||||
15
Robust.Shared/Toolshed/Commands/Entities/PausedCommand.cs
Normal file
15
Robust.Shared/Toolshed/Commands/Entities/PausedCommand.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Entities;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class PausedCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public IEnumerable<EntityUid> Paused([PipedArgument] IEnumerable<EntityUid> entities, [CommandInverted] bool inverted)
|
||||
{
|
||||
return entities.Where(x => Comp<MetaDataComponent>(x).EntityPaused ^ inverted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Entities;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class PrototypedCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation()]
|
||||
public IEnumerable<EntityUid> Prototyped([PipedArgument] IEnumerable<EntityUid> input,
|
||||
[CommandArgument] string prototype)
|
||||
=> input.Where(x => MetaData(x).EntityPrototype?.ID == prototype);
|
||||
|
||||
}
|
||||
16
Robust.Shared/Toolshed/Commands/Entities/WithCommand.cs
Normal file
16
Robust.Shared/Toolshed/Commands/Entities/WithCommand.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Toolshed.TypeParsers;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Entities;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class WithCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public IEnumerable<EntityUid> With([PipedArgument] IEnumerable<EntityUid> input, [CommandArgument] ComponentType ty, [CommandInverted] bool inverted)
|
||||
{
|
||||
return input.Where(x => EntityManager.HasComponent(x, ty.Ty) ^ inverted);
|
||||
}
|
||||
}
|
||||
11
Robust.Shared/Toolshed/Commands/Generic/AnyCommand.cs
Normal file
11
Robust.Shared/Toolshed/Commands/Generic/AnyCommand.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class AnyCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public bool Any<T>([PipedArgument] IEnumerable<T> input) => input.Any();
|
||||
}
|
||||
32
Robust.Shared/Toolshed/Commands/Generic/ArrowCommand.cs
Normal file
32
Robust.Shared/Toolshed/Commands/Generic/ArrowCommand.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic ;
|
||||
|
||||
[ToolshedCommand(Name = "=>")]
|
||||
internal sealed class ArrowCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T Arrow<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T input,
|
||||
[CommandArgument] ValueRef<T> @ref
|
||||
)
|
||||
{
|
||||
@ref.Set(ctx, input);
|
||||
return input;
|
||||
}
|
||||
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public List<T> Arrow<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] IEnumerable<T> input,
|
||||
[CommandArgument] ValueRef<List<T>> @ref
|
||||
)
|
||||
{
|
||||
var list = input.ToList();
|
||||
@ref.Set(ctx, list);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
13
Robust.Shared/Toolshed/Commands/Generic/AsCommand.cs
Normal file
13
Robust.Shared/Toolshed/Commands/Generic/AsCommand.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class AsCommand : ToolshedCommand
|
||||
{
|
||||
public override Type[] TypeParameterParsers => new[] {typeof(Type)};
|
||||
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public TOut? As<TOut, TIn>([PipedArgument] TIn value)
|
||||
=> (TOut?)(object?)value;
|
||||
}
|
||||
14
Robust.Shared/Toolshed/Commands/Generic/CountCommand.cs
Normal file
14
Robust.Shared/Toolshed/Commands/Generic/CountCommand.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class CountCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public int Count<T>([PipedArgument] IEnumerable<T> enumerable)
|
||||
{
|
||||
return enumerable.Count();
|
||||
}
|
||||
}
|
||||
157
Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs
Normal file
157
Robust.Shared/Toolshed/Commands/Generic/EmplaceCommand.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand, MapLikeCommand(false)]
|
||||
internal sealed class EmplaceCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
IEnumerable<TOut> Emplace<TIn, TOut>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] IEnumerable<TIn> value,
|
||||
[CommandArgument] Block<TOut> block
|
||||
)
|
||||
{
|
||||
|
||||
foreach (var v in value)
|
||||
{
|
||||
var emplaceCtx = new EmplaceContext<TIn>(ctx, v, EntityManager);
|
||||
yield return block.Invoke(null, emplaceCtx)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal record struct EmplaceContext<T>(IInvocationContext Inner, T Value, IEntityManager EntityManager) : IInvocationContext
|
||||
{
|
||||
public bool CheckInvokable(CommandSpec command, out IConError? error)
|
||||
{
|
||||
return Inner.CheckInvokable(command, out error);
|
||||
}
|
||||
|
||||
public ICommonSession? Session => Inner.Session;
|
||||
|
||||
public void WriteLine(string line)
|
||||
{
|
||||
Inner.WriteLine(line);
|
||||
}
|
||||
|
||||
public void ReportError(IConError err)
|
||||
{
|
||||
Inner.ReportError(err);
|
||||
}
|
||||
|
||||
public IEnumerable<IConError> GetErrors()
|
||||
{
|
||||
return Inner.GetErrors();
|
||||
}
|
||||
|
||||
public void ClearErrors()
|
||||
{
|
||||
Inner.ClearErrors();
|
||||
}
|
||||
|
||||
public Dictionary<string, object?> Variables => default!; // we never use this.
|
||||
|
||||
public IEnumerable<string> GetVars()
|
||||
{
|
||||
// note: this lies.
|
||||
return Inner.GetVars();
|
||||
}
|
||||
|
||||
public object? ReadVar(string name)
|
||||
{
|
||||
if (name == "value")
|
||||
return Value;
|
||||
|
||||
if (Value is IEmplaceBreakout breakout)
|
||||
{
|
||||
if (breakout.TryReadVar(name, out var value))
|
||||
return value;
|
||||
}
|
||||
|
||||
// TODO: Emplace behavior should be generalized and not hardcoded.
|
||||
if (Value is EntityUid id)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "wy":
|
||||
case "wx":
|
||||
{
|
||||
var xform = EntityManager.GetComponent<TransformComponent>(id);
|
||||
var sys = EntityManager.System<SharedTransformSystem>();
|
||||
var coords = sys.GetWorldPosition(xform);
|
||||
if (name == "wx")
|
||||
return coords.X;
|
||||
else
|
||||
return coords.Y;
|
||||
}
|
||||
case "proto":
|
||||
case "desc":
|
||||
case "name":
|
||||
case "paused":
|
||||
{
|
||||
var meta = EntityManager.GetComponent<MetaDataComponent>(id);
|
||||
switch (name)
|
||||
{
|
||||
case "proto":
|
||||
return meta.EntityPrototype?.ID ?? "";
|
||||
case "desc":
|
||||
return meta.EntityDescription;
|
||||
case "name":
|
||||
return meta.EntityName;
|
||||
case "paused":
|
||||
return meta.EntityPaused;
|
||||
}
|
||||
|
||||
throw new UnreachableException();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Value is ICommonSession session)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "ent":
|
||||
{
|
||||
return session.AttachedEntity!;
|
||||
}
|
||||
case "name":
|
||||
{
|
||||
return session.Name;
|
||||
}
|
||||
case "userid":
|
||||
{
|
||||
return session.UserId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Inner.ReadVar(name);
|
||||
}
|
||||
|
||||
public void WriteVar(string name, object? value)
|
||||
{
|
||||
if (name == "value")
|
||||
return;
|
||||
|
||||
if (Value is IEmplaceBreakout v)
|
||||
{
|
||||
if (v.VarsOverriden.Contains(name))
|
||||
return;
|
||||
}
|
||||
|
||||
Inner.WriteVar(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IEmplaceBreakout
|
||||
{
|
||||
public ImmutableHashSet<string> VarsOverriden { get; }
|
||||
public bool TryReadVar(string name, out object? value);
|
||||
}
|
||||
11
Robust.Shared/Toolshed/Commands/Generic/FirstCommand.cs
Normal file
11
Robust.Shared/Toolshed/Commands/Generic/FirstCommand.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class FirstCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T First<T>([PipedArgument] IEnumerable<T> input) => input.First();
|
||||
}
|
||||
22
Robust.Shared/Toolshed/Commands/Generic/IsEmptyCommand.cs
Normal file
22
Robust.Shared/Toolshed/Commands/Generic/IsEmptyCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class IsEmptyCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public bool IsEmpty<T>([PipedArgument] T? input, [CommandInverted] bool inverted)
|
||||
{
|
||||
if (input is null)
|
||||
return true ^ inverted; // Null is empty for all we care.
|
||||
|
||||
if (input is IEnumerable @enum)
|
||||
{
|
||||
return !@enum.Cast<object?>().Any() ^ inverted;
|
||||
}
|
||||
|
||||
return false ^ inverted; // Not a collection, cannot be empty.
|
||||
}
|
||||
}
|
||||
8
Robust.Shared/Toolshed/Commands/Generic/IsNullCommand.cs
Normal file
8
Robust.Shared/Toolshed/Commands/Generic/IsNullCommand.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class IsNullCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public bool IsNull([PipedArgument] object? input, [CommandInverted] bool inverted) => input is null ^ inverted;
|
||||
}
|
||||
22
Robust.Shared/Toolshed/Commands/Generic/MapCommand.cs
Normal file
22
Robust.Shared/Toolshed/Commands/Generic/MapCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand, MapLikeCommand]
|
||||
internal sealed class MapCommand : ToolshedCommand
|
||||
{
|
||||
public override Type[] TypeParameterParsers => new[] {typeof(Type)};
|
||||
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public IEnumerable<TOut>? Map<TOut, TIn>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] IEnumerable<TIn> value,
|
||||
[CommandArgument] Block<TIn, TOut> block
|
||||
)
|
||||
{
|
||||
return value.Select(x => block.Invoke(x, ctx)).Where(x => x != null).Cast<TOut>();
|
||||
}
|
||||
}
|
||||
38
Robust.Shared/Toolshed/Commands/Generic/SelectCommand.cs
Normal file
38
Robust.Shared/Toolshed/Commands/Generic/SelectCommand.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Toolshed.TypeParsers;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class SelectCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public IEnumerable<TR> Select<TR>([PipedArgument] IEnumerable<TR> enumerable, [CommandArgument] Quantity quantity, [CommandInverted] bool inverted)
|
||||
{
|
||||
var arr = enumerable.ToArray();
|
||||
_random.Shuffle(arr);
|
||||
|
||||
if (quantity is {Amount: { } amount})
|
||||
{
|
||||
|
||||
var taken = (int) System.Math.Ceiling(amount);
|
||||
if (inverted)
|
||||
taken = System.Math.Max(0, arr.Length - taken);
|
||||
|
||||
return arr.Take(taken);
|
||||
}
|
||||
else
|
||||
{
|
||||
var percent = inverted
|
||||
? (int) System.Math.Floor(arr.Length * System.Math.Clamp(1 - (double) quantity.Percentage!, 0, 1))
|
||||
: (int) System.Math.Floor(arr.Length * System.Math.Clamp((double) quantity.Percentage!, 0, 1));
|
||||
|
||||
return arr.Take(percent);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Robust.Shared/Toolshed/Commands/Generic/SplatCommand.cs
Normal file
27
Robust.Shared/Toolshed/Commands/Generic/SplatCommand.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class SplatCommand : ToolshedCommand
|
||||
{
|
||||
public override Type[] TypeParameterParsers => new[] {typeof(Type)};
|
||||
|
||||
[CommandImplementation]
|
||||
public IEnumerable<T> Splat<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[CommandArgument] ValueRef<T> value,
|
||||
[CommandArgument] ValueRef<int> amountValue)
|
||||
{
|
||||
var amount = amountValue.Evaluate(ctx);
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
yield return value.Evaluate(ctx)!;
|
||||
if (ctx.GetErrors().Any())
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Robust.Shared/Toolshed/Commands/Generic/UniqueCommand.cs
Normal file
12
Robust.Shared/Toolshed/Commands/Generic/UniqueCommand.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class UniqueCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public IEnumerable<T> Unique<T>([PipedArgument] IEnumerable<T> input)
|
||||
=> input.Distinct();
|
||||
}
|
||||
16
Robust.Shared/Toolshed/Commands/Generic/ValCommand.cs
Normal file
16
Robust.Shared/Toolshed/Commands/Generic/ValCommand.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class ValCommand : ToolshedCommand
|
||||
{
|
||||
public override Type[] TypeParameterParsers => new[] {typeof(Type)};
|
||||
|
||||
[CommandImplementation]
|
||||
public T Val<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[CommandArgument] ValueRef<T> value
|
||||
) => value.Evaluate(ctx)!;
|
||||
}
|
||||
28
Robust.Shared/Toolshed/Commands/Generic/WhereCommand.cs
Normal file
28
Robust.Shared/Toolshed/Commands/Generic/WhereCommand.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Generic;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class WhereCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public IEnumerable<T> Where<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] IEnumerable<T> input,
|
||||
[CommandArgument] Block<T, bool> check
|
||||
)
|
||||
{
|
||||
foreach (var i in input)
|
||||
{
|
||||
var res = check.Invoke(i, ctx);
|
||||
|
||||
if (ctx.GetErrors().Any())
|
||||
yield break;
|
||||
|
||||
if (res)
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
166
Robust.Shared/Toolshed/Commands/Math/ArithmeticCommands.cs
Normal file
166
Robust.Shared/Toolshed/Commands/Math/ArithmeticCommands.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Math;
|
||||
|
||||
[ToolshedCommand(Name = "+")]
|
||||
public sealed class AddCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T Operation<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IAdditionOperators<T, T, T>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx);
|
||||
if (yVal is null)
|
||||
return x;
|
||||
return x + yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand(Name = "-")]
|
||||
public sealed class SubtractCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T Operation<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : ISubtractionOperators<T, T, T>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx);
|
||||
if (yVal is null)
|
||||
return x;
|
||||
return x - yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand(Name = "*")]
|
||||
public sealed class MultiplyCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T Operation<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IMultiplyOperators<T, T, T>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx)!;
|
||||
return x * yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand(Name = "/")]
|
||||
public sealed class DivideCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T Operation<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IDivisionOperators<T, T, T>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx)!;
|
||||
return x / yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand]
|
||||
public sealed class MinCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T Operation<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IComparisonOperators<T, T, bool>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx)!;
|
||||
return x > yVal ? yVal : x;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand]
|
||||
public sealed class MaxCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T Operation<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IComparisonOperators<T, T, bool>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx)!;
|
||||
return x > yVal ? x : yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand(Name = "&")]
|
||||
public sealed class BitAndCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T Operation<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IBitwiseOperators<T, T, T>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx)!;
|
||||
return x & yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand(Name = "|")]
|
||||
public sealed class BitOrCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T Operation<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IBitwiseOperators<T, T, T>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx)!;
|
||||
return x | yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand(Name = "^")]
|
||||
public sealed class BitXorCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T Operation<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IBitwiseOperators<T, T, T>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx)!;
|
||||
return x ^ yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand]
|
||||
public sealed class NegCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public T Operation<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x
|
||||
)
|
||||
where T : IUnaryNegationOperators<T, T>
|
||||
{
|
||||
return -x;
|
||||
}
|
||||
}
|
||||
113
Robust.Shared/Toolshed/Commands/Math/ComparisonCommands.cs
Normal file
113
Robust.Shared/Toolshed/Commands/Math/ComparisonCommands.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Math;
|
||||
|
||||
[ToolshedCommand(Name = ">")]
|
||||
public sealed class GreaterThanCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public bool Comparison<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : INumber<T>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx);
|
||||
if (yVal is null)
|
||||
return false;
|
||||
return x > yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand(Name = "<")]
|
||||
public sealed class LessThanCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public bool Comparison<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IComparisonOperators<T, T, bool>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx);
|
||||
if (yVal is null)
|
||||
return false;
|
||||
return x > yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand(Name = ">=")]
|
||||
public sealed class GreaterThanOrEqualCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public bool Comparison<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : INumber<T>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx);
|
||||
if (yVal is null)
|
||||
return false;
|
||||
return x >= yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand(Name = "<=")]
|
||||
public sealed class LessThanOrEqualCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public bool Comparison<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IComparisonOperators<T, T, bool>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx);
|
||||
if (yVal is null)
|
||||
return false;
|
||||
return x <= yVal;
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand(Name = "==")]
|
||||
public sealed class EqualCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public bool Comparison<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx);
|
||||
if (yVal is null)
|
||||
return false;
|
||||
return x.Equals(yVal);
|
||||
}
|
||||
}
|
||||
|
||||
[ToolshedCommand(Name = "!=")]
|
||||
public sealed class NotEqualCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public bool Comparison<T>(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[PipedArgument] T x,
|
||||
[CommandArgument] ValueRef<T> y
|
||||
)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
var yVal = y.Evaluate(ctx);
|
||||
if (yVal is null)
|
||||
return false;
|
||||
return !x.Equals(yVal);
|
||||
}
|
||||
}
|
||||
27
Robust.Shared/Toolshed/Commands/Misc/BuildInfoCommand.cs
Normal file
27
Robust.Shared/Toolshed/Commands/Misc/BuildInfoCommand.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Misc;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class BuildInfoCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private static readonly string Gold = Color.Gold.ToHex();
|
||||
|
||||
[CommandImplementation]
|
||||
public void BuildInfo([CommandInvocationContext] IInvocationContext ctx)
|
||||
{
|
||||
var game = _cfg.GetCVar(CVars.BuildForkId);
|
||||
ctx.WriteLine(FormattedMessage.FromMarkup($"[color={Gold}]Game:[/color] {game}"));
|
||||
var buildCommit = _cfg.GetCVar(CVars.BuildHash);
|
||||
ctx.WriteLine(FormattedMessage.FromMarkup($"[color={Gold}]Build commit:[/color] {buildCommit}"));
|
||||
var buildManifest = _cfg.GetCVar(CVars.BuildManifestHash);
|
||||
ctx.WriteLine(FormattedMessage.FromMarkup($"[color={Gold}]Manifest hash:[/color] {buildManifest}"));
|
||||
var engine = _cfg.GetCVar(CVars.BuildEngineVersion);
|
||||
ctx.WriteLine(FormattedMessage.FromMarkup($"[color={Gold}]Engine ver:[/color] {engine}"));
|
||||
}
|
||||
}
|
||||
32
Robust.Shared/Toolshed/Commands/Misc/CmdCommand.cs
Normal file
32
Robust.Shared/Toolshed/Commands/Misc/CmdCommand.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Misc;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class CmdCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation("list")]
|
||||
public IEnumerable<CommandSpec> List()
|
||||
=> Toolshed.AllCommands();
|
||||
|
||||
[CommandImplementation("moo")]
|
||||
public string Moo()
|
||||
=> "Have you mooed today?";
|
||||
|
||||
[CommandImplementation("descloc")]
|
||||
public string GetLogStr([PipedArgument] CommandSpec cmd) => cmd.DescLocStr();
|
||||
|
||||
#if CLIENT_SCRIPTING
|
||||
[CommandImplementation("getshim")]
|
||||
public MethodInfo GetShim([CommandArgument] Block block)
|
||||
{
|
||||
|
||||
// this is gross sue me
|
||||
var invocable = block.CommandRun.Commands.Last().Item1.Invocable;
|
||||
return invocable.GetMethodInfo();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
21
Robust.Shared/Toolshed/Commands/Misc/ExplainCommand.cs
Normal file
21
Robust.Shared/Toolshed/Commands/Misc/ExplainCommand.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Misc;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class ExplainCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public void Explain(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[CommandArgument] CommandRun expr
|
||||
)
|
||||
{
|
||||
foreach (var (cmd, _) in expr.Commands)
|
||||
{
|
||||
ctx.WriteLine(cmd.Command.GetHelp(cmd.SubCommand));
|
||||
ctx.WriteLine($"{cmd.PipedType?.PrettyName() ?? "[none]"} -> {cmd.ReturnType?.PrettyName() ?? "[none]"}");
|
||||
ctx.WriteLine("");
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Robust.Shared/Toolshed/Commands/Misc/HelpCommand.cs
Normal file
28
Robust.Shared/Toolshed/Commands/Misc/HelpCommand.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Misc;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class HelpCommand : ToolshedCommand
|
||||
{
|
||||
private static readonly string Gold = Color.Gold.ToHex();
|
||||
private static readonly string Aqua = Color.Aqua.ToHex();
|
||||
|
||||
[CommandImplementation]
|
||||
public void Help([CommandInvocationContext] IInvocationContext ctx)
|
||||
{
|
||||
ctx.WriteLine($@"
|
||||
TOOLSHED
|
||||
/.\\\\\\\\
|
||||
/___\\\\\\\\
|
||||
|''''|'''''|
|
||||
| 8 | === |
|
||||
|_0__|_____|");
|
||||
ctx.WriteMarkup($@"
|
||||
For a list of commands, run [color={Gold}]cmd:list[/color].
|
||||
To search for commands, run [color={Gold}]cmd:list search ""[color={Aqua}]query[/color]""[/color].
|
||||
For a breakdown of how a string of commands flows, run [color={Gold}]explain [color={Aqua}]commands here[/color][/color].
|
||||
For help with old console commands, run [color={Gold}]oldhelp[/color].
|
||||
");
|
||||
}
|
||||
}
|
||||
15
Robust.Shared/Toolshed/Commands/Misc/IoCCommand.cs
Normal file
15
Robust.Shared/Toolshed/Commands/Misc/IoCCommand.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Misc;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class IoCCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation("registered")]
|
||||
public IEnumerable<Type> Registered() => IoCManager.Instance!.GetRegisteredTypes();
|
||||
|
||||
[CommandImplementation("get")]
|
||||
public object? Get([PipedArgument] Type t) => IoCManager.ResolveType(t);
|
||||
}
|
||||
17
Robust.Shared/Toolshed/Commands/Misc/LocCommand.cs
Normal file
17
Robust.Shared/Toolshed/Commands/Misc/LocCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Misc;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class LocCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation("tryloc")]
|
||||
public string? TryLocalize([PipedArgument] string str)
|
||||
{
|
||||
Loc.TryGetString(str, out var loc);
|
||||
return loc;
|
||||
}
|
||||
|
||||
[CommandImplementation("loc")]
|
||||
public string Localize([PipedArgument] string str) => Loc.GetString(str);
|
||||
}
|
||||
48
Robust.Shared/Toolshed/Commands/Misc/PhysicsCommand.cs
Normal file
48
Robust.Shared/Toolshed/Commands/Misc/PhysicsCommand.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Misc;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class PhysicsCommand : ToolshedCommand
|
||||
{
|
||||
private SharedTransformSystem? _xform = default!;
|
||||
|
||||
[CommandImplementation("velocity")]
|
||||
public IEnumerable<float> Velocity([PipedArgument] IEnumerable<EntityUid> input)
|
||||
{
|
||||
var physQuery = GetEntityQuery<PhysicsComponent>();
|
||||
|
||||
foreach (var ent in input)
|
||||
{
|
||||
if (!physQuery.TryGetComponent(ent, out var comp))
|
||||
continue;
|
||||
|
||||
yield return comp.LinearVelocity.Length();
|
||||
}
|
||||
}
|
||||
|
||||
[CommandImplementation("parent")]
|
||||
public IEnumerable<EntityUid> Parent([PipedArgument] IEnumerable<EntityUid> input)
|
||||
{
|
||||
_xform ??= GetSys<SharedTransformSystem>();
|
||||
return input.Select(x => Comp<TransformComponent>(x).ParentUid);
|
||||
}
|
||||
|
||||
[CommandImplementation("angular_velocity")]
|
||||
public IEnumerable<float> AngularVelocity([PipedArgument] IEnumerable<EntityUid> input)
|
||||
{
|
||||
var physQuery = GetEntityQuery<PhysicsComponent>();
|
||||
|
||||
foreach (var ent in input)
|
||||
{
|
||||
if (!physQuery.TryGetComponent(ent, out var comp))
|
||||
continue;
|
||||
|
||||
yield return comp.AngularVelocity;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Robust.Shared/Toolshed/Commands/Misc/SearchCommand.cs
Normal file
24
Robust.Shared/Toolshed/Commands/Misc/SearchCommand.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Misc;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class SearchCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation, TakesPipedTypeAsGeneric]
|
||||
public IEnumerable<FormattedMessage> Search<T>([PipedArgument] IEnumerable<T> input, [CommandArgument] string term)
|
||||
{
|
||||
var list = input.Select(x => Toolshed.PrettyPrintType(x)).ToList();
|
||||
return list.Where(x => x.Contains(term, StringComparison.InvariantCultureIgnoreCase)).Select(x =>
|
||||
{
|
||||
var startIdx = x.IndexOf(term, StringComparison.InvariantCultureIgnoreCase);
|
||||
return ConHelpers.HighlightSpan(x, (startIdx, startIdx + term.Length), Color.Aqua);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
19
Robust.Shared/Toolshed/Commands/Misc/StopwatchCommand.cs
Normal file
19
Robust.Shared/Toolshed/Commands/Misc/StopwatchCommand.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Misc;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class StopwatchCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public object? Stopwatch([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] CommandRun expr)
|
||||
{
|
||||
var watch = new Stopwatch();
|
||||
watch.Start();
|
||||
var result = expr.Invoke(null, ctx);
|
||||
ctx.WriteMarkup($"Ran expression in [color={Color.Aqua.ToHex()}]{watch.Elapsed:g}[/color]");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
43
Robust.Shared/Toolshed/Commands/Misc/TypesCommand.cs
Normal file
43
Robust.Shared/Toolshed/Commands/Misc/TypesCommand.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Misc;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class TypesCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation("consumers")]
|
||||
public void Consumers([CommandInvocationContext] IInvocationContext ctx, [PipedArgument] object? input)
|
||||
{
|
||||
var t = input is Type ? (Type)input : input!.GetType();
|
||||
|
||||
ctx.WriteLine($"Valid intakers for {t.PrettyName()}:");
|
||||
|
||||
foreach (var (command, subCommand) in Toolshed.CommandsTakingType(t))
|
||||
{
|
||||
if (subCommand is null)
|
||||
ctx.WriteLine($"{command.Name}");
|
||||
else
|
||||
ctx.WriteLine($"{command.Name}:{subCommand}");
|
||||
}
|
||||
}
|
||||
|
||||
[CommandImplementation("tree")]
|
||||
public IEnumerable<Type> Tree([CommandInvocationContext] IInvocationContext ctx, [PipedArgument] object? input)
|
||||
{
|
||||
var t = input is Type ? (Type)input : input!.GetType();
|
||||
return Toolshed.AllSteppedTypes(t);
|
||||
}
|
||||
|
||||
[CommandImplementation("gettype")]
|
||||
public Type GetType([PipedArgument] object? input)
|
||||
{
|
||||
return input?.GetType() ?? typeof(void);
|
||||
}
|
||||
|
||||
[CommandImplementation("fullname")]
|
||||
public string FullName([PipedArgument] Type input)
|
||||
{
|
||||
return input.FullName!;
|
||||
}
|
||||
}
|
||||
13
Robust.Shared/Toolshed/Commands/Misc/VarsCommand.cs
Normal file
13
Robust.Shared/Toolshed/Commands/Misc/VarsCommand.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Misc;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class VarsCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public void Vars([CommandInvocationContext] IInvocationContext ctx)
|
||||
{
|
||||
ctx.WriteLine(Toolshed.PrettyPrintType(ctx.GetVars().Select(x => $"{x} = {ctx.ReadVar(x)}")));
|
||||
}
|
||||
}
|
||||
25
Robust.Shared/Toolshed/Commands/Players/SelfCommand.cs
Normal file
25
Robust.Shared/Toolshed/Commands/Players/SelfCommand.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Players;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class SelfCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public EntityUid Self([CommandInvocationContext] IInvocationContext ctx)
|
||||
{
|
||||
if (ctx.Session is null)
|
||||
{
|
||||
ctx.ReportError(new NotForServerConsoleError());
|
||||
return default!;
|
||||
}
|
||||
|
||||
if (ctx.Session.AttachedEntity is { } ent)
|
||||
return ent;
|
||||
|
||||
ctx.ReportError(new SessionHasNoEntityError(ctx.Session));
|
||||
return default!;
|
||||
}
|
||||
}
|
||||
|
||||
49
Robust.Shared/Toolshed/Commands/Types/MethodsCommand.cs
Normal file
49
Robust.Shared/Toolshed/Commands/Types/MethodsCommand.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Types;
|
||||
|
||||
#if CLIENT_SCRIPTING
|
||||
[ToolshedCommand]
|
||||
internal sealed class MethodsCommand : ToolshedCommand
|
||||
{
|
||||
[CommandImplementation("get")]
|
||||
public IEnumerable<MethodInfo> Get([PipedArgument] IEnumerable<Type> types)
|
||||
{
|
||||
foreach (var ty in types)
|
||||
{
|
||||
foreach (var method in ty.GetMethods())
|
||||
{
|
||||
yield return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[CommandImplementation("overrides")]
|
||||
public IEnumerable<MethodInfo> Overrides([PipedArgument] IEnumerable<Type> types)
|
||||
{
|
||||
foreach (var ty in types)
|
||||
{
|
||||
foreach (var method in ty.GetMethods())
|
||||
{
|
||||
if (method.DeclaringType != ty)
|
||||
yield return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[CommandImplementation("overridesfrom")]
|
||||
public IEnumerable<MethodInfo> OverridesFrom([PipedArgument] IEnumerable<Type> types, [CommandArgument] Type t)
|
||||
{
|
||||
foreach (var ty in types)
|
||||
{
|
||||
foreach (var method in ty.GetMethods())
|
||||
{
|
||||
if (method.DeclaringType == t)
|
||||
yield return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
27
Robust.Shared/Toolshed/Commands/Vfs/CdCommand.cs
Normal file
27
Robust.Shared/Toolshed/Commands/Vfs/CdCommand.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Vfs;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class CdCommand : VfsCommand
|
||||
{
|
||||
[CommandImplementation]
|
||||
public void Cd(
|
||||
[CommandInvocationContext] IInvocationContext ctx,
|
||||
[CommandArgument] ResPath path
|
||||
)
|
||||
{
|
||||
var curPath = CurrentPath(ctx);
|
||||
|
||||
if (path.IsRooted)
|
||||
{
|
||||
curPath = path;
|
||||
}
|
||||
else
|
||||
{
|
||||
curPath /= path;
|
||||
}
|
||||
|
||||
SetPath(ctx, curPath);
|
||||
}
|
||||
}
|
||||
31
Robust.Shared/Toolshed/Commands/Vfs/LsCommand.cs
Normal file
31
Robust.Shared/Toolshed/Commands/Vfs/LsCommand.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Vfs;
|
||||
|
||||
[ToolshedCommand]
|
||||
internal sealed class LsCommand : VfsCommand
|
||||
{
|
||||
[CommandImplementation("here")]
|
||||
public IEnumerable<ResPath> LsHere([CommandInvocationContext] IInvocationContext ctx)
|
||||
{
|
||||
var curPath = CurrentPath(ctx);
|
||||
return Resources.ContentGetDirectoryEntries(curPath).Select(x => curPath/x);
|
||||
}
|
||||
|
||||
[CommandImplementation("in")]
|
||||
public IEnumerable<ResPath> LsIn([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] ResPath @in)
|
||||
{
|
||||
var curPath = CurrentPath(ctx);
|
||||
if (@in.IsRooted)
|
||||
{
|
||||
curPath = @in;
|
||||
}
|
||||
else
|
||||
{
|
||||
curPath /= @in;
|
||||
}
|
||||
return Resources.ContentGetDirectoryEntries(curPath).Select(x => curPath/x);
|
||||
}
|
||||
}
|
||||
29
Robust.Shared/Toolshed/Commands/Vfs/VfsCommand.cs
Normal file
29
Robust.Shared/Toolshed/Commands/Vfs/VfsCommand.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Commands.Vfs;
|
||||
|
||||
/// <summary>
|
||||
/// A simple base class for commands that work with the VFS and would like to manipulate the user's current location within the VFS.
|
||||
/// </summary>
|
||||
/// <seealso cref="UserVfsLocVariableName"/>
|
||||
[PublicAPI]
|
||||
public abstract class VfsCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] protected readonly IResourceManager Resources = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the variable storing a <see cref="ResPath">ResPath?</see> representing the user's current VFS location.
|
||||
/// </summary>
|
||||
public const string UserVfsLocVariableName = "user_vfs_loc";
|
||||
|
||||
protected ResPath CurrentPath(IInvocationContext ctx) => ((ResPath?) ctx.ReadVar(UserVfsLocVariableName)) ?? ResPath.Root;
|
||||
|
||||
protected void SetPath(IInvocationContext ctx, ResPath path)
|
||||
{
|
||||
ctx.WriteVar(UserVfsLocVariableName, (ResPath?) path.Clean());
|
||||
}
|
||||
}
|
||||
123
Robust.Shared/Toolshed/Errors/IConError.cs
Normal file
123
Robust.Shared/Toolshed/Errors/IConError.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Errors;
|
||||
|
||||
/// <summary>
|
||||
/// A Toolshed-oriented representation of an error.
|
||||
/// Contains metadata about where in an executed command it occurred, and supports formatting.
|
||||
/// <code>
|
||||
/// > entities runverbas self "yeet"
|
||||
/// entities runverbas self "yeet"
|
||||
/// ^^^^^
|
||||
/// You must be logged in with a client to use this, the server console isn't workable.
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public interface IConError
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a user friendly description of the error.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This calls <see cref="M:Robust.Shared.Toolshed.Errors.IConError.DescribeInner"/> for the actual description by default.
|
||||
/// If you fully override this, you should provide your own context provider, as the default implementation includes where in the expression the error occurred.
|
||||
/// </remarks>
|
||||
public FormattedMessage Describe()
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
if (Expression is { } expr && IssueSpan is { } span)
|
||||
{
|
||||
msg.AddMessage(ConHelpers.HighlightSpan(expr, span, Color.Red));
|
||||
msg.PushNewline();
|
||||
msg.AddMessage(ConHelpers.ArrowSpan(span));
|
||||
msg.PushNewline();
|
||||
}
|
||||
msg.AddMessage(DescribeInner());
|
||||
#if TOOLS
|
||||
if (Trace is not null)
|
||||
{
|
||||
msg.PushNewline();
|
||||
msg.AddText(Trace.ToString());
|
||||
}
|
||||
#endif
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the error, called by <see cref="M:Robust.Shared.Toolshed.Errors.IConError.Describe"/>'s default implementation.
|
||||
/// </summary>
|
||||
protected FormattedMessage DescribeInner();
|
||||
/// <summary>
|
||||
/// The expression this error was raised in or on.
|
||||
/// </summary>
|
||||
public string? Expression { get; protected set; }
|
||||
/// <summary>
|
||||
/// Where in the expression this error was raised.
|
||||
/// </summary>
|
||||
public Vector2i? IssueSpan { get; protected set; }
|
||||
/// <summary>
|
||||
/// The stack trace for this error if any.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is not present in release builds.
|
||||
/// </remarks>
|
||||
public StackTrace? Trace { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Attaches additional context to an error, namely where it occurred.
|
||||
/// </summary>
|
||||
/// <param name="expression">Expression the error occured in or on.</param>
|
||||
/// <param name="issueSpan">Where in the expression it occurred.</param>
|
||||
public void Contextualize(string expression, Vector2i issueSpan)
|
||||
{
|
||||
if (Expression is not null && IssueSpan is not null)
|
||||
return;
|
||||
|
||||
#if TOOLS
|
||||
Trace = new StackTrace(skipFrames: 1);
|
||||
#endif
|
||||
|
||||
Expression = expression;
|
||||
IssueSpan = issueSpan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pile of helpers for console formatting.
|
||||
/// </summary>
|
||||
public static class ConHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Highlights a section of the input a given color.
|
||||
/// </summary>
|
||||
/// <param name="input">Input text.</param>
|
||||
/// <param name="span">Span to highlight.</param>
|
||||
/// <param name="color">Color to use.</param>
|
||||
/// <returns>A formatted message with highlighting applied.</returns>
|
||||
public static FormattedMessage HighlightSpan(string input, Vector2i span, Color color)
|
||||
{
|
||||
if (span.X == span.Y)
|
||||
return new FormattedMessage();
|
||||
var msg = FormattedMessage.FromMarkup(input[..span.X]);
|
||||
msg.PushColor(color);
|
||||
msg.AddText(input[span.X..span.Y]);
|
||||
msg.Pop();
|
||||
msg.AddText(input[span.Y..]);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a string with up arrows (<c>^</c>) under the given span.
|
||||
/// </summary>
|
||||
/// <param name="span">Span to underline.</param>
|
||||
/// <returns>A string of whitespace with (<c>^</c>) under the given span.</returns>
|
||||
public static FormattedMessage ArrowSpan(Vector2i span)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(' ', span.X);
|
||||
builder.Append('^', span.Y - span.X);
|
||||
return FormattedMessage.FromMarkup(builder.ToString());
|
||||
}
|
||||
}
|
||||
18
Robust.Shared/Toolshed/Errors/NotForServerConsoleError.cs
Normal file
18
Robust.Shared/Toolshed/Errors/NotForServerConsoleError.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Diagnostics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Errors;
|
||||
|
||||
public sealed class NotForServerConsoleError : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromMarkup(
|
||||
"You must be logged in with a client to use this, the server console isn't workable.");
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
18
Robust.Shared/Toolshed/Errors/SessionHasNoEntityError.cs
Normal file
18
Robust.Shared/Toolshed/Errors/SessionHasNoEntityError.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Diagnostics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Errors;
|
||||
|
||||
public record struct SessionHasNoEntityError(ICommonSession Session) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromMarkup($"The user {Session.Name} has no attached entity.");
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
29
Robust.Shared/Toolshed/Errors/UnhandledExceptionError.cs
Normal file
29
Robust.Shared/Toolshed/Errors/UnhandledExceptionError.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Errors;
|
||||
|
||||
public sealed class UnhandledExceptionError : IConError
|
||||
{
|
||||
[PublicAPI]
|
||||
public Exception Exception;
|
||||
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText(Exception.ToString());
|
||||
return msg;
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
|
||||
public UnhandledExceptionError(Exception exception)
|
||||
{
|
||||
Exception = exception;
|
||||
}
|
||||
}
|
||||
191
Robust.Shared/Toolshed/ForwardParser.cs
Normal file
191
Robust.Shared/Toolshed/ForwardParser.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed;
|
||||
|
||||
public sealed class ForwardParser
|
||||
{
|
||||
[Dependency] public readonly ToolshedManager Toolshed = default!;
|
||||
|
||||
public readonly string Input;
|
||||
public int MaxIndex { get; private set; }
|
||||
|
||||
public int Index { get; private set; } = 0;
|
||||
|
||||
public ForwardParser(string input, ToolshedManager toolshed)
|
||||
{
|
||||
Toolshed = toolshed;
|
||||
Input = input;
|
||||
MaxIndex = input.Length - 1;
|
||||
}
|
||||
|
||||
private ForwardParser(ForwardParser parser, int sliceSize, int? index)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
DebugTools.Assert(sliceSize > 0);
|
||||
Input = parser.Input;
|
||||
Index = index ?? parser.Index;
|
||||
MaxIndex = Math.Min(parser.MaxIndex, Index + sliceSize - 1);
|
||||
}
|
||||
|
||||
public bool SpanInRange(int length)
|
||||
{
|
||||
return MaxIndex >= (Index + length - 1);
|
||||
}
|
||||
|
||||
public bool EatMatch(char c)
|
||||
{
|
||||
if (PeekChar() == c)
|
||||
{
|
||||
Index++;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public char? PeekChar()
|
||||
{
|
||||
if (!SpanInRange(1))
|
||||
return null;
|
||||
|
||||
return Input[Index];
|
||||
}
|
||||
|
||||
public char? GetChar()
|
||||
{
|
||||
if (PeekChar() is { } c)
|
||||
{
|
||||
Index++;
|
||||
return c;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void DebugPrint()
|
||||
{
|
||||
Logger.DebugS("parser", Input);
|
||||
MakeDebugPointer(Index);
|
||||
MakeDebugPointer(MaxIndex, '|');
|
||||
}
|
||||
|
||||
private void MakeDebugPointer(int pointAt, char pointer = '^')
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(' ', pointAt);
|
||||
builder.Append(pointer);
|
||||
Logger.DebugS("parser", builder.ToString());
|
||||
}
|
||||
|
||||
private string? MaybeGetWord(bool advanceIndex, Func<char, bool>? test)
|
||||
{
|
||||
var startingIndex = Index;
|
||||
test ??= static c => c != ' ';
|
||||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
Consume(char.IsWhiteSpace);
|
||||
|
||||
// Walk forward until we run into whitespace
|
||||
while (PeekChar() is { } c && test(c))
|
||||
{
|
||||
builder.Append(GetChar());
|
||||
}
|
||||
|
||||
if (startingIndex == Index)
|
||||
return null;
|
||||
|
||||
if (!advanceIndex)
|
||||
Index = startingIndex;
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public string? PeekWord(Func<char, bool>? test = null) => MaybeGetWord(false, test);
|
||||
|
||||
public string? GetWord(Func<char, bool>? test = null) => MaybeGetWord(true, test);
|
||||
|
||||
public ParserRestorePoint Save()
|
||||
{
|
||||
return new ParserRestorePoint(Index);
|
||||
}
|
||||
|
||||
public void Restore(ParserRestorePoint point)
|
||||
{
|
||||
Index = point.Index;
|
||||
}
|
||||
|
||||
public int Consume(Func<char, bool> control)
|
||||
{
|
||||
var amount = 0;
|
||||
|
||||
while (PeekChar() is { } c && control(c))
|
||||
{
|
||||
GetChar();
|
||||
amount++;
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
public ForwardParser? SliceBlock(char startDelim, char endDelim)
|
||||
{
|
||||
var checkpoint = Save();
|
||||
|
||||
Consume(char.IsWhiteSpace);
|
||||
|
||||
if (GetChar() != startDelim)
|
||||
{
|
||||
Restore(checkpoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
var blockStart = Index;
|
||||
|
||||
var stack = 1;
|
||||
|
||||
while (stack > 0)
|
||||
{
|
||||
var c = GetChar();
|
||||
if (c == startDelim)
|
||||
stack++;
|
||||
|
||||
if (c == endDelim)
|
||||
{
|
||||
if (--stack == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == null)
|
||||
{
|
||||
Restore(checkpoint);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new ForwardParser(this, Index - blockStart, blockStart);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct ParserRestorePoint(int Index);
|
||||
|
||||
public record struct OutOfInputError : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromMarkup("Ran out of input data when data was expected.");
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
152
Robust.Shared/Toolshed/IInvocationContext.cs
Normal file
152
Robust.Shared/Toolshed/IInvocationContext.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed;
|
||||
|
||||
/// <summary>
|
||||
/// A context in which Toolshed commands can be run using <see cref="M:Robust.Shared.Toolshed.ToolshedManager.InvokeCommand(Robust.Shared.Toolshed.IInvocationContext,System.String,System.Object,System.Object@)"/>.
|
||||
/// </summary>
|
||||
public interface IInvocationContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Test if the given command spec can be invoked in this context.
|
||||
/// </summary>
|
||||
/// <param name="command">Command to test.</param>
|
||||
/// <param name="error">An error to report, if any.</param>
|
||||
/// <returns>Whether or not the given command can be invoked</returns>
|
||||
/// <remarks>
|
||||
/// THIS IS A SECURITY BOUNDARY.
|
||||
/// If you want to avoid players being able to just reboot your server, you should probably implement this!
|
||||
/// </remarks>
|
||||
public bool CheckInvokable(CommandSpec command, out IConError? error);
|
||||
|
||||
/// <summary>
|
||||
/// The session this context is for, if any.
|
||||
/// </summary>
|
||||
ICommonSession? Session { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line to this context's output.
|
||||
/// </summary>
|
||||
/// <param name="line">The text to print.</param>
|
||||
/// <remarks>
|
||||
/// This can be stubbed safely, there's no requirement that the side effects of this function be observable.
|
||||
/// </remarks>
|
||||
public void WriteLine(string line);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes a formatted message to this context's output.
|
||||
/// </summary>
|
||||
/// <param name="line">The formatted message to print.</param>
|
||||
/// <remarks>
|
||||
/// This can be stubbed safely, there's no requirement that the side effects of this function be observable.
|
||||
/// </remarks>
|
||||
public void WriteLine(FormattedMessage line)
|
||||
{
|
||||
// Cut markup for server.
|
||||
if (Session is null)
|
||||
{
|
||||
WriteLine(line.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
WriteLine(line.ToMarkup());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given markup to this context's output.
|
||||
/// </summary>
|
||||
/// <param name="markup">The markup to print.</param>
|
||||
/// <remarks>
|
||||
/// This can be stubbed safely, there's no requirement that the side effects of this function be observable.
|
||||
/// </remarks>
|
||||
public void WriteMarkup(string markup)
|
||||
{
|
||||
WriteLine(FormattedMessage.FromMarkup(markup));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given error to the context's output.
|
||||
/// </summary>
|
||||
/// <param name="error">The error to write out.</param>
|
||||
/// <remarks>
|
||||
/// This can be stubbed safely, there's no requirement that the side effects of this function be observable.
|
||||
/// </remarks>
|
||||
public void WriteError(IConError error)
|
||||
{
|
||||
WriteLine(error.Describe());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reports the given error to the context.
|
||||
/// </summary>
|
||||
/// <param name="err">Error to report.</param>
|
||||
/// <remarks>
|
||||
/// This may have arbitrary side effects. Usually, it'll push the error to some list you can retrieve with GetErrors().
|
||||
/// </remarks>
|
||||
public void ReportError(IConError err);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of unobserved errors.
|
||||
/// </summary>
|
||||
/// <returns>An enumerable of console errors.</returns>
|
||||
/// <remarks>
|
||||
/// This is not required to contain anything, may contain errors you did not ReportError(), and may not contain errors you did ReportError().
|
||||
/// </remarks>
|
||||
public IEnumerable<IConError> GetErrors();
|
||||
|
||||
/// <summary>
|
||||
/// Clears the list of unobserved errors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// After calling this, assuming atomicity (no threads), GetErrors() MUST be empty in order for an IInvocationContext to be compliant.
|
||||
/// </remarks>
|
||||
public void ClearErrors();
|
||||
|
||||
/// <summary>
|
||||
/// The backing variable storage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You don't have to use this at all.
|
||||
/// </remarks>
|
||||
protected Dictionary<string, object?> Variables { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reads the given variable from the context.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <returns>The contents of the variable.</returns>
|
||||
/// <remarks>
|
||||
/// This may behave arbitrarily, but it's advised it behave somewhat sanely.
|
||||
/// </remarks>
|
||||
public virtual object? ReadVar(string name)
|
||||
{
|
||||
Variables.TryGetValue(name, out var res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given variable to the context.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the variable.</param>
|
||||
/// <param name="value">The contents of the variable.</param>
|
||||
/// <remarks>
|
||||
/// Writes may be ignored or manipulated.
|
||||
/// </remarks>
|
||||
public virtual void WriteVar(string name, object? value)
|
||||
{
|
||||
Variables[name] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a list of all variables that have been written to at some point.
|
||||
/// </summary>
|
||||
/// <returns>List of all variables.</returns>
|
||||
public virtual IEnumerable<string> GetVars()
|
||||
{
|
||||
return Variables.Keys;
|
||||
}
|
||||
}
|
||||
10
Robust.Shared/Toolshed/IPermissionController.cs
Normal file
10
Robust.Shared/Toolshed/IPermissionController.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
|
||||
namespace Robust.Shared.Toolshed;
|
||||
|
||||
public interface IPermissionController
|
||||
{
|
||||
public bool CheckInvokable(CommandSpec command, ICommonSession? user, out IConError? error);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Invocation;
|
||||
|
||||
/// <inheritdoc />
|
||||
internal sealed class OldShellInvocationContext : IInvocationContext
|
||||
{
|
||||
[Dependency] private readonly ToolshedManager _toolshed = default!;
|
||||
private readonly List<IConError> _errors = new();
|
||||
|
||||
/// <summary>
|
||||
/// Old system's shell associated with this context
|
||||
/// </summary>
|
||||
public IConsoleShell Shell;
|
||||
|
||||
public OldShellInvocationContext(IConsoleShell shell)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
Shell = shell;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CheckInvokable(CommandSpec command, out IConError? error)
|
||||
{
|
||||
if (_toolshed.ActivePermissionController is { } controller)
|
||||
return controller.CheckInvokable(command, Session, out error);
|
||||
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICommonSession? Session => Shell.Player;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteLine(string line)
|
||||
{
|
||||
Shell.WriteLine(line);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteLine(FormattedMessage line)
|
||||
{
|
||||
Shell.WriteLine(line);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReportError(IConError err)
|
||||
{
|
||||
_errors.Add(err);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IConError> GetErrors()
|
||||
{
|
||||
return _errors;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ClearErrors()
|
||||
{
|
||||
_errors.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, object?> Variables { get; } = new();
|
||||
}
|
||||
|
||||
294
Robust.Shared/Toolshed/ReflectionExtensions.cs
Normal file
294
Robust.Shared/Toolshed/ReflectionExtensions.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Shared.Toolshed;
|
||||
|
||||
// TODO: Audit this for sandboxability and expose some of these to content.
|
||||
internal static class ReflectionExtensions
|
||||
{
|
||||
public static bool CanBeNull(this Type t)
|
||||
{
|
||||
return !t.IsValueType || t.IsGenericType(typeof(Nullable<>));
|
||||
}
|
||||
|
||||
public static bool CanBeEmpty(this Type t)
|
||||
{
|
||||
return t.CanBeNull() || t.IsGenericType(typeof(IEnumerable<>));
|
||||
}
|
||||
|
||||
public static bool IsGenericType(this Type t, Type genericType)
|
||||
{
|
||||
return t.IsGenericType && t.GetGenericTypeDefinition() == genericType;
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetVariants(this Type t, ToolshedManager toolshed)
|
||||
{
|
||||
var args = t.GetGenericArguments();
|
||||
var generic = t.GetGenericTypeDefinition();
|
||||
var genericArgs = generic.GetGenericArguments();
|
||||
var variantCount = genericArgs.Count(x => (x.GenericParameterAttributes & GenericParameterAttributes.VarianceMask) != 0);
|
||||
|
||||
if (variantCount > 1)
|
||||
{
|
||||
throw new NotImplementedException("I swear to god I am NOT supporting more than one variant type parameter. Absolutely no combinatorial explosions in this house, factorials can go home.");
|
||||
}
|
||||
|
||||
yield return t;
|
||||
|
||||
if (variantCount < 1)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var variant = 0;
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
if ((genericArgs[i].GenericParameterAttributes & GenericParameterAttributes.VarianceMask) != 0)
|
||||
{
|
||||
variant = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var newArgs = (Type[]) args.Clone();
|
||||
|
||||
foreach (var type in toolshed.AllSteppedTypes(args[variant], false))
|
||||
{
|
||||
newArgs[variant] = type;
|
||||
yield return generic.MakeGenericType(newArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<(string, List<MethodInfo>)> BySubCommand(this IEnumerable<MethodInfo> methods)
|
||||
{
|
||||
var output = new Dictionary<string, List<MethodInfo>>();
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var subCommand = method.GetCustomAttribute<CommandImplementationAttribute>()!.SubCommand ?? "";
|
||||
if (!output.TryGetValue(subCommand, out var methodList))
|
||||
{
|
||||
methodList = new();
|
||||
output[subCommand] = methodList;
|
||||
}
|
||||
methodList.Add(method);
|
||||
}
|
||||
|
||||
return output.Select(x => (x.Key, x.Value));
|
||||
}
|
||||
|
||||
public static Type StepDownConstraints(this Type t)
|
||||
{
|
||||
if (!t.IsGenericType || t.IsGenericTypeDefinition)
|
||||
return t;
|
||||
|
||||
var oldArgs = t.GenericTypeArguments;
|
||||
var newArgs = new Type[oldArgs.Length];
|
||||
|
||||
for (var i = 0; i < oldArgs.Length; i++)
|
||||
{
|
||||
if (oldArgs[i].IsGenericType)
|
||||
newArgs[i] = oldArgs[i].GetGenericTypeDefinition();
|
||||
else
|
||||
newArgs[i] = oldArgs[i];
|
||||
}
|
||||
|
||||
return t.GetGenericTypeDefinition().MakeGenericType(newArgs);
|
||||
}
|
||||
|
||||
public static string PrettyName(this Type type)
|
||||
{
|
||||
var name = type.Name;
|
||||
|
||||
if (type.IsGenericParameter)
|
||||
return type.ToString();
|
||||
|
||||
if (type.DeclaringType is not null)
|
||||
{
|
||||
name = $"{PrettyName(type.DeclaringType!)}.{type.Name}";
|
||||
}
|
||||
|
||||
if (type.GetGenericArguments().Length == 0)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
if (!name.Contains('`'))
|
||||
return name + "<>";
|
||||
|
||||
var genericArguments = type.GetGenericArguments();
|
||||
var exactName = name.Substring(0, name.IndexOf('`', StringComparison.InvariantCulture));
|
||||
return exactName + "<" + string.Join(",", genericArguments.Select(PrettyName)) + ">";
|
||||
}
|
||||
|
||||
public static ParameterInfo? ConsoleGetPipedArgument(this MethodInfo method)
|
||||
{
|
||||
var p = method.GetParameters().Where(x => x.GetCustomAttribute<PipedArgumentAttribute>() is not null).ToList();
|
||||
return p.FirstOrDefault();
|
||||
}
|
||||
|
||||
public static IEnumerable<ParameterInfo> ConsoleGetArguments(this MethodInfo method)
|
||||
{
|
||||
return method.GetParameters().Where(x => x.GetCustomAttribute<CommandArgumentAttribute>() is not null);
|
||||
}
|
||||
|
||||
public static Expression CreateEmptyExpr(this Type t)
|
||||
{
|
||||
if (!t.CanBeEmpty())
|
||||
throw new TypeArgumentException();
|
||||
|
||||
if (t.IsGenericType(typeof(IEnumerable<>)))
|
||||
{
|
||||
var array = Array.CreateInstance(t.GetGenericArguments().First(), 0);
|
||||
return Expression.Constant(array, t);
|
||||
}
|
||||
|
||||
if (t.CanBeNull())
|
||||
{
|
||||
if (Nullable.GetUnderlyingType(t) is not null)
|
||||
return Expression.Constant(t.GetConstructor(BindingFlags.CreateInstance, Array.Empty<Type>())!.Invoke(null, null), t);
|
||||
|
||||
return Expression.Constant(null, t);
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// IEnumerable<EntityUid> ^ IEnumerable<T> -> EntityUid
|
||||
public static Type Intersect(this Type left, Type right)
|
||||
{
|
||||
if (!left.IsGenericType)
|
||||
return left;
|
||||
|
||||
if (!right.IsGenericType)
|
||||
return left;
|
||||
|
||||
return left.GetGenericArguments().First();
|
||||
}
|
||||
|
||||
public static void DumpGenericInfo(this Type t)
|
||||
{
|
||||
Logger.Debug($"Info for {t.PrettyName()}");
|
||||
Logger.Debug(
|
||||
$"GP {t.IsGenericParameter} | MP {t.IsGenericMethodParameter} | TP {t.IsGenericTypeParameter} | DEF {t.IsGenericTypeDefinition} | TY {t.IsGenericType} | CON {t.IsConstructedGenericType}");
|
||||
if (t.IsGenericParameter)
|
||||
Logger.Debug($"CONSTRAINTS: {string.Join(", ", t.GetGenericParameterConstraints().Select(PrettyName))}");
|
||||
if (!t.IsGenericTypeDefinition && IsGenericRelated(t) && t.IsGenericType)
|
||||
DumpGenericInfo(t.GetGenericTypeDefinition());
|
||||
foreach (var p in t.GetGenericArguments())
|
||||
{
|
||||
DumpGenericInfo(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static bool IsAssignableToGeneric(this Type left, Type right, ToolshedManager toolshed, bool recursiveDescent = true)
|
||||
{
|
||||
if (left.IsAssignableTo(right))
|
||||
return true;
|
||||
|
||||
if (right.IsInterface)
|
||||
{
|
||||
foreach (var i in left.GetInterfaces())
|
||||
{
|
||||
if (left.IsAssignableToGeneric(i, toolshed, recursiveDescent))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (left.Constructable() && right.IsGenericParameter)
|
||||
{
|
||||
var constraints = right.GetGenericParameterConstraints();
|
||||
foreach (var t in constraints)
|
||||
{
|
||||
if (!left.IsAssignableToGeneric(t, toolshed, recursiveDescent))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (left.IsGenericType && right.IsGenericType && left.GenericTypeArguments.Length == right.GenericTypeArguments.Length)
|
||||
{
|
||||
var equal = left.GetGenericTypeDefinition() == right.GetGenericTypeDefinition();
|
||||
|
||||
if (!equal)
|
||||
goto next;
|
||||
|
||||
var res = true;
|
||||
foreach (var (leftTy, rightTy) in left.GenericTypeArguments.Zip(right.GenericTypeArguments))
|
||||
{
|
||||
res &= leftTy.IsAssignableToGeneric(rightTy, toolshed, false);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
next:
|
||||
if (recursiveDescent)
|
||||
{
|
||||
foreach (var leftSubTy in toolshed.AllSteppedTypes(left))
|
||||
{
|
||||
if (leftSubTy.IsAssignableToGeneric(right, toolshed, false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsGenericRelated(this Type t)
|
||||
{
|
||||
return t.IsGenericParameter | t.IsGenericType | t.IsGenericMethodParameter | t.IsGenericTypeDefinition | t.IsConstructedGenericType | t.IsGenericTypeParameter;
|
||||
}
|
||||
|
||||
public static bool Constructable(this Type t)
|
||||
{
|
||||
if (!IsGenericRelated(t))
|
||||
return true;
|
||||
|
||||
if (!t.IsGenericType)
|
||||
return false;
|
||||
|
||||
var r = true;
|
||||
|
||||
foreach (var arg in t.GetGenericArguments())
|
||||
{
|
||||
r &= Constructable(arg);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public static PropertyInfo? FindIndexerProperty(
|
||||
this Type type)
|
||||
{
|
||||
var defaultPropertyAttribute = type.GetCustomAttributes<DefaultMemberAttribute>().FirstOrDefault();
|
||||
|
||||
return defaultPropertyAttribute == null
|
||||
? null
|
||||
: type.GetRuntimeProperties()
|
||||
.FirstOrDefault(
|
||||
pi =>
|
||||
pi.Name == defaultPropertyAttribute.MemberName
|
||||
&& pi.IsIndexerProperty()
|
||||
&& pi.SetMethod?.GetParameters() is { } parameters
|
||||
&& parameters.Length == 2
|
||||
&& parameters[0].ParameterType == typeof(string));
|
||||
}
|
||||
|
||||
public static bool IsIndexerProperty(this PropertyInfo propertyInfo)
|
||||
{
|
||||
var indexParams = propertyInfo.GetIndexParameters();
|
||||
return indexParams.Length == 1
|
||||
&& indexParams[0].ParameterType == typeof(string);
|
||||
}
|
||||
}
|
||||
155
Robust.Shared/Toolshed/Syntax/Block.cs
Normal file
155
Robust.Shared/Toolshed/Syntax/Block.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
public sealed class Block
|
||||
{
|
||||
internal CommandRun CommandRun { get; set; }
|
||||
|
||||
public static bool TryParse(
|
||||
bool doAutoComplete,
|
||||
ForwardParser parser,
|
||||
Type? pipedType,
|
||||
[NotNullWhen(true)] out Block? block,
|
||||
out ValueTask<(CompletionResult?, IConError?)>? autoComplete,
|
||||
out IConError? error
|
||||
)
|
||||
{
|
||||
parser.Consume(char.IsWhiteSpace);
|
||||
|
||||
var enclosed = parser.EatMatch('{');
|
||||
|
||||
CommandRun.TryParse(enclosed, doAutoComplete, parser, pipedType, null, !enclosed, out var expr, out autoComplete, out error);
|
||||
|
||||
if (expr is null)
|
||||
{
|
||||
block = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enclosed && !parser.EatMatch('}'))
|
||||
{
|
||||
error = new MissingClosingBrace();
|
||||
block = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
block = new Block(expr);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Block(CommandRun expr)
|
||||
{
|
||||
CommandRun = expr;
|
||||
}
|
||||
|
||||
public object? Invoke(object? input, IInvocationContext ctx)
|
||||
{
|
||||
return CommandRun.Invoke(input, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Something more akin to actual expressions.
|
||||
/// </summary>
|
||||
public sealed class Block<T>
|
||||
{
|
||||
internal CommandRun<T> CommandRun { get; set; }
|
||||
|
||||
public static bool TryParse(bool doAutoComplete, ForwardParser parser, Type? pipedType,
|
||||
[NotNullWhen(true)] out Block<T>? block, out ValueTask<(CompletionResult?, IConError?)>? autoComplete, out IConError? error)
|
||||
{
|
||||
parser.Consume(char.IsWhiteSpace);
|
||||
|
||||
var enclosed = parser.EatMatch('{');
|
||||
|
||||
CommandRun<T>.TryParse(enclosed, doAutoComplete, parser, pipedType, !enclosed, out var expr, out autoComplete, out error);
|
||||
|
||||
if (expr is null)
|
||||
{
|
||||
block = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enclosed && !parser.EatMatch('}'))
|
||||
{
|
||||
error = new MissingClosingBrace();
|
||||
block = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
block = new Block<T>(expr);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Block(CommandRun<T> expr)
|
||||
{
|
||||
CommandRun = expr;
|
||||
}
|
||||
|
||||
public T? Invoke(object? input, IInvocationContext ctx)
|
||||
{
|
||||
return CommandRun.Invoke(input, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Block<TIn, TOut>
|
||||
{
|
||||
internal CommandRun<TIn, TOut> CommandRun { get; set; }
|
||||
|
||||
public static bool TryParse(bool doAutoComplete, ForwardParser parser, Type? pipedType,
|
||||
[NotNullWhen(true)] out Block<TIn, TOut>? block, out ValueTask<(CompletionResult?, IConError?)>? autoComplete, out IConError? error)
|
||||
{
|
||||
parser.Consume(char.IsWhiteSpace);
|
||||
|
||||
var enclosed = parser.EatMatch('{');
|
||||
|
||||
CommandRun<TIn, TOut>.TryParse(enclosed, doAutoComplete, parser, !enclosed, out var expr, out autoComplete, out error);
|
||||
|
||||
if (expr is null)
|
||||
{
|
||||
block = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enclosed && !parser.EatMatch('}'))
|
||||
{
|
||||
error = new MissingClosingBrace();
|
||||
block = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
block = new Block<TIn, TOut>(expr);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Block(CommandRun<TIn, TOut> expr)
|
||||
{
|
||||
CommandRun = expr;
|
||||
}
|
||||
|
||||
public TOut? Invoke(object? input, IInvocationContext ctx)
|
||||
{
|
||||
return CommandRun.Invoke(input, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public record struct MissingClosingBrace() : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromMarkup("Expected a closing brace.");
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
187
Robust.Shared/Toolshed/Syntax/Expression.cs
Normal file
187
Robust.Shared/Toolshed/Syntax/Expression.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
/// <summary>
|
||||
/// A "run" of commands. Not a true expression.
|
||||
/// </summary>
|
||||
public sealed class CommandRun
|
||||
{
|
||||
public readonly List<(ParsedCommand, Vector2i)> Commands;
|
||||
private readonly string _originalExpr;
|
||||
|
||||
public static bool TryParse(
|
||||
bool blockMode,
|
||||
bool doAutocomplete,
|
||||
ForwardParser parser,
|
||||
Type? pipedType,
|
||||
Type? targetOutput,
|
||||
bool once,
|
||||
[NotNullWhen(true)] out CommandRun? expr,
|
||||
out ValueTask<(CompletionResult?, IConError?)>? autocomplete,
|
||||
out IConError? error
|
||||
)
|
||||
{
|
||||
autocomplete = null;
|
||||
error = null;
|
||||
var cmds = new List<(ParsedCommand, Vector2i)>();
|
||||
var start = parser.Index;
|
||||
var noCommand = false;
|
||||
parser.Consume(char.IsWhiteSpace);
|
||||
|
||||
while ((!once || cmds.Count < 1) && ParsedCommand.TryParse(doAutocomplete, parser, pipedType, out var cmd, out error, out noCommand, out autocomplete, targetOutput))
|
||||
{
|
||||
var end = parser.Index;
|
||||
pipedType = cmd.ReturnType;
|
||||
cmds.Add((cmd, (start, end)));
|
||||
parser.Consume(char.IsWhiteSpace);
|
||||
start = parser.Index;
|
||||
|
||||
if (blockMode && parser.PeekChar() == '}')
|
||||
break;
|
||||
}
|
||||
|
||||
if (error is OutOfInputError && noCommand)
|
||||
error = null;
|
||||
|
||||
if (error is not null and not OutOfInputError || error is OutOfInputError && !noCommand || cmds.Count == 0)
|
||||
{
|
||||
expr = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(cmds.Last().Item1.ReturnType?.IsAssignableTo(targetOutput) ?? false) && targetOutput is not null)
|
||||
{
|
||||
error = new ExpressionOfWrongType(targetOutput, cmds.Last().Item1.ReturnType!, once);
|
||||
expr = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
expr = new CommandRun(cmds, parser.Input);
|
||||
return true;
|
||||
}
|
||||
|
||||
public object? Invoke(object? input, IInvocationContext ctx, bool reportErrors = true)
|
||||
{
|
||||
var ret = input;
|
||||
foreach (var (cmd, span) in Commands)
|
||||
{
|
||||
ret = cmd.Invoke(ret, ctx);
|
||||
if (ctx.GetErrors().Any())
|
||||
{
|
||||
// Got an error, we need to report it and break out.
|
||||
foreach (var err in ctx.GetErrors())
|
||||
{
|
||||
err.Contextualize(_originalExpr, span);
|
||||
ctx.WriteLine(err.Describe());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private CommandRun(List<(ParsedCommand, Vector2i)> commands, string originalExpr)
|
||||
{
|
||||
Commands = commands;
|
||||
_originalExpr = originalExpr;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CommandRun<TIn, TOut>
|
||||
{
|
||||
internal readonly CommandRun InnerCommandRun;
|
||||
|
||||
public static bool TryParse(bool blockMode, bool doAutoComplete, ForwardParser parser, bool once,
|
||||
[NotNullWhen(true)] out CommandRun<TIn, TOut>? expr,
|
||||
out ValueTask<(CompletionResult?, IConError?)>? autocomplete, out IConError? error)
|
||||
{
|
||||
if (!CommandRun.TryParse(blockMode, doAutoComplete, parser, typeof(TIn), typeof(TOut), once, out var innerExpr, out autocomplete, out error))
|
||||
{
|
||||
expr = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
expr = new CommandRun<TIn, TOut>(innerExpr);
|
||||
return true;
|
||||
}
|
||||
|
||||
public TOut? Invoke(object? input, IInvocationContext ctx)
|
||||
{
|
||||
var res = InnerCommandRun.Invoke(input, ctx);
|
||||
if (res is null)
|
||||
return default;
|
||||
return (TOut?) res;
|
||||
}
|
||||
|
||||
private CommandRun(CommandRun commandRun)
|
||||
{
|
||||
InnerCommandRun = commandRun;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CommandRun<TRes>
|
||||
{
|
||||
internal readonly CommandRun _innerCommandRun;
|
||||
|
||||
public static bool TryParse(bool blockMode, bool doAutoComplete, ForwardParser parser, Type? pipedType, bool once,
|
||||
[NotNullWhen(true)] out CommandRun<TRes>? expr, out ValueTask<(CompletionResult?, IConError?)>? completion,
|
||||
out IConError? error)
|
||||
{
|
||||
if (!CommandRun.TryParse(blockMode, doAutoComplete, parser, pipedType, typeof(TRes), once, out var innerExpr, out completion, out error))
|
||||
{
|
||||
expr = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
expr = new CommandRun<TRes>(innerExpr);
|
||||
return true;
|
||||
}
|
||||
|
||||
public TRes? Invoke(object? input, IInvocationContext ctx)
|
||||
{
|
||||
var res = _innerCommandRun.Invoke(input, ctx);
|
||||
if (res is null)
|
||||
return default;
|
||||
return (TRes?) res;
|
||||
}
|
||||
|
||||
private CommandRun(CommandRun commandRun)
|
||||
{
|
||||
_innerCommandRun = commandRun;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct ExpressionOfWrongType(Type Expected, Type Got, bool Once) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
var msg = FormattedMessage.FromMarkup(
|
||||
$"Expected an expression of type {Expected.PrettyName()}, but got {Got.PrettyName()}");
|
||||
|
||||
if (Once)
|
||||
{
|
||||
msg.PushNewline();
|
||||
msg.AddText("Note: A single command is expected here, if you were trying to chain commands please surround the run with { } to form a block.");
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
329
Robust.Shared/Toolshed/Syntax/ParsedCommand.cs
Normal file
329
Robust.Shared/Toolshed/Syntax/ParsedCommand.cs
Normal file
@@ -0,0 +1,329 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
using Invocable = Func<CommandInvocationArguments, object?>;
|
||||
|
||||
public sealed class ParsedCommand
|
||||
{
|
||||
public ToolshedCommand Command { get; }
|
||||
public Type? ReturnType { get; }
|
||||
|
||||
public Type? PipedType => Bundle.PipedArgumentType;
|
||||
internal Invocable Invocable { get; }
|
||||
private CommandArgumentBundle Bundle { get; }
|
||||
public string? SubCommand { get; }
|
||||
|
||||
public static bool TryParse(
|
||||
bool doAutoComplete,
|
||||
ForwardParser parser,
|
||||
Type? pipedArgumentType,
|
||||
[NotNullWhen(true)] out ParsedCommand? result,
|
||||
out IConError? error,
|
||||
out bool noCommand,
|
||||
out ValueTask<(CompletionResult?, IConError?)>? autocomplete,
|
||||
Type? targetType = null
|
||||
)
|
||||
{
|
||||
noCommand = false;
|
||||
var checkpoint = parser.Save();
|
||||
var bundle = new CommandArgumentBundle()
|
||||
{Arguments = new(), Inverted = false, PipedArgumentType = pipedArgumentType, TypeArguments = Array.Empty<Type>()};
|
||||
|
||||
autocomplete = null;
|
||||
if (!TryDigestModifiers(parser, bundle, out error)
|
||||
|| !TryParseCommand(doAutoComplete, parser, bundle, pipedArgumentType, targetType, out var subCommand, out var invocable, out var command, out error, out noCommand, out autocomplete)
|
||||
|| !command.TryGetReturnType(subCommand, pipedArgumentType, bundle.TypeArguments, out var retType)
|
||||
)
|
||||
{
|
||||
result = null;
|
||||
parser.Restore(checkpoint);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
result = new(bundle, invocable, command, retType, subCommand);
|
||||
return true;
|
||||
}
|
||||
|
||||
private ParsedCommand(CommandArgumentBundle bundle, Invocable invocable, ToolshedCommand command, Type? returnType, string? subCommand)
|
||||
{
|
||||
Invocable = invocable;
|
||||
Bundle = bundle;
|
||||
Command = command;
|
||||
ReturnType = returnType;
|
||||
SubCommand = subCommand;
|
||||
}
|
||||
|
||||
private static bool TryDigestModifiers(ForwardParser parser, CommandArgumentBundle bundle, out IConError? error)
|
||||
{
|
||||
error = null;
|
||||
if (parser.PeekWord() == "not")
|
||||
{
|
||||
parser.GetWord(); //yum
|
||||
bundle.Inverted = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryParseCommand(
|
||||
bool makeCompletions,
|
||||
ForwardParser parser,
|
||||
CommandArgumentBundle bundle,
|
||||
Type? pipedType,
|
||||
Type? targetType,
|
||||
out string? subCommand,
|
||||
[NotNullWhen(true)] out Invocable? invocable,
|
||||
[NotNullWhen(true)] out ToolshedCommand? command,
|
||||
out IConError? error,
|
||||
out bool noCommand,
|
||||
out ValueTask<(CompletionResult?, IConError?)>? autocomplete
|
||||
)
|
||||
{
|
||||
noCommand = false;
|
||||
var start = parser.Index;
|
||||
var cmd = parser.GetWord(c => c is not ':' and not '{' and not '}' && !char.IsWhiteSpace(c));
|
||||
subCommand = null;
|
||||
invocable = null;
|
||||
command = null;
|
||||
if (cmd is null)
|
||||
{
|
||||
if (parser.PeekChar() is null)
|
||||
{
|
||||
noCommand = true;
|
||||
error = new OutOfInputError();
|
||||
error.Contextualize(parser.Input, (parser.Index, parser.Index));
|
||||
autocomplete = null;
|
||||
if (makeCompletions)
|
||||
{
|
||||
var cmds = parser.Toolshed.CommandsTakingType(pipedType ?? typeof(void));
|
||||
autocomplete = ValueTask.FromResult<(CompletionResult?, IConError?)>((CompletionResult.FromHintOptions(cmds.Select(x => x.AsCompletion()), "<command>"), error));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
noCommand = true;
|
||||
error = new NotValidCommandError(targetType);
|
||||
error.Contextualize(parser.Input, (start, parser.Index));
|
||||
autocomplete = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parser.Toolshed.TryGetCommand(cmd, out var cmdImpl))
|
||||
{
|
||||
error = new UnknownCommandError(cmd);
|
||||
error.Contextualize(parser.Input, (start, parser.Index));
|
||||
autocomplete = null;
|
||||
if (makeCompletions)
|
||||
{
|
||||
var cmds = parser.Toolshed.CommandsTakingType(pipedType ?? typeof(void));
|
||||
autocomplete = ValueTask.FromResult<(CompletionResult?, IConError?)>((CompletionResult.FromHintOptions(cmds.Select(x => x.AsCompletion()), "<command>"), error));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cmdImpl.HasSubCommands)
|
||||
{
|
||||
error = null;
|
||||
autocomplete = null;
|
||||
if (makeCompletions)
|
||||
{
|
||||
var cmds = parser.Toolshed.CommandsTakingType(pipedType ?? typeof(void)).Where(x => x.Cmd.Name == cmd);
|
||||
autocomplete = ValueTask.FromResult<(CompletionResult?, IConError?)>((
|
||||
CompletionResult.FromHintOptions(cmds.Select(x => x.AsCompletion()), "<command>"), error));
|
||||
}
|
||||
|
||||
if (parser.GetChar() is not ':')
|
||||
{
|
||||
error = new OutOfInputError();
|
||||
error.Contextualize(parser.Input, (parser.Index, parser.Index));
|
||||
return false;
|
||||
}
|
||||
|
||||
var subCmdStart = parser.Index;
|
||||
|
||||
if (parser.GetWord() is not { } subcmd)
|
||||
{
|
||||
error = new OutOfInputError();
|
||||
error.Contextualize(parser.Input, (parser.Index, parser.Index));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!cmdImpl.Subcommands.Contains(subcmd))
|
||||
{
|
||||
error = new UnknownSubcommandError(cmd, subcmd, cmdImpl);
|
||||
error.Contextualize(parser.Input, (subCmdStart, parser.Index));
|
||||
return false;
|
||||
}
|
||||
|
||||
subCommand = subcmd;
|
||||
}
|
||||
|
||||
if (parser.Consume(char.IsWhiteSpace) == 0 && makeCompletions)
|
||||
{
|
||||
error = null;
|
||||
var cmds = parser.Toolshed.CommandsTakingType(pipedType ?? typeof(void));
|
||||
autocomplete = ValueTask.FromResult<(CompletionResult?, IConError?)>((CompletionResult.FromHintOptions(cmds.Select(x => x.AsCompletion()), "<command>"), null));
|
||||
return false;
|
||||
}
|
||||
|
||||
var argsStart = parser.Index;
|
||||
|
||||
if (!cmdImpl.TryParseArguments(makeCompletions, parser, pipedType, subCommand, out var args, out var types, out error, out autocomplete))
|
||||
{
|
||||
error?.Contextualize(parser.Input, (argsStart, parser.Index));
|
||||
return false;
|
||||
}
|
||||
|
||||
bundle.TypeArguments = types;
|
||||
|
||||
if (!cmdImpl.TryGetImplementation(bundle.PipedArgumentType, subCommand, types, out var impl))
|
||||
{
|
||||
error = new NoImplementationError(cmd, types, subCommand, bundle.PipedArgumentType);
|
||||
error.Contextualize(parser.Input, (start, parser.Index));
|
||||
autocomplete = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bundle.Arguments = args;
|
||||
invocable = impl;
|
||||
command = cmdImpl;
|
||||
autocomplete = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool _passedInvokeTest = false;
|
||||
|
||||
public object? Invoke(object? pipedIn, IInvocationContext ctx)
|
||||
{
|
||||
if (!_passedInvokeTest && !ctx.CheckInvokable(new CommandSpec(Command, SubCommand), out var error))
|
||||
{
|
||||
// Could not invoke the command for whatever reason, i.e. permission errors.
|
||||
if (error is not null)
|
||||
ctx.ReportError(error);
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: This optimization might be dangerous if blocks can be passed to other people through vars.
|
||||
// Or not if it can only be done deliberately, but social engineering is a thing.
|
||||
_passedInvokeTest = true;
|
||||
|
||||
try
|
||||
{
|
||||
return Invocable.Invoke(new CommandInvocationArguments()
|
||||
{Bundle = Bundle, PipedArgument = pipedIn, Context = ctx});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ctx.ReportError(new UnhandledExceptionError(e));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record struct UnknownCommandError(string Cmd) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromMarkup($"Got unknown command {Cmd}.");
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
|
||||
public record struct NoImplementationError(string Cmd, Type[] Types, string? SubCommand, Type? PipedType) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
var newCon = IoCManager.Resolve<ToolshedManager>();
|
||||
|
||||
var msg = FormattedMessage.FromMarkup($"Could not find an implementation for {Cmd} given the input type {PipedType?.PrettyName() ?? "void"}.");
|
||||
msg.PushNewline();
|
||||
|
||||
var typeArgs = "";
|
||||
|
||||
if (Types.Length != 0)
|
||||
{
|
||||
typeArgs = "<" + string.Join(",", Types.Select(ReflectionExtensions.PrettyName)) + ">";
|
||||
}
|
||||
|
||||
msg.AddText($"Signature: {Cmd}{(SubCommand is not null ? $":{SubCommand}" : "")}{typeArgs} {PipedType?.PrettyName() ?? "void"} -> ???");
|
||||
|
||||
var piped = PipedType ?? typeof(void);
|
||||
var cmdImpl = newCon.GetCommand(Cmd);
|
||||
var accepted = cmdImpl.AcceptedTypes(SubCommand).ToHashSet();
|
||||
|
||||
foreach (var (command, subCommand) in newCon.CommandsTakingType(piped))
|
||||
{
|
||||
if (!command.TryGetReturnType(subCommand, piped, Array.Empty<Type>(), out var retType) || !accepted.Any(x => retType.IsAssignableTo(x)))
|
||||
continue;
|
||||
|
||||
if (!cmdImpl.TryGetReturnType(SubCommand, retType, Types, out var myRetType))
|
||||
continue;
|
||||
|
||||
msg.PushNewline();
|
||||
msg.AddText($"The command {command.Name}{(subCommand is not null ? $":{subCommand}" : "")} can convert from {piped.PrettyName()} to {retType.PrettyName()}.");
|
||||
msg.PushNewline();
|
||||
msg.AddText($"With this fix, the new signature will be: {Cmd}{(SubCommand is not null ? $":{SubCommand}" : "")}{typeArgs} {retType?.PrettyName() ?? "void"} -> {myRetType?.PrettyName() ?? "void"}.");
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
|
||||
public record struct UnknownSubcommandError(string Cmd, string SubCmd, ToolshedCommand Command) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText($"The command group {Cmd} doesn't have command {SubCmd}.");
|
||||
msg.PushNewline();
|
||||
msg.AddText($"The valid commands are: {string.Join(", ", Command.Subcommands)}.");
|
||||
return msg;
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
|
||||
public record struct NotValidCommandError(Type? TargetType) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddText("Ran into an invalid command, could not parse.");
|
||||
if (TargetType is not null && TargetType != typeof(void))
|
||||
{
|
||||
msg.PushNewline();
|
||||
msg.AddText($"The parser was trying to obtain a run of type {TargetType.PrettyName()}, make sure your run actually returns that value.");
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
106
Robust.Shared/Toolshed/Syntax/ValueRef.cs
Normal file
106
Robust.Shared/Toolshed/Syntax/ValueRef.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
public sealed class ValueRef<T> : ValueRef<T, T>
|
||||
{
|
||||
public ValueRef(ValueRef<T, T> inner)
|
||||
{
|
||||
InnerBlock = inner.InnerBlock;
|
||||
VarName = inner.VarName;
|
||||
HasValue = inner.HasValue;
|
||||
Value = inner.Value;
|
||||
Expression = inner.Expression;
|
||||
RefSpan = inner.RefSpan;
|
||||
}
|
||||
public ValueRef(string varName) : base(varName)
|
||||
{
|
||||
}
|
||||
|
||||
public ValueRef(Block<T> innerBlock) : base(innerBlock)
|
||||
{
|
||||
}
|
||||
|
||||
public ValueRef(T value) : base(value)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Virtual]
|
||||
public class ValueRef<T, TAuto>
|
||||
{
|
||||
internal Block<T>? InnerBlock;
|
||||
internal string? VarName;
|
||||
internal bool HasValue = false;
|
||||
internal T? Value;
|
||||
internal string? Expression;
|
||||
internal Vector2i? RefSpan;
|
||||
|
||||
protected ValueRef()
|
||||
{
|
||||
}
|
||||
|
||||
public ValueRef(string varName)
|
||||
{
|
||||
VarName = varName;
|
||||
}
|
||||
|
||||
public ValueRef(Block<T> innerBlock)
|
||||
{
|
||||
InnerBlock = innerBlock;
|
||||
}
|
||||
|
||||
public ValueRef(T value)
|
||||
{
|
||||
Value = value;
|
||||
HasValue = true;
|
||||
}
|
||||
|
||||
public bool LikelyConst => VarName is not null || HasValue;
|
||||
|
||||
|
||||
public T? Evaluate(IInvocationContext ctx)
|
||||
{
|
||||
if (Value is not null && HasValue)
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
else if (VarName is not null)
|
||||
{
|
||||
return (T?)ctx.ReadVar(VarName);
|
||||
}
|
||||
else if (InnerBlock is not null)
|
||||
{
|
||||
return InnerBlock.Invoke(null, ctx);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnreachableException();
|
||||
}
|
||||
}
|
||||
|
||||
public void Set(IInvocationContext ctx, T? value)
|
||||
{
|
||||
if (VarName is null)
|
||||
throw new NotImplementedException();
|
||||
|
||||
ctx.WriteVar(VarName!, value);
|
||||
}
|
||||
}
|
||||
|
||||
public record struct BadVarTypeError(Type Got, Type Expected, string VarName) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromMarkup(
|
||||
$"Got unexpected type {Got.PrettyName()} in {VarName}, expected {Expected.PrettyName()}");
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
113
Robust.Shared/Toolshed/ToolshedCommand.Entities.cs
Normal file
113
Robust.Shared/Toolshed/ToolshedCommand.Entities.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Toolshed;
|
||||
|
||||
public abstract partial class ToolshedCommand
|
||||
{
|
||||
[PublicAPI, IoC.Dependency]
|
||||
protected readonly IEntityManager EntityManager = default!;
|
||||
|
||||
[PublicAPI, IoC.Dependency]
|
||||
protected readonly IEntitySystemManager EntitySystemManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for retrieving <see cref="MetaDataComponent"/> for an entity.
|
||||
/// </summary>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected MetaDataComponent MetaData(EntityUid entity)
|
||||
=> EntityManager.GetComponent<MetaDataComponent>(entity);
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for retrieving <see cref="TransformComponent"/> for an entity.
|
||||
/// </summary>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected TransformComponent Transform(EntityUid entity)
|
||||
=> EntityManager.GetComponent<TransformComponent>(entity);
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for retrieving an entity's name.
|
||||
/// </summary>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected string EntName(EntityUid entity)
|
||||
=> EntityManager.GetComponent<MetaDataComponent>(entity).EntityName;
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for deleting an entity.
|
||||
/// </summary>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void Del(EntityUid entityUid)
|
||||
=> EntityManager.DeleteEntity(entityUid);
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for checking if an entity is deleted or otherwise non-existant.
|
||||
/// </summary>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Deleted(EntityUid entity)
|
||||
=> EntityManager.Deleted(entity);
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for retrieving the given component for an entity.
|
||||
/// </summary>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected T Comp<T>(EntityUid entity)
|
||||
where T: IComponent
|
||||
=> EntityManager.GetComponent<T>(entity);
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for checking if an entity has the given component.
|
||||
/// </summary>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool HasComp<T>(EntityUid entityUid)
|
||||
where T: IComponent
|
||||
=> EntityManager.HasComponent<T>(entityUid);
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for attempting to retrieve the given component for an entity.
|
||||
/// </summary>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryComp<T>(EntityUid? entity, [NotNullWhen(true)] out T? component)
|
||||
where T: IComponent
|
||||
=> EntityManager.TryGetComponent(entity, out component);
|
||||
|
||||
/// <inheritdoc cref="TryComp{T}(System.Nullable{Robust.Shared.GameObjects.EntityUid},out T?)"/>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryComp<T>(EntityUid entity, [NotNullWhen(true)] out T? component)
|
||||
where T: IComponent
|
||||
=> EntityManager.TryGetComponent(entity, out component);
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for adding a component to the given entity.
|
||||
/// </summary>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected T AddComp<T>(EntityUid entity)
|
||||
where T : Component, new()
|
||||
=> EntityManager.AddComponent<T>(entity);
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for ensuring an entity has the given component.
|
||||
/// </summary>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected T EnsureComp<T>(EntityUid entity)
|
||||
where T: Component, new()
|
||||
=> EntityManager.EnsureComponent<T>(entity);
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for retrieving an entity system.
|
||||
/// </summary>
|
||||
/// <remarks>This may be replaced with some form of dependency attribute in the future.</remarks>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected T GetSys<T>()
|
||||
where T: EntitySystem
|
||||
=> EntitySystemManager.GetEntitySystem<T>();
|
||||
|
||||
/// <summary>
|
||||
/// A shorthand for retrieving an entity query.
|
||||
/// </summary>
|
||||
[PublicAPI, MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityQuery<T> GetEntityQuery<T>()
|
||||
where T : Component
|
||||
=> EntityManager.GetEntityQuery<T>();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user