mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Entity console commands system. (#5267)
* Entity console commands system. This adds a new base type, LocalizedEntityCommands, which is able to import entity systems as dependencies. This is done by only registering these while the entity system is active. Handling registration separately like this required a bit of changes around ConsoleHost to make it more suitable for this purpose: You can now directly register command instances, and also have a system to suppress `UpdateAvailableCommands` on the client so there's no bad O(N*M) behavior. * Convert TeleportCommands.cs to new entity commands. Removes some obsoletion warnings without pain from having to manually import transform system. * Fix RobustServerSimulation dependency issue. --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
0ba4a66787
commit
08970e745b
@@ -39,6 +39,9 @@ END TEMPLATE-->
|
||||
|
||||
### New features
|
||||
|
||||
* Added `LocalizedEntityCommands`, which are console commands that have the ability to take entity system dependencies.
|
||||
* Added `BeginRegistrationRegion` to `IConsoleHost` to allow efficient bulk-registration of console commands.
|
||||
* Added `IConsoleHost.RegisterCommand` overload that takes an `IConsoleCommand`.
|
||||
* Added a `Finished` boolean to `AnimationCompletedEvent` which allows distinguishing if an animation was removed prematurely or completed naturally.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -9,18 +9,17 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console.Commands;
|
||||
|
||||
internal sealed class TeleportCommand : LocalizedCommands
|
||||
internal sealed class TeleportCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override string Command => "tp";
|
||||
public override bool RequireServerOrSingleplayer => true;
|
||||
@@ -36,11 +35,10 @@ internal sealed class TeleportCommand : LocalizedCommands
|
||||
return;
|
||||
}
|
||||
|
||||
var xformSystem = _entitySystem.GetEntitySystem<SharedTransformSystem>();
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(entity);
|
||||
var position = new Vector2(posX, posY);
|
||||
|
||||
xformSystem.AttachToGridOrMap(entity, transform);
|
||||
_transform.AttachToGridOrMap(entity, transform);
|
||||
|
||||
MapId mapId;
|
||||
if (args.Length == 3 && int.TryParse(args[2], out var intMapId))
|
||||
@@ -56,25 +54,26 @@ internal sealed class TeleportCommand : LocalizedCommands
|
||||
|
||||
if (_map.TryFindGridAt(mapId, position, out var gridUid, out var grid))
|
||||
{
|
||||
var gridPos = Vector2.Transform(position, xformSystem.GetInvWorldMatrix(gridUid));
|
||||
var gridPos = Vector2.Transform(position, _transform.GetInvWorldMatrix(gridUid));
|
||||
|
||||
xformSystem.SetCoordinates(entity, transform, new EntityCoordinates(gridUid, gridPos));
|
||||
_transform.SetCoordinates(entity, transform, new EntityCoordinates(gridUid, gridPos));
|
||||
}
|
||||
else
|
||||
{
|
||||
var mapEnt = _map.GetMapEntityIdOrThrow(mapId);
|
||||
xformSystem.SetWorldPosition(transform, position);
|
||||
xformSystem.SetParent(entity, transform, mapEnt);
|
||||
_transform.SetWorldPosition(transform, position);
|
||||
_transform.SetParent(entity, transform, mapEnt);
|
||||
}
|
||||
|
||||
shell.WriteLine($"Teleported {shell.Player} to {mapId}:{posX},{posY}.");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TeleportToCommand : LocalizedCommands
|
||||
public sealed class TeleportToCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly ISharedPlayerManager _players = default!;
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override string Command => "tpto";
|
||||
public override bool RequireServerOrSingleplayer => true;
|
||||
@@ -89,7 +88,6 @@ public sealed class TeleportToCommand : LocalizedCommands
|
||||
if (!TryGetTransformFromUidOrUsername(target, shell, out var targetUid, out _))
|
||||
return;
|
||||
|
||||
var transformSystem = _entities.System<SharedTransformSystem>();
|
||||
var targetCoords = new EntityCoordinates(targetUid.Value, Vector2.Zero);
|
||||
|
||||
if (_entities.TryGetComponent(targetUid, out PhysicsComponent? targetPhysics))
|
||||
@@ -127,8 +125,8 @@ public sealed class TeleportToCommand : LocalizedCommands
|
||||
|
||||
foreach (var victim in victims)
|
||||
{
|
||||
transformSystem.SetCoordinates(victim.Entity, targetCoords);
|
||||
transformSystem.AttachToGridOrMap(victim.Entity, victim.Transform);
|
||||
_transform.SetCoordinates(victim.Entity, targetCoords);
|
||||
_transform.AttachToGridOrMap(victim.Entity, victim.Transform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,9 +176,10 @@ public sealed class TeleportToCommand : LocalizedCommands
|
||||
}
|
||||
}
|
||||
|
||||
sealed class LocationCommand : LocalizedCommands
|
||||
sealed class LocationCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override string Command => "loc";
|
||||
|
||||
@@ -192,18 +191,19 @@ sealed class LocationCommand : LocalizedCommands
|
||||
var pt = _ent.GetComponent<TransformComponent>(entity);
|
||||
var pos = pt.Coordinates;
|
||||
|
||||
shell.WriteLine($"MapID:{pos.GetMapId(_ent)} GridUid:{pos.GetGridUid(_ent)} X:{pos.X:N2} Y:{pos.Y:N2}");
|
||||
var mapId = _transform.GetMapId(pos);
|
||||
var gridUid = _transform.GetGrid(pos);
|
||||
|
||||
shell.WriteLine($"MapID:{mapId} GridUid:{gridUid} X:{pos.X:N2} Y:{pos.Y:N2}");
|
||||
}
|
||||
}
|
||||
|
||||
sealed class TpGridCommand : LocalizedCommands
|
||||
sealed class TpGridCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
public override string Command => "tpgrid";
|
||||
public override string Description => Loc.GetString("cmd-tpgrid-desc");
|
||||
public override string Help => Loc.GetString("cmd-tpgrid-help");
|
||||
public override bool RequireServerOrSingleplayer => true;
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
@@ -246,14 +246,14 @@ sealed class TpGridCommand : LocalizedCommands
|
||||
mapId = new MapId(map);
|
||||
}
|
||||
|
||||
var id = _map.GetMapEntityId(mapId);
|
||||
var id = _map.GetMap(mapId);
|
||||
if (id == EntityUid.Invalid)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-mapid", ("arg", mapId.Value)));
|
||||
return;
|
||||
}
|
||||
|
||||
var pos = new EntityCoordinates(_map.GetMapEntityId(mapId), new Vector2(xPos, yPos));
|
||||
var pos = new EntityCoordinates(id, new Vector2(xPos, yPos));
|
||||
_ent.System<SharedTransformSystem>().SetCoordinates(uid.Value, pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Robust.Shared.Console
|
||||
[Dependency] protected readonly ILocalizationManager LocalizationManager = default!;
|
||||
|
||||
[ViewVariables] protected readonly Dictionary<string, IConsoleCommand> RegisteredCommands = new();
|
||||
[ViewVariables] private readonly HashSet<string> _autoRegisteredCommands = [];
|
||||
|
||||
private bool _isInRegistrationRegion;
|
||||
|
||||
private readonly CommandBuffer _commandBuffer = new CommandBuffer();
|
||||
|
||||
@@ -61,6 +64,11 @@ namespace Robust.Shared.Console
|
||||
// search for all client commands in all assemblies, and register them
|
||||
foreach (var type in ReflectionManager.GetAllChildren<IConsoleCommand>())
|
||||
{
|
||||
// This sucks but I can't come up with anything better
|
||||
// that won't just be 10x worse complexity for no gain.
|
||||
if (type.IsAssignableTo(typeof(IEntityConsoleCommand)))
|
||||
continue;
|
||||
|
||||
var instance = (IConsoleCommand)_typeFactory.CreateInstanceUnchecked(type, true);
|
||||
if (AvailableCommands.TryGetValue(instance.Command, out var duplicate))
|
||||
{
|
||||
@@ -69,6 +77,7 @@ namespace Robust.Shared.Console
|
||||
}
|
||||
|
||||
RegisteredCommands[instance.Command] = instance;
|
||||
_autoRegisteredCommands.Add(instance.Command);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +85,23 @@ namespace Robust.Shared.Console
|
||||
{
|
||||
}
|
||||
|
||||
public void BeginRegistrationRegion()
|
||||
{
|
||||
if (_isInRegistrationRegion)
|
||||
throw new InvalidOperationException("Cannot enter registration region twice!");
|
||||
|
||||
_isInRegistrationRegion = true;
|
||||
}
|
||||
|
||||
public void EndRegistrationRegion()
|
||||
{
|
||||
if (!_isInRegistrationRegion)
|
||||
throw new InvalidOperationException("Was not in registration region.");
|
||||
|
||||
_isInRegistrationRegion = false;
|
||||
UpdateAvailableCommands();
|
||||
}
|
||||
|
||||
#region RegisterCommand
|
||||
public void RegisterCommand(
|
||||
string command,
|
||||
@@ -88,8 +114,7 @@ namespace Robust.Shared.Console
|
||||
throw new InvalidOperationException($"Command already registered: {command}");
|
||||
|
||||
var newCmd = new RegisteredCommand(command, description, help, callback, requireServerOrSingleplayer);
|
||||
RegisteredCommands.Add(command, newCmd);
|
||||
UpdateAvailableCommands();
|
||||
RegisterCommand(newCmd);
|
||||
}
|
||||
|
||||
public void RegisterCommand(
|
||||
@@ -104,8 +129,7 @@ namespace Robust.Shared.Console
|
||||
throw new InvalidOperationException($"Command already registered: {command}");
|
||||
|
||||
var newCmd = new RegisteredCommand(command, description, help, callback, completionCallback, requireServerOrSingleplayer);
|
||||
RegisteredCommands.Add(command, newCmd);
|
||||
UpdateAvailableCommands();
|
||||
RegisterCommand(newCmd);
|
||||
}
|
||||
|
||||
public void RegisterCommand(
|
||||
@@ -120,8 +144,7 @@ namespace Robust.Shared.Console
|
||||
throw new InvalidOperationException($"Command already registered: {command}");
|
||||
|
||||
var newCmd = new RegisteredCommand(command, description, help, callback, completionCallback, requireServerOrSingleplayer);
|
||||
RegisteredCommands.Add(command, newCmd);
|
||||
UpdateAvailableCommands();
|
||||
RegisterCommand(newCmd);
|
||||
}
|
||||
|
||||
public void RegisterCommand(string command, ConCommandCallback callback,
|
||||
@@ -153,6 +176,15 @@ namespace Robust.Shared.Console
|
||||
var help = LocalizationManager.TryGetString($"cmd-{command}-help", out var val) ? val : "";
|
||||
RegisterCommand(command, description, help, callback, completionCallback, requireServerOrSingleplayer);
|
||||
}
|
||||
|
||||
public void RegisterCommand(IConsoleCommand command)
|
||||
{
|
||||
RegisteredCommands.Add(command.Command, command);
|
||||
|
||||
if (!_isInRegistrationRegion)
|
||||
UpdateAvailableCommands();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -161,12 +193,14 @@ namespace Robust.Shared.Console
|
||||
if (!RegisteredCommands.TryGetValue(command, out var cmd))
|
||||
throw new KeyNotFoundException($"Command {command} is not registered.");
|
||||
|
||||
if (cmd is not RegisteredCommand)
|
||||
if (_autoRegisteredCommands.Contains(command))
|
||||
throw new InvalidOperationException(
|
||||
"You cannot unregister commands that have been registered automatically.");
|
||||
|
||||
RegisteredCommands.Remove(command);
|
||||
UpdateAvailableCommands();
|
||||
|
||||
if (!_isInRegistrationRegion)
|
||||
UpdateAvailableCommands();
|
||||
}
|
||||
|
||||
//TODO: Pull up
|
||||
|
||||
54
Robust.Shared/Console/EntityConsoleHost.cs
Normal file
54
Robust.Shared/Console/EntityConsoleHost.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console;
|
||||
|
||||
/// <summary>
|
||||
/// Manages registration for "entity" console commands.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see cref="LocalizedEntityCommands"/> for details on what "entity" console commands are.
|
||||
/// </remarks>
|
||||
internal sealed class EntityConsoleHost
|
||||
{
|
||||
[Dependency] private readonly IConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
private readonly HashSet<string> _entityCommands = [];
|
||||
|
||||
public void Startup()
|
||||
{
|
||||
DebugTools.Assert(_entityCommands.Count == 0);
|
||||
|
||||
var deps = ((EntitySystemManager)_entitySystemManager).SystemDependencyCollection;
|
||||
|
||||
_consoleHost.BeginRegistrationRegion();
|
||||
|
||||
// search for all client commands in all assemblies, and register them
|
||||
foreach (var type in _reflectionManager.GetAllChildren<IEntityConsoleCommand>())
|
||||
{
|
||||
var instance = (IConsoleCommand)Activator.CreateInstance(type)!;
|
||||
deps.InjectDependencies(instance, oneOff: true);
|
||||
|
||||
_entityCommands.Add(instance.Command);
|
||||
_consoleHost.RegisterCommand(instance);
|
||||
}
|
||||
|
||||
_consoleHost.EndRegistrationRegion();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
foreach (var command in _entityCommands)
|
||||
{
|
||||
_consoleHost.UnregisterCommand(command);
|
||||
}
|
||||
|
||||
_entityCommands.Clear();
|
||||
}
|
||||
}
|
||||
@@ -83,4 +83,11 @@ namespace Robust.Shared.Console
|
||||
return ValueTask.FromResult(GetCompletion(shell, args));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Special marker interface used to indicate "entity" commands.
|
||||
/// See <see cref="LocalizedEntityCommands"/> for an overview.
|
||||
/// </summary>
|
||||
/// <seealso cref="EntityConsoleHost"/>
|
||||
internal interface IEntityConsoleCommand : IConsoleCommand;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
@@ -173,6 +174,33 @@ namespace Robust.Shared.Console
|
||||
ConCommandCallback callback,
|
||||
ConCommandCompletionAsyncCallback completionCallback,
|
||||
bool requireServerOrSingleplayer = false);
|
||||
|
||||
/// <summary>
|
||||
/// Register an existing console command instance directly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For this to be useful, the command has to be somehow excluded from automatic registration,
|
||||
/// such as by using the <see cref="ReflectAttribute"/>.
|
||||
/// </remarks>
|
||||
/// <param name="command">The command to register.</param>
|
||||
/// <seealso cref="BeginRegistrationRegion"/>
|
||||
void RegisterCommand(IConsoleCommand command);
|
||||
|
||||
/// <summary>
|
||||
/// Begin a region for registering many console commands in one go.
|
||||
/// The region can be ended with <see cref="EndRegistrationRegion"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Commands registered inside this region temporarily suppress some updating
|
||||
/// logic that would cause significant wasted work. This logic runs when the region is ended instead.
|
||||
/// </remarks>
|
||||
void BeginRegistrationRegion();
|
||||
|
||||
/// <summary>
|
||||
/// End a registration region started with <see cref="BeginRegistrationRegion"/>.
|
||||
/// </summary>
|
||||
void EndRegistrationRegion();
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -34,3 +34,17 @@ public abstract class LocalizedCommands : IConsoleCommand
|
||||
return ValueTask.FromResult(GetCompletion(shell, args));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for localized console commands that run in "entity space".
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This type of command is registered only while the entity system is active.
|
||||
/// On the client this means that the commands are only available while connected to a server or in single player.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// These commands are allowed to take dependencies on entity systems, reducing boilerplate for many usages.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract class LocalizedEntityCommands : LocalizedCommands, IEntityConsoleCommand;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Prometheus;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Log;
|
||||
@@ -42,6 +43,7 @@ namespace Robust.Shared.GameObjects
|
||||
[IoC.Dependency] private readonly ProfManager _prof = default!;
|
||||
[IoC.Dependency] private readonly INetManager _netMan = default!;
|
||||
[IoC.Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
[IoC.Dependency] private readonly EntityConsoleHost _entityConsoleHost = default!;
|
||||
|
||||
// I feel like PJB might shed me for putting a system dependency here, but its required for setting entity
|
||||
// positions on spawn....
|
||||
@@ -216,6 +218,7 @@ namespace Robust.Shared.GameObjects
|
||||
TransformQuery = GetEntityQuery<TransformComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_actorQuery = GetEntityQuery<ActorComponent>();
|
||||
_entityConsoleHost.Startup();
|
||||
}
|
||||
|
||||
public virtual void Shutdown()
|
||||
@@ -227,6 +230,7 @@ namespace Robust.Shared.GameObjects
|
||||
ClearComponents();
|
||||
ShuttingDown = false;
|
||||
Started = false;
|
||||
_entityConsoleHost.Shutdown();
|
||||
}
|
||||
|
||||
public virtual void Cleanup()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -50,6 +51,7 @@ namespace Robust.Shared
|
||||
deps.Register<ToolshedManager>();
|
||||
deps.Register<HttpClientHolder>();
|
||||
deps.Register<RobustMemoryManager>();
|
||||
deps.Register<EntityConsoleHost>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +232,7 @@ namespace Robust.UnitTesting.Server
|
||||
container.Register<IParallelManagerInternal, TestingParallelManager>();
|
||||
// Needed for grid fixture debugging.
|
||||
container.Register<IConGroupController, ConGroupController>();
|
||||
container.Register<EntityConsoleHost>();
|
||||
|
||||
// I just wanted to load pvs system
|
||||
container.Register<IServerEntityManager, ServerEntityManager>();
|
||||
|
||||
Reference in New Issue
Block a user