diff --git a/Content.Client/Administration/Systems/AdminFrozenSystem.cs b/Content.Client/Administration/Systems/AdminFrozenSystem.cs
new file mode 100644
index 0000000000..885585f985
--- /dev/null
+++ b/Content.Client/Administration/Systems/AdminFrozenSystem.cs
@@ -0,0 +1,7 @@
+using Content.Shared.Administration;
+
+namespace Content.Client.Administration.Systems;
+
+public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
+{
+}
diff --git a/Content.Server/Administration/Systems/AdminFrozenSystem.cs b/Content.Server/Administration/Systems/AdminFrozenSystem.cs
new file mode 100644
index 0000000000..baf7b682b8
--- /dev/null
+++ b/Content.Server/Administration/Systems/AdminFrozenSystem.cs
@@ -0,0 +1,16 @@
+using Content.Shared.Administration;
+
+namespace Content.Server.Administration.Systems;
+
+public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
+{
+ ///
+ /// Freezes and mutes the given entity.
+ ///
+ public void FreezeAndMute(EntityUid uid)
+ {
+ var comp = EnsureComp(uid);
+ comp.Muted = true;
+ Dirty(uid, comp);
+ }
+}
diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs
index 5bb75b4c99..7aa4c8b400 100644
--- a/Content.Server/Administration/Systems/AdminVerbSystem.cs
+++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs
@@ -67,6 +67,7 @@ namespace Content.Server.Administration.Systems
[Dependency] private readonly StationSystem _stations = default!;
[Dependency] private readonly StationSpawningSystem _spawning = default!;
[Dependency] private readonly ExamineSystemShared _examine = default!;
+ [Dependency] private readonly AdminFrozenSystem _freeze = default!;
private readonly Dictionary> _openSolutionUis = new();
@@ -131,24 +132,57 @@ namespace Content.Server.Administration.Systems
args.Verbs.Add(prayerVerb);
// Freeze
- var frozen = HasComp(args.Target);
- args.Verbs.Add(new Verb
+ var frozen = TryComp(args.Target, out var frozenComp);
+ var frozenAndMuted = frozenComp?.Muted ?? false;
+
+ if (!frozen)
{
- Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
- Text = frozen
- ? Loc.GetString("admin-verbs-unfreeze")
- : Loc.GetString("admin-verbs-freeze"),
- Category = VerbCategory.Admin,
- Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
- Act = () =>
+ args.Verbs.Add(new Verb
{
- if (frozen)
- RemComp(args.Target);
- else
+ Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
+ Text = Loc.GetString("admin-verbs-freeze"),
+ Category = VerbCategory.Admin,
+ Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
+ Act = () =>
+ {
EnsureComp(args.Target);
- },
- Impact = LogImpact.Medium,
- });
+ },
+ Impact = LogImpact.Medium,
+ });
+ }
+
+ if (!frozenAndMuted)
+ {
+ // allow you to additionally mute someone when they are already frozen
+ args.Verbs.Add(new Verb
+ {
+ Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
+ Text = Loc.GetString("admin-verbs-freeze-and-mute"),
+ Category = VerbCategory.Admin,
+ Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
+ Act = () =>
+ {
+ _freeze.FreezeAndMute(args.Target);
+ },
+ Impact = LogImpact.Medium,
+ });
+ }
+
+ if (frozen)
+ {
+ args.Verbs.Add(new Verb
+ {
+ Priority = -1, // This is just so it doesn't change position in the menu between freeze/unfreeze.
+ Text = Loc.GetString("admin-verbs-unfreeze"),
+ Category = VerbCategory.Admin,
+ Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
+ Act = () =>
+ {
+ RemComp(args.Target);
+ },
+ Impact = LogImpact.Medium,
+ });
+ }
// Erase
args.Verbs.Add(new Verb
diff --git a/Content.Server/Chat/Commands/SuicideCommand.cs b/Content.Server/Chat/Commands/SuicideCommand.cs
index c967ba78d7..0c541b48b7 100644
--- a/Content.Server/Chat/Commands/SuicideCommand.cs
+++ b/Content.Server/Chat/Commands/SuicideCommand.cs
@@ -1,4 +1,5 @@
using Content.Server.GameTicking;
+using Content.Server.Popups;
using Content.Shared.Administration;
using Content.Shared.Mind;
using Robust.Shared.Console;
@@ -26,17 +27,27 @@ namespace Content.Server.Chat.Commands
if (player.Status != SessionStatus.InGame || player.AttachedEntity == null)
return;
- var minds = IoCManager.Resolve().System();
+ var entityManager = IoCManager.Resolve();
+ var minds = entityManager.System();
// This check also proves mind not-null for at the end when the mob is ghosted.
if (!minds.TryGetMind(player, out var mindId, out var mind) ||
mind.OwnedEntity is not { Valid: true } victim)
{
- shell.WriteLine("You don't have a mind!");
+ shell.WriteLine(Loc.GetString("suicide-command-no-mind"));
return;
}
- var gameTicker = EntitySystem.Get();
- var suicideSystem = EntitySystem.Get();
+ if (entityManager.HasComponent(victim))
+ {
+ var deniedMessage = Loc.GetString("suicide-command-denied");
+ shell.WriteLine(deniedMessage);
+ entityManager.System()
+ .PopupEntity(deniedMessage, victim, victim);
+ return;
+ }
+
+ var gameTicker = entityManager.System();
+ var suicideSystem = entityManager.System();
if (suicideSystem.Suicide(victim))
{
// Prevent the player from returning to the body.
@@ -48,7 +59,7 @@ namespace Content.Server.Chat.Commands
if (gameTicker.OnGhostAttempt(mindId, true, mind: mind))
return;
- shell.WriteLine("You can't ghost right now.");
+ shell.WriteLine(Loc.GetString("ghost-command-denied"));
}
}
}
diff --git a/Content.Server/Ghost/Ghost.cs b/Content.Server/Ghost/Ghost.cs
index 1453bf3faa..69d81d9592 100644
--- a/Content.Server/Ghost/Ghost.cs
+++ b/Content.Server/Ghost/Ghost.cs
@@ -1,4 +1,5 @@
using Content.Server.GameTicking;
+using Content.Server.Popups;
using Content.Shared.Administration;
using Content.Shared.Mind;
using Robust.Shared.Console;
@@ -11,15 +12,25 @@ namespace Content.Server.Ghost
[Dependency] private readonly IEntityManager _entities = default!;
public string Command => "ghost";
- public string Description => "Give up on life and become a ghost.";
- public string Help => "ghost";
+ public string Description => Loc.GetString("ghost-command-description");
+ public string Help => Loc.GetString("ghost-command-help-text");
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var player = shell.Player;
if (player == null)
{
- shell.WriteLine("You have no session, you can't ghost.");
+ shell.WriteLine(Loc.GetString("ghost-command-no-session"));
+ return;
+ }
+
+ if (player.AttachedEntity is { Valid: true } frozen &&
+ _entities.HasComponent(frozen))
+ {
+ var deniedMessage = Loc.GetString("ghost-command-denied");
+ shell.WriteLine(deniedMessage);
+ _entities.System()
+ .PopupEntity(deniedMessage, frozen, frozen);
return;
}
@@ -30,9 +41,9 @@ namespace Content.Server.Ghost
mind = _entities.GetComponent(mindId);
}
- if (!EntitySystem.Get().OnGhostAttempt(mindId, true, true, mind))
+ if (!_entities.System().OnGhostAttempt(mindId, true, true, mind))
{
- shell.WriteLine("You can't ghost right now.");
+ shell.WriteLine(Loc.GetString("ghost-command-denied"));
}
}
}
diff --git a/Content.Shared/Administration/AdminFrozenComponent.cs b/Content.Shared/Administration/AdminFrozenComponent.cs
index 164cf764c8..bfcf1db526 100644
--- a/Content.Shared/Administration/AdminFrozenComponent.cs
+++ b/Content.Shared/Administration/AdminFrozenComponent.cs
@@ -2,8 +2,13 @@
namespace Content.Shared.Administration;
-[RegisterComponent, Access(typeof(AdminFrozenSystem))]
-[NetworkedComponent]
+[RegisterComponent, Access(typeof(SharedAdminFrozenSystem))]
+[NetworkedComponent, AutoGenerateComponentState]
public sealed partial class AdminFrozenComponent : Component
{
+ ///
+ /// Whether the player is also muted.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Muted;
}
diff --git a/Content.Shared/Administration/AdminFrozenSystem.cs b/Content.Shared/Administration/SharedAdminFrozenSystem.cs
similarity index 78%
rename from Content.Shared/Administration/AdminFrozenSystem.cs
rename to Content.Shared/Administration/SharedAdminFrozenSystem.cs
index 4ec9600b0b..2fa22e0005 100644
--- a/Content.Shared/Administration/AdminFrozenSystem.cs
+++ b/Content.Shared/Administration/SharedAdminFrozenSystem.cs
@@ -1,15 +1,17 @@
using Content.Shared.ActionBlocker;
+using Content.Shared.Emoting;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Events;
using Content.Shared.Movement.Pulling.Systems;
+using Content.Shared.Speech;
using Content.Shared.Throwing;
namespace Content.Shared.Administration;
-public sealed class AdminFrozenSystem : EntitySystem
+public abstract class SharedAdminFrozenSystem : EntitySystem
{
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly PullingSystem _pulling = default!;
@@ -28,6 +30,16 @@ public sealed class AdminFrozenSystem : EntitySystem
SubscribeLocalEvent(OnPullAttempt);
SubscribeLocalEvent(OnAttempt);
SubscribeLocalEvent(OnAttempt);
+ SubscribeLocalEvent(OnEmoteAttempt);
+ SubscribeLocalEvent(OnSpeakAttempt);
+ }
+
+ private void OnSpeakAttempt(EntityUid uid, AdminFrozenComponent component, SpeakAttemptEvent args)
+ {
+ if (!component.Muted)
+ return;
+
+ args.Cancel();
}
private void OnAttempt(EntityUid uid, AdminFrozenComponent component, CancellableEntityEventArgs args)
@@ -62,4 +74,10 @@ public sealed class AdminFrozenSystem : EntitySystem
{
_blocker.UpdateCanMove(uid);
}
+
+ private void OnEmoteAttempt(EntityUid uid, AdminFrozenComponent component, EmoteAttemptEvent args)
+ {
+ if (component.Muted)
+ args.Cancel();
+ }
}
diff --git a/Resources/Locale/en-US/administration/admin-verbs.ftl b/Resources/Locale/en-US/administration/admin-verbs.ftl
index 03f92f7c42..16715087ee 100644
--- a/Resources/Locale/en-US/administration/admin-verbs.ftl
+++ b/Resources/Locale/en-US/administration/admin-verbs.ftl
@@ -6,6 +6,7 @@ admin-verbs-admin-logs-entity = Entity Logs
admin-verbs-teleport-to = Teleport To
admin-verbs-teleport-here = Teleport Here
admin-verbs-freeze = Freeze
+admin-verbs-freeze-and-mute = Freeze And Mute
admin-verbs-unfreeze = Unfreeze
admin-verbs-erase = Erase
admin-verbs-erase-description = Removes the player from the round and crew manifest and deletes their chat messages.
diff --git a/Resources/Locale/en-US/chat/commands/ghost-command.ftl b/Resources/Locale/en-US/chat/commands/ghost-command.ftl
new file mode 100644
index 0000000000..08e78d34ce
--- /dev/null
+++ b/Resources/Locale/en-US/chat/commands/ghost-command.ftl
@@ -0,0 +1,5 @@
+ghost-command-description = Give up on life and become a ghost.
+ghost-command-help-text = The ghost command turns you into a ghost and makes the character you played permanently catatonic.
+ Please note that you cannot return to your character's body after ghosting.
+ghost-command-no-session = You have no session, you can't ghost.
+ghost-command-denied = You cannot ghost right now.
diff --git a/Resources/Locale/en-US/chat/commands/suicide-command.ftl b/Resources/Locale/en-US/chat/commands/suicide-command.ftl
index 6748aa630c..36e861169b 100644
--- a/Resources/Locale/en-US/chat/commands/suicide-command.ftl
+++ b/Resources/Locale/en-US/chat/commands/suicide-command.ftl
@@ -6,3 +6,5 @@ suicide-command-help-text = The suicide command gives you a quick way out of a r
suicide-command-default-text-others = {$name} is attempting to bite their own tongue!
suicide-command-default-text-self = You attempt to bite your own tongue!
suicide-command-already-dead = You can't suicide. You're dead.
+suicide-command-no-mind = You have no mind!
+suicide-command-denied = You cannot suicide right now.