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.
This commit is contained in:
Moony
2022-07-21 17:30:00 -05:00
committed by GitHub
parent ec18f438bc
commit f98df73fae
76 changed files with 3708 additions and 88 deletions

View File

@@ -1,3 +1,4 @@
using Content.Client.Administration.Systems;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;

View File

@@ -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
{
}

View File

@@ -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;
/// <summary>
/// This handles the client portion of quick dialogs.
/// </summary>
public sealed class QuickDialogSystem : EntitySystem
{
/// <inheritdoc/>
public override void Initialize()
{
SubscribeNetworkEvent<QuickDialogOpenEvent>(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<string, LineEdit>();
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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -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
{

View File

@@ -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
{

View File

@@ -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
{
/// <summary>
/// Client-side admin verb system. These usually open some sort of UIs.

View File

@@ -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

View File

@@ -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<HeadstandComponent, ComponentStartup>(OnHeadstandAdded);
SubscribeLocalEvent<HeadstandComponent, ComponentShutdown>(OnHeadstandRemoved);
}
private void OnHeadstandAdded(EntityUid uid, HeadstandComponent component, ComponentStartup args)
{
if (!TryComp<SpriteComponent>(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<SpriteComponent>(uid, out var sprite))
return;
foreach (var layer in sprite.AllLayers)
{
layer.Rotation -= Angle.FromDegrees(180.0f);
}
}
}

View File

@@ -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
{

View File

@@ -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">
<TabContainer Name="MasterTabContainer">
<adminTab:AdminTab />
<adminbusTab:AdminbusTab />
@@ -12,5 +13,6 @@
<tabs:RoundTab />
<tabs:ServerTab />
<playerTab:PlayerTab Name="PlayerTabControl" Access="Public" />
<objectsTab:ObjectsTab Name="ObjectsTabControl" Access="Public" />
</TabContainer>
</DefaultWindow>

View File

@@ -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()

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,16 @@
<Control xmlns="https://spacestation14.io"
xmlns:pt="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label HorizontalExpand="True" SizeFlagsStretchRatio="0.50"
Text="{Loc Object type:}" />
<OptionButton Name="ObjectTypeOptions" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"/>
</BoxContainer>
<cc:HSeparator/>
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" Name="ObjectList">
</BoxContainer>
</ScrollContainer>
</BoxContainer>
</Control>

View File

@@ -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<ObjectsTabEntry> _objects = new();
private List<ObjectsTabSelection> _selections = new();
public event Action<BaseButton.ButtonEventArgs>? 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<StationSystem>().Stations.ToList(),
ObjectsTabSelection.Grids => _entityManager.EntityQuery<IMapGridComponent>(true).Select(x => x.Owner).ToList(),
ObjectsTabSelection.Maps => _entityManager.EntityQuery<IMapComponent>(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<MetaDataComponent>(entity).EntityName, entity);
_objects.Add(ctrl);
ObjectList.AddChild(ctrl);
ctrl.OnPressed += args => OnEntryPressed?.Invoke(args);
}
}
private enum ObjectsTabSelection
{
Grids,
Maps,
Stations,
}
}

View File

@@ -0,0 +1,18 @@
<ContainerButton xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
EnableAllKeybinds="True">
<PanelContainer Name="BackgroundColorPanel"/>
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
SeparationOverride="4">
<Label Name="NameLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Label Name="EIDLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"/>
</BoxContainer>
</ContainerButton>

View File

@@ -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;
}
}

View File

@@ -1,3 +1,4 @@
using Content.Client.Administration.Systems;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,32 @@
using Content.Shared.Station;
namespace Content.Client.Station;
/// <summary>
/// This handles letting the client know stations are a thing. Only really used by an admin menu.
/// </summary>
public sealed class StationSystem : EntitySystem
{
private readonly HashSet<EntityUid> _stations = new();
/// <summary>
/// All stations that currently exist.
/// </summary>
/// <remarks>
/// I'd have this just invoke an entity query, but we're on the client and the client barely knows about stations.
/// </remarks>
public IReadOnlySet<EntityUid> Stations => _stations;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeNetworkEvent<StationsUpdatedEvent>(StationsUpdated);
}
private void StationsUpdated(StationsUpdatedEvent ev)
{
_stations.Clear();
_stations.UnionWith(ev.Stations);
}
}

View File

@@ -1,4 +1,5 @@
<ui:FancyWindow xmlns="https://spacestation14.io"

