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.