diff --git a/Content.Client/Maps/GridDraggingCommand.cs b/Content.Client/Maps/GridDraggingCommand.cs
new file mode 100644
index 0000000000..eb8579aa5f
--- /dev/null
+++ b/Content.Client/Maps/GridDraggingCommand.cs
@@ -0,0 +1,18 @@
+using Content.Shared.Maps;
+using Robust.Shared.Console;
+
+namespace Content.Client.Maps;
+
+///
+/// Toggles GridDragging on the system.
+///
+public sealed class GridDraggingCommand : IConsoleCommand
+{
+ public string Command => SharedGridDraggingSystem.CommandName;
+ public string Description => $"Allows someone with permissions to drag grids around.";
+ public string Help => $"{Command}";
+ public void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ IoCManager.Resolve().GetEntitySystem().Enabled ^= true;
+ }
+}
diff --git a/Content.Client/Maps/GridDraggingSystem.cs b/Content.Client/Maps/GridDraggingSystem.cs
new file mode 100644
index 0000000000..556cd12992
--- /dev/null
+++ b/Content.Client/Maps/GridDraggingSystem.cs
@@ -0,0 +1,114 @@
+using Content.Client.Administration.Managers;
+using Content.Shared.Maps;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.Input;
+using Robust.Shared.Input;
+using Robust.Shared.Map;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Maps;
+
+///
+public sealed class GridDraggingSystem : SharedGridDraggingSystem
+{
+ [Dependency] private readonly IEyeManager _eyeManager = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IInputManager _inputManager = default!;
+ [Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly InputSystem _inputSystem = default!;
+
+ public bool Enabled { get; set; }
+
+ private EntityUid? _dragging;
+ private Vector2 _localPosition;
+ private MapCoordinates? _lastMousePosition;
+
+ private void StartDragging(EntityUid grid, Vector2 localPosition)
+ {
+ _dragging = grid;
+ _localPosition = localPosition;
+
+ if (TryComp(grid, out var body))
+ {
+ RaiseNetworkEvent(new GridDragVelocityRequest()
+ {
+ Grid = grid,
+ LinearVelocity = Vector2.Zero
+ });
+ }
+ }
+
+ private void StopDragging()
+ {
+ if (_dragging == null) return;
+
+ if (_lastMousePosition != null && TryComp(_dragging.Value, out var xform) &&
+ TryComp(_dragging.Value, out var body) &&
+ xform.MapID == _lastMousePosition.Value.MapId)
+ {
+ var tickTime = _gameTiming.TickPeriod;
+ var distance = _lastMousePosition.Value.Position - xform.WorldPosition;
+ RaiseNetworkEvent(new GridDragVelocityRequest()
+ {
+ Grid = _dragging.Value,
+ LinearVelocity = distance.LengthSquared > 0f ? (distance / (float) tickTime.TotalSeconds) * 0.25f : Vector2.Zero,
+ });
+ }
+
+ _dragging = null;
+ _localPosition = Vector2.Zero;
+ _lastMousePosition = null;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ if (!Enabled || !_gameTiming.IsFirstTimePredicted) return;
+
+ var state = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
+
+ if (state != BoundKeyState.Down)
+ {
+ StopDragging();
+ return;
+ }
+
+ var mouseScreenPos = _inputManager.MouseScreenPosition;
+ var mousePos = _eyeManager.ScreenToMap(mouseScreenPos);
+
+ if (_dragging == null)
+ {
+ if (!_mapManager.TryFindGridAt(mousePos, out var grid))
+ return;
+
+ StartDragging(grid.GridEntityId, Transform(grid.GridEntityId).InvWorldMatrix.Transform(mousePos.Position));
+ }
+
+ if (!TryComp(_dragging, out var xform))
+ {
+ StopDragging();
+ return;
+ }
+
+ if (xform.MapID != mousePos.MapId)
+ {
+ StopDragging();
+ return;
+ }
+
+ var localToWorld = xform.WorldMatrix.Transform(_localPosition);
+
+ if (localToWorld.EqualsApprox(mousePos.Position, 0.01f)) return;
+
+ var requestedGridOrigin = mousePos.Position - xform.WorldRotation.RotateVec(_localPosition);
+ _lastMousePosition = new MapCoordinates(requestedGridOrigin, mousePos.MapId);
+
+ RaiseNetworkEvent(new GridDragRequestPosition()
+ {
+ Grid = _dragging.Value,
+ WorldPosition = requestedGridOrigin,
+ });
+ }
+}
diff --git a/Content.Server/Maps/GridDraggingSystem.cs b/Content.Server/Maps/GridDraggingSystem.cs
new file mode 100644
index 0000000000..2bfd0c8dac
--- /dev/null
+++ b/Content.Server/Maps/GridDraggingSystem.cs
@@ -0,0 +1,42 @@
+using Content.Shared.Maps;
+using Robust.Server.Console;
+using Robust.Server.Player;
+
+namespace Content.Server.Maps;
+
+///
+public sealed class GridDraggingSystem : SharedGridDraggingSystem
+{
+ [Dependency] private readonly IConGroupController _admin = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeNetworkEvent(OnRequestDrag);
+ SubscribeNetworkEvent(OnRequestVelocity);
+ }
+
+ private void OnRequestVelocity(GridDragVelocityRequest ev, EntitySessionEventArgs args)
+ {
+ if (args.SenderSession is not IPlayerSession playerSession ||
+ !_admin.CanCommand(playerSession, CommandName) ||
+ !Exists(ev.Grid) ||
+ Deleted(ev.Grid)) return;
+
+ var gridBody = Comp(ev.Grid);
+ gridBody.LinearVelocity = ev.LinearVelocity;
+ gridBody.AngularVelocity = 0f;
+ }
+
+ private void OnRequestDrag(GridDragRequestPosition msg, EntitySessionEventArgs args)
+ {
+ if (args.SenderSession is not IPlayerSession playerSession ||
+ !_admin.CanCommand(playerSession, CommandName) ||
+ !Exists(msg.Grid) ||
+ Deleted(msg.Grid)) return;
+
+ var gridXform = Transform(msg.Grid);
+
+ gridXform.WorldPosition = msg.WorldPosition;
+ }
+}
diff --git a/Content.Shared/Maps/SharedGridDraggingSystem.cs b/Content.Shared/Maps/SharedGridDraggingSystem.cs
new file mode 100644
index 0000000000..02ff95751d
--- /dev/null
+++ b/Content.Shared/Maps/SharedGridDraggingSystem.cs
@@ -0,0 +1,29 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Maps;
+
+///
+/// Helper system to allow you to move grids with a mouse.
+///
+public abstract class SharedGridDraggingSystem : EntitySystem
+{
+ public const string CommandName = "griddrag";
+}
+
+
+///
+/// Raised on the client to request a grid move to a specific position.
+///
+[Serializable, NetSerializable]
+public sealed class GridDragRequestPosition : EntityEventArgs
+{
+ public EntityUid Grid;
+ public Vector2 WorldPosition;
+}
+
+[Serializable, NetSerializable]
+public sealed class GridDragVelocityRequest : EntityEventArgs
+{
+ public EntityUid Grid;
+ public Vector2 LinearVelocity;
+}
diff --git a/Resources/clientCommandPerms.yml b/Resources/clientCommandPerms.yml
index 7805d0d24a..9f7ff9b6bd 100644
--- a/Resources/clientCommandPerms.yml
+++ b/Resources/clientCommandPerms.yml
@@ -17,6 +17,7 @@
- Flags: MAPPING
Commands:
+ - griddrag
- showmarkers
- showsubfloor
- showsubfloorforever