<ui:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface"
MouseFilter="Stop"
MinWidth="200" MinHeight="150">
@@ -15,6 +16,6 @@
</BoxContainer>
</Control>
<PanelContainer StyleClasses="LowDivider" />
<Control Name="ContentsContainer" Margin="0 2" RectClipContent="True" VerticalExpand="true" />
<Control Access="Public" Name="ContentsContainer" Margin="0 2" RectClipContent="True" VerticalExpand="true" />
</BoxContainer>
</ui:FancyWindow>

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Administration.Components;
/// <summary>
/// This is used for forcing someone to be disarmed 100% of the time.
/// </summary>
[RegisterComponent]
public sealed class DisarmProneComponent : Component { }

View File

@@ -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
{
}

View File

@@ -0,0 +1,10 @@
namespace Content.Server.Administration.Components;
/// <summary>
/// This is used for the admin map-wide/station-wide/grid-wide infinite power trick.
/// </summary>
[RegisterComponent]
public sealed class StationInfiniteBatteryTargetComponent : Component
{
}

View File

@@ -0,0 +1,176 @@
using Content.Shared.Administration;
using JetBrains.Annotations;
using Robust.Server.Player;
namespace Content.Server.Administration;
public sealed partial class QuickDialogSystem
{
/// <summary>
/// Opens a dialog for the given client, allowing them to enter in the desired data.
/// </summary>
/// <param name="session">Client to show a dialog for.</param>
/// <param name="title">Title of the dialog.</param>
/// <param name="prompt">The prompt.</param>
/// <param name="okAction">The action to execute upon Ok being pressed.</param>
/// <param name="cancelAction">The action to execute upon the dialog being cancelled.</param>
/// <typeparam name="T1">Type of the input.</typeparam>
[PublicAPI]
public void OpenDialog<T1>(IPlayerSession session, string title, string prompt, Action<T1> okAction,
Action? cancelAction = null)
{
OpenDialogInternal(
session,
title,
new List<QuickDialogEntry>
{
new("1", TypeToEntryType(typeof(T1)), prompt)
},
QuickDialogButtonFlag.OkButton | QuickDialogButtonFlag.CancelButton,
(ev =>
{
if (TryParseQuickDialog<T1>(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 ?? (() => { })
);
}
/// <summary>
/// Opens a dialog for the given client, allowing them to enter in the desired data.
/// </summary>
/// <param name="session">Client to show a dialog for.</param>
/// <param name="title">Title of the dialog.</param>
/// <param name="prompt1">The first prompt.</param>
/// <param name="prompt2">The second prompt.</param>
/// <param name="okAction">The action to execute upon Ok being pressed.</param>
/// <param name="cancelAction">The action to execute upon the dialog being cancelled.</param>
/// <typeparam name="T1">Type of the first input.</typeparam>
/// <typeparam name="T2">Type of the second input.</typeparam>
[PublicAPI]
public void OpenDialog<T1, T2>(IPlayerSession session, string title, string prompt1, string prompt2,
Action<T1, T2> okAction, Action? cancelAction = null)
{
OpenDialogInternal(
session,
title,
new List<QuickDialogEntry>
{
new("1", TypeToEntryType(typeof(T1)), prompt1),
new("2", TypeToEntryType(typeof(T2)), prompt2)
},
QuickDialogButtonFlag.OkButton | QuickDialogButtonFlag.CancelButton,
(ev =>
{
if (TryParseQuickDialog<T1>(TypeToEntryType(typeof(T1)), ev.Responses["1"], out var v1) &&
TryParseQuickDialog<T2>(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 ?? (() => { })
);
}
/// <summary>
/// Opens a dialog for the given client, allowing them to enter in the desired data.
/// </summary>
/// <param name="session">Client to show a dialog for.</param>
/// <param name="title">Title of the dialog.</param>
/// <param name="prompt1">The first prompt.</param>
/// <param name="prompt2">The second prompt.</param>
/// <param name="prompt3">The third prompt.</param>
/// <param name="okAction">The action to execute upon Ok being pressed.</param>
/// <param name="cancelAction">The action to execute upon the dialog being cancelled.</param>
/// <typeparam name="T1">Type of the first input.</typeparam>
/// <typeparam name="T2">Type of the second input.</typeparam>
/// <typeparam name="T3">Type of the third input.</typeparam>
[PublicAPI]
public void OpenDialog<T1, T2, T3>(IPlayerSession session, string title, string prompt1, string prompt2,
string prompt3, Action<T1, T2, T3> okAction, Action? cancelAction = null)
{
OpenDialogInternal(
session,
title,
new List<QuickDialogEntry>
{
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<T1>(TypeToEntryType(typeof(T1)), ev.Responses["1"], out var v1) &&
TryParseQuickDialog<T2>(TypeToEntryType(typeof(T2)), ev.Responses["2"], out var v2) &&
TryParseQuickDialog<T3>(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 ?? (() => { })
);
}
/// <summary>
/// Opens a dialog for the given client, allowing them to enter in the desired data.
/// </summary>
/// <param name="session">Client to show a dialog for.</param>
/// <param name="title">Title of the dialog.</param>
/// <param name="prompt1">The first prompt.</param>
/// <param name="prompt2">The second prompt.</param>
/// <param name="prompt3">The third prompt.</param>
/// <param name="prompt4">The fourth prompt.</param>
/// <param name="okAction">The action to execute upon Ok being pressed.</param>
/// <param name="cancelAction">The action to execute upon the dialog being cancelled.</param>
/// <typeparam name="T1">Type of the first input.</typeparam>
/// <typeparam name="T2">Type of the second input.</typeparam>
/// <typeparam name="T3">Type of the third input.</typeparam>
/// <typeparam name="T4">Type of the fourth input.</typeparam>
[PublicAPI]
public void OpenDialog<T1, T2, T3, T4>(IPlayerSession session, string title, string prompt1, string prompt2,
string prompt3, string prompt4, Action<T1, T2, T3, T4> okAction, Action? cancelAction = null)
{
OpenDialogInternal(
session,
title,
new List<QuickDialogEntry>
{
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<T1>(TypeToEntryType(typeof(T1)), ev.Responses["1"], out var v1) &&
TryParseQuickDialog<T2>(TypeToEntryType(typeof(T2)), ev.Responses["2"], out var v2) &&
TryParseQuickDialog<T3>(TypeToEntryType(typeof(T3)), ev.Responses["3"], out var v3) &&
TryParseQuickDialog<T4>(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 ?? (() => { })
);
}
}

View File

@@ -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;
/// <summary>
/// This handles the server portion of quick dialogs, including opening them.
/// </summary>
public sealed partial class QuickDialogSystem : EntitySystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
/// <summary>
/// Contains the success/cancel actions for a dialog.
/// </summary>
private readonly Dictionary<int, (Action<QuickDialogResponseEvent> okAction, Action cancelAction)> _openDialogs = new();
private readonly Dictionary<NetUserId, List<int>> _openDialogsByUser = new();
private int _nextDialogId = 1;
/// <inheritdoc/>
public override void Initialize()
{
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
SubscribeNetworkEvent<QuickDialogResponseEvent>(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<QuickDialogEntry> entries, QuickDialogButtonFlag buttons, Action<QuickDialogResponseEvent> 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<int>());
_openDialogsByUser[session.UserId].Add(did);
}
private bool TryParseQuickDialog<T>(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}.");
}
}
/// <summary>
/// A type used with quick dialogs to indicate you want a large entry window for text and not a short one.
/// </summary>
/// <param name="String">The string retrieved.</param>
public record struct LongString(string String);

View File

@@ -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;
/// <summary>
/// This handles the administrative test arena maps, and loading them.
/// </summary>
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<NetUserId, EntityUid> ArenaMap { get; private set; } = new();
public Dictionary<NetUserId, EntityUid> 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<MapComponent>(ArenaMap[admin.UserId]).WorldMap, ArenaMapPath);
ArenaGrid[admin.UserId] = grids.First();
return (ArenaMap[admin.UserId], ArenaGrid[admin.UserId]);
}
}

View File

@@ -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<Verb> args)
@@ -76,6 +82,10 @@ public sealed partial class AdminVerbSystem
if (!_adminManager.HasAdminFlag(player, AdminFlags.Fun))
return;
// 1984.
if (HasComp<IMapComponent>(args.Target) || HasComp<IMapGridComponent>(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<DiseaseCarrierComponent>(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<StomachComponent>(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<LungComponent>(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<PhysicsComponent>(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<ActorComponent>(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<KillSignComponent>(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<ClothingComponent>(clothing))
EnsureComp<UnremoveableComponent>(clothing);
EnsureComp<ClumsyComponent>(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<PointingArrowAngeringComponent>(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<BufferingComponent>(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<MovementIgnoreGravityComponent>(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<EntityStorageComponent>(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<HeadstandComponent>(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<EyeComponent>(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<EyeComponent>(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<MovementSpeedModifierComponent>(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<BackwardsAccentComponent>(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<DisarmProneComponent>(args.Target);
},
Impact = LogImpact.Extreme,
Message = Loc.GetString("admin-smite-disarm-prone-description"),
};
args.Verbs.Add(disarmProne);
}
}

View File

@@ -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<Verb> args)
{
if (!EntityManager.TryGetComponent<ActorComponent?>(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<AirlockComponent>(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<DamageableComponent>(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<BatteryComponent>(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<BatterySelfRechargerComponent>(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<AnchorableComponent>(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<GasTankComponent>(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<InventoryComponent>(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<GasTankComponent>(entity, out var tank))
continue;
RefillGasTank(entity.Value, Gas.Oxygen, tank);
}
foreach (var held in _handsSystem.EnumerateHeld(args.Target))
{
if (!TryComp<GasTankComponent>(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<GasTankComponent>(entity, out var tank))
continue;
RefillGasTank(entity.Value, Gas.Nitrogen, tank);
}
foreach (var held in _handsSystem.EnumerateHeld(args.Target))
{
if (!TryComp<GasTankComponent>(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<GasTankComponent>(entity, out var tank))
continue;
RefillGasTank(entity.Value, Gas.Plasma, tank);
}
foreach (var held in _handsSystem.EnumerateHeld(args.Target))
{
if (!TryComp<GasTankComponent>(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<AccessComponent>(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<StackComponent>(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<StationDataComponent>(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<StationCargoOrderDatabaseComponent>(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<StationInfiniteBatteryTargetComponent>(ent))
continue;
var battery = EnsureComp<BatteryComponent>(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<StationInfiniteBatteryTargetComponent>(ent))
continue;
var battery = EnsureComp<BatteryComponent>(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<StationInfiniteBatteryTargetComponent>(ent))
continue;
var recharger = EnsureComp<BatterySelfRechargerComponent>(ent);
var battery = EnsureComp<BatteryComponent>(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<PhysicsComponent>(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<IMapComponent>(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<JointComponent>(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<GunComponent>(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<BallisticAmmoProviderComponent>(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<EntityUid>? enumerator)
{
if (!HasComp<IMapComponent>(target) && !HasComp<IMapGridComponent>(target) &&
!HasComp<StationDataComponent>(target))
{
enumerator = null;
return false;
}
enumerator = GetGridChildrenInner(target);
return true;
}
// ew. This finds everything supposedly on a grid.
private IEnumerable<EntityUid> GetGridChildrenInner(EntityUid target)
{
if (TryComp<StationDataComponent>(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<IMapComponent>(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<AccessComponent>(slotEntity))
{
return slotEntity.Value;
}
else if (TryComp<PDAComponent>(slotEntity, out var pda))
{
if (pda.ContainedID != null)
{
return pda.ContainedID.Owner;
}
}
}
else if (TryComp<HandsComponent>(target, out var hands))
{
foreach (var held in _handsSystem.EnumerateHeld(target, hands))
{
if (HasComp<AccessComponent>(held))
{
return held;
}
}
}
return null;
}
private void GiveAllAccess(EntityUid entity)
{
var allAccess = _prototypeManager
.EnumeratePrototypes<AccessLevelPrototype>()
.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,
}
}

View File

@@ -53,6 +53,7 @@ namespace Content.Server.Administration.Systems
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddAdminVerbs);
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddDebugVerbs);
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddSmiteVerbs);
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddTricksVerbs);
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddAntagVerbs);
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<SolutionContainerManagerComponent, SolutionChangedEvent>(OnSolutionChanged);

View File

@@ -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<DisarmProneComponent>(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<DisarmProneComponent>(disarmer))
return 1.0f;
if (HasComp<DisarmProneComponent>(disarmed))
return 0.0f;
if (TryComp<DamageableComponent>(disarmer, out var disarmerDamage) && TryComp<DamageableComponent>(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.

View File

@@ -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<DisarmProneComponent>(target))
cuffTime = 0.0f; // cuff them instantly.
var doAfterEventArgs = new DoAfterEventArgs(user, cuffTime, default, target)
{
BreakOnTargetMove = true,

View File

@@ -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;

View File

@@ -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)

View File

@@ -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))

View File

@@ -0,0 +1,6 @@
namespace Content.Shared.Administration.Components;
/// <summary>
/// Flips the target's sprite on it's head, so they do a headstand.
/// </summary>
public abstract class SharedHeadstandComponent : Component { }

View File

@@ -0,0 +1,129 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Administration;
/// <summary>
/// A networked event raised when the server wants to open a quick dialog.
/// </summary>
[Serializable, NetSerializable]
public sealed class QuickDialogOpenEvent : EntityEventArgs
{
/// <summary>
/// The title of the dialog.
/// </summary>
public string Title;
/// <summary>
/// The internal dialog ID.
/// </summary>
public int DialogId;
/// <summary>
/// The prompts to show the user.
/// </summary>
public List<QuickDialogEntry> Prompts;
/// <summary>
/// The buttons presented for the user.
/// </summary>
public QuickDialogButtonFlag Buttons = QuickDialogButtonFlag.OkButton;
public QuickDialogOpenEvent(string title, List<QuickDialogEntry> prompts, int dialogId, QuickDialogButtonFlag buttons)
{
Title = title;
Prompts = prompts;
Buttons = buttons;
DialogId = dialogId;
}
}
/// <summary>
/// A networked event raised when the client replies to a quick dialog.
/// </summary>
[Serializable, NetSerializable]
public sealed class QuickDialogResponseEvent : EntityEventArgs
{
/// <summary>
/// The internal dialog ID.
/// </summary>
public int DialogId;
/// <summary>
/// The responses to the prompts.
/// </summary>
public Dictionary<string, string> Responses;
/// <summary>
/// The button pressed when responding.
/// </summary>
public QuickDialogButtonFlag ButtonPressed;
public QuickDialogResponseEvent(int dialogId, Dictionary<string, string> responses, QuickDialogButtonFlag buttonPressed)
{
DialogId = dialogId;
Responses = responses;
ButtonPressed = buttonPressed;
}
}
/// <summary>
/// An entry in a quick dialog.
/// </summary>
[Serializable, NetSerializable]
public sealed class QuickDialogEntry
{
/// <summary>
/// ID of the dialog field.
/// </summary>
public string FieldId;
/// <summary>
/// Type of the field, for checks.
/// </summary>
public QuickDialogEntryType Type;
/// <summary>
/// The prompt to show the user.
/// </summary>
public string Prompt;
public QuickDialogEntry(string fieldId, QuickDialogEntryType type, string prompt)
{
FieldId = fieldId;
Type = type;
Prompt = prompt;
}
}
/// <summary>
/// The buttons available in a quick dialog.
/// </summary>
[Flags]
public enum QuickDialogButtonFlag
{
OkButton = 1,
CancelButton = 2,
}
/// <summary>
/// The entry types for a quick dialog.
/// </summary>
public enum QuickDialogEntryType
{
/// <summary>
/// Any integer.
/// </summary>
Integer,
/// <summary>
/// Any floating point value.
/// </summary>
Float,
/// <summary>
/// Maximum of 100 characters string.
/// </summary>
ShortText,
/// <summary>
/// Maximum of 2,000 characters string.
/// </summary>
LongText,
}

View File

@@ -0,0 +1,14 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Station;
[NetSerializable, Serializable]
public sealed class StationsUpdatedEvent : EntityEventArgs
{
public readonly HashSet<EntityUid> Stations;
public StationsUpdatedEvent(HashSet<EntityUid> stations)
{
Stations = stations;
}
}

View File

@@ -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");

View File

@@ -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.

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -91,6 +91,7 @@
max: 1
- !type:DoActsBehavior
acts: [ "Destruction" ]
- type: StationInfiniteBatteryTarget
# APCs in use
- type: entity

View File

@@ -63,6 +63,7 @@
containers:
machine_parts: !type:Container
machine_board: !type:Container
- type: StationInfiniteBatteryTarget
# SMES' in use

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

View File

@@ -540,6 +540,7 @@ public sealed class $CLASS$ : Shared$CLASS$ {
<s:Boolean x:Key="/Default/UserDictionary/Words/=prefs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Protolathe/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pullable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Redescribe/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Reparenting/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=roundstart/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ruinable/@EntryIndexedValue">True</s:Boolean>