From f98df73fae87147a587b44114566e470984ef8f1 Mon Sep 17 00:00:00 2001 From: Moony Date: Thu, 21 Jul 2022 17:30:00 -0500 Subject: [PATCH] Adds even more smites and a bunch of tools. (#9825) * Adds three new smites, headstand, locker stuff, and reptilian species swap. * Localize all the smites. * save work * More smites... * Final tweaks. * oops * !PLEH * Adds disarm prone and improved hand removal options. * fix chances. * take out the trash. * Add some admin TRICKS instead of more smites. * oop * Implements the admin test arena and associated trick. * Tricks for granting/revoking access. * e * mfw * Implement quick dialogs, for when you don't want to spend 20 minutes writing a simple dialog prompt. * Forgot the rejuv icon. * E * docs * augh * Add rename/redescribe buttons. * Adds objects menu, implements a couple tricks for stations. * 1984 * Adds a trick for effectively infinite power. * fixes some icon uggo. * a * HALT! * Pause/unpause buttons. * Forgor the textures. * they broke every bone in their body. * i added more * more battery actions, touch up battery icon. * Address reviews. --- .../Administration/AdminNameOverlay.cs | 1 + .../Components/HeadstandComponent.cs | 10 + .../Administration/QuickDialogSystem.cs | 154 ++ .../{ => Systems}/AdminSystem.Menu.cs | 29 +- .../{ => Systems}/AdminSystem.Overlay.cs | 2 +- .../{ => Systems}/AdminSystem.cs | 5 +- .../{ => Systems}/AdminVerbSystem.cs | 5 +- .../{ => Systems}/BwoinkSystem.cs | 11 +- .../Administration/Systems/HeadstandSystem.cs | 35 + .../{ => Systems}/KillSignSystem.cs | 2 +- .../Administration/UI/AdminMenuWindow.xaml | 4 +- .../Administration/UI/AdminMenuWindow.xaml.cs | 1 + .../Administration/UI/BwoinkWindow.xaml.cs | 1 + .../UI/CustomControls/BwoinkPanel.xaml.cs | 1 + .../CustomControls/PlayerListControl.xaml.cs | 1 + .../UI/Tabs/ObjectsTab/ObjectsTab.xaml | 16 + .../UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs | 73 + .../UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml | 18 + .../Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs | 19 + .../UI/Tabs/PlayerTab/PlayerTab.xaml.cs | 1 + Content.Client/Commands/OpenAHelpCommand.cs | 1 + Content.Client/HUD/GameHud.cs | 1 + Content.Client/Station/StationSystem.cs | 32 + Content.Client/UserInterface/FancyWindow.xaml | 5 +- .../Components/DisarmProneComponent.cs | 7 + .../Components/HeadstandComponent.cs | 10 + .../StationInfiniteBatteryTargetComponent.cs | 10 + .../QuickDialogSystem.OpenDialog.cs | 176 +++ .../Administration/QuickDialogSystem.cs | 176 +++ .../Systems/AdminTestArenaSystem.cs | 33 + .../Systems/AdminVerbSystem.Smites.cs | 342 ++++- .../Systems/AdminVerbSystem.Tools.cs | 928 ++++++++++++ .../Administration/Systems/AdminVerbSystem.cs | 1 + Content.Server/CombatMode/CombatModeSystem.cs | 10 +- .../Cuffs/Components/HandcuffComponent.cs | 4 + .../Station/Systems/StationJobsSystem.cs | 4 - .../Station/Systems/StationSystem.cs | 22 + .../Tools/Systems/WeldableSystem.cs | 12 + .../Components/SharedHeadstandComponent.cs | 6 + .../Administration/QuickDialogOpenEvent.cs | 129 ++ .../Station/StationsUpdatedEvent.cs | 14 + Content.Shared/Verbs/VerbCategory.cs | 4 +- .../Locale/en-US/administration/smites.ftl | 79 + .../administration/ui/admin-menu-window.ftl | 1 + Resources/Locale/en-US/verbs/verb-system.ftl | 1 + Resources/Maps/Test/admin_test_arena.yml | 1324 +++++++++++++++++ .../Entities/Structures/Power/apc.yml | 1 + .../Entities/Structures/Power/smes.yml | 1 + .../Entities/Structures/Power/substation.yml | 2 + Resources/Prototypes/Polymorphs/admin.yml | 37 + Resources/Prototypes/Polymorphs/polymorph.yml | 20 +- .../Roles/Jobs/Civilian/janitor.yml | 13 + .../Interface/AdminActions/adjust-stack.png | Bin 0 -> 210 bytes .../Interface/AdminActions/bar_jobslots.png | Bin 0 -> 307 bytes .../Textures/Interface/AdminActions/bolt.png | Bin 0 -> 267 bytes .../Interface/AdminActions/drain_battery.png | Bin 0 -> 226 bytes .../AdminActions/emergency_access.png | Bin 0 -> 238 bytes .../Interface/AdminActions/fill-stack.png | Bin 0 -> 213 bytes .../Interface/AdminActions/fill_battery.png | Bin 0 -> 228 bytes .../Textures/Interface/AdminActions/flip.png | Bin 0 -> 264 bytes .../Textures/Interface/AdminActions/halt.png | Bin 0 -> 145 bytes .../Interface/AdminActions/help-backwards.png | Bin 0 -> 188 bytes .../AdminActions/infinite_battery.png | Bin 0 -> 240 bytes .../Textures/Interface/AdminActions/pause.png | Bin 0 -> 128 bytes .../Textures/Interface/AdminActions/play.png | Bin 0 -> 198 bytes .../Interface/AdminActions/redescribe.png | Bin 0 -> 212 bytes .../Interface/AdminActions/rejuvenate.png | Bin 0 -> 253 bytes .../Interface/AdminActions/remove-hand.png | Bin 0 -> 221 bytes .../Interface/AdminActions/remove-hands.png | Bin 0 -> 234 bytes .../Interface/AdminActions/rename.png | Bin 0 -> 218 bytes .../AdminActions/rename_and_redescribe.png | Bin 0 -> 292 bytes .../Interface/AdminActions/run-walk-swap.png | Bin 0 -> 302 bytes .../Interface/AdminActions/snap_joints.png | Bin 0 -> 290 bytes .../Interface/AdminActions/unbolt.png | Bin 0 -> 272 bytes .../Textures/Interface/AdminActions/zoom.png | Bin 0 -> 270 bytes SpaceStation14.sln.DotSettings | 1 + 76 files changed, 3708 insertions(+), 88 deletions(-) create mode 100644 Content.Client/Administration/Components/HeadstandComponent.cs create mode 100644 Content.Client/Administration/QuickDialogSystem.cs rename Content.Client/Administration/{ => Systems}/AdminSystem.Menu.cs (85%) rename Content.Client/Administration/{ => Systems}/AdminSystem.Overlay.cs (96%) rename Content.Client/Administration/{ => Systems}/AdminSystem.cs (94%) rename Content.Client/Administration/{ => Systems}/AdminVerbSystem.cs (91%) rename Content.Client/Administration/{ => Systems}/BwoinkSystem.cs (96%) create mode 100644 Content.Client/Administration/Systems/HeadstandSystem.cs rename Content.Client/Administration/{ => Systems}/KillSignSystem.cs (96%) create mode 100644 Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml create mode 100644 Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTab.xaml.cs create mode 100644 Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml create mode 100644 Content.Client/Administration/UI/Tabs/ObjectsTab/ObjectsTabEntry.xaml.cs create mode 100644 Content.Client/Station/StationSystem.cs create mode 100644 Content.Server/Administration/Components/DisarmProneComponent.cs create mode 100644 Content.Server/Administration/Components/HeadstandComponent.cs create mode 100644 Content.Server/Administration/Components/StationInfiniteBatteryTargetComponent.cs create mode 100644 Content.Server/Administration/QuickDialogSystem.OpenDialog.cs create mode 100644 Content.Server/Administration/QuickDialogSystem.cs create mode 100644 Content.Server/Administration/Systems/AdminTestArenaSystem.cs create mode 100644 Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs create mode 100644 Content.Shared/Administration/Components/SharedHeadstandComponent.cs create mode 100644 Content.Shared/Administration/QuickDialogOpenEvent.cs create mode 100644 Content.Shared/Station/StationsUpdatedEvent.cs create mode 100644 Resources/Maps/Test/admin_test_arena.yml create mode 100644 Resources/Prototypes/Polymorphs/admin.yml create mode 100644 Resources/Textures/Interface/AdminActions/adjust-stack.png create mode 100644 Resources/Textures/Interface/AdminActions/bar_jobslots.png create mode 100644 Resources/Textures/Interface/AdminActions/bolt.png create mode 100644 Resources/Textures/Interface/AdminActions/drain_battery.png create mode 100644 Resources/Textures/Interface/AdminActions/emergency_access.png create mode 100644 Resources/Textures/Interface/AdminActions/fill-stack.png create mode 100644 Resources/Textures/Interface/AdminActions/fill_battery.png create mode 100644 Resources/Textures/Interface/AdminActions/flip.png create mode 100644 Resources/Textures/Interface/AdminActions/halt.png create mode 100644 Resources/Textures/Interface/AdminActions/help-backwards.png create mode 100644 Resources/Textures/Interface/AdminActions/infinite_battery.png create mode 100644 Resources/Textures/Interface/AdminActions/pause.png create mode 100644 Resources/Textures/Interface/AdminActions/play.png create mode 100644 Resources/Textures/Interface/AdminActions/redescribe.png create mode 100644 Resources/Textures/Interface/AdminActions/rejuvenate.png create mode 100644 Resources/Textures/Interface/AdminActions/remove-hand.png create mode 100644 Resources/Textures/Interface/AdminActions/remove-hands.png create mode 100644 Resources/Textures/Interface/AdminActions/rename.png create mode 100644 Resources/Textures/Interface/AdminActions/rename_and_redescribe.png create mode 100644 Resources/Textures/Interface/AdminActions/run-walk-swap.png create mode 100644 Resources/Textures/Interface/AdminActions/snap_joints.png create mode 100644 Resources/Textures/Interface/AdminActions/unbolt.png create mode 100644 Resources/Textures/Interface/AdminActions/zoom.png 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 0000000000000000000000000000000000000000..9f81b7d751af27c23646791b8910102c92cb825f GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}J)SO(ArY-_ zr`_gjP~dS6Km7myr~LYvXIr_dOrODKUEvo$n{Hi&O8L)c zvL*YR$`iOfl(LQWBVRSdU0|u$alF9n>AJPN42Cir4xKB{t2LA{*rV*H>7*PdSoP;{ zqyB&XP{y_Vm%=uF;dsGxha+qsXI^>9b(ZF&mx32qbNrUs=>#j-e|81Bi^0>?&t;uc GLK6T;mP-Ku literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/bar_jobslots.png b/Resources/Textures/Interface/AdminActions/bar_jobslots.png new file mode 100644 index 0000000000000000000000000000000000000000..e6fe06bbd4cd4bf575520172531cff53cda5b878 GIT binary patch literal 307 zcmV-30nGl1P)Px#?MXyIR9J=Gmq8MPAPhx6J(3=!J!X3py%Js3K}wt+$}&! zc;Kf5sIbCM3Dgz%%riTegPR4oz4yXR0ugXP`5JoQ&t=gp?f(w+fV%~H!2v}NSh}5p zOD7~?tU%Aq%>of{wLlacX!JnNIro4|eIQmKGV`W@bg=Y-vjU^wq`*k{L0~j|CvXLP z6SxWv6yE{q@}RwX9Tsn#13Q6Qg8G@zifF)WNZaw3@dfp2s6E1Tnb80M002ovPDHLk FV1i?%e1rf1 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/bolt.png b/Resources/Textures/Interface/AdminActions/bolt.png new file mode 100644 index 0000000000000000000000000000000000000000..25f068a61c4b7ea1bf5612cd3500e6b6af012dca GIT binary patch literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}M?GB}Ln2!5 zPCqTwpupj}^#A|ow?Y>8afk~nkI;V3VYh0^{-c%cWj3ydXFqXC`e(qHcXg6WM?=V` znJv){xAXlTec;My&b@f*iE~1D=hN8%lRUB=Ww+*3alcYfDhfZqRj*ohaDHuqSVzNb zR+A6xN_+V?FfhG;-jnC-aeztu`1_XCtZP0zJG9ZxZFSf>p$OjjTqy@48+CpJCSWIBkCL%L|197pEF-H_r*1l)D3_UcOZPUg?I)i<&Rtx;EPm*$aw?h|d4r=e_?F P&_fKKu6{1-oD!M5 zGm5p3BpUWk;5rbWCzWmA?c-}Ym0^p(q6Y>CVvWy7hFxa3kz&&BW_?wMal=Bk2S3FR zFs$xX+52D0jXUn(j`AmB3$jlMq_G(>&un}v8Q>ZlSSn!ni{-n`)$RMcVy&x9nCD;T YZ0N7?Y`5O(4|FGkr>mdKI;Vst0OQtIHvj+t literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/emergency_access.png b/Resources/Textures/Interface/AdminActions/emergency_access.png new file mode 100644 index 0000000000000000000000000000000000000000..06a736bace127d81c5aa7d38266322ed550be13e GIT binary patch literal 238 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}D?D8sLn2z= zPBY|dFyL_gxon->@!#j=j$d8qXX@c4czFwVzVL_N+7aF+k{`NX-`T7av7OoI+0v!m zVS)U@)82gw-ywcET7czXiG$0O;;?dU28~LF^V_wPO?#vEp4nM;NJ;7F7S@cfHq%Uw zPwSLgEBe{yylSKgQ{I=<2J6*(dyV*O^tSxo|LC7|UFKx=?>t*Jq&YleaL9Gk51#gK l0&}OCzw$g;7Ds^@cY=)yM8&pGSpsx3gQu&X%Q~loCID{GTSEW< literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/fill-stack.png b/Resources/Textures/Interface/AdminActions/fill-stack.png new file mode 100644 index 0000000000000000000000000000000000000000..8dceb38f03e1eb2c58ee89763da86af8df26688e GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}{hlt4ArY-_ zr+M==DDb#QKK{Et#@vjN>C@KD3-Xt;B>p;iL%@&ScD9SlGJOWS_dC+~?;VX~dZHS) zJ6rne&d|f5T})+@-PgWnaCpx+nI}W?@d;tZX{P55Pc{T`_1$AGkzbI0ptj*1b9#zH zq5O+^>_z|n*qgBS@GVi@_>TE(<$`aGteu-yGK(F(RNbI=ppxM&Pk?*7hZxA=44$rj JF6*2UngFWVPPhO7 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/fill_battery.png b/Resources/Textures/Interface/AdminActions/fill_battery.png new file mode 100644 index 0000000000000000000000000000000000000000..268400f01506abcbdd7486236f48b49812f7dbc0 GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}^E_P~Ln2z= zPCv-kV8G!#_v8QiopCBNyFC)6__w8N_+Qg0j$E>Tb*MDsgOn>#C$+Dd@Rj-s8UH4h99@&I42uLdJuo?NcZ1j3%x;D=OU##tS%2|ioRP>9u#-EZ zp-!WiMTdv?0q?A<&%ZYqE#T2$KGk64z{ucG#{7kO8tVtP&{vlj!k;ZPtYld_zf;{= a>D^U2)IO-t3x zYPp&eIR5chU7mFKudWAo-}TCk&;B-q%Ad1-`shrpl$~&Rmk7V7^0P<%w>cRY&P`vg z5vaT9@VgG*G++WFz3%f8wQuPPdj?~LKg&Y`OAK#N^{DU lpEtFBdSz$y7(ToF#$vsV+Puf^+kh@$@O1TaS?83{1OOBUNTvV) literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/infinite_battery.png b/Resources/Textures/Interface/AdminActions/infinite_battery.png new file mode 100644 index 0000000000000000000000000000000000000000..94192f632eae2146171b63e215bc4d45a0cad105 GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}t2|vCLn2z= zhB@*zDDW^p{=eTwf6{`)b!&xu7Chfr=4YmLO9RgZ>A$)-_e!o;Mh4EbZ<;_Z+hpy(3b%c=Q> zVe|7d7Z+XMFmIh&kY1XXzR?Axa0lxJ^J4fnFt63z&#~}}Zi0ZN@gF`N#>1=*s~58g nMxK3GfArGF4@XuU&&ZQ$oStIBcV1^X(A5l{u6{1-oD!M?f$&Wd- TWDb@c1c`XM`njxgN@xNAX|pB# literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/play.png b/Resources/Textures/Interface/AdminActions/play.png new file mode 100644 index 0000000000000000000000000000000000000000..2f665c26f9a6eabca728be1a28e7585826f742eb GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}4W2HJArY-_ zr{3mjaNuw;{q;Y7-E=XbuCGR$Sc~sneWN~bcH8<{O&^GRObyN%(V!Ugq;B(XiV2hJSWm-S)6QHLkiPP{sInQugTe~DWM4fyWmHx literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/redescribe.png b/Resources/Textures/Interface/AdminActions/redescribe.png new file mode 100644 index 0000000000000000000000000000000000000000..f2051008e408b244d90230e9fb693208c3184a6b GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}eV#6kArY-_ zr+M==7;v~qKK{GDM|!pi*OY7fXKtVTKExobWkS2--vz67aWg#l8C7>a{Ed=Qu=m32 z7ShQ-vu@|8ruvCK{d&cO)#VE7HeTiV>G^#M%~y4dttK{_Pq2(kQ}^1VxgdQd!-Erx z%-jWZ!_>~7l3)9?xa8@(eamawL>w4JK4czxeeLPlpZ*F@cy;o3L@4Y1{qzjzG6qjq KKbLh*2~7ab*HiKU literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/rejuvenate.png b/Resources/Textures/Interface/AdminActions/rejuvenate.png new file mode 100644 index 0000000000000000000000000000000000000000..da8f6636d89c8c98e3917aa8fa327c995dad61e9 GIT binary patch literal 253 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}+dW+zLn2zA zPK_37P~c$L`1}6Mx$qGFFt!*Au-3cI3EMvZRn~0Iy+AK8c)I$ztaD0e0sw6s BVw(T} literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/remove-hand.png b/Resources/Textures/Interface/AdminActions/remove-hand.png new file mode 100644 index 0000000000000000000000000000000000000000..535031a83a7d406f1102ddb1ce923848c1d4e8d5 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}(>+}rLn2y} z6C_v{Cy4Yk1sWV^5S;%+N_p{rgR&Dliz}`QF&S*Wa8~ga;}ff<3F;n23>*6tT;(I% zCUSkSoV7wh^}67Z7BSC4uP)~fUwx6KZ}c9QS3$Q0`bcOOPdEL2RH?Y61X z=Sf6eda(FW&dEyb0m1=K8p_|>FSt-r&S1(A$rtcZd$GoMko{b4j7C5gZ#G?fm%&I4 P=tKrjS3j3^P6STBH@ztC%Q~kcJd#csXySO1oH~U3GbdSldCT zZJ9kwRvl#Ha635HH#SAxCFzS$3By{(i+mN||1op!9s$fl;e2HRU?zopr0GR<&F8}}l literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/rename.png b/Resources/Textures/Interface/AdminActions/rename.png new file mode 100644 index 0000000000000000000000000000000000000000..444de5a36e46f52c7f4e21fae739565eef3421e5 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Q#@T9Ln2z= zPQA@{K!L-#`{w`uM%A~RHoSTly=k8LMW@i71_AwKh6nHX7}C-TjgC+MsO9%@MgPV% z-MgimdKI;Vst0O1@}UH||9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e35aaccd2e3aca977c2af83c995dfb9c01a88d09 GIT binary patch literal 292 zcmV+<0o(qGP)Px#-bqA3R9J=WR@n~2FbH$1{r@ksr%aTZBR#ZD!fz!UP9RQ!FJl0JhzJ0TF@|2W z_WAt@Hg+AaZ8lEFZ>%5#TeG(L9p$ljv847SG`!P@h}u|!Ft< zBT>bn1<}x41tWJLkdeq{AOgEZbOv;x#?zq6w{u8(9<65z3OG8Dky`P|6x6*1UT{Sv zS8`GU5p)9Y@ruY;d)k5uSh1>3ET2GSXO)?l{*=JVP*tVd^GRE9RuQ9@u~n8Ld*`3R qE!VIKG=V1Y*Es!)eM|9QcoW{3^1L9uu5^|F0000UqTg literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6cac9b851b4b8189b7a78b1c706e7780da268279 GIT binary patch literal 302 zcmV+}0nz@6P)Px#=t)FDR9J=WR!bJcAP59s?|)^wN&5(n2!5I_oRtX(6CeQ5!{C-cM5Iz?W-UX{ zR)+L7N`@^WHLKetz!##9vm{N`b$F)PZooC$qP2#9jWpKv-hF0fSOeY&jSiU>tZwmyp7D`&9rcu=A3UJ2jxH?P z)+4Q06R!wFwiqHm1)^Z@Sla~&t?`x3=Px#+(|@1R9J=WmQfOeAPhu>BXO1<)3bP`_)}&gC1eAMW2fCuhiqOJP`B-`Z_r)} zMS15aV|Re46if+3KtKSm<4E3(O#ocpDVQeX&An2Yz6EW2xm zCQ|Gig5E-!s{j^=S&3$CSJgz^bV`K%|}r zu(JP>MxJT{Gy89;xd22=&%Jy8;v4L;$e4mQ2=_}!fdv9hvHHQc05P7~!pv#z67QFL o4|_leq*SMQi?aS)tk(UePKyqXU6HqV{{R3007*qoM6N<$f|#v%0ssI2 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/AdminActions/unbolt.png b/Resources/Textures/Interface/AdminActions/unbolt.png new file mode 100644 index 0000000000000000000000000000000000000000..bd361904899840a38c8311aa9738424689d2e0f5 GIT binary patch literal 272 zcmV+r0q_2aP)Px#%1J~)R9J=Wmdg@=AP7aF|NrOORAUBSM9`Tg*2+18FQTu-eui~CN5db8~ih(_S>rJM!V#xw<@SKhS=v&wEd zhM7ID=f09;!|NR4h2eoCA=8%({$L&F9L4J%P>)LjTojOCS#VXLgl58<0yE%$5D^_* W1&A`Tl23X70000Px#$Vo&&R9J=WmeCEuFbqV`D09GIFayj2Gr(Z5NcgFdaS|sjBp}uAtIAovtJ|~z z)(!zcj4>7qh^VBuYD7d*lk1zM$>$(L>cGKDsNuStZfVwfG67krgDpk&(LFv{X$xG_kz zC^up@zg>0!fc^2F4bc*$D3+eBHfO95R*?UlKrP6ycfbn5A*A`p)6`eK1CH}^o=f7C Ut-IL!L;wH)07*qoM6N<$f?2?JSpWb4 literal 0 HcmV?d00001 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