diff --git a/Content.Client/Administration/AdminNameOverlay.cs b/Content.Client/Administration/AdminNameOverlay.cs
index 01793a2fa1..0d2526831d 100644
--- a/Content.Client/Administration/AdminNameOverlay.cs
+++ b/Content.Client/Administration/AdminNameOverlay.cs
@@ -1,3 +1,4 @@
+using Content.Client.Administration.Systems;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
diff --git a/Content.Client/Administration/Components/HeadstandComponent.cs b/Content.Client/Administration/Components/HeadstandComponent.cs
new file mode 100644
index 0000000000..403d326092
--- /dev/null
+++ b/Content.Client/Administration/Components/HeadstandComponent.cs
@@ -0,0 +1,10 @@
+using Content.Shared.Administration.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Client.Administration.Components;
+
+[RegisterComponent, NetworkedComponent]
+public sealed class HeadstandComponent : SharedHeadstandComponent
+{
+
+}
diff --git a/Content.Client/Administration/QuickDialogSystem.cs b/Content.Client/Administration/QuickDialogSystem.cs
new file mode 100644
index 0000000000..fcd439d986
--- /dev/null
+++ b/Content.Client/Administration/QuickDialogSystem.cs
@@ -0,0 +1,154 @@
+using System.Linq;
+using Content.Client.UserInterface;
+using Content.Shared.Administration;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.Administration;
+
+///
+/// This handles the client portion of quick dialogs.
+///
+public sealed class QuickDialogSystem : EntitySystem
+{
+ ///
+ public override void Initialize()
+ {
+ SubscribeNetworkEvent(OpenDialog);
+ }
+
+ private void OpenDialog(QuickDialogOpenEvent ev)
+ {
+ var window = new FancyWindow()
+ {
+ Title = ev.Title
+ };
+
+ var entryContainer = new BoxContainer()
+ {
+ Orientation = BoxContainer.LayoutOrientation.Vertical,
+ Margin = new Thickness(8),
+ };
+
+ var promptsDict = new Dictionary();
+
+ foreach (var entry in ev.Prompts)
+ {
+ var entryBox = new BoxContainer()
+ {
+ Orientation = BoxContainer.LayoutOrientation.Horizontal
+ };
+
+ entryBox.AddChild(new Label { Text = entry.Prompt, HorizontalExpand = true, SizeFlagsStretchRatio = 0.5f });
+ var edit = new LineEdit() { HorizontalExpand = true};
+ entryBox.AddChild(edit);
+ switch (entry.Type)
+ {
+ case QuickDialogEntryType.Integer:
+ edit.IsValid += VerifyInt;
+ edit.PlaceHolder = "Integer..";
+ break;
+ case QuickDialogEntryType.Float:
+ edit.IsValid += VerifyFloat;
+ edit.PlaceHolder = "Float..";
+ break;
+ case QuickDialogEntryType.ShortText:
+ edit.IsValid += VerifyShortText;
+ edit.PlaceHolder = "Short text..";
+ break;
+ case QuickDialogEntryType.LongText:
+ edit.IsValid += VerifyLongText;
+ edit.PlaceHolder = "Long text..";
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ promptsDict.Add(entry.FieldId, edit);
+ entryContainer.AddChild(entryBox);
+ }
+
+ var buttonsBox = new BoxContainer()
+ {
+ Orientation = BoxContainer.LayoutOrientation.Horizontal,
+ HorizontalAlignment = Control.HAlignment.Center,
+ };
+
+ var alreadyReplied = false;
+
+ if ((ev.Buttons & QuickDialogButtonFlag.OkButton) != 0)
+ {
+ var okButton = new Button()
+ {
+ Text = "Ok",
+ };
+
+ okButton.OnPressed += _ =>
+ {
+ RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
+ promptsDict.Select(x => (x.Key, x.Value.Text)).ToDictionary(x => x.Key, x => x.Text),
+ QuickDialogButtonFlag.OkButton));
+ alreadyReplied = true;
+ window.Close();
+ };
+
+ buttonsBox.AddChild(okButton);
+ }
+
+ if ((ev.Buttons & QuickDialogButtonFlag.OkButton) != 0)
+ {
+ var cancelButton = new Button()
+ {
+ Text = "Cancel",
+ };
+
+ cancelButton.OnPressed += _ =>
+ {
+ RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
+ new(),
+ QuickDialogButtonFlag.CancelButton));
+ alreadyReplied = true;
+ window.Close();
+ };
+
+ buttonsBox.AddChild(cancelButton);
+ }
+
+ window.OnClose += () =>
+ {
+ if (!alreadyReplied)
+ {
+ RaiseNetworkEvent(new QuickDialogResponseEvent(ev.DialogId,
+ new(),
+ QuickDialogButtonFlag.CancelButton));
+ }
+ };
+
+ entryContainer.AddChild(buttonsBox);
+
+ window.ContentsContainer.AddChild(entryContainer);
+
+ window.MinWidth *= 2; // Just double it.
+
+ window.OpenCentered();
+ }
+
+ private bool VerifyInt(string input)
+ {
+ return int.TryParse(input, out var _);
+ }
+
+ private bool VerifyFloat(string input)
+ {
+ return float.TryParse(input, out var _);
+ }
+
+ private bool VerifyShortText(string input)
+ {
+ return input.Length <= 100;
+ }
+
+ private bool VerifyLongText(string input)
+ {
+ return input.Length <= 2000;
+ }
+}
diff --git a/Content.Client/Administration/AdminSystem.Menu.cs b/Content.Client/Administration/Systems/AdminSystem.Menu.cs
similarity index 85%
rename from Content.Client/Administration/AdminSystem.Menu.cs
rename to Content.Client/Administration/Systems/AdminSystem.Menu.cs
index 0def38a099..c10cadd48a 100644
--- a/Content.Client/Administration/AdminSystem.Menu.cs
+++ b/Content.Client/Administration/Systems/AdminSystem.Menu.cs
@@ -1,6 +1,6 @@
-using System.Collections.Generic;
using Content.Client.Administration.Managers;
using Content.Client.Administration.UI;
+using Content.Client.Administration.UI.Tabs.ObjectsTab;
using Content.Client.Administration.UI.Tabs.PlayerTab;
using Content.Client.HUD;
using Content.Client.Verbs;
@@ -11,13 +11,11 @@ using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
-using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
-using Robust.Shared.IoC;
using Robust.Shared.Network;
-namespace Content.Client.Administration
+namespace Content.Client.Administration.Systems
{
public sealed partial class AdminSystem
{
@@ -101,13 +99,18 @@ namespace Content.Client.Administration
}
_window.PlayerTabControl.OnEntryPressed += PlayerTabEntryPressed;
+ _window.ObjectsTabControl.OnEntryPressed += ObjectsTabEntryPressed;
_window.OpenCentered();
}
public void Close()
{
if (_window != null)
+ {
_window.PlayerTabControl.OnEntryPressed -= PlayerTabEntryPressed;
+ _window.ObjectsTabControl.OnEntryPressed -= ObjectsTabEntryPressed;
+ }
+
_window?.Close();
foreach (var window in _commandWindows)
@@ -163,5 +166,23 @@ namespace Content.Client.Administration
args.Event.Handle();
}
+
+ private void ObjectsTabEntryPressed(BaseButton.ButtonEventArgs args)
+ {
+ if (args.Button is not ObjectsTabEntry button)
+ return;
+
+ var uid = button.AssocEntity;
+ var function = args.Event.Function;
+
+ if (function == EngineKeyFunctions.UIClick)
+ _clientConsoleHost.ExecuteCommand($"vv {uid}");
+ else if (function == ContentKeyFunctions.OpenContextMenu)
+ _verbSystem.VerbMenu.OpenVerbMenu(uid, true);
+ else
+ return;
+
+ args.Event.Handle();
+ }
}
}
diff --git a/Content.Client/Administration/AdminSystem.Overlay.cs b/Content.Client/Administration/Systems/AdminSystem.Overlay.cs
similarity index 96%
rename from Content.Client/Administration/AdminSystem.Overlay.cs
rename to Content.Client/Administration/Systems/AdminSystem.Overlay.cs
index febb9d6421..85611626d5 100644
--- a/Content.Client/Administration/AdminSystem.Overlay.cs
+++ b/Content.Client/Administration/Systems/AdminSystem.Overlay.cs
@@ -1,7 +1,7 @@
using Content.Client.Administration.Managers;
using Robust.Client.Graphics;
-namespace Content.Client.Administration
+namespace Content.Client.Administration.Systems
{
public sealed partial class AdminSystem
{
diff --git a/Content.Client/Administration/AdminSystem.cs b/Content.Client/Administration/Systems/AdminSystem.cs
similarity index 94%
rename from Content.Client/Administration/AdminSystem.cs
rename to Content.Client/Administration/Systems/AdminSystem.cs
index 137a4ea3fb..de62caa4ef 100644
--- a/Content.Client/Administration/AdminSystem.cs
+++ b/Content.Client/Administration/Systems/AdminSystem.cs
@@ -1,13 +1,10 @@
-using System;
-using System.Collections.Generic;
using System.Linq;
using Content.Shared.Administration;
using Content.Shared.Administration.Events;
using Content.Shared.GameTicking;
-using Robust.Shared.GameObjects;
using Robust.Shared.Network;
-namespace Content.Client.Administration
+namespace Content.Client.Administration.Systems
{
public sealed partial class AdminSystem : EntitySystem
{
diff --git a/Content.Client/Administration/AdminVerbSystem.cs b/Content.Client/Administration/Systems/AdminVerbSystem.cs
similarity index 91%
rename from Content.Client/Administration/AdminVerbSystem.cs
rename to Content.Client/Administration/Systems/AdminVerbSystem.cs
index cb440a92da..9a6bfd705d 100644
--- a/Content.Client/Administration/AdminVerbSystem.cs
+++ b/Content.Client/Administration/Systems/AdminVerbSystem.cs
@@ -1,10 +1,7 @@
using Content.Shared.Verbs;
using Robust.Client.Console;
-using Robust.Client.ViewVariables;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-namespace Content.Client.Verbs
+namespace Content.Client.Administration.Systems
{
///
/// Client-side admin verb system. These usually open some sort of UIs.
diff --git a/Content.Client/Administration/BwoinkSystem.cs b/Content.Client/Administration/Systems/BwoinkSystem.cs
similarity index 96%
rename from Content.Client/Administration/BwoinkSystem.cs
rename to Content.Client/Administration/Systems/BwoinkSystem.cs
index c04b5fdfd3..96076d96cf 100644
--- a/Content.Client/Administration/BwoinkSystem.cs
+++ b/Content.Client/Administration/Systems/BwoinkSystem.cs
@@ -1,7 +1,5 @@
#nullable enable
-using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using Content.Client.Administration.Managers;
using Content.Client.Administration.UI;
using Content.Client.Administration.UI.CustomControls;
@@ -9,16 +7,13 @@ using Content.Client.HUD;
using Content.Shared.Administration;
using JetBrains.Annotations;
using Robust.Client.Graphics;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.Player;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Player;
-using Robust.Shared.Localization;
+using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Audio;
-using Robust.Shared.IoC;
using Robust.Shared.Network;
+using Robust.Shared.Player;
-namespace Content.Client.Administration
+namespace Content.Client.Administration.Systems
{
[UsedImplicitly]
public sealed class BwoinkSystem : SharedBwoinkSystem
diff --git a/Content.Client/Administration/Systems/HeadstandSystem.cs b/Content.Client/Administration/Systems/HeadstandSystem.cs
new file mode 100644
index 0000000000..d0634e4ddd
--- /dev/null
+++ b/Content.Client/Administration/Systems/HeadstandSystem.cs
@@ -0,0 +1,35 @@
+using Content.Client.Administration.Components;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Administration.Systems;
+
+public sealed class HeadstandSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnHeadstandAdded);
+ SubscribeLocalEvent(OnHeadstandRemoved);
+ }
+
+ private void OnHeadstandAdded(EntityUid uid, HeadstandComponent component, ComponentStartup args)
+ {
+ if (!TryComp(uid, out var sprite))
+ return;
+
+ foreach (var layer in sprite.AllLayers)
+ {
+ layer.Rotation += Angle.FromDegrees(180.0f);
+ }
+ }
+
+ private void OnHeadstandRemoved(EntityUid uid, HeadstandComponent component, ComponentShutdown args)
+ {
+ if (!TryComp(uid, out var sprite))
+ return;
+
+ foreach (var layer in sprite.AllLayers)
+ {
+ layer.Rotation -= Angle.FromDegrees(180.0f);
+ }
+ }
+}
diff --git a/Content.Client/Administration/KillSignSystem.cs b/Content.Client/Administration/Systems/KillSignSystem.cs
similarity index 96%
rename from Content.Client/Administration/KillSignSystem.cs
rename to Content.Client/Administration/Systems/KillSignSystem.cs
index 2959593bb9..5d86c0d91e 100644
--- a/Content.Client/Administration/KillSignSystem.cs
+++ b/Content.Client/Administration/Systems/KillSignSystem.cs
@@ -2,7 +2,7 @@
using Robust.Client.GameObjects;
using Robust.Shared.Utility;
-namespace Content.Client.Administration;
+namespace Content.Client.Administration.Systems;
public sealed class KillSignSystem : EntitySystem
{
diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml b/Content.Client/Administration/UI/AdminMenuWindow.xaml
index f148f5c1be..458184db35 100644
--- a/Content.Client/Administration/UI/AdminMenuWindow.xaml
+++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml
@@ -4,7 +4,8 @@
xmlns:adminbusTab="clr-namespace:Content.Client.Administration.UI.Tabs.AdminbusTab"
xmlns:atmosTab="clr-namespace:Content.Client.Administration.UI.Tabs.AtmosTab"
xmlns:tabs="clr-namespace:Content.Client.Administration.UI.Tabs"
- xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab">
+ xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
+ xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab">
@@ -12,5 +13,6 @@
+
diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
index be0c5da45d..561e958f60 100644
--- a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
+++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
@@ -25,6 +25,7 @@ namespace Content.Client.Administration.UI
MasterTabContainer.SetTabTitle(3, Loc.GetString("admin-menu-round-tab"));
MasterTabContainer.SetTabTitle(4, Loc.GetString("admin-menu-server-tab"));
MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-players-tab"));
+ MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-objects-tab"));
}
protected override void EnteredTree()
diff --git a/Content.Client/Administration/UI/BwoinkWindow.xaml.cs b/Content.Client/Administration/UI/BwoinkWindow.xaml.cs
index 7306715512..a514cb9521 100644
--- a/Content.Client/Administration/UI/BwoinkWindow.xaml.cs
+++ b/Content.Client/Administration/UI/BwoinkWindow.xaml.cs
@@ -3,6 +3,7 @@ using System.Linq;
using System.Text;
using System.Threading;
using Content.Client.Administration.Managers;
+using Content.Client.Administration.Systems;
using Content.Client.Administration.UI.Tabs.AdminTab;
using Content.Client.Stylesheets;
using Content.Shared.Administration;
diff --git a/Content.Client/Administration/UI/CustomControls/BwoinkPanel.xaml.cs b/Content.Client/Administration/UI/CustomControls/BwoinkPanel.xaml.cs
index 582147b69b..70714b9247 100644
--- a/Content.Client/Administration/UI/CustomControls/BwoinkPanel.xaml.cs
+++ b/Content.Client/Administration/UI/CustomControls/BwoinkPanel.xaml.cs
@@ -1,5 +1,6 @@
#nullable enable
using System;
+using Content.Client.Administration.Systems;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
index 48f58a6efa..f2579348e0 100644
--- a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
+++ b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Content.Client.Administration.Systems;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml
new file mode 100644
index 0000000000..2a13604e99
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
new file mode 100644
index 0000000000..b581a21715
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs
@@ -0,0 +1,73 @@
+using System.Linq;
+using Content.Client.Station;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
+
+[GenerateTypedNameReferences]
+public sealed partial class ObjectsTab : Control
+{
+ [Dependency] private readonly EntityManager _entityManager = default!;
+
+ private readonly List _objects = new();
+ private List _selections = new();
+
+ public event Action? OnEntryPressed;
+
+ public ObjectsTab()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ ObjectTypeOptions.OnItemSelected += ev =>
+ {
+ ObjectTypeOptions.SelectId(ev.Id);
+ RefreshObjectList(_selections[ev.Id]);
+ };
+
+ foreach (var type in Enum.GetValues(typeof(ObjectsTabSelection)))
+ {
+ _selections.Add((ObjectsTabSelection)type!);
+ ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type!)!);
+ }
+
+ RefreshObjectList(_selections[ObjectTypeOptions.SelectedId]);
+ }
+
+ private void RefreshObjectList(ObjectsTabSelection selection)
+ {
+ var entities = selection switch
+ {
+ ObjectsTabSelection.Stations => _entityManager.EntitySysManager.GetEntitySystem().Stations.ToList(),
+ ObjectsTabSelection.Grids => _entityManager.EntityQuery(true).Select(x => x.Owner).ToList(),
+ ObjectsTabSelection.Maps => _entityManager.EntityQuery(true).Select(x => x.Owner).ToList(),
+ _ => throw new ArgumentOutOfRangeException(nameof(selection), selection, null)
+ };
+
+ foreach (var control in _objects)
+ {
+ ObjectList.RemoveChild(control);
+ }
+
+ _objects.Clear();
+
+ foreach (var entity in entities)
+ {
+ var ctrl = new ObjectsTabEntry(_entityManager.GetComponent(entity).EntityName, entity);
+ _objects.Add(ctrl);
+ ObjectList.AddChild(ctrl);
+ ctrl.OnPressed += args => OnEntryPressed?.Invoke(args);
+ }
+ }
+
+ private enum ObjectsTabSelection
+ {
+ Grids,
+ Maps,
+ Stations,
+ }
+}
+
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml
new file mode 100644
index 0000000000..92d5278ab5
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs
new file mode 100644
index 0000000000..98cfe53af1
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs
@@ -0,0 +1,19 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
+
+[GenerateTypedNameReferences]
+public sealed partial class ObjectsTabEntry : ContainerButton
+{
+ public EntityUid AssocEntity;
+
+ public ObjectsTabEntry(string name, EntityUid euid)
+ {
+ RobustXamlLoader.Load(this);
+ AssocEntity = euid;
+ EIDLabel.Text = euid.ToString();
+ NameLabel.Text = name;
+ }
+}
diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs
index 84a4817da6..c7bd84f767 100644
--- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs
+++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs
@@ -1,3 +1,4 @@
+using Content.Client.Administration.Systems;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
diff --git a/Content.Client/Commands/OpenAHelpCommand.cs b/Content.Client/Commands/OpenAHelpCommand.cs
index 6cff7c08eb..5d6ecfaed1 100644
--- a/Content.Client/Commands/OpenAHelpCommand.cs
+++ b/Content.Client/Commands/OpenAHelpCommand.cs
@@ -1,5 +1,6 @@
using System;
using Content.Client.Administration;
+using Content.Client.Administration.Systems;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
diff --git a/Content.Client/HUD/GameHud.cs b/Content.Client/HUD/GameHud.cs
index e8c91826d7..5b22c04aa0 100644
--- a/Content.Client/HUD/GameHud.cs
+++ b/Content.Client/HUD/GameHud.cs
@@ -3,6 +3,7 @@ using System.Linq;
using Content.Client.HUD.UI;
using Content.Client.Info;
using Content.Client.Administration;
+using Content.Client.Administration.Systems;
using Content.Client.Resources;
using Content.Client.Targeting;
using Content.Shared.CCVar;
diff --git a/Content.Client/Station/StationSystem.cs b/Content.Client/Station/StationSystem.cs
new file mode 100644
index 0000000000..85c9272066
--- /dev/null
+++ b/Content.Client/Station/StationSystem.cs
@@ -0,0 +1,32 @@
+using Content.Shared.Station;
+
+namespace Content.Client.Station;
+
+///
+/// This handles letting the client know stations are a thing. Only really used by an admin menu.
+///
+public sealed class StationSystem : EntitySystem
+{
+
+ private readonly HashSet _stations = new();
+
+ ///
+ /// All stations that currently exist.
+ ///
+ ///
+ /// I'd have this just invoke an entity query, but we're on the client and the client barely knows about stations.
+ ///
+ public IReadOnlySet Stations => _stations;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeNetworkEvent(StationsUpdated);
+ }
+
+ private void StationsUpdated(StationsUpdatedEvent ev)
+ {
+ _stations.Clear();
+ _stations.UnionWith(ev.Stations);
+ }
+}
diff --git a/Content.Client/UserInterface/FancyWindow.xaml b/Content.Client/UserInterface/FancyWindow.xaml
index d9306ad4c2..bde9cb8884 100644
--- a/Content.Client/UserInterface/FancyWindow.xaml
+++ b/Content.Client/UserInterface/FancyWindow.xaml
@@ -1,4 +1,5 @@
-
@@ -15,6 +16,6 @@
-
+
diff --git a/Content.Server/Administration/Components/DisarmProneComponent.cs b/Content.Server/Administration/Components/DisarmProneComponent.cs
new file mode 100644
index 0000000000..d20a349a23
--- /dev/null
+++ b/Content.Server/Administration/Components/DisarmProneComponent.cs
@@ -0,0 +1,7 @@
+namespace Content.Server.Administration.Components;
+
+///
+/// This is used for forcing someone to be disarmed 100% of the time.
+///
+[RegisterComponent]
+public sealed class DisarmProneComponent : Component { }
diff --git a/Content.Server/Administration/Components/HeadstandComponent.cs b/Content.Server/Administration/Components/HeadstandComponent.cs
new file mode 100644
index 0000000000..0b71311281
--- /dev/null
+++ b/Content.Server/Administration/Components/HeadstandComponent.cs
@@ -0,0 +1,10 @@
+using Content.Shared.Administration.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Server.Administration.Components;
+
+[RegisterComponent, NetworkedComponent]
+public sealed class HeadstandComponent : SharedHeadstandComponent
+{
+
+}
diff --git a/Content.Server/Administration/Components/StationInfiniteBatteryTargetComponent.cs b/Content.Server/Administration/Components/StationInfiniteBatteryTargetComponent.cs
new file mode 100644
index 0000000000..2ef7c26c79
--- /dev/null
+++ b/Content.Server/Administration/Components/StationInfiniteBatteryTargetComponent.cs
@@ -0,0 +1,10 @@
+namespace Content.Server.Administration.Components;
+
+///
+/// This is used for the admin map-wide/station-wide/grid-wide infinite power trick.
+///
+[RegisterComponent]
+public sealed class StationInfiniteBatteryTargetComponent : Component
+{
+
+}
diff --git a/Content.Server/Administration/QuickDialogSystem.OpenDialog.cs b/Content.Server/Administration/QuickDialogSystem.OpenDialog.cs
new file mode 100644
index 0000000000..521bee776f
--- /dev/null
+++ b/Content.Server/Administration/QuickDialogSystem.OpenDialog.cs
@@ -0,0 +1,176 @@
+using Content.Shared.Administration;
+using JetBrains.Annotations;
+using Robust.Server.Player;
+
+namespace Content.Server.Administration;
+
+public sealed partial class QuickDialogSystem
+{
+ ///
+ /// Opens a dialog for the given client, allowing them to enter in the desired data.
+ ///
+ /// Client to show a dialog for.
+ /// Title of the dialog.
+ /// The prompt.
+ /// The action to execute upon Ok being pressed.
+ /// The action to execute upon the dialog being cancelled.
+ /// Type of the input.
+ [PublicAPI]
+ public void OpenDialog(IPlayerSession session, string title, string prompt, Action okAction,
+ Action? cancelAction = null)
+ {
+ OpenDialogInternal(
+ session,
+ title,
+ new List
+ {
+ new("1", TypeToEntryType(typeof(T1)), prompt)
+ },
+ QuickDialogButtonFlag.OkButton | QuickDialogButtonFlag.CancelButton,
+ (ev =>
+ {
+ if (TryParseQuickDialog(TypeToEntryType(typeof(T1)), ev.Responses["1"], out var v1))
+ okAction.Invoke(v1);
+ else
+ {
+ session.ConnectedClient.Disconnect("Replied with invalid quick dialog data.");
+ cancelAction?.Invoke();
+ }
+ }),
+ cancelAction ?? (() => { })
+ );
+ }
+
+ ///
+ /// Opens a dialog for the given client, allowing them to enter in the desired data.
+ ///
+ /// Client to show a dialog for.
+ /// Title of the dialog.
+ /// The first prompt.
+ /// The second prompt.
+ /// The action to execute upon Ok being pressed.
+ /// The action to execute upon the dialog being cancelled.
+ /// Type of the first input.
+ /// Type of the second input.
+ [PublicAPI]
+ public void OpenDialog(IPlayerSession session, string title, string prompt1, string prompt2,
+ Action okAction, Action? cancelAction = null)
+ {
+ OpenDialogInternal(
+ session,
+ title,
+ new List
+ {
+ new("1", TypeToEntryType(typeof(T1)), prompt1),
+ new("2", TypeToEntryType(typeof(T2)), prompt2)
+ },
+ QuickDialogButtonFlag.OkButton | QuickDialogButtonFlag.CancelButton,
+ (ev =>
+ {
+
+ if (TryParseQuickDialog(TypeToEntryType(typeof(T1)), ev.Responses["1"], out var v1) &&
+ TryParseQuickDialog(TypeToEntryType(typeof(T2)), ev.Responses["2"], out var v2)
+ )
+ okAction.Invoke(v1, v2);
+ else
+ {
+ session.ConnectedClient.Disconnect("Replied with invalid quick dialog data.");
+ cancelAction?.Invoke();
+ }
+ }),
+ cancelAction ?? (() => { })
+ );
+ }
+
+ ///
+ /// Opens a dialog for the given client, allowing them to enter in the desired data.
+ ///
+ /// Client to show a dialog for.
+ /// Title of the dialog.
+ /// The first prompt.
+ /// The second prompt.
+ /// The third prompt.
+ /// The action to execute upon Ok being pressed.
+ /// The action to execute upon the dialog being cancelled.
+ /// Type of the first input.
+ /// Type of the second input.
+ /// Type of the third input.
+ [PublicAPI]
+ public void OpenDialog(IPlayerSession session, string title, string prompt1, string prompt2,
+ string prompt3, Action okAction, Action? cancelAction = null)
+ {
+ OpenDialogInternal(
+ session,
+ title,
+ new List
+ {
+ new("1", TypeToEntryType(typeof(T1)), prompt1),
+ new("2", TypeToEntryType(typeof(T2)), prompt2),
+ new("3", TypeToEntryType(typeof(T3)), prompt3)
+ },
+ QuickDialogButtonFlag.OkButton | QuickDialogButtonFlag.CancelButton,
+ (ev =>
+ {
+ if (TryParseQuickDialog(TypeToEntryType(typeof(T1)), ev.Responses["1"], out var v1) &&
+ TryParseQuickDialog(TypeToEntryType(typeof(T2)), ev.Responses["2"], out var v2) &&
+ TryParseQuickDialog(TypeToEntryType(typeof(T3)), ev.Responses["3"], out var v3)
+ )
+ okAction.Invoke(v1, v2, v3);
+ else
+ {
+ session.ConnectedClient.Disconnect("Replied with invalid quick dialog data.");
+ cancelAction?.Invoke();
+ }
+ }),
+ cancelAction ?? (() => { })
+ );
+ }
+
+ ///
+ /// Opens a dialog for the given client, allowing them to enter in the desired data.
+ ///
+ /// Client to show a dialog for.
+ /// Title of the dialog.
+ /// The first prompt.
+ /// The second prompt.
+ /// The third prompt.
+ /// The fourth prompt.
+ /// The action to execute upon Ok being pressed.
+ /// The action to execute upon the dialog being cancelled.
+ /// Type of the first input.
+ /// Type of the second input.
+ /// Type of the third input.
+ /// Type of the fourth input.
+ [PublicAPI]
+ public void OpenDialog(IPlayerSession session, string title, string prompt1, string prompt2,
+ string prompt3, string prompt4, Action okAction, Action? cancelAction = null)
+ {
+ OpenDialogInternal(
+ session,
+ title,
+ new List
+ {
+ new("1", TypeToEntryType(typeof(T1)), prompt1),
+ new("2", TypeToEntryType(typeof(T2)), prompt2),
+ new("3", TypeToEntryType(typeof(T3)), prompt3),
+ new("4", TypeToEntryType(typeof(T4)), prompt4),
+ },
+ QuickDialogButtonFlag.OkButton | QuickDialogButtonFlag.CancelButton,
+ (ev =>
+ {
+ if (TryParseQuickDialog(TypeToEntryType(typeof(T1)), ev.Responses["1"], out var v1) &&
+ TryParseQuickDialog(TypeToEntryType(typeof(T2)), ev.Responses["2"], out var v2) &&
+ TryParseQuickDialog(TypeToEntryType(typeof(T3)), ev.Responses["3"], out var v3) &&
+ TryParseQuickDialog(TypeToEntryType(typeof(T4)), ev.Responses["4"], out var v4)
+ )
+ okAction.Invoke(v1, v2, v3, v4);
+ else
+ {
+ session.ConnectedClient.Disconnect("Replied with invalid quick dialog data.");
+ cancelAction?.Invoke();
+ }
+ }),
+ cancelAction ?? (() => { })
+ );
+ }
+}
diff --git a/Content.Server/Administration/QuickDialogSystem.cs b/Content.Server/Administration/QuickDialogSystem.cs
new file mode 100644
index 0000000000..b2037d4da3
--- /dev/null
+++ b/Content.Server/Administration/QuickDialogSystem.cs
@@ -0,0 +1,176 @@
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Administration;
+using Robust.Server.Player;
+using Robust.Shared.Enums;
+using Robust.Shared.Network;
+using Robust.Shared.Player;
+
+namespace Content.Server.Administration;
+
+///
+/// This handles the server portion of quick dialogs, including opening them.
+///
+public sealed partial class QuickDialogSystem : EntitySystem
+{
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+
+ ///
+ /// Contains the success/cancel actions for a dialog.
+ ///
+ private readonly Dictionary okAction, Action cancelAction)> _openDialogs = new();
+ private readonly Dictionary> _openDialogsByUser = new();
+
+ private int _nextDialogId = 1;
+
+ ///
+ public override void Initialize()
+ {
+ _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
+
+ SubscribeNetworkEvent(Handler);
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ _playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged;
+ }
+
+ private void Handler(QuickDialogResponseEvent msg, EntitySessionEventArgs args)
+ {
+ if (!_openDialogs.ContainsKey(msg.DialogId) || !_openDialogsByUser[args.SenderSession.UserId].Contains(msg.DialogId))
+ {
+ args.SenderSession.ConnectedClient.Disconnect($"Replied with invalid quick dialog data with id {msg.DialogId}.");
+ return;
+ }
+
+ switch (msg.ButtonPressed)
+ {
+ case QuickDialogButtonFlag.OkButton:
+ _openDialogs[msg.DialogId].okAction.Invoke(msg);
+ break;
+ case QuickDialogButtonFlag.CancelButton:
+ _openDialogs[msg.DialogId].cancelAction.Invoke();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ _openDialogs.Remove(msg.DialogId);
+ _openDialogsByUser[args.SenderSession.UserId].Remove(msg.DialogId);
+ }
+
+ private int GetDialogId()
+ {
+ return _nextDialogId++;
+ }
+
+ private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
+ {
+ if (e.NewStatus != SessionStatus.Disconnected && e.NewStatus != SessionStatus.Zombie)
+ return;
+
+ var user = e.Session.UserId;
+
+ if (!_openDialogsByUser.ContainsKey(user))
+ return;
+
+ foreach (var dialogId in _openDialogsByUser[user])
+ {
+ _openDialogs[dialogId].cancelAction.Invoke();
+ _openDialogs.Remove(dialogId);
+ }
+
+ _openDialogsByUser.Remove(user);
+ }
+
+ private void OpenDialogInternal(IPlayerSession session, string title, List entries, QuickDialogButtonFlag buttons, Action okAction, Action cancelAction)
+ {
+ var did = GetDialogId();
+ RaiseNetworkEvent(
+ new QuickDialogOpenEvent(
+ title,
+ entries,
+ did,
+ buttons),
+ Filter.SinglePlayer(session)
+ );
+
+ _openDialogs.Add(did, (okAction, cancelAction));
+ if (!_openDialogsByUser.ContainsKey(session.UserId))
+ _openDialogsByUser.Add(session.UserId, new List());
+
+ _openDialogsByUser[session.UserId].Add(did);
+ }
+
+ private bool TryParseQuickDialog(QuickDialogEntryType entryType, string input, [NotNullWhen(true)] out T? output)
+ {
+ switch (entryType)
+ {
+ case QuickDialogEntryType.Integer:
+ {
+ var result = int.TryParse(input, out var val);
+ output = (T?) (object?) val;
+ return result;
+ }
+ case QuickDialogEntryType.Float:
+ {
+ var result = float.TryParse(input, out var val);
+ output = (T?) (object?) val;
+ return result;
+ }
+ case QuickDialogEntryType.ShortText:
+ {
+ if (input.Length > 100)
+ {
+ output = default;
+ return false;
+ }
+
+ output = (T?) (object?) input;
+ return output is not null;
+ }
+ case QuickDialogEntryType.LongText:
+ {
+ if (input.Length > 2000)
+ {
+ output = default;
+ return false;
+ }
+
+ output = (T?) (object?) input;
+ return output is not null;
+ }
+ default:
+ throw new ArgumentOutOfRangeException(nameof(entryType), entryType, null);
+ }
+ }
+
+ private QuickDialogEntryType TypeToEntryType(Type T)
+ {
+ if (T == typeof(int) || T == typeof(uint) || T == typeof(long) || T == typeof(ulong))
+ {
+ return QuickDialogEntryType.Integer;
+ }
+ else if (T == typeof(float) || T == typeof(double))
+ {
+ return QuickDialogEntryType.Float;
+ }
+ else if (T == typeof(string)) // People are more likely to notice the input box is too short than they are to notice it's too long.
+ {
+ return QuickDialogEntryType.ShortText;
+ } else if (T == typeof(LongString))
+ {
+ return QuickDialogEntryType.LongText;
+ }
+
+ throw new ArgumentException($"Tried to open a dialog with unsupported type {T}.");
+ }
+}
+
+///
+/// A type used with quick dialogs to indicate you want a large entry window for text and not a short one.
+///
+/// The string retrieved.
+public record struct LongString(string String);
diff --git a/Content.Server/Administration/Systems/AdminTestArenaSystem.cs b/Content.Server/Administration/Systems/AdminTestArenaSystem.cs
new file mode 100644
index 0000000000..561e371595
--- /dev/null
+++ b/Content.Server/Administration/Systems/AdminTestArenaSystem.cs
@@ -0,0 +1,33 @@
+using System.Linq;
+using Robust.Server.Maps;
+using Robust.Server.Player;
+using Robust.Shared.Map;
+using Robust.Shared.Network;
+
+namespace Content.Server.Administration.Systems;
+
+///
+/// This handles the administrative test arena maps, and loading them.
+///
+public sealed class AdminTestArenaSystem : EntitySystem
+{
+ [Dependency] private readonly IMapLoader _mapLoader = default!;
+ [Dependency] private readonly IMapManager _mapManager = default!;
+
+ public const string ArenaMapPath = "/Maps/Test/admin_test_arena.yml";
+
+ public Dictionary ArenaMap { get; private set; } = new();
+ public Dictionary ArenaGrid { get; private set; } = new();
+
+ public (EntityUid, EntityUid) AssertArenaLoaded(IPlayerSession admin)
+ {
+ if (ArenaMap.TryGetValue(admin.UserId, out var arenaMap) && !Deleted(arenaMap) && !Terminating(arenaMap))
+ return (arenaMap, ArenaGrid[admin.UserId]);
+
+ ArenaMap[admin.UserId] = _mapManager.GetMapEntityId(_mapManager.CreateMap());
+ var (grids, _) = _mapLoader.LoadMap(Comp(ArenaMap[admin.UserId]).WorldMap, ArenaMapPath);
+ ArenaGrid[admin.UserId] = grids.First();
+
+ return (ArenaMap[admin.UserId], ArenaGrid[admin.UserId]);
+ }
+}
diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
index 2a44779a6b..7806f636bd 100644
--- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
+++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
@@ -19,8 +19,12 @@ using Content.Server.Nutrition.EntitySystems;
using Content.Server.Pointing.Components;
using Content.Server.Polymorph.Systems;
using Content.Server.Popups;
+using Content.Server.Speech.Components;
+using Content.Server.Storage.Components;
+using Content.Server.Storage.EntitySystems;
using Content.Server.Tabletop;
using Content.Server.Tabletop.Components;
+using Content.Server.Tools.Systems;
using Content.Shared.Administration;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
@@ -50,20 +54,22 @@ namespace Content.Server.Administration.Systems;
public sealed partial class AdminVerbSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly GhostKickManager _ghostKickManager = default!;
- [Dependency] private readonly PolymorphableSystem _polymorphableSystem = default!;
- [Dependency] private readonly InventorySystem _inventorySystem = default!;
- [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!;
- [Dependency] private readonly CreamPieSystem _creamPieSystem = default!;
- [Dependency] private readonly DiseaseSystem _diseaseSystem = default!;
- [Dependency] private readonly TabletopSystem _tabletopSystem = default!;
- [Dependency] private readonly ExplosionSystem _explosionSystem = default!;
- [Dependency] private readonly FlammableSystem _flammableSystem = default!;
- [Dependency] private readonly GodmodeSystem _godmodeSystem = default!;
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
- [Dependency] private readonly VomitSystem _vomitSystem = default!;
+ [Dependency] private readonly CreamPieSystem _creamPieSystem = default!;
+ [Dependency] private readonly DiseaseSystem _diseaseSystem = default!;
+ [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!;
+ [Dependency] private readonly EntityStorageSystem _entityStorageSystem = default!;
+ [Dependency] private readonly ExplosionSystem _explosionSystem = default!;
+ [Dependency] private readonly FlammableSystem _flammableSystem = default!;
+ [Dependency] private readonly GhostKickManager _ghostKickManager = default!;
+ [Dependency] private readonly GodmodeSystem _godmodeSystem = default!;
+ [Dependency] private readonly InventorySystem _inventorySystem = default!;
+ [Dependency] private readonly PolymorphableSystem _polymorphableSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
+ [Dependency] private readonly TabletopSystem _tabletopSystem = default!;
+ [Dependency] private readonly VomitSystem _vomitSystem = default!;
+ [Dependency] private readonly WeldableSystem _weldableSystem = default!;
// All smite verbs have names so invokeverb works.
private void AddSmiteVerbs(GetVerbsEvent args)
@@ -76,6 +82,10 @@ public sealed partial class AdminVerbSystem
if (!_adminManager.HasAdminFlag(player, AdminFlags.Fun))
return;
+ // 1984.
+ if (HasComp(args.Target) || HasComp(args.Target))
+ return;
+
Verb explode = new()
{
Text = "Explode",
@@ -95,7 +105,7 @@ public sealed partial class AdminVerbSystem
}
},
Impact = LogImpact.Extreme,
- Message = "Explode them.",
+ Message = Loc.GetString("admin-smite-explode-description")
};
args.Verbs.Add(explode);
@@ -121,7 +131,7 @@ public sealed partial class AdminVerbSystem
xform.WorldRotation = Angle.Zero;
},
Impact = LogImpact.Extreme,
- Message = "Banishment to the Chess Dimension.",
+ Message = Loc.GetString("admin-smite-chess-dimension-description")
};
args.Verbs.Add(chess);
@@ -144,7 +154,7 @@ public sealed partial class AdminVerbSystem
Filter.PvsExcept(args.Target), PopupType.MediumCaution);
},
Impact = LogImpact.Extreme,
- Message = "Makes them burn.",
+ Message = Loc.GetString("admin-smite-set-alight-description")
};
args.Verbs.Add(flames);
}
@@ -159,10 +169,24 @@ public sealed partial class AdminVerbSystem
_polymorphableSystem.PolymorphEntity(args.Target, "AdminMonkeySmite");
},
Impact = LogImpact.Extreme,
- Message = "Monkey mode.",
+ Message = Loc.GetString("admin-smite-monkeyify-description")
};
args.Verbs.Add(monkey);
+ Verb disposalBin = new()
+ {
+ Text = "Garbage Can",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Structures/Piping/disposal.rsi/disposal.png",
+ Act = () =>
+ {
+ _polymorphableSystem.PolymorphEntity(args.Target, "AdminDisposalsSmite");
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-garbage-can-description")
+ };
+ args.Verbs.Add(disposalBin);
+
if (TryComp(args.Target, out var carrier))
{
Verb lungCancer = new()
@@ -176,7 +200,7 @@ public sealed partial class AdminVerbSystem
1.0f, true);
},
Impact = LogImpact.Extreme,
- Message = "Stage IIIA Lung Cancer, for when they really like the hit show Breaking Bad.",
+ Message = Loc.GetString("admin-smite-lung-cancer-description")
};
args.Verbs.Add(lungCancer);
}
@@ -192,11 +216,11 @@ public sealed partial class AdminVerbSystem
Act = () =>
{
int damageToDeal;
- var critState = mobState._highestToLowestStates.Where(x => x.Value.IsCritical()).FirstOrNull();
+ var critState = mobState._highestToLowestStates.Where(x => x.Value == DamageState.Critical).FirstOrNull();
if (critState is null)
{
// We can't crit them so try killing them.
- var deadState = mobState._highestToLowestStates.Where(x => x.Value.IsDead()).FirstOrNull();
+ var deadState = mobState._highestToLowestStates.Where(x => x.Value == DamageState.Dead).FirstOrNull();
if (deadState is null)
return; // whelp.
@@ -225,7 +249,7 @@ public sealed partial class AdminVerbSystem
TimeSpan.FromSeconds(30), true);
},
Impact = LogImpact.Extreme,
- Message = "Electrocutes them, rendering anything they were wearing useless.",
+ Message = Loc.GetString("admin-smite-electrocute-description")
};
args.Verbs.Add(hardElectrocute);
}
@@ -242,7 +266,7 @@ public sealed partial class AdminVerbSystem
_creamPieSystem.SetCreamPied(args.Target, creamPied, true);
},
Impact = LogImpact.Extreme,
- Message = "A cream pie, condensed into a button.",
+ Message = Loc.GetString("admin-smite-creampie-description")
};
args.Verbs.Add(creamPie);
}
@@ -264,7 +288,7 @@ public sealed partial class AdminVerbSystem
Filter.PvsExcept(args.Target), PopupType.MediumCaution);
},
Impact = LogImpact.Extreme,
- Message = "Removes their blood. All of it.",
+ Message = Loc.GetString("admin-smite-remove-blood-description")
};
args.Verbs.Add(bloodRemoval);
}
@@ -297,15 +321,15 @@ public sealed partial class AdminVerbSystem
Filter.PvsExcept(args.Target), PopupType.MediumCaution);
},
Impact = LogImpact.Extreme,
- Message = "Causes them to vomit, including their internal organs.",
+ Message = Loc.GetString("admin-smite-vomit-organs-description")
};
args.Verbs.Add(vomitOrgans);
- Verb handRemoval = new()
+ Verb handsRemoval = new()
{
Text = "Remove hands",
Category = VerbCategory.Smite,
- IconTexture = "/Textures/Interface/fist.svg.192dpi.png",
+ IconTexture = "/Textures/Interface/AdminActions/remove-hands.png",
Act = () =>
{
var baseXform = Transform(args.Target);
@@ -320,9 +344,81 @@ public sealed partial class AdminVerbSystem
Filter.PvsExcept(args.Target), PopupType.Medium);
},
Impact = LogImpact.Extreme,
- Message = "Removes the target's hands.",
+ Message = Loc.GetString("admin-smite-remove-hands-description")
+ };
+ args.Verbs.Add(handsRemoval);
+
+ Verb handRemoval = new()
+ {
+ Text = "Remove hands",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Interface/AdminActions/remove-hand.png",
+ Act = () =>
+ {
+ var baseXform = Transform(args.Target);
+ foreach (var part in body.GetPartsOfType(BodyPartType.Hand))
+ {
+ body.RemovePart(part);
+ Transform(part.Owner).Coordinates = baseXform.Coordinates;
+ break;
+ }
+ _popupSystem.PopupEntity(Loc.GetString("admin-smite-remove-hands-self"), args.Target,
+ Filter.Entities(args.Target), PopupType.LargeCaution);
+ _popupSystem.PopupCoordinates(Loc.GetString("admin-smite-remove-hands-others", ("name", args.Target)), baseXform.Coordinates,
+ Filter.PvsExcept(args.Target), PopupType.Medium);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-remove-hand-description")
};
args.Verbs.Add(handRemoval);
+
+ Verb stomachRemoval = new()
+ {
+ Text = "Stomach Removal",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Mobs/Species/Human/organs.rsi/stomach.png",
+ Act = () =>
+ {
+ foreach (var part in body.Parts)
+ {
+ foreach (var mechanism in part.Key.Mechanisms)
+ {
+ if (HasComp(mechanism.Owner))
+ QueueDel(mechanism.Owner);
+ }
+ }
+
+ _popupSystem.PopupEntity(Loc.GetString("admin-smite-stomach-removal-self"), args.Target,
+ Filter.Entities(args.Target), PopupType.LargeCaution);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-stomach-removal-description"),
+ };
+ args.Verbs.Add(stomachRemoval);
+
+ Verb lungRemoval = new()
+ {
+ Text = "Lungs Removal",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Mobs/Species/Human/organs.rsi/lung-r.png",
+ Act = () =>
+ {
+ foreach (var part in body.Parts)
+ {
+ foreach (var mechanism in part.Key.Mechanisms)
+ {
+ if (HasComp(mechanism.Owner))
+ QueueDel(mechanism.Owner);
+ }
+ }
+
+ _popupSystem.PopupEntity(Loc.GetString("admin-smite-lung-removal-self"), args.Target,
+ Filter.Entities(args.Target), PopupType.LargeCaution);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-lung-removal-description"),
+ };
+ args.Verbs.Add(lungRemoval);
}
if (TryComp(args.Target, out var physics))
@@ -353,8 +449,7 @@ public sealed partial class AdminVerbSystem
physics.AngularDamping = 0.0f;
},
Impact = LogImpact.Extreme,
- Message =
- "Turns them into a super bouncy ball, flinging them around until they clip through the station into the abyss.",
+ Message = Loc.GetString("admin-smite-pinball-description")
};
args.Verbs.Add(pinball);
@@ -382,7 +477,7 @@ public sealed partial class AdminVerbSystem
physics.AngularDamping = 0.0f;
},
Impact = LogImpact.Extreme,
- Message = "Banishes them into the depths of space by turning on no-clip and tossing them.",
+ Message = Loc.GetString("admin-smite-yeet-description")
};
args.Verbs.Add(yeet);
}
@@ -397,10 +492,24 @@ public sealed partial class AdminVerbSystem
_polymorphableSystem.PolymorphEntity(args.Target, "AdminBreadSmite");
},
Impact = LogImpact.Extreme,
- Message = "It turns them into bread. Really. That's all it does.",
+ Message = Loc.GetString("admin-smite-become-bread-description")
};
args.Verbs.Add(bread);
+ Verb mouse = new()
+ {
+ Text = "Become Mouse",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Mobs/Animals/mouse.rsi/icon-0.png",
+ Act = () =>
+ {
+ _polymorphableSystem.PolymorphEntity(args.Target, "AdminMouseSmite");
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-become-mouse-description")
+ };
+ args.Verbs.Add(mouse);
+
if (TryComp(args.Target, out var actorComponent))
{
Verb ghostKick = new()
@@ -413,7 +522,7 @@ public sealed partial class AdminVerbSystem
_ghostKickManager.DoDisconnect(actorComponent.PlayerSession.ConnectedClient, "Smitten.");
},
Impact = LogImpact.Extreme,
- Message = "Silently kicks the user, dropping their connection.",
+ Message = Loc.GetString("admin-smite-ghostkick-description")
};
args.Verbs.Add(ghostKick);
}
@@ -432,7 +541,7 @@ public sealed partial class AdminVerbSystem
_inventorySystem.TryEquip(args.Target, ears, "head", true, true, false, inventory);
},
Impact = LogImpact.Extreme,
- Message = "Forcibly adds cat ears. There is no escape.",
+ Message = Loc.GetString("admin-smite-nyanify-description")
};
args.Verbs.Add(nyanify);
@@ -446,7 +555,7 @@ public sealed partial class AdminVerbSystem
EnsureComp(args.Target);
},
Impact = LogImpact.Extreme,
- Message = "Marks a player for death by their fellows.",
+ Message = Loc.GetString("admin-smite-kill-sign-description")
};
args.Verbs.Add(killSign);
@@ -466,9 +575,30 @@ public sealed partial class AdminVerbSystem
});
},
Impact = LogImpact.Extreme,
- Message = "Clowns them. The suit cannot be removed.",
+ Message = Loc.GetString("admin-smite-clown-description")
};
args.Verbs.Add(clown);
+
+ Verb maiden = new()
+ {
+ Text = "Maid",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Clothing/Uniforms/Jumpskirt/janimaid.rsi/icon.png",
+ Act = () =>
+ {
+ SetOutfitCommand.SetOutfit(args.Target, "JanitorMaidGear", EntityManager, (_, clothing) =>
+ {
+ if (HasComp(clothing))
+ EnsureComp(clothing);
+ EnsureComp(args.Target);
+ });
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-maid-description")
+ };
+ args.Verbs.Add(maiden);
+
+
}
Verb angerPointingArrows = new()
@@ -481,7 +611,7 @@ public sealed partial class AdminVerbSystem
EnsureComp(args.Target);
},
Impact = LogImpact.Extreme,
- Message = "Angers the pointing arrows, causing them to assault this entity.",
+ Message = Loc.GetString("admin-smite-anger-pointing-arrows-description")
};
args.Verbs.Add(angerPointingArrows);
@@ -498,7 +628,7 @@ public sealed partial class AdminVerbSystem
Filter.Pvs(args.Target), PopupType.LargeCaution);
},
Impact = LogImpact.Extreme,
- Message = "Reduces the target to a small pile of ash.",
+ Message = Loc.GetString("admin-smite-dust-description"),
};
args.Verbs.Add(dust);
@@ -512,7 +642,7 @@ public sealed partial class AdminVerbSystem
EnsureComp(args.Target);
},
Impact = LogImpact.Extreme,
- Message = "Causes the target to randomly start buffering, freezing them in place for a short timespan while they load.",
+ Message = Loc.GetString("admin-smite-buffering-description"),
};
args.Verbs.Add(youtubeVideoSimulation);
@@ -526,7 +656,7 @@ public sealed partial class AdminVerbSystem
_polymorphableSystem.PolymorphEntity(args.Target, "AdminInstrumentSmite");
},
Impact = LogImpact.Extreme,
- Message = "It turns them into a supersynth. Really. That's all it does.",
+ Message = Loc.GetString("admin-smite-become-instrument-description"),
};
args.Verbs.Add(instrumentation);
@@ -539,10 +669,146 @@ public sealed partial class AdminVerbSystem
{
var grav = EnsureComp(args.Target);
grav.Weightless = true;
+
+ Dirty(grav);
},
Impact = LogImpact.Extreme,
- Message = "Grants them anti-gravity.",
+ Message = Loc.GetString("admin-smite-remove-gravity-description"),
};
args.Verbs.Add(noGravity);
+
+ Verb reptilian = new()
+ {
+ Text = "Reptilian Species Swap",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Objects/Fun/toys.rsi/plushie_lizard.png",
+ Act = () =>
+ {
+ _polymorphableSystem.PolymorphEntity(args.Target, "AdminLizardSmite");
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-reptilian-species-swap-description"),
+ };
+ args.Verbs.Add(reptilian);
+
+ Verb locker = new()
+ {
+ Text = "Locker stuff",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Structures/Storage/closet.rsi/generic.png",
+ Act = () =>
+ {
+ var xform = Transform(args.Target);
+ var locker = Spawn("ClosetMaintenance", xform.Coordinates);
+ if (TryComp(locker, out var storage))
+ {
+ _entityStorageSystem.ToggleOpen(args.Target, locker, storage);
+ _entityStorageSystem.Insert(args.Target, locker, storage);
+ _entityStorageSystem.ToggleOpen(args.Target, locker, storage);
+ }
+ _weldableSystem.ForceWeldedState(locker, true);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-locker-stuff-description"),
+ };
+ args.Verbs.Add(locker);
+
+ Verb headstand = new()
+ {
+ Text = "Headstand",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Interface/VerbIcons/refresh.svg.192dpi.png",
+ Act = () =>
+ {
+ EnsureComp(args.Target);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-headstand-description"),
+ };
+ args.Verbs.Add(headstand);
+
+ Verb zoomIn = new()
+ {
+ Text = "Zoom in",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Interface/AdminActions/zoom.png",
+ Act = () =>
+ {
+ var eye = EnsureComp(args.Target);
+
+ eye.Zoom *= Vector2.One * 0.2f;
+
+ Dirty(eye);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-zoom-in-description"),
+ };
+ args.Verbs.Add(zoomIn);
+
+ Verb flipEye = new()
+ {
+ Text = "Flip eye",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Interface/AdminActions/flip.png",
+ Act = () =>
+ {
+ var eye = EnsureComp(args.Target);
+
+ eye.Zoom *= -1;
+
+ Dirty(eye);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-flip-eye-description"),
+ };
+ args.Verbs.Add(flipEye);
+
+ Verb runWalkSwap = new()
+ {
+ Text = "Run Walk Swap",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Interface/AdminActions/run-walk-swap.png",
+ Act = () =>
+ {
+ var movementSpeed = EnsureComp(args.Target);
+ (movementSpeed.BaseSprintSpeed, movementSpeed.BaseWalkSpeed) = (movementSpeed.BaseWalkSpeed, movementSpeed.BaseSprintSpeed);
+
+ Dirty(movementSpeed);
+
+ _popupSystem.PopupEntity(Loc.GetString("admin-smite-run-walk-swap-prompt"), args.Target,
+ Filter.Entities(args.Target), PopupType.LargeCaution);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-run-walk-swap-description"),
+ };
+ args.Verbs.Add(runWalkSwap);
+
+ Verb backwardsAccent = new()
+ {
+ Text = "Speak Backwards",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Interface/AdminActions/help-backwards.png",
+ Act = () =>
+ {
+ EnsureComp(args.Target);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-speak-backwards-description"),
+ };
+ args.Verbs.Add(backwardsAccent);
+
+ Verb disarmProne = new()
+ {
+ Text = "Disarm Prone",
+ Category = VerbCategory.Smite,
+ IconTexture = "/Textures/Interface/Actions/disarm.png",
+ Act = () =>
+ {
+ EnsureComp(args.Target);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-smite-disarm-prone-description"),
+ };
+ args.Verbs.Add(disarmProne);
}
}
diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs
new file mode 100644
index 0000000000..08ac16cdb8
--- /dev/null
+++ b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs
@@ -0,0 +1,928 @@
+
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Server.Administration.Commands;
+using Content.Server.Administration.Components;
+using Content.Server.Atmos;
+using Content.Server.Atmos.Components;
+using Content.Server.Cargo.Components;
+using Content.Server.Cargo.Systems;
+using Content.Server.Doors.Components;
+using Content.Server.Doors.Systems;
+using Content.Server.Hands.Components;
+using Content.Server.Hands.Systems;
+using Content.Server.Power.Components;
+using Content.Server.Stack;
+using Content.Server.Station.Components;
+using Content.Server.Station.Systems;
+using Content.Shared.Access;
+using Content.Shared.Access.Components;
+using Content.Shared.Access.Systems;
+using Content.Shared.Administration;
+using Content.Shared.Atmos;
+using Content.Shared.Construction.Components;
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Damage;
+using Content.Shared.Database;
+using Content.Shared.Inventory;
+using Content.Shared.PDA;
+using Content.Shared.Verbs;
+using Content.Shared.Weapons.Ranged.Components;
+using Robust.Server.GameObjects;
+using Robust.Server.Physics;
+using Robust.Shared.Map;
+using Robust.Shared.Physics;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Administration.Systems;
+
+public sealed partial class AdminVerbSystem
+{
+ [Dependency] private readonly AirlockSystem _airlockSystem = default!;
+ [Dependency] private readonly StackSystem _stackSystem = default!;
+ [Dependency] private readonly AccessSystem _accessSystem = default!;
+ [Dependency] private readonly HandsSystem _handsSystem = default!;
+ [Dependency] private readonly QuickDialogSystem _quickDialog = default!;
+ [Dependency] private readonly AdminTestArenaSystem _adminTestArenaSystem = default!;
+ [Dependency] private readonly StationJobsSystem _stationJobsSystem = default!;
+ [Dependency] private readonly JointSystem _jointSystem = default!;
+
+ private void AddTricksVerbs(GetVerbsEvent args)
+ {
+ if (!EntityManager.TryGetComponent(args.User, out var actor))
+ return;
+
+ var player = actor.PlayerSession;
+
+ if (!_adminManager.HasAdminFlag(player, AdminFlags.Admin))
+ return;
+
+ if (_adminManager.HasAdminFlag(player, AdminFlags.Admin))
+ {
+ if (TryComp(args.Target, out var airlock))
+ {
+ Verb bolt = new()
+ {
+ Text = airlock.BoltsDown ? "Unbolt" : "Bolt",
+ Category = VerbCategory.Tricks,
+ IconTexture = airlock.BoltsDown
+ ? "/Textures/Interface/AdminActions/unbolt.png"
+ : "/Textures/Interface/AdminActions/bolt.png",
+ Act = () =>
+ {
+ airlock.SetBoltsWithAudio(!airlock.BoltsDown);
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString(airlock.BoltsDown
+ ? "admin-trick-unbolt-description"
+ : "admin-trick-bolt-description"),
+ Priority = (int) (airlock.BoltsDown ? TricksVerbPriorities.Unbolt : TricksVerbPriorities.Bolt),
+
+ };
+ args.Verbs.Add(bolt);
+
+ Verb emergencyAccess = new()
+ {
+ Text = airlock.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/emergency_access.png",
+ Act = () =>
+ {
+ _airlockSystem.ToggleEmergencyAccess(airlock);
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString(airlock.EmergencyAccess
+ ? "admin-trick-emergency-access-off-description"
+ : "admin-trick-emergency-access-on-description"),
+ Priority = (int) (airlock.EmergencyAccess
+ ? TricksVerbPriorities.EmergencyAccessOff
+ : TricksVerbPriorities.EmergencyAccessOn),
+ };
+ args.Verbs.Add(emergencyAccess);
+ }
+
+ if (HasComp(args.Target))
+ {
+ Verb rejuvenate = new()
+ {
+ Text = "Rejuvenate",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/rejuvenate.png",
+ Act = () =>
+ {
+ RejuvenateCommand.PerformRejuvenate(args.Target);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-rejuvenate-description"),
+ Priority = (int) TricksVerbPriorities.Rejuvenate,
+ };
+ args.Verbs.Add(rejuvenate);
+ }
+
+ if (!_godmodeSystem.HasGodmode(args.Target))
+ {
+ Verb makeIndestructible = new()
+ {
+ Text = "Make Indestructible",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/VerbIcons/plus.svg.192dpi.png",
+ Act = () =>
+ {
+ _godmodeSystem.EnableGodmode(args.Target);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-make-indestructible-description"),
+ Priority = (int) TricksVerbPriorities.MakeIndestructible,
+ };
+ args.Verbs.Add(makeIndestructible);
+ }
+ else
+ {
+ Verb makeVulnerable = new()
+ {
+ Text = "Make Vulnerable",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/VerbIcons/plus.svg.192dpi.png",
+ Act = () =>
+ {
+ _godmodeSystem.DisableGodmode(args.Target);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-make-vulnerable-description"),
+ Priority = (int) TricksVerbPriorities.MakeVulnerable,
+ };
+ args.Verbs.Add(makeVulnerable);
+ }
+
+ if (TryComp(args.Target, out var battery))
+ {
+ Verb refillBattery = new()
+ {
+ Text = "Refill Battery",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/fill_battery.png",
+ Act = () =>
+ {
+ battery.CurrentCharge = battery.MaxCharge;
+ Dirty(battery);
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-refill-battery-description"),
+ Priority = (int) TricksVerbPriorities.RefillBattery,
+ };
+ args.Verbs.Add(refillBattery);
+
+ Verb drainBattery = new()
+ {
+ Text = "Drain Battery",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/drain_battery.png",
+ Act = () =>
+ {
+ battery.CurrentCharge = 0;
+ Dirty(battery);
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-drain-battery-description"),
+ Priority = (int) TricksVerbPriorities.DrainBattery,
+ };
+ args.Verbs.Add(drainBattery);
+
+ Verb infiniteBattery = new()
+ {
+ Text = "Infinite Battery",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/infinite_battery.png",
+ Act = () =>
+ {
+ var recharger = EnsureComp(args.Target);
+ recharger.AutoRecharge = true;
+ recharger.AutoRechargeRate = battery.MaxCharge; // Instant refill.
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-infinite-battery-object-description"),
+ Priority = (int) TricksVerbPriorities.InfiniteBattery,
+ };
+ args.Verbs.Add(infiniteBattery);
+ }
+
+ if (TryComp(args.Target, out var anchor))
+ {
+ Verb blockUnanchor = new()
+ {
+ Text = "Block Unanchoring",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/VerbIcons/anchor.svg.192dpi.png",
+ Act = () =>
+ {
+ RemComp(args.Target, anchor);
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-block-unanchoring-description"),
+ Priority = (int) TricksVerbPriorities.BlockUnanchoring,
+ };
+ args.Verbs.Add(blockUnanchor);
+ }
+
+ if (TryComp(args.Target, out var tank))
+ {
+ Verb refillInternalsO2 = new()
+ {
+ Text = "Refill Internals Oxygen",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Tanks/oxygen.rsi/icon.png",
+ Act = () =>
+ {
+ RefillGasTank(args.Target, Gas.Oxygen, tank);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-internals-refill-oxygen-description"),
+ Priority = (int) TricksVerbPriorities.RefillOxygen,
+ };
+ args.Verbs.Add(refillInternalsO2);
+
+ Verb refillInternalsN2 = new()
+ {
+ Text = "Refill Internals Nitrogen",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Tanks/red.rsi/icon.png",
+ Act = () =>
+ {
+ RefillGasTank(args.Target, Gas.Nitrogen, tank);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-internals-refill-nitrogen-description"),
+ Priority = (int) TricksVerbPriorities.RefillNitrogen,
+ };
+ args.Verbs.Add(refillInternalsN2);
+
+ Verb refillInternalsPlasma = new()
+ {
+ Text = "Refill Internals Plasma",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Tanks/plasma.rsi/icon.png",
+ Act = () =>
+ {
+ RefillGasTank(args.Target, Gas.Plasma, tank);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-internals-refill-plasma-description"),
+ Priority = (int) TricksVerbPriorities.RefillPlasma,
+ };
+ args.Verbs.Add(refillInternalsPlasma);
+ }
+
+ if (TryComp(args.Target, out var inventory))
+ {
+ Verb refillInternalsO2 = new()
+ {
+ Text = "Refill Internals Oxygen",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Tanks/oxygen.rsi/icon.png",
+ Act = () =>
+ {
+ foreach (var slot in _inventorySystem.GetSlots(args.Target))
+ {
+ if (!_inventorySystem.TryGetSlotEntity(args.Target, slot.Name, out var entity))
+ continue;
+
+ if (!TryComp(entity, out var tank))
+ continue;
+
+ RefillGasTank(entity.Value, Gas.Oxygen, tank);
+ }
+
+ foreach (var held in _handsSystem.EnumerateHeld(args.Target))
+ {
+ if (!TryComp(held, out var tank))
+ continue;
+
+ RefillGasTank(held, Gas.Oxygen, tank);
+ }
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-internals-refill-oxygen-description"),
+ Priority = (int) TricksVerbPriorities.RefillOxygen,
+ };
+ args.Verbs.Add(refillInternalsO2);
+
+ Verb refillInternalsN2 = new()
+ {
+ Text = "Refill Internals Nitrogen",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Tanks/red.rsi/icon.png",
+ Act = () =>
+ {
+ foreach (var slot in _inventorySystem.GetSlots(args.Target))
+ {
+ if (!_inventorySystem.TryGetSlotEntity(args.Target, slot.Name, out var entity))
+ continue;
+
+ if (!TryComp(entity, out var tank))
+ continue;
+
+ RefillGasTank(entity.Value, Gas.Nitrogen, tank);
+ }
+
+ foreach (var held in _handsSystem.EnumerateHeld(args.Target))
+ {
+ if (!TryComp(held, out var tank))
+ continue;
+
+ RefillGasTank(held, Gas.Nitrogen, tank);
+ }
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-internals-refill-nitrogen-description"),
+ Priority = (int) TricksVerbPriorities.RefillNitrogen,
+ };
+ args.Verbs.Add(refillInternalsN2);
+
+ Verb refillInternalsPlasma = new()
+ {
+ Text = "Refill Internals Plasma",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Tanks/plasma.rsi/icon.png",
+ Act = () =>
+ {
+ foreach (var slot in _inventorySystem.GetSlots(args.Target))
+ {
+ if (!_inventorySystem.TryGetSlotEntity(args.Target, slot.Name, out var entity))
+ continue;
+
+ if (!TryComp(entity, out var tank))
+ continue;
+
+ RefillGasTank(entity.Value, Gas.Plasma, tank);
+ }
+
+ foreach (var held in _handsSystem.EnumerateHeld(args.Target))
+ {
+ if (!TryComp(held, out var tank))
+ continue;
+
+ RefillGasTank(held, Gas.Plasma, tank);
+ }
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-internals-refill-plasma-description"),
+ Priority = (int) TricksVerbPriorities.RefillPlasma,
+ };
+ args.Verbs.Add(refillInternalsPlasma);
+ }
+
+ Verb sendToTestArena = new()
+ {
+ Text = "Send to test arena",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png",
+ Act = () =>
+ {
+ var (_, arenaGrid) = _adminTestArenaSystem.AssertArenaLoaded(player);
+
+ Transform(args.Target).Coordinates = new EntityCoordinates(arenaGrid, Vector2.One);
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-send-to-test-arena-description"),
+ Priority = (int) TricksVerbPriorities.SendToTestArena,
+ };
+ args.Verbs.Add(sendToTestArena);
+
+ var activeId = FindActiveId(args.Target);
+
+ if (activeId is not null)
+ {
+ Verb grantAllAccess = new()
+ {
+ Text = "Grant All Access",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Misc/id_cards.rsi/centcom.png",
+ Act = () =>
+ {
+ GiveAllAccess(activeId.Value);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-grant-all-access-description"),
+ Priority = (int) TricksVerbPriorities.GrantAllAccess,
+ };
+ args.Verbs.Add(grantAllAccess);
+
+ Verb revokeAllAccess = new()
+ {
+ Text = "Revoke All Access",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Misc/id_cards.rsi/default.png",
+ Act = () =>
+ {
+ RevokeAllAccess(activeId.Value);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-revoke-all-access-description"),
+ Priority = (int) TricksVerbPriorities.RevokeAllAccess,
+ };
+ args.Verbs.Add(revokeAllAccess);
+ }
+
+ if (HasComp(args.Target))
+ {
+ Verb grantAllAccess = new()
+ {
+ Text = "Grant All Access",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Misc/id_cards.rsi/centcom.png",
+ Act = () =>
+ {
+ GiveAllAccess(args.Target);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-grant-all-access-description"),
+ Priority = (int) TricksVerbPriorities.GrantAllAccess,
+ };
+ args.Verbs.Add(grantAllAccess);
+
+ Verb revokeAllAccess = new()
+ {
+ Text = "Revoke All Access",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Misc/id_cards.rsi/default.png",
+ Act = () =>
+ {
+ RevokeAllAccess(args.Target);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-revoke-all-access-description"),
+ Priority = (int) TricksVerbPriorities.RevokeAllAccess,
+ };
+ args.Verbs.Add(revokeAllAccess);
+ }
+ }
+
+ if (TryComp(args.Target, out var stack))
+ {
+ Verb adjustStack = new()
+ {
+ Text = "Adjust Stack",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/adjust-stack.png",
+ Act = () =>
+ {
+ // Unbounded intentionally.
+ _quickDialog.OpenDialog(player, "Adjust stack", $"Amount (max {stack.MaxCount})", (int newAmount) =>
+ {
+ _stackSystem.SetCount(args.Target, newAmount, stack);
+ });
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-adjust-stack-description"),
+ Priority = (int) TricksVerbPriorities.AdjustStack,
+ };
+ args.Verbs.Add(adjustStack);
+
+ Verb fillStack = new()
+ {
+ Text = "Fill Stack",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/fill-stack.png",
+ Act = () =>
+ {
+ _stackSystem.SetCount(args.Target, stack.MaxCount, stack);
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-fill-stack-description"),
+ Priority = (int) TricksVerbPriorities.FillStack,
+ };
+ args.Verbs.Add(fillStack);
+ }
+
+ Verb rename = new()
+ {
+ Text = "Rename",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/rename.png",
+ Act = () =>
+ {
+ _quickDialog.OpenDialog(player, "Rename", "Name", (string newName) =>
+ {
+ MetaData(args.Target).EntityName = newName;
+ });
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-rename-description"),
+ Priority = (int) TricksVerbPriorities.Rename,
+ };
+ args.Verbs.Add(rename);
+
+ Verb redescribe = new()
+ {
+ Text = "Redescribe",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/redescribe.png",
+ Act = () =>
+ {
+ _quickDialog.OpenDialog(player, "Redescribe", "Description", (LongString newDescription) =>
+ {
+ MetaData(args.Target).EntityDescription = newDescription.String;
+ });
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-redescribe-description"),
+ Priority = (int) TricksVerbPriorities.Redescribe,
+ };
+ args.Verbs.Add(redescribe);
+
+ Verb renameAndRedescribe = new()
+ {
+ Text = "Redescribe",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/rename_and_redescribe.png",
+ Act = () =>
+ {
+ _quickDialog.OpenDialog(player, "Rename & Redescribe", "Name", "Description",
+ (string newName, LongString newDescription) =>
+ {
+ var meta = MetaData(args.Target);
+ meta.EntityName = newName;
+ meta.EntityDescription = newDescription.String;
+ });
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-rename-and-redescribe-description"),
+ Priority = (int) TricksVerbPriorities.RenameAndRedescribe,
+ };
+ args.Verbs.Add(renameAndRedescribe);
+
+ if (TryComp(args.Target, out var stationData))
+ {
+ if (_adminManager.HasAdminFlag(player, AdminFlags.Round))
+ {
+ Verb barJobSlots = new()
+ {
+ Text = "Bar job slots",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/bar_jobslots.png",
+ Act = () =>
+ {
+ foreach (var (job, _) in _stationJobsSystem.GetJobs(args.Target))
+ {
+ _stationJobsSystem.TrySetJobSlot(args.Target, job, 0, true);
+ }
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-bar-job-slots-description"),
+ Priority = (int) TricksVerbPriorities.BarJobSlots,
+ };
+ args.Verbs.Add(barJobSlots);
+ }
+
+ Verb locateCargoShuttle = new()
+ {
+ Text = "Locate Cargo Shuttle",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Clothing/Head/Soft/cargosoft.rsi/icon.png",
+ Act = () =>
+ {
+ var shuttle = Comp(args.Target).Shuttle;
+
+ if (shuttle is null)
+ return;
+
+ Transform(args.User).Coordinates = new EntityCoordinates(shuttle.Value, Vector2.Zero);
+ },
+ Impact = LogImpact.Low,
+ Message = Loc.GetString("admin-trick-locate-cargo-shuttle-description"),
+ Priority = (int) TricksVerbPriorities.LocateCargoShuttle,
+ };
+ args.Verbs.Add(locateCargoShuttle);
+ }
+
+ if (TryGetGridChildren(args.Target, out var childEnum))
+ {
+ Verb refillBattery = new()
+ {
+ Text = "Refill Battery",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/fill_battery.png",
+ Act = () =>
+ {
+ foreach (var ent in childEnum)
+ {
+ if (!HasComp(ent))
+ continue;
+ var battery = EnsureComp(ent);
+ battery.CurrentCharge = battery.MaxCharge;
+ Dirty(battery);
+ }
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-refill-battery-description"),
+ Priority = (int) TricksVerbPriorities.RefillBattery,
+ };
+ args.Verbs.Add(refillBattery);
+
+ Verb drainBattery = new()
+ {
+ Text = "Drain Battery",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/drain_battery.png",
+ Act = () =>
+ {
+ foreach (var ent in childEnum)
+ {
+ if (!HasComp(ent))
+ continue;
+ var battery = EnsureComp(ent);
+ battery.CurrentCharge = 0;
+ Dirty(battery);
+ }
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-drain-battery-description"),
+ Priority = (int) TricksVerbPriorities.DrainBattery,
+ };
+ args.Verbs.Add(drainBattery);
+
+ Verb infiniteBattery = new()
+ {
+ Text = "Infinite Battery",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/infinite_battery.png",
+ Act = () =>
+ {
+ // this kills the sloth
+ foreach (var ent in childEnum)
+ {
+ if (!HasComp(ent))
+ continue;
+
+ var recharger = EnsureComp(ent);
+ var battery = EnsureComp(ent);
+
+ recharger.AutoRecharge = true;
+ recharger.AutoRechargeRate = battery.MaxCharge; // Instant refill.
+ }
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-infinite-battery-description"),
+ Priority = (int) TricksVerbPriorities.InfiniteBattery,
+ };
+ args.Verbs.Add(infiniteBattery);
+ }
+
+ if (TryComp(args.Target, out var physics))
+ {
+ Verb haltMovement = new()
+ {
+ Text = "Halt Movement",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/halt.png",
+ Act = () =>
+ {
+ physics.LinearVelocity = Vector2.Zero;
+ physics.AngularVelocity = 0.0f;
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-halt-movement-description"),
+ Priority = (int) TricksVerbPriorities.HaltMovement,
+ };
+ args.Verbs.Add(haltMovement);
+ }
+
+ if (TryComp(args.Target, out var map))
+ {
+ if (_adminManager.HasAdminFlag(player, AdminFlags.Mapping))
+ {
+ if (_mapManager.IsMapPaused(map.WorldMap))
+ {
+ Verb unpauseMap = new()
+ {
+ Text = "Unpause Map",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/play.png",
+ Act = () =>
+ {
+ _mapManager.SetMapPaused(map.WorldMap, false);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-unpause-map-description"),
+ Priority = (int) TricksVerbPriorities.Unpause,
+ };
+ args.Verbs.Add(unpauseMap);
+ }
+ else
+ {
+ Verb pauseMap = new()
+ {
+ Text = "Pause Map",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/pause.png",
+ Act = () =>
+ {
+ _mapManager.SetMapPaused(map.WorldMap, true);
+ },
+ Impact = LogImpact.Extreme,
+ Message = Loc.GetString("admin-trick-pause-map-description"),
+ Priority = (int) TricksVerbPriorities.Pause,
+ };
+ args.Verbs.Add(pauseMap);
+ }
+ }
+ }
+
+ if (TryComp(args.Target, out var joints))
+ {
+ Verb snapJoints = new()
+ {
+ Text = "Snap Joints",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Interface/AdminActions/snap_joints.png",
+ Act = () =>
+ {
+ _jointSystem.ClearJoints(joints);
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-snap-joints-description"),
+ Priority = (int) TricksVerbPriorities.SnapJoints,
+ };
+ args.Verbs.Add(snapJoints);
+ }
+
+ if (TryComp(args.Target, out var gun))
+ {
+ Verb minigunFire = new()
+ {
+ Text = "Make Minigun",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Weapons/Guns/HMGs/minigun.rsi/icon.png",
+ Act = () =>
+ {
+ gun.FireRate = 15;
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-minigun-fire-description"),
+ Priority = (int) TricksVerbPriorities.MakeMinigun,
+ };
+ args.Verbs.Add(minigunFire);
+ }
+
+ if (TryComp(args.Target, out var ballisticAmmo))
+ {
+ Verb setCapacity = new()
+ {
+ Text = "Set Bullet Amount",
+ Category = VerbCategory.Tricks,
+ IconTexture = "/Textures/Objects/Fun/caps.rsi/mag-6.png",
+ Act = () =>
+ {
+ _quickDialog.OpenDialog(player, "Set Bullet Amount", $"Amount (max {ballisticAmmo.Capacity}):", (int amount) =>
+ {
+ ballisticAmmo.UnspawnedCount = amount;
+ });
+ },
+ Impact = LogImpact.Medium,
+ Message = Loc.GetString("admin-trick-set-bullet-amount-description"),
+ Priority = (int) TricksVerbPriorities.SetBulletAmount,
+ };
+ args.Verbs.Add(setCapacity);
+ }
+ }
+
+ private void RefillGasTank(EntityUid tank, Gas gasType, GasTankComponent? tankComponent)
+ {
+ if (!Resolve(tank, ref tankComponent))
+ return;
+
+ var mixSize = tankComponent.Air.Volume;
+ var newMix = new GasMixture(mixSize);
+ newMix.SetMoles(gasType, (1000.0f * mixSize) / (Atmospherics.R * Atmospherics.T20C)); // Fill the tank to 1000KPA.
+ newMix.Temperature = Atmospherics.T20C;
+ tankComponent.Air = newMix;
+ }
+
+ private bool TryGetGridChildren(EntityUid target, [NotNullWhen(true)] out IEnumerable? enumerator)
+ {
+ if (!HasComp(target) && !HasComp(target) &&
+ !HasComp(target))
+ {
+ enumerator = null;
+ return false;
+ }
+
+ enumerator = GetGridChildrenInner(target);
+ return true;
+ }
+
+ // ew. This finds everything supposedly on a grid.
+ private IEnumerable GetGridChildrenInner(EntityUid target)
+ {
+ if (TryComp(target, out var station))
+ {
+ foreach (var grid in station.Grids)
+ {
+ foreach (var ent in Transform(grid).ChildEntities)
+ {
+ yield return ent;
+ }
+ }
+
+ yield break;
+ }
+
+ else if (HasComp(target))
+ {
+ foreach (var possibleGrid in Transform(target).ChildEntities)
+ {
+ foreach (var ent in Transform(possibleGrid).ChildEntities)
+ {
+ yield return ent;
+ }
+ }
+
+ yield break;
+ }
+ else
+ {
+ foreach (var ent in Transform(target).ChildEntities)
+ {
+ yield return ent;
+ }
+ }
+ }
+
+ private EntityUid? FindActiveId(EntityUid target)
+ {
+ if (_inventorySystem.TryGetSlotEntity(target, "id", out var slotEntity))
+ {
+ if (HasComp(slotEntity))
+ {
+ return slotEntity.Value;
+ }
+ else if (TryComp(slotEntity, out var pda))
+ {
+ if (pda.ContainedID != null)
+ {
+ return pda.ContainedID.Owner;
+ }
+ }
+ }
+ else if (TryComp(target, out var hands))
+ {
+ foreach (var held in _handsSystem.EnumerateHeld(target, hands))
+ {
+ if (HasComp(held))
+ {
+ return held;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void GiveAllAccess(EntityUid entity)
+ {
+ var allAccess = _prototypeManager
+ .EnumeratePrototypes()
+ .Select(p => p.ID).ToArray();
+
+ _accessSystem.TrySetTags(entity, allAccess);
+ }
+
+ private void RevokeAllAccess(EntityUid entity)
+ {
+ _accessSystem.TrySetTags(entity, new string[]{});
+ }
+
+ public enum TricksVerbPriorities
+ {
+ Bolt = 0,
+ Unbolt = 0,
+ EmergencyAccessOn = -1, // These are separate intentionally for `invokeverb` shenanigans.
+ EmergencyAccessOff = -1,
+ MakeIndestructible = -2,
+ MakeVulnerable = -2,
+ BlockUnanchoring = -3,
+ RefillBattery = -4,
+ DrainBattery = -5,
+ RefillOxygen = -6,
+ RefillNitrogen = -7,
+ RefillPlasma = -8,
+ SendToTestArena = -9,
+ GrantAllAccess = -10,
+ RevokeAllAccess = -11,
+ Rejuvenate = -12,
+ AdjustStack = -13,
+ FillStack = -14,
+ Rename = -15,
+ Redescribe = -16,
+ RenameAndRedescribe = -17,
+ BarJobSlots = -18,
+ LocateCargoShuttle = -19,
+ InfiniteBattery = -20,
+ HaltMovement = -21,
+ Unpause = -22,
+ Pause = -23,
+ SnapJoints = -24,
+ MakeMinigun = -25,
+ SetBulletAmount = -26,
+ }
+}
diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs
index 975d7f9151..a03dd94513 100644
--- a/Content.Server/Administration/Systems/AdminVerbSystem.cs
+++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs
@@ -53,6 +53,7 @@ namespace Content.Server.Administration.Systems
SubscribeLocalEvent>(AddAdminVerbs);
SubscribeLocalEvent>(AddDebugVerbs);
SubscribeLocalEvent>(AddSmiteVerbs);
+ SubscribeLocalEvent>(AddTricksVerbs);
SubscribeLocalEvent>(AddAntagVerbs);
SubscribeLocalEvent(Reset);
SubscribeLocalEvent(OnSolutionChanged);
diff --git a/Content.Server/CombatMode/CombatModeSystem.cs b/Content.Server/CombatMode/CombatModeSystem.cs
index 8432744bba..730c10778c 100644
--- a/Content.Server/CombatMode/CombatModeSystem.cs
+++ b/Content.Server/CombatMode/CombatModeSystem.cs
@@ -1,4 +1,5 @@
using Content.Server.Actions.Events;
+using Content.Server.Administration.Components;
using Content.Server.Administration.Logs;
using Content.Server.CombatMode.Disarm;
using Content.Server.Hands.Components;
@@ -100,7 +101,7 @@ namespace Content.Server.CombatMode
SoundSystem.Play(component.DisarmSuccessSound.GetSound(), filterAll, args.Performer, AudioHelpers.WithVariation(0.025f));
_adminLogger.Add(LogType.DisarmedAction, $"{ToPrettyString(args.Performer):user} used disarm on {ToPrettyString(args.Target):target}");
- var eventArgs = new DisarmedEvent() { Target = args.Target, Source = args.Performer, PushProbability = chance };
+ var eventArgs = new DisarmedEvent() { Target = args.Target, Source = args.Performer, PushProbability = HasComp(args.Target) ? 1.0f : chance };
RaiseLocalEvent(args.Target, eventArgs, true);
}
@@ -108,6 +109,13 @@ namespace Content.Server.CombatMode
private float CalculateDisarmChance(EntityUid disarmer, EntityUid disarmed, EntityUid? inTargetHand, SharedCombatModeComponent disarmerComp)
{
float healthMod = 0;
+
+ if (HasComp(disarmer))
+ return 1.0f;
+
+ if (HasComp(disarmed))
+ return 0.0f;
+
if (TryComp(disarmer, out var disarmerDamage) && TryComp(disarmed, out var disarmedDamage))
{
// I wanted this to consider their mob state thresholds too but I'm not touching that shitcode after having a go at this.
diff --git a/Content.Server/Cuffs/Components/HandcuffComponent.cs b/Content.Server/Cuffs/Components/HandcuffComponent.cs
index 7d2877ba40..1909f7031c 100644
--- a/Content.Server/Cuffs/Components/HandcuffComponent.cs
+++ b/Content.Server/Cuffs/Components/HandcuffComponent.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
+using Content.Server.Administration.Components;
using Content.Server.DoAfter;
using Content.Server.Hands.Components;
using Content.Shared.Cuffs.Components;
@@ -147,6 +148,9 @@ namespace Content.Server.Cuffs.Components
cuffTime = MathF.Max(0.1f, cuffTime - StunBonus);
}
+ if (_entities.HasComponent(target))
+ cuffTime = 0.0f; // cuff them instantly.
+
var doAfterEventArgs = new DoAfterEventArgs(user, cuffTime, default, target)
{
BreakOnTargetMove = true,
diff --git a/Content.Server/Station/Systems/StationJobsSystem.cs b/Content.Server/Station/Systems/StationJobsSystem.cs
index bd48eb407d..750f06ed2b 100644
--- a/Content.Server/Station/Systems/StationJobsSystem.cs
+++ b/Content.Server/Station/Systems/StationJobsSystem.cs
@@ -222,10 +222,6 @@ public sealed partial class StationJobsSystem : EntitySystem
UpdateJobsAvailable();
return true;
case true:
- // Job is unlimited so just say we adjusted it and do nothing.
- if (jobList[jobPrototypeId] == null)
- return true;
-
stationJobs.TotalJobs += amount - (int)jobList[jobPrototypeId]!.Value;
jobList[jobPrototypeId] = (uint)amount;
diff --git a/Content.Server/Station/Systems/StationSystem.cs b/Content.Server/Station/Systems/StationSystem.cs
index 59acbfcaa2..f3b75d5b7b 100644
--- a/Content.Server/Station/Systems/StationSystem.cs
+++ b/Content.Server/Station/Systems/StationSystem.cs
@@ -5,10 +5,12 @@ using Content.Server.Chat.Systems;
using Content.Server.GameTicking;
using Content.Server.Station.Components;
using Content.Shared.CCVar;
+using Content.Shared.Station;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.Collections;
using Robust.Shared.Configuration;
+using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Random;
@@ -63,6 +65,22 @@ public sealed class StationSystem : EntitySystem
_configurationManager.OnValueChanged(CCVars.StationOffset, x => _randomStationOffset = x, true);
_configurationManager.OnValueChanged(CCVars.MaxStationOffset, x => _maxRandomStationOffset = x, true);
_configurationManager.OnValueChanged(CCVars.StationRotation, x => _randomStationRotation = x, true);
+
+ _player.PlayerStatusChanged += OnPlayerStatusChanged;
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ _player.PlayerStatusChanged -= OnPlayerStatusChanged;
+ }
+
+ private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
+ {
+ if (e.NewStatus == SessionStatus.Connected)
+ {
+ RaiseNetworkEvent(new StationsUpdatedEvent(_stations), Filter.SinglePlayer(e.Session));
+ }
}
#region Event handlers
@@ -70,11 +88,15 @@ public sealed class StationSystem : EntitySystem
private void OnStationStartup(EntityUid uid, StationDataComponent component, ComponentAdd args)
{
_stations.Add(uid);
+
+ RaiseNetworkEvent(new StationsUpdatedEvent(_stations), Filter.Broadcast());
}
private void OnStationDeleted(EntityUid uid, StationDataComponent component, ComponentShutdown args)
{
_stations.Remove(uid);
+
+ RaiseNetworkEvent(new StationsUpdatedEvent(_stations), Filter.Broadcast());
}
private void OnPreGameMapLoad(PreGameMapLoad ev)
diff --git a/Content.Server/Tools/Systems/WeldableSystem.cs b/Content.Server/Tools/Systems/WeldableSystem.cs
index 37118563e9..3d94fd205c 100644
--- a/Content.Server/Tools/Systems/WeldableSystem.cs
+++ b/Content.Server/Tools/Systems/WeldableSystem.cs
@@ -96,6 +96,18 @@ public sealed class WeldableSystem : EntitySystem
appearance.SetData(WeldableVisuals.IsWelded, component.IsWelded);
}
+ public void ForceWeldedState(EntityUid uid, bool state, WeldableComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ component.IsWelded = state;
+
+ RaiseLocalEvent(uid, new WeldableChangedEvent(component.IsWelded));
+
+ UpdateAppearance(uid, component);
+ }
+
public void SetWeldingTime(EntityUid uid, TimeSpan time, WeldableComponent? component = null)
{
if (!Resolve(uid, ref component))
diff --git a/Content.Shared/Administration/Components/SharedHeadstandComponent.cs b/Content.Shared/Administration/Components/SharedHeadstandComponent.cs
new file mode 100644
index 0000000000..fdba980610
--- /dev/null
+++ b/Content.Shared/Administration/Components/SharedHeadstandComponent.cs
@@ -0,0 +1,6 @@
+namespace Content.Shared.Administration.Components;
+
+///
+/// Flips the target's sprite on it's head, so they do a headstand.
+///
+public abstract class SharedHeadstandComponent : Component { }
diff --git a/Content.Shared/Administration/QuickDialogOpenEvent.cs b/Content.Shared/Administration/QuickDialogOpenEvent.cs
new file mode 100644
index 0000000000..3479de5736
--- /dev/null
+++ b/Content.Shared/Administration/QuickDialogOpenEvent.cs
@@ -0,0 +1,129 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Administration;
+
+///
+/// A networked event raised when the server wants to open a quick dialog.
+///
+[Serializable, NetSerializable]
+public sealed class QuickDialogOpenEvent : EntityEventArgs
+{
+ ///
+ /// The title of the dialog.
+ ///
+ public string Title;
+
+ ///
+ /// The internal dialog ID.
+ ///
+ public int DialogId;
+
+ ///
+ /// The prompts to show the user.
+ ///
+ public List Prompts;
+
+ ///
+ /// The buttons presented for the user.
+ ///
+ public QuickDialogButtonFlag Buttons = QuickDialogButtonFlag.OkButton;
+
+ public QuickDialogOpenEvent(string title, List prompts, int dialogId, QuickDialogButtonFlag buttons)
+ {
+ Title = title;
+ Prompts = prompts;
+ Buttons = buttons;
+ DialogId = dialogId;
+ }
+}
+
+///
+/// A networked event raised when the client replies to a quick dialog.
+///
+[Serializable, NetSerializable]
+public sealed class QuickDialogResponseEvent : EntityEventArgs
+{
+ ///
+ /// The internal dialog ID.
+ ///
+ public int DialogId;
+
+ ///
+ /// The responses to the prompts.
+ ///
+ public Dictionary Responses;
+
+ ///
+ /// The button pressed when responding.
+ ///
+ public QuickDialogButtonFlag ButtonPressed;
+
+ public QuickDialogResponseEvent(int dialogId, Dictionary responses, QuickDialogButtonFlag buttonPressed)
+ {
+ DialogId = dialogId;
+ Responses = responses;
+ ButtonPressed = buttonPressed;
+ }
+}
+
+///
+/// An entry in a quick dialog.
+///
+[Serializable, NetSerializable]
+public sealed class QuickDialogEntry
+{
+ ///
+ /// ID of the dialog field.
+ ///
+ public string FieldId;
+
+ ///
+ /// Type of the field, for checks.
+ ///
+ public QuickDialogEntryType Type;
+
+ ///
+ /// The prompt to show the user.
+ ///
+ public string Prompt;
+
+ public QuickDialogEntry(string fieldId, QuickDialogEntryType type, string prompt)
+ {
+ FieldId = fieldId;
+ Type = type;
+ Prompt = prompt;
+ }
+}
+
+///
+/// The buttons available in a quick dialog.
+///
+[Flags]
+public enum QuickDialogButtonFlag
+{
+ OkButton = 1,
+ CancelButton = 2,
+}
+
+///
+/// The entry types for a quick dialog.
+///
+public enum QuickDialogEntryType
+{
+ ///
+ /// Any integer.
+ ///
+ Integer,
+ ///
+ /// Any floating point value.
+ ///
+ Float,
+ ///
+ /// Maximum of 100 characters string.
+ ///
+ ShortText,
+ ///
+ /// Maximum of 2,000 characters string.
+ ///
+ LongText,
+}
diff --git a/Content.Shared/Station/StationsUpdatedEvent.cs b/Content.Shared/Station/StationsUpdatedEvent.cs
new file mode 100644
index 0000000000..6e35630bbe
--- /dev/null
+++ b/Content.Shared/Station/StationsUpdatedEvent.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Station;
+
+[NetSerializable, Serializable]
+public sealed class StationsUpdatedEvent : EntityEventArgs
+{
+ public readonly HashSet Stations;
+
+ public StationsUpdatedEvent(HashSet stations)
+ {
+ Stations = stations;
+ }
+}
diff --git a/Content.Shared/Verbs/VerbCategory.cs b/Content.Shared/Verbs/VerbCategory.cs
index 59f3b69206..141dc04aad 100644
--- a/Content.Shared/Verbs/VerbCategory.cs
+++ b/Content.Shared/Verbs/VerbCategory.cs
@@ -63,7 +63,9 @@ namespace Content.Shared.Verbs
new("verb-categories-rotate", "/Textures/Interface/VerbIcons/refresh.svg.192dpi.png", iconsOnly: true) { Columns = 5 };
public static readonly VerbCategory Smite =
- new("verb-categories-smite", "/Textures/Interface/VerbIcons/smite.svg.192dpi.png", iconsOnly: true) { Columns = 5 };
+ new("verb-categories-smite", "/Textures/Interface/VerbIcons/smite.svg.192dpi.png", iconsOnly: true) { Columns = 6 };
+ public static readonly VerbCategory Tricks =
+ new("verb-categories-tricks", "/Textures/Interface/AdminActions/tricks.png", iconsOnly: true) { Columns = 5 };
public static readonly VerbCategory SetTransferAmount =
new("verb-categories-transfer", "/Textures/Interface/VerbIcons/spill.svg.192dpi.png");
diff --git a/Resources/Locale/en-US/administration/smites.ftl b/Resources/Locale/en-US/administration/smites.ftl
index cdbbef14a6..d382162c77 100644
--- a/Resources/Locale/en-US/administration/smites.ftl
+++ b/Resources/Locale/en-US/administration/smites.ftl
@@ -9,3 +9,82 @@ admin-smite-vomit-organs-others = {CAPITALIZE($name)} vomits up their organs!
admin-smite-remove-hands-self = Your hands fall off!
admin-smite-remove-hands-other = {CAPITALIZE($name)}'s hands fall off!
admin-smite-turned-ash-other = {CAPITALISE($name)} turns into a pile of ash!
+admin-smite-stomach-removal-self = Your stomach feels hollow...
+admin-smite-run-walk-swap-prompt = You have to press shift to run!
+admin-smite-lung-removal-self = You can't breath!
+
+
+## Smite descriptions
+
+admin-smite-explode-description = Explode them.
+admin-smite-chess-dimension-description = Banishment to the Chess Dimension.
+admin-smite-set-alight-description = Makes them burn.
+admin-smite-monkeyify-description = Turns the target into a monkey.
+admin-smite-lung-cancer-description = Stage IIIA Lung Cancer, for when they really like the hit show Breaking Bad.
+admin-smite-electrocute-description = Electrocutes them, rendering anything they were wearing useless.
+admin-smite-creampie-description = A creampie, condensed into a button.
+admin-smite-remove-blood-description = Removes all of their blood, messily.
+admin-smite-vomit-organs-description = Causes them to vomit, organs included.
+admin-smite-remove-hands-description = Removes their hands.
+admin-smite-pinball-description = Turns them into a super bouncy ball, flinging them around until they clip through the station into the abyss.
+admin-smite-yeet-description = Banishes them into the depths of space by turning on no-clip and tossing them.
+admin-smite-become-bread-description = It turns them into bread. Really, that's all it does.
+admin-smite-ghostkick-description = Silently kicks the user, dropping their connection.
+admin-smite-nyanify-description = Forcibly add cat ears, there is no escape.
+admin-smite-kill-sign-description = Marks a player for death by their fellows.
+admin-smite-clown-description = Clowns them. The suit cannot be removed.
+admin-smite-anger-pointing-arrows-description = Angers the pointing arrows, causing them to assault this entity explosively.
+admin-smite-dust-description = Reduces the target to a small pile of ash.
+admin-smite-buffering-description = Causes the target to randomly start buffering, freezing them in place for a short timespan while they load.
+admin-smite-become-instrument-description = It turns them into a supersynth. Really. That's all it does.
+admin-smite-remove-gravity-description = Grants them anti-gravity.
+admin-smite-reptilian-species-swap-description = It changes their species to Reptilian. Useful for people who were being space racist.
+admin-smite-locker-stuff-description = Stuffs them in a (welded) locker.
+admin-smite-headstand-description = Vertically flips their sprite.
+admin-smite-plasma-internals-description = Replaces the contents of their internals with plasma.
+admin-smite-become-mouse-description = They become a rat. Ratatouille.
+admin-smite-maid-description = Forcibly converts them into a janitorial cat maid. This is actual torture for some players, use it wisely.
+admin-smite-zoom-in-description = Zooms in their view so that they can no longer see their surroundings.
+admin-smite-flip-eye-description = Flips their view, effectively reversing their controls and making the game annoying to play.
+admin-smite-run-walk-swap-description = Swaps running and walking, forcing them to hold shift to move fast.
+admin-smite-stomach-removal-description = Removes their stomach, rendering them unable to eat.
+admin-smite-speak-backwards-description = Forces them to speak backwards, so they can't call for help.
+admin-smite-lung-removal-description = Removes their lungs, drowning them.
+admin-smite-remove-hand-description = Removes only one of their hands instead of all of them.
+admin-smite-disarm-prone-description = Makes them get disarmed 100% of the time and cuffed instantly.
+admin-smite-garbage-can-description = Turn them into a garbage bin to emphasize what they remind you of.
+
+
+## Tricks descriptions
+
+admin-trick-unbolt-description = Unbolts the targetted airlock.
+admin-trick-bolt-description = Bolts the targetted airlock.
+admin-trick-emergency-access-on-description = Turns on emergency access for the targetted airlock.
+admin-trick-emergency-access-off-description = Turns off emergency access for the targetted airlock.
+admin-trick-make-indestructible-description = Makes the given object indestructible, effectively godmode.
+admin-trick-make-vulnerable-description = Makes the given object vulnerable again, turning off godmode.
+admin-trick-block-unanchoring-description = Prevents unanchoring the given object.
+admin-trick-refill-battery-description = Refills the internal battery of the given object.
+admin-trick-drain-battery-description = Empties the internal battery of the given object.
+admin-trick-internals-refill-oxygen-description = Refills oxygen in the target tank or target's internals.
+admin-trick-internals-refill-nitrogen-description = Refills nitrogen in the target tank or target's internals.
+admin-trick-internals-refill-plasma-description = Refills plasma in the target tank or target's internals.
+admin-trick-send-to-test-arena-description = Sends an object to the admin testing arena. This arena is per-admin.
+admin-trick-grant-all-access-description = Grants the target all access.
+admin-trick-revoke-all-access-description = Revokes all accesses on the target.
+admin-trick-rejuvenate-description = Rejuvenates the target, healing them of all ailments.
+admin-trick-adjust-stack-description = Adjusts the contents of a stack of items to the given value.
+admin-trick-fill-stack-description = Refills a stack of items to max.
+admin-trick-rename-description = Renames the given object. Note this is not equivalent to the `rename` command and won't fix their ID.
+admin-trick-redescribe-description = Redescribes the given object.
+admin-trick-rename-and-redescribe-description = Convenient bundle of both rename and redescribe into one button.
+admin-trick-bar-job-slots-description = Closes all job slots on the station, such that nobody can join it.
+admin-trick-locate-cargo-shuttle-description = Teleports you directly to the station's cargo shuttle, if it exists.
+admin-trick-infinite-battery-description = Reconfigures the SMESes and substations on the grid/station/map to self-recharge rapidly.
+admin-trick-infinite-battery-object-description = Reconfigures the item so that it's battery rapidly refills.
+admin-trick-halt-movement-description = Halts the movement of the target object, at least until something moves it again.
+admin-trick-unpause-map-description = Unpause the selected map. NOTE THIS CAN CAUSE BAD BEHAVIOR WITH STORAGE MAPS!
+admin-trick-pause-map-description = Pause the selected map. Note this doesn't entirely stop player movement!
+admin-trick-snap-joints-description = Remove all physics joints from an object. Unfortunately does not snap every bone in their body.
+admin-trick-minigun-fire-description = Makes the targetted gun fire like a minigun (very fast).
+admin-trick-set-bullet-amount-description = Quickly set the amount of unspawned bullets in a gun.
diff --git a/Resources/Locale/en-US/administration/ui/admin-menu-window.ftl b/Resources/Locale/en-US/administration/ui/admin-menu-window.ftl
index 88fc03f6e5..ce256a2cd5 100644
--- a/Resources/Locale/en-US/administration/ui/admin-menu-window.ftl
+++ b/Resources/Locale/en-US/administration/ui/admin-menu-window.ftl
@@ -7,3 +7,4 @@ admin-menu-atmos-tab = Atmos
admin-menu-round-tab = Round
admin-menu-server-tab = Server
admin-menu-players-tab = Players
+admin-menu-objects-tab = Objects
diff --git a/Resources/Locale/en-US/verbs/verb-system.ftl b/Resources/Locale/en-US/verbs/verb-system.ftl
index 27d4da4985..55d894d120 100644
--- a/Resources/Locale/en-US/verbs/verb-system.ftl
+++ b/Resources/Locale/en-US/verbs/verb-system.ftl
@@ -18,6 +18,7 @@ verb-categories-buckle = Buckle
verb-categories-unbuckle = Unbuckle
verb-categories-rotate = Rotate
verb-categories-smite = Smite
+verb-categories-tricks = Tricks
verb-categories-transfer = Set Transfer Amount
verb-categories-split = Split
verb-categories-instrument-style = Instrument Style
diff --git a/Resources/Maps/Test/admin_test_arena.yml b/Resources/Maps/Test/admin_test_arena.yml
new file mode 100644
index 0000000000..6e15891eee
--- /dev/null
+++ b/Resources/Maps/Test/admin_test_arena.yml
@@ -0,0 +1,1324 @@
+meta:
+ format: 2
+ name: DemoStation
+ author: Space-Wizards
+ postmapinit: false
+tilemap:
+ 0: space
+ 1: FloorArcadeBlue
+ 2: FloorArcadeBlue2
+ 3: FloorArcadeRed
+ 4: FloorAsteroidIronsand1
+ 5: FloorAsteroidIronsand2
+ 6: FloorAsteroidIronsand3
+ 7: FloorAsteroidIronsand4
+ 8: FloorBoxing
+ 9: FloorCarpetClown
+ 10: FloorCarpetOffice
+ 11: FloorEighties
+ 12: FloorGrassJungle
+ 13: FloorGym
+ 14: FloorMetalDiamond
+ 15: FloorShuttleBlue
+ 16: FloorShuttleOrange
+ 17: FloorShuttlePurple
+ 18: FloorShuttleRed
+ 19: FloorShuttleWhite
+ 20: floor_asteroid_coarse_sand0
+ 21: floor_asteroid_coarse_sand1
+ 22: floor_asteroid_coarse_sand2
+ 23: floor_asteroid_coarse_sand_dug
+ 24: floor_asteroid_sand
+ 25: floor_asteroid_tile
+ 26: floor_bar
+ 27: floor_blue
+ 28: floor_blue_circuit
+ 29: floor_clown
+ 30: floor_dark
+ 31: floor_elevator_shaft
+ 32: floor_freezer
+ 33: floor_glass
+ 34: floor_gold
+ 35: floor_grass
+ 36: floor_green_circuit
+ 37: floor_hydro
+ 38: floor_kitchen
+ 39: floor_laundry
+ 40: floor_lino
+ 41: floor_mime
+ 42: floor_mono
+ 43: floor_reinforced
+ 44: floor_rglass
+ 45: floor_rock_vault
+ 46: floor_showroom
+ 47: floor_silver
+ 48: floor_snow
+ 49: floor_steel
+ 50: floor_steel_dirty
+ 51: floor_techmaint
+ 52: floor_white
+ 53: floor_wood
+ 54: lattice
+ 55: plating
+grids:
+- settings:
+ chunksize: 16
+ tilesize: 1
+ chunks:
+ - ind: -1,-1
+ tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcAAAA3AAAANwAAADcAAAA3AAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAAMQAAADEAAAAxAAAAMQAAADEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwAAADEAAAAxAAAAMQAAADEAAAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcAAAAxAAAAMQAAADEAAAAxAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAANwAAADcAAAA3AAAANwAAADEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwAAADEAAAAxAAAAMQAAADEAAAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcAAAAxAAAAMQAAADEAAAAxAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAAMQAAADEAAAAxAAAAMQAAADEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwAAADEAAAAxAAAAMQAAAA4AAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcAAAAxAAAAMQAAADEAAAAOAAAADgAAAA==
+ - ind: -1,0
+ tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcAAAAxAAAAMQAAADEAAAAOAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAAMQAAADEAAAAxAAAAMQAAADEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwAAADEAAAAxAAAAMQAAADEAAAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcAAAAxAAAAMQAAADEAAAAxAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcAAAA3AAAANwAAADcAAAA3AAAANwAAADcAAAA3AAAANwAAADEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAANwAAADcAAAA3AAAANwAAADEAAAAxAAAAMQAAADEAAAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwAAADcAAAA3AAAANwAAADcAAAAxAAAAMQAAADEAAAAxAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcAAAA3AAAANwAAADcAAAA3AAAAMQAAADEAAAAxAAAAMQAAADEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAANwAAADcAAAA3AAAANwAAADcAAAA3AAAANwAAADcAAAA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+ - ind: 0,0
+ tiles: DgAAADEAAAAxAAAAMQAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEAAAAxAAAAMQAAADEAAAA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxAAAAMQAAADEAAAAxAAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAADEAAAAxAAAAMQAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcAAAA3AAAANwAAADcAAAA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxAAAAMQAAADEAAAAxAAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAADEAAAAxAAAAMQAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEAAAAxAAAAMQAAADEAAAA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAANwAAADcAAAA3AAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+ - ind: 0,-1
+ tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwAAADcAAAA3AAAANwAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEAAAAxAAAAMQAAADEAAAA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxAAAAMQAAADEAAAAxAAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAADEAAAAxAAAAMQAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcAAAA3AAAANwAAADcAAAA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxAAAAMQAAADEAAAAxAAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAADEAAAAxAAAAMQAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEAAAAxAAAAMQAAADEAAAA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAMQAAADEAAAAxAAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAADEAAAAxAAAAMQAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+entities:
+- uid: 0
+ type: CableApcExtension
+ components:
+ - pos: -0.5,1.5
+ parent: 104
+ type: Transform
+- uid: 1
+ type: CableApcExtension
+ components:
+ - pos: -0.5,2.5
+ parent: 104
+ type: Transform
+- uid: 2
+ type: CableApcExtension
+ components:
+ - pos: -0.5,3.5
+ parent: 104
+ type: Transform
+- uid: 3
+ type: CableApcExtension
+ components:
+ - pos: -0.5,4.5
+ parent: 104
+ type: Transform
+- uid: 4
+ type: CableApcExtension
+ components:
+ - pos: -0.5,5.5
+ parent: 104
+ type: Transform
+- uid: 5
+ type: CableApcExtension
+ components:
+ - pos: 2.5,6.5
+ parent: 104
+ type: Transform
+- uid: 6
+ type: CableApcExtension
+ components:
+ - pos: 1.5,6.5
+ parent: 104
+ type: Transform
+- uid: 7
+ type: CableApcExtension
+ components:
+ - pos: 0.5,6.5
+ parent: 104
+ type: Transform
+- uid: 8
+ type: CableApcExtension
+ components:
+ - pos: -0.5,6.5
+ parent: 104
+ type: Transform
+- uid: 9
+ type: CableApcExtension
+ components:
+ - pos: -1.5,6.5
+ parent: 104
+ type: Transform
+- uid: 10
+ type: CableApcExtension
+ components:
+ - pos: -2.5,6.5
+ parent: 104
+ type: Transform
+- uid: 11
+ type: CableApcExtension
+ components:
+ - pos: -3.5,6.5
+ parent: 104
+ type: Transform
+- uid: 12
+ type: CableApcExtension
+ components:
+ - pos: -4.5,6.5
+ parent: 104
+ type: Transform
+- uid: 13
+ type: CableApcExtension
+ components:
+ - pos: -5.5,6.5
+ parent: 104
+ type: Transform
+- uid: 14
+ type: CableApcExtension
+ components:
+ - pos: -6.5,6.5
+ parent: 104
+ type: Transform
+- uid: 15
+ type: CableApcExtension
+ components:
+ - pos: -7.5,6.5
+ parent: 104
+ type: Transform
+- uid: 16
+ type: CableApcExtension
+ components:
+ - pos: -7.5,5.5
+ parent: 104
+ type: Transform
+- uid: 17
+ type: CableApcExtension
+ components:
+ - pos: -7.5,4.5
+ parent: 104
+ type: Transform
+- uid: 18
+ type: APCHyperCapacity
+ components:
+ - rot: 3.141592653589793 rad
+ pos: -7.5,4.5
+ parent: 104
+ type: Transform
+- uid: 19
+ type: SubstationBasic
+ components:
+ - pos: -8.5,6.5
+ parent: 104
+ type: Transform
+ - containers:
+ - machine_parts
+ - machine_board
+ type: Construction
+- uid: 20
+ type: CableHV
+ components:
+ - pos: -8.5,6.5
+ parent: 104
+ type: Transform
+- uid: 21
+ type: CableHV
+ components:
+ - pos: -6.5,7.5
+ parent: 104
+ type: Transform
+- uid: 22
+ type: CableHV
+ components:
+ - pos: -7.5,7.5
+ parent: 104
+ type: Transform
+- uid: 23
+ type: CableHV
+ components:
+ - pos: -8.5,7.5
+ parent: 104
+ type: Transform
+- uid: 24
+ type: WallRiveted
+ components:
+ - pos: -6.5,4.5
+ parent: 104
+ type: Transform
+- uid: 25
+ type: WallRiveted
+ components:
+ - pos: -7.5,4.5
+ parent: 104
+ type: Transform
+- uid: 26
+ type: WallRiveted
+ components:
+ - pos: -8.5,4.5
+ parent: 104
+ type: Transform
+- uid: 27
+ type: WallRiveted
+ components:
+ - pos: -9.5,4.5
+ parent: 104
+ type: Transform
+- uid: 28
+ type: WallRiveted
+ components:
+ - pos: -9.5,5.5
+ parent: 104
+ type: Transform
+- uid: 29
+ type: WallRiveted
+ components:
+ - pos: -9.5,6.5
+ parent: 104
+ type: Transform
+- uid: 30
+ type: WallRiveted
+ components:
+ - pos: -9.5,8.5
+ parent: 104
+ type: Transform
+- uid: 31
+ type: WallRiveted
+ components:
+ - pos: -8.5,8.5
+ parent: 104
+ type: Transform
+- uid: 32
+ type: WallRiveted
+ components:
+ - pos: -7.5,8.5
+ parent: 104
+ type: Transform
+- uid: 33
+ type: WallRiveted
+ components:
+ - pos: -6.5,8.5
+ parent: 104
+ type: Transform
+- uid: 34
+ type: GeneratorUranium
+ components:
+ - pos: -6.5,7.5
+ parent: 104
+ type: Transform
+ - containers:
+ - machine_parts
+ - machine_board
+ type: Construction
+- uid: 35
+ type: GeneratorUranium
+ components:
+ - pos: -7.5,7.5
+ parent: 104
+ type: Transform
+ - containers:
+ - machine_parts
+ - machine_board
+ type: Construction
+- uid: 36
+ type: GeneratorUranium
+ components:
+ - pos: -8.5,7.5
+ parent: 104
+ type: Transform
+ - containers:
+ - machine_parts
+ - machine_board
+ type: Construction
+- uid: 37
+ type: AirlockCommandLocked
+ components:
+ - pos: -0.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 38
+ type: AirlockCommandLocked
+ components:
+ - pos: -0.5,4.5
+ parent: 104
+ type: Transform
+- uid: 39
+ type: WallRiveted
+ components:
+ - pos: -4.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 40
+ type: WallRiveted
+ components:
+ - pos: -3.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 41
+ type: WallRiveted
+ components:
+ - pos: -2.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 42
+ type: WallRiveted
+ components:
+ - pos: -1.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 43
+ type: WallRiveted
+ components:
+ - pos: 0.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 44
+ type: WallRiveted
+ components:
+ - pos: 1.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 45
+ type: WallRiveted
+ components:
+ - pos: 2.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 46
+ type: WallRiveted
+ components:
+ - pos: 3.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 47
+ type: WallRiveted
+ components:
+ - pos: 3.5,4.5
+ parent: 104
+ type: Transform
+- uid: 48
+ type: WallRiveted
+ components:
+ - pos: 2.5,4.5
+ parent: 104
+ type: Transform
+- uid: 49
+ type: WallRiveted
+ components:
+ - pos: 1.5,4.5
+ parent: 104
+ type: Transform
+- uid: 50
+ type: WallRiveted
+ components:
+ - pos: 0.5,4.5
+ parent: 104
+ type: Transform
+- uid: 51
+ type: WallRiveted
+ components:
+ - pos: -1.5,4.5
+ parent: 104
+ type: Transform
+- uid: 52
+ type: WallRiveted
+ components:
+ - pos: -2.5,4.5
+ parent: 104
+ type: Transform
+- uid: 53
+ type: WallRiveted
+ components:
+ - pos: -3.5,4.5
+ parent: 104
+ type: Transform
+- uid: 54
+ type: WallRiveted
+ components:
+ - pos: -4.5,4.5
+ parent: 104
+ type: Transform
+- uid: 55
+ type: WallRiveted
+ components:
+ - pos: -5.5,3.5
+ parent: 104
+ type: Transform
+- uid: 56
+ type: WallRiveted
+ components:
+ - pos: -5.5,2.5
+ parent: 104
+ type: Transform
+- uid: 57
+ type: WallRiveted
+ components:
+ - pos: -5.5,1.5
+ parent: 104
+ type: Transform
+- uid: 58
+ type: WallRiveted
+ components:
+ - pos: -5.5,0.5
+ parent: 104
+ type: Transform
+- uid: 59
+ type: WallRiveted
+ components:
+ - pos: -5.5,-0.5
+ parent: 104
+ type: Transform
+- uid: 60
+ type: WallRiveted
+ components:
+ - pos: -5.5,-1.5
+ parent: 104
+ type: Transform
+- uid: 61
+ type: WallRiveted
+ components:
+ - pos: -5.5,-2.5
+ parent: 104
+ type: Transform
+- uid: 62
+ type: WallRiveted
+ components:
+ - pos: -5.5,-3.5
+ parent: 104
+ type: Transform
+- uid: 63
+ type: WallRiveted
+ components:
+ - pos: -5.5,-4.5
+ parent: 104
+ type: Transform
+- uid: 64
+ type: WallRiveted
+ components:
+ - pos: -5.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 65
+ type: WallRiveted
+ components:
+ - pos: -5.5,-6.5
+ parent: 104
+ type: Transform
+- uid: 66
+ type: WallRiveted
+ components:
+ - pos: -5.5,-7.5
+ parent: 104
+ type: Transform
+- uid: 67
+ type: WallRiveted
+ components:
+ - pos: -5.5,-8.5
+ parent: 104
+ type: Transform
+- uid: 68
+ type: WallRiveted
+ components:
+ - pos: -5.5,-9.5
+ parent: 104
+ type: Transform
+- uid: 69
+ type: WallRiveted
+ components:
+ - pos: -3.5,-9.5
+ parent: 104
+ type: Transform
+- uid: 70
+ type: WallRiveted
+ components:
+ - pos: -2.5,-9.5
+ parent: 104
+ type: Transform
+- uid: 71
+ type: WallRiveted
+ components:
+ - pos: -0.5,-9.5
+ parent: 104
+ type: Transform
+- uid: 72
+ type: WallRiveted
+ components:
+ - pos: 1.5,-9.5
+ parent: 104
+ type: Transform
+- uid: 73
+ type: WallRiveted
+ components:
+ - pos: 3.5,-9.5
+ parent: 104
+ type: Transform
+- uid: 74
+ type: WallRiveted
+ components:
+ - pos: 4.5,-9.5
+ parent: 104
+ type: Transform
+- uid: 75
+ type: WallRiveted
+ components:
+ - pos: 4.5,-8.5
+ parent: 104
+ type: Transform
+- uid: 76
+ type: WallRiveted
+ components:
+ - pos: 4.5,-7.5
+ parent: 104
+ type: Transform
+- uid: 77
+ type: WallRiveted
+ components:
+ - pos: 4.5,-6.5
+ parent: 104
+ type: Transform
+- uid: 78
+ type: WallRiveted
+ components:
+ - pos: 4.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 79
+ type: WallRiveted
+ components:
+ - pos: 4.5,-4.5
+ parent: 104
+ type: Transform
+- uid: 80
+ type: WallRiveted
+ components:
+ - pos: 4.5,-2.5
+ parent: 104
+ type: Transform
+- uid: 81
+ type: WallRiveted
+ components:
+ - pos: 4.5,-0.5
+ parent: 104
+ type: Transform
+- uid: 82
+ type: WallRiveted
+ components:
+ - pos: 4.5,0.5
+ parent: 104
+ type: Transform
+- uid: 83
+ type: WallRiveted
+ components:
+ - pos: 4.5,1.5
+ parent: 104
+ type: Transform
+- uid: 84
+ type: WallRiveted
+ components:
+ - pos: 4.5,2.5
+ parent: 104
+ type: Transform
+- uid: 85
+ type: WallRiveted
+ components:
+ - pos: 4.5,3.5
+ parent: 104
+ type: Transform
+- uid: 86
+ type: WallRiveted
+ components:
+ - pos: 4.5,4.5
+ parent: 104
+ type: Transform
+- uid: 87
+ type: WallRiveted
+ components:
+ - pos: 4.5,5.5
+ parent: 104
+ type: Transform
+- uid: 88
+ type: WallRiveted
+ components:
+ - pos: 4.5,6.5
+ parent: 104
+ type: Transform
+- uid: 89
+ type: WallRiveted
+ components:
+ - pos: 4.5,7.5
+ parent: 104
+ type: Transform
+- uid: 90
+ type: WallRiveted
+ components:
+ - pos: 4.5,8.5
+ parent: 104
+ type: Transform
+- uid: 91
+ type: WallRiveted
+ components:
+ - pos: 3.5,8.5
+ parent: 104
+ type: Transform
+- uid: 92
+ type: WallRiveted
+ components:
+ - pos: 2.5,8.5
+ parent: 104
+ type: Transform
+- uid: 93
+ type: WallRiveted
+ components:
+ - pos: 1.5,8.5
+ parent: 104
+ type: Transform
+- uid: 94
+ type: WallRiveted
+ components:
+ - pos: 0.5,8.5
+ parent: 104
+ type: Transform
+- uid: 95
+ type: WallRiveted
+ components:
+ - pos: -0.5,8.5
+ parent: 104
+ type: Transform
+- uid: 96
+ type: WallRiveted
+ components:
+ - pos: -1.5,8.5
+ parent: 104
+ type: Transform
+- uid: 97
+ type: WallRiveted
+ components:
+ - pos: -2.5,8.5
+ parent: 104
+ type: Transform
+- uid: 98
+ type: WallRiveted
+ components:
+ - pos: -3.5,8.5
+ parent: 104
+ type: Transform
+- uid: 99
+ type: WallRiveted
+ components:
+ - pos: -4.5,8.5
+ parent: 104
+ type: Transform
+- uid: 100
+ type: WallRiveted
+ components:
+ - pos: -5.5,8.5
+ parent: 104
+ type: Transform
+- uid: 101
+ type: WallRiveted
+ components:
+ - pos: -5.5,4.5
+ parent: 104
+ type: Transform
+- uid: 102
+ type: WallRiveted
+ components:
+ - pos: -5.5,5.5
+ parent: 104
+ type: Transform
+- uid: 103
+ type: WallRiveted
+ components:
+ - pos: -5.5,6.5
+ parent: 104
+ type: Transform
+- uid: 104
+ components:
+ - pos: 0.43750095,0.583333
+ parent: null
+ type: Transform
+ - index: 0
+ type: MapGrid
+ - angularDamping: 100
+ linearDamping: 50
+ fixedRotation: False
+ bodyType: Dynamic
+ type: Physics
+ - fixtures: []
+ type: Fixtures
+ - gravityShakeSound: !type:SoundPathSpecifier
+ path: /Audio/Effects/alert.ogg
+ type: Gravity
+ - chunkCollection: {}
+ type: DecalGrid
+ - tiles:
+ -2,-2: 0
+ -2,-1: 0
+ -1,-2: 0
+ -1,-1: 0
+ -2,0: 0
+ -1,0: 0
+ 0,0: 0
+ 0,-2: 0
+ 0,-1: 0
+ -6,-10: 0
+ -6,-9: 0
+ -6,-8: 0
+ -6,-7: 0
+ -6,-6: 0
+ -6,-5: 0
+ -6,-4: 0
+ -6,-3: 0
+ -6,-2: 0
+ -6,-1: 0
+ -5,-10: 0
+ -5,-9: 0
+ -5,-8: 0
+ -5,-7: 0
+ -5,-6: 0
+ -5,-5: 0
+ -5,-4: 0
+ -5,-3: 0
+ -5,-2: 0
+ -5,-1: 0
+ -4,-10: 0
+ -4,-9: 0
+ -4,-8: 0
+ -4,-7: 0
+ -4,-6: 0
+ -4,-5: 0
+ -4,-4: 0
+ -4,-3: 0
+ -4,-2: 0
+ -4,-1: 0
+ -3,-10: 0
+ -3,-9: 0
+ -3,-8: 0
+ -3,-7: 0
+ -3,-6: 0
+ -3,-5: 0
+ -3,-4: 0
+ -3,-3: 0
+ -3,-2: 0
+ -3,-1: 0
+ -2,-10: 0
+ -2,-9: 0
+ -2,-8: 0
+ -2,-7: 0
+ -2,-6: 0
+ -2,-5: 0
+ -2,-4: 0
+ -2,-3: 0
+ -1,-10: 0
+ -1,-9: 0
+ -1,-8: 0
+ -1,-7: 0
+ -1,-6: 0
+ -1,-5: 0
+ -1,-4: 0
+ -1,-3: 0
+ -10,4: 0
+ -10,5: 0
+ -10,6: 0
+ -10,7: 0
+ -10,8: 0
+ -9,4: 0
+ -9,5: 0
+ -9,6: 0
+ -9,7: 0
+ -9,8: 0
+ -8,4: 0
+ -8,5: 0
+ -8,6: 0
+ -8,7: 0
+ -8,8: 0
+ -7,4: 0
+ -7,5: 0
+ -7,6: 0
+ -7,7: 0
+ -7,8: 0
+ -6,0: 0
+ -6,1: 0
+ -6,2: 0
+ -6,3: 0
+ -6,4: 0
+ -6,5: 0
+ -6,6: 0
+ -6,7: 0
+ -6,8: 0
+ -5,0: 0
+ -5,1: 0
+ -5,2: 0
+ -5,3: 0
+ -5,4: 0
+ -5,5: 0
+ -5,6: 0
+ -5,7: 0
+ -5,8: 0
+ -4,0: 0
+ -4,1: 0
+ -4,2: 0
+ -4,3: 0
+ -4,4: 0
+ -4,5: 0
+ -4,6: 0
+ -4,7: 0
+ -4,8: 0
+ -3,0: 0
+ -3,1: 0
+ -3,2: 0
+ -3,3: 0
+ -3,4: 0
+ -3,5: 0
+ -3,6: 0
+ -3,7: 0
+ -3,8: 0
+ -2,1: 0
+ -2,2: 0
+ -2,3: 0
+ -2,4: 0
+ -2,5: 0
+ -2,6: 0
+ -2,7: 0
+ -2,8: 0
+ -1,1: 0
+ -1,2: 0
+ -1,3: 0
+ -1,4: 0
+ -1,5: 0
+ -1,6: 0
+ -1,7: 0
+ -1,8: 0
+ 0,1: 0
+ 0,2: 0
+ 0,3: 0
+ 0,4: 0
+ 0,5: 0
+ 0,6: 0
+ 0,7: 0
+ 0,8: 0
+ 1,0: 0
+ 1,1: 0
+ 1,2: 0
+ 1,3: 0
+ 1,4: 0
+ 1,5: 0
+ 1,6: 0
+ 1,7: 0
+ 1,8: 0
+ 2,0: 0
+ 2,1: 0
+ 2,2: 0
+ 2,3: 0
+ 2,4: 0
+ 2,5: 0
+ 2,6: 0
+ 2,7: 0
+ 2,8: 0
+ 3,0: 0
+ 3,1: 0
+ 3,2: 0
+ 3,3: 0
+ 3,4: 0
+ 3,5: 0
+ 3,6: 0
+ 3,7: 0
+ 3,8: 0
+ 4,0: 0
+ 4,1: 0
+ 4,2: 0
+ 4,3: 0
+ 4,4: 0
+ 4,5: 0
+ 4,6: 0
+ 4,7: 0
+ 4,8: 0
+ 0,-10: 0
+ 0,-9: 0
+ 0,-8: 0
+ 0,-7: 0
+ 0,-6: 0
+ 0,-5: 0
+ 0,-4: 0
+ 0,-3: 0
+ 1,-10: 0
+ 1,-9: 0
+ 1,-8: 0
+ 1,-7: 0
+ 1,-6: 0
+ 1,-5: 0
+ 1,-4: 0
+ 1,-3: 0
+ 1,-2: 0
+ 1,-1: 0
+ 2,-10: 0
+ 2,-9: 0
+ 2,-8: 0
+ 2,-7: 0
+ 2,-6: 0
+ 2,-5: 0
+ 2,-4: 0
+ 2,-3: 0
+ 2,-2: 0
+ 2,-1: 0
+ 3,-10: 0
+ 3,-9: 0
+ 3,-8: 0
+ 3,-7: 0
+ 3,-6: 0
+ 3,-5: 0
+ 3,-4: 0
+ 3,-3: 0
+ 3,-2: 0
+ 3,-1: 0
+ 4,-10: 0
+ 4,-9: 0
+ 4,-8: 0
+ 4,-7: 0
+ 4,-6: 0
+ 4,-5: 0
+ 4,-4: 0
+ 4,-3: 0
+ 4,-2: 0
+ 4,-1: 0
+ uniqueMixes:
+ - volume: 2500
+ temperature: 293.15
+ moles:
+ - 21.824879
+ - 82.10312
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ type: GridAtmosphere
+- uid: 105
+ type: WallRiveted
+ components:
+ - pos: -9.5,7.5
+ parent: 104
+ type: Transform
+- uid: 106
+ type: WallRiveted
+ components:
+ - pos: -4.5,-9.5
+ parent: 104
+ type: Transform
+- uid: 107
+ type: WallRiveted
+ components:
+ - pos: -1.5,-9.5
+ parent: 104
+ type: Transform
+- uid: 108
+ type: WallRiveted
+ components:
+ - pos: 0.5,-9.5
+ parent: 104
+ type: Transform
+- uid: 109
+ type: WallRiveted
+ components:
+ - pos: 2.5,-9.5
+ parent: 104
+ type: Transform
+- uid: 110
+ type: WallRiveted
+ components:
+ - pos: 4.5,-3.5
+ parent: 104
+ type: Transform
+- uid: 111
+ type: WallRiveted
+ components:
+ - pos: 4.5,-1.5
+ parent: 104
+ type: Transform
+- uid: 112
+ type: WallRiveted
+ components:
+ - pos: -5.5,7.5
+ parent: 104
+ type: Transform
+- uid: 113
+ type: CableApcExtension
+ components:
+ - pos: -0.5,0.5
+ parent: 104
+ type: Transform
+- uid: 114
+ type: CableApcExtension
+ components:
+ - pos: -0.5,-0.5
+ parent: 104
+ type: Transform
+- uid: 115
+ type: CableApcExtension
+ components:
+ - pos: -0.5,-1.5
+ parent: 104
+ type: Transform
+- uid: 116
+ type: CableApcExtension
+ components:
+ - pos: -0.5,-2.5
+ parent: 104
+ type: Transform
+- uid: 117
+ type: CableApcExtension
+ components:
+ - pos: -0.5,-3.5
+ parent: 104
+ type: Transform
+- uid: 118
+ type: CableApcExtension
+ components:
+ - pos: -0.5,-4.5
+ parent: 104
+ type: Transform
+- uid: 119
+ type: CableApcExtension
+ components:
+ - pos: -0.5,-5.5
+ parent: 104
+ type: Transform
+- uid: 120
+ type: CableApcExtension
+ components:
+ - pos: -0.5,-6.5
+ parent: 104
+ type: Transform
+- uid: 121
+ type: CableApcExtension
+ components:
+ - pos: -0.5,-7.5
+ parent: 104
+ type: Transform
+- uid: 122
+ type: CableApcExtension
+ components:
+ - pos: -1.5,-7.5
+ parent: 104
+ type: Transform
+- uid: 123
+ type: CableApcExtension
+ components:
+ - pos: -2.5,-7.5
+ parent: 104
+ type: Transform
+- uid: 124
+ type: CableApcExtension
+ components:
+ - pos: -3.5,-7.5
+ parent: 104
+ type: Transform
+- uid: 125
+ type: CableApcExtension
+ components:
+ - pos: 0.5,-7.5
+ parent: 104
+ type: Transform
+- uid: 126
+ type: CableApcExtension
+ components:
+ - pos: 1.5,-7.5
+ parent: 104
+ type: Transform
+- uid: 127
+ type: CableApcExtension
+ components:
+ - pos: 2.5,-7.5
+ parent: 104
+ type: Transform
+- uid: 128
+ type: CableApcExtension
+ components:
+ - pos: -1.5,-3.5
+ parent: 104
+ type: Transform
+- uid: 129
+ type: CableApcExtension
+ components:
+ - pos: -2.5,-3.5
+ parent: 104
+ type: Transform
+- uid: 130
+ type: CableApcExtension
+ components:
+ - pos: -3.5,-3.5
+ parent: 104
+ type: Transform
+- uid: 131
+ type: CableApcExtension
+ components:
+ - pos: 0.5,-3.5
+ parent: 104
+ type: Transform
+- uid: 132
+ type: CableApcExtension
+ components:
+ - pos: 1.5,-3.5
+ parent: 104
+ type: Transform
+- uid: 133
+ type: CableApcExtension
+ components:
+ - pos: 2.5,-3.5
+ parent: 104
+ type: Transform
+- uid: 134
+ type: CableApcExtension
+ components:
+ - pos: 0.5,-0.5
+ parent: 104
+ type: Transform
+- uid: 135
+ type: CableApcExtension
+ components:
+ - pos: 1.5,-0.5
+ parent: 104
+ type: Transform
+- uid: 136
+ type: CableApcExtension
+ components:
+ - pos: 2.5,-0.5
+ parent: 104
+ type: Transform
+- uid: 137
+ type: CableApcExtension
+ components:
+ - pos: -1.5,-0.5
+ parent: 104
+ type: Transform
+- uid: 138
+ type: CableApcExtension
+ components:
+ - pos: -2.5,-0.5
+ parent: 104
+ type: Transform
+- uid: 139
+ type: CableApcExtension
+ components:
+ - pos: -3.5,-0.5
+ parent: 104
+ type: Transform
+- uid: 140
+ type: CableApcExtension
+ components:
+ - pos: -3.5,2.5
+ parent: 104
+ type: Transform
+- uid: 141
+ type: CableApcExtension
+ components:
+ - pos: -2.5,2.5
+ parent: 104
+ type: Transform
+- uid: 142
+ type: CableApcExtension
+ components:
+ - pos: -1.5,2.5
+ parent: 104
+ type: Transform
+- uid: 143
+ type: CableApcExtension
+ components:
+ - pos: 0.5,2.5
+ parent: 104
+ type: Transform
+- uid: 144
+ type: CableApcExtension
+ components:
+ - pos: 1.5,2.5
+ parent: 104
+ type: Transform
+- uid: 145
+ type: CableApcExtension
+ components:
+ - pos: 2.5,2.5
+ parent: 104
+ type: Transform
+- uid: 146
+ type: AlwaysPoweredWallLight
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -4.5,1.5
+ parent: 104
+ type: Transform
+- uid: 147
+ type: AlwaysPoweredWallLight
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 3.5,1.5
+ parent: 104
+ type: Transform
+- uid: 148
+ type: AlwaysPoweredWallLight
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 3.5,-2.5
+ parent: 104
+ type: Transform
+- uid: 149
+ type: AlwaysPoweredWallLight
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -4.5,-2.5
+ parent: 104
+ type: Transform
+- uid: 150
+ type: AlwaysPoweredWallLight
+ components:
+ - rot: 3.141592653589793 rad
+ pos: -2.5,-8.5
+ parent: 104
+ type: Transform
+- uid: 151
+ type: AlwaysPoweredWallLight
+ components:
+ - rot: 3.141592653589793 rad
+ pos: 1.5,-8.5
+ parent: 104
+ type: Transform
+- uid: 152
+ type: AlwaysPoweredWallLight
+ components:
+ - rot: 3.141592653589793 rad
+ pos: -7.5,5.5
+ parent: 104
+ type: Transform
+- uid: 153
+ type: AlwaysPoweredWallLight
+ components:
+ - pos: -2.5,7.5
+ parent: 104
+ type: Transform
+- uid: 154
+ type: AlwaysPoweredWallLight
+ components:
+ - pos: 1.5,7.5
+ parent: 104
+ type: Transform
+- uid: 155
+ type: CableMV
+ components:
+ - rot: 3.141592653589793 rad
+ pos: -8.5,6.5
+ parent: 104
+ type: Transform
+- uid: 156
+ type: CableMV
+ components:
+ - pos: -7.5,6.5
+ parent: 104
+ type: Transform
+- uid: 157
+ type: CableMV
+ components:
+ - pos: -7.5,5.5
+ parent: 104
+ type: Transform
+- uid: 158
+ type: CableMV
+ components:
+ - pos: -7.5,4.5
+ parent: 104
+ type: Transform
+- uid: 159
+ type: GravityGeneratorMini
+ components:
+ - pos: -7.5,6.5
+ parent: 104
+ type: Transform
+ - enabled: False
+ type: AmbientSound
+ - powerLoad: 500
+ type: ApcPowerReceiver
+ - radius: 2.5
+ type: PointLight
+...
diff --git a/Resources/Prototypes/Entities/Structures/Power/apc.yml b/Resources/Prototypes/Entities/Structures/Power/apc.yml
index 74603897b8..730baf8e24 100644
--- a/Resources/Prototypes/Entities/Structures/Power/apc.yml
+++ b/Resources/Prototypes/Entities/Structures/Power/apc.yml
@@ -91,6 +91,7 @@
max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
+ - type: StationInfiniteBatteryTarget
# APCs in use
- type: entity
diff --git a/Resources/Prototypes/Entities/Structures/Power/smes.yml b/Resources/Prototypes/Entities/Structures/Power/smes.yml
index ebefd6c014..9328d35934 100644
--- a/Resources/Prototypes/Entities/Structures/Power/smes.yml
+++ b/Resources/Prototypes/Entities/Structures/Power/smes.yml
@@ -63,6 +63,7 @@
containers:
machine_parts: !type:Container
machine_board: !type:Container
+ - type: StationInfiniteBatteryTarget
# SMES' in use
diff --git a/Resources/Prototypes/Entities/Structures/Power/substation.yml b/Resources/Prototypes/Entities/Structures/Power/substation.yml
index 4122f28b24..ec67a2dda0 100644
--- a/Resources/Prototypes/Entities/Structures/Power/substation.yml
+++ b/Resources/Prototypes/Entities/Structures/Power/substation.yml
@@ -77,6 +77,7 @@
containers:
machine_parts: !type:Container
machine_board: !type:Container
+ - type: StationInfiniteBatteryTarget
# Compact Wall Substation Base
- type: entity
@@ -161,6 +162,7 @@
maxIntensity: 50
intensitySlope: 2
totalIntensity: 100
+ - type: StationInfiniteBatteryTarget
# Substations in use
diff --git a/Resources/Prototypes/Polymorphs/admin.yml b/Resources/Prototypes/Polymorphs/admin.yml
new file mode 100644
index 0000000000..70dd9b5656
--- /dev/null
+++ b/Resources/Prototypes/Polymorphs/admin.yml
@@ -0,0 +1,37 @@
+- type: polymorph
+ id: AdminLizardSmite
+ entity: MobReptilian
+ forced: true
+ transferName: true
+ transferHumanoidAppearance: true
+ inventory: Transfer
+
+- type: polymorph
+ id: AdminMonkeySmite
+ entity: MobMonkey
+ forced: true
+ inventory: Drop
+
+- type: polymorph
+ id: AdminBreadSmite
+ entity: FoodBreadPlain
+ forced: true
+ inventory: Drop
+
+- type: polymorph
+ id: AdminInstrumentSmite
+ entity: SuperSynthesizerInstrument
+ forced: true
+ inventory: Drop
+
+- type: polymorph
+ id: AdminMouseSmite
+ entity: MobMouse
+ forced: true
+ inventory: Drop
+
+- type: polymorph
+ id: AdminDisposalsSmite
+ entity: DisposalUnit
+ forced: true
+ inventory: Drop
diff --git a/Resources/Prototypes/Polymorphs/polymorph.yml b/Resources/Prototypes/Polymorphs/polymorph.yml
index e0000c6f22..1bad0ffb8b 100644
--- a/Resources/Prototypes/Polymorphs/polymorph.yml
+++ b/Resources/Prototypes/Polymorphs/polymorph.yml
@@ -46,24 +46,6 @@
transferHumanoidAppearance: true
inventory: Transfer
-- type: polymorph
- id: AdminMonkeySmite
- entity: MobMonkey
- forced: true
- inventory: Drop
-
-- type: polymorph
- id: AdminBreadSmite
- entity: FoodBreadPlain
- forced: true
- inventory: Drop
-
-- type: polymorph
- id: AdminInstrumentSmite
- entity: SuperSynthesizerInstrument
- forced: true
- inventory: Drop
-
- type: polymorph
id: AMIVMorph
entity: MobMonkey
@@ -71,4 +53,4 @@
inventory: Transfer
transferName: true
revertOnCrit: false
- revertOnDeath: false
\ No newline at end of file
+ revertOnDeath: false
diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml b/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml
index 14617c18b1..d27e7543fb 100644
--- a/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml
+++ b/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml
@@ -27,3 +27,16 @@
innerclothingskirt: ClothingUniformJumpskirtJanitor
satchel: ClothingBackpackSatchelFilled
duffelbag: ClothingBackpackDuffelFilled
+
+- type: startingGear
+ id: JanitorMaidGear
+ equipment:
+ jumpsuit: ClothingUniformJumpskirtJanimaid
+ back: ClothingBackpackFilled
+ id: JanitorPDA
+ head: ClothingHeadHatCatEars
+ ears: ClothingHeadsetService
+ belt: ClothingBeltJanitorFilled
+ innerclothingskirt: ClothingUniformJumpskirtJanimaid
+ satchel: ClothingBackpackSatchelFilled
+ duffelbag: ClothingBackpackDuffelFilled
diff --git a/Resources/Textures/Interface/AdminActions/adjust-stack.png b/Resources/Textures/Interface/AdminActions/adjust-stack.png
new file mode 100644
index 0000000000..9f81b7d751
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/adjust-stack.png differ
diff --git a/Resources/Textures/Interface/AdminActions/bar_jobslots.png b/Resources/Textures/Interface/AdminActions/bar_jobslots.png
new file mode 100644
index 0000000000..e6fe06bbd4
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/bar_jobslots.png differ
diff --git a/Resources/Textures/Interface/AdminActions/bolt.png b/Resources/Textures/Interface/AdminActions/bolt.png
new file mode 100644
index 0000000000..25f068a61c
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/bolt.png differ
diff --git a/Resources/Textures/Interface/AdminActions/drain_battery.png b/Resources/Textures/Interface/AdminActions/drain_battery.png
new file mode 100644
index 0000000000..a51f96ca15
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/drain_battery.png differ
diff --git a/Resources/Textures/Interface/AdminActions/emergency_access.png b/Resources/Textures/Interface/AdminActions/emergency_access.png
new file mode 100644
index 0000000000..06a736bace
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/emergency_access.png differ
diff --git a/Resources/Textures/Interface/AdminActions/fill-stack.png b/Resources/Textures/Interface/AdminActions/fill-stack.png
new file mode 100644
index 0000000000..8dceb38f03
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/fill-stack.png differ
diff --git a/Resources/Textures/Interface/AdminActions/fill_battery.png b/Resources/Textures/Interface/AdminActions/fill_battery.png
new file mode 100644
index 0000000000..268400f015
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/fill_battery.png differ
diff --git a/Resources/Textures/Interface/AdminActions/flip.png b/Resources/Textures/Interface/AdminActions/flip.png
new file mode 100644
index 0000000000..5fb205a4d0
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/flip.png differ
diff --git a/Resources/Textures/Interface/AdminActions/halt.png b/Resources/Textures/Interface/AdminActions/halt.png
new file mode 100644
index 0000000000..a8138fabba
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/halt.png differ
diff --git a/Resources/Textures/Interface/AdminActions/help-backwards.png b/Resources/Textures/Interface/AdminActions/help-backwards.png
new file mode 100644
index 0000000000..87d683aca2
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/help-backwards.png differ
diff --git a/Resources/Textures/Interface/AdminActions/infinite_battery.png b/Resources/Textures/Interface/AdminActions/infinite_battery.png
new file mode 100644
index 0000000000..94192f632e
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/infinite_battery.png differ
diff --git a/Resources/Textures/Interface/AdminActions/pause.png b/Resources/Textures/Interface/AdminActions/pause.png
new file mode 100644
index 0000000000..283d2db6c4
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/pause.png differ
diff --git a/Resources/Textures/Interface/AdminActions/play.png b/Resources/Textures/Interface/AdminActions/play.png
new file mode 100644
index 0000000000..2f665c26f9
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/play.png differ
diff --git a/Resources/Textures/Interface/AdminActions/redescribe.png b/Resources/Textures/Interface/AdminActions/redescribe.png
new file mode 100644
index 0000000000..f2051008e4
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/redescribe.png differ
diff --git a/Resources/Textures/Interface/AdminActions/rejuvenate.png b/Resources/Textures/Interface/AdminActions/rejuvenate.png
new file mode 100644
index 0000000000..da8f6636d8
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/rejuvenate.png differ
diff --git a/Resources/Textures/Interface/AdminActions/remove-hand.png b/Resources/Textures/Interface/AdminActions/remove-hand.png
new file mode 100644
index 0000000000..535031a83a
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/remove-hand.png differ
diff --git a/Resources/Textures/Interface/AdminActions/remove-hands.png b/Resources/Textures/Interface/AdminActions/remove-hands.png
new file mode 100644
index 0000000000..cacbe62523
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/remove-hands.png differ
diff --git a/Resources/Textures/Interface/AdminActions/rename.png b/Resources/Textures/Interface/AdminActions/rename.png
new file mode 100644
index 0000000000..444de5a36e
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/rename.png differ
diff --git a/Resources/Textures/Interface/AdminActions/rename_and_redescribe.png b/Resources/Textures/Interface/AdminActions/rename_and_redescribe.png
new file mode 100644
index 0000000000..e35aaccd2e
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/rename_and_redescribe.png differ
diff --git a/Resources/Textures/Interface/AdminActions/run-walk-swap.png b/Resources/Textures/Interface/AdminActions/run-walk-swap.png
new file mode 100644
index 0000000000..6cac9b851b
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/run-walk-swap.png differ
diff --git a/Resources/Textures/Interface/AdminActions/snap_joints.png b/Resources/Textures/Interface/AdminActions/snap_joints.png
new file mode 100644
index 0000000000..e18d61c57e
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/snap_joints.png differ
diff --git a/Resources/Textures/Interface/AdminActions/unbolt.png b/Resources/Textures/Interface/AdminActions/unbolt.png
new file mode 100644
index 0000000000..bd36190489
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/unbolt.png differ
diff --git a/Resources/Textures/Interface/AdminActions/zoom.png b/Resources/Textures/Interface/AdminActions/zoom.png
new file mode 100644
index 0000000000..507e73b24b
Binary files /dev/null and b/Resources/Textures/Interface/AdminActions/zoom.png differ
diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings
index 30fb5be573..45867d6f23 100644
--- a/SpaceStation14.sln.DotSettings
+++ b/SpaceStation14.sln.DotSettings
@@ -540,6 +540,7 @@ public sealed class $CLASS$ : Shared$CLASS$ {
True
True
True
+ True
True
True
True