diff --git a/Content.Server/Administration/BanPanelEui.cs b/Content.Server/Administration/BanPanelEui.cs
index 3e33693208..1ef983cf10 100644
--- a/Content.Server/Administration/BanPanelEui.cs
+++ b/Content.Server/Administration/BanPanelEui.cs
@@ -131,13 +131,12 @@ public sealed class BanPanelEui : BaseEui
}
if (erase &&
- targetUid != null &&
- _playerManager.TryGetSessionById(targetUid.Value, out var targetPlayer))
+ targetUid != null)
{
try
{
if (_entities.TrySystem(out AdminSystem? adminSystem))
- adminSystem.Erase(targetPlayer);
+ adminSystem.Erase(targetUid.Value);
}
catch (Exception e)
{
diff --git a/Content.Server/Administration/Commands/EraseCommand.cs b/Content.Server/Administration/Commands/EraseCommand.cs
new file mode 100644
index 0000000000..cb01d742a0
--- /dev/null
+++ b/Content.Server/Administration/Commands/EraseCommand.cs
@@ -0,0 +1,47 @@
+using System.Linq;
+using Content.Server.Administration.Systems;
+using Content.Shared.Administration;
+using Robust.Server.Player;
+using Robust.Shared.Console;
+
+namespace Content.Server.Administration.Commands;
+
+[AdminCommand(AdminFlags.Admin)]
+public sealed class EraseCommand : LocalizedEntityCommands
+{
+ [Dependency] private readonly IPlayerLocator _locator = default!;
+ [Dependency] private readonly IPlayerManager _players = default!;
+ [Dependency] private readonly AdminSystem _admin = default!;
+
+ public override string Command => "erase";
+
+ public override async void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (args.Length != 1)
+ {
+ shell.WriteError(Loc.GetString("cmd-erase-invalid-args"));
+ shell.WriteLine(Help);
+ return;
+ }
+
+ var located = await _locator.LookupIdByNameOrIdAsync(args[0]);
+
+ if (located == null)
+ {
+ shell.WriteError(Loc.GetString("cmd-erase-player-not-found"));
+ return;
+ }
+
+ _admin.Erase(located.UserId);
+ }
+
+ public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
+ {
+ if (args.Length != 1)
+ return CompletionResult.Empty;
+
+ var options = _players.Sessions.OrderBy(c => c.Name).Select(c => c.Name).ToArray();
+
+ return CompletionResult.FromHintOptions(options, Loc.GetString("cmd-erase-player-completion"));
+ }
+}
diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs
index 05e7b39837..2abacf289c 100644
--- a/Content.Server/Administration/Systems/AdminSystem.cs
+++ b/Content.Server/Administration/Systems/AdminSystem.cs
@@ -17,6 +17,7 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.Mind;
using Content.Shared.PDA;
+using Content.Shared.Players;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Popups;
using Content.Shared.Roles;
@@ -386,30 +387,32 @@ public sealed class AdminSystem : EntitySystem
}
}
- ///
- /// Erases a player from the round.
- /// This removes them and any trace of them from the round, deleting their
- /// chat messages and showing a popup to other players.
- /// Their items are dropped on the ground.
- ///
- public void Erase(ICommonSession player)
- {
- var entity = player.AttachedEntity;
- _chat.DeleteMessagesBy(player);
-
- if (entity != null && !TerminatingOrDeleted(entity.Value))
+ ///
+ /// Erases a player from the round.
+ /// This removes them and any trace of them from the round, deleting their
+ /// chat messages and showing a popup to other players.
+ /// Their items are dropped on the ground.
+ ///
+ public void Erase(NetUserId uid)
{
- if (TryComp(entity.Value, out TransformComponent? transform))
+ _chat.DeleteMessagesBy(uid);
+
+ if (!_minds.TryGetMind(uid, out var mindId, out var mind) || mind.OwnedEntity == null || TerminatingOrDeleted(mind.OwnedEntity.Value))
+ return;
+
+ var entity = mind.OwnedEntity.Value;
+
+ if (TryComp(entity, out TransformComponent? transform))
{
- var coordinates = _transform.GetMoverCoordinates(entity.Value, transform);
- var name = Identity.Entity(entity.Value, EntityManager);
+ var coordinates = _transform.GetMoverCoordinates(entity, transform);
+ var name = Identity.Entity(entity, EntityManager);
_popup.PopupCoordinates(Loc.GetString("admin-erase-popup", ("user", name)), coordinates, PopupType.LargeCaution);
var filter = Filter.Pvs(coordinates, 1, EntityManager, _playerManager);
var audioParams = new AudioParams().WithVolume(3);
_audio.PlayStatic("/Audio/DeltaV/Misc/reducedtoatmos.ogg", filter, coordinates, true, audioParams);
}
- foreach (var item in _inventory.GetHandOrInventoryEntities(entity.Value))
+ foreach (var item in _inventory.GetHandOrInventoryEntities(entity))
{
if (TryComp(item, out PdaComponent? pda) &&
TryComp(pda.ContainedId, out StationRecordKeyStorageComponent? keyStorage) &&
@@ -433,30 +436,30 @@ public sealed class AdminSystem : EntitySystem
}
}
- if (_inventory.TryGetContainerSlotEnumerator(entity.Value, out var enumerator))
+ if (_inventory.TryGetContainerSlotEnumerator(entity, out var enumerator))
{
while (enumerator.NextItem(out var item, out var slot))
{
- if (_inventory.TryUnequip(entity.Value, entity.Value, slot.Name, true, true))
+ if (_inventory.TryUnequip(entity, entity, slot.Name, true, true))
_physics.ApplyAngularImpulse(item, ThrowingSystem.ThrowAngularImpulse);
}
}
- if (TryComp(entity.Value, out HandsComponent? hands))
+ if (TryComp(entity, out HandsComponent? hands))
{
- foreach (var hand in _hands.EnumerateHands(entity.Value, hands))
+ foreach (var hand in _hands.EnumerateHands(entity, hands))
{
- _hands.TryDrop(entity.Value, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands);
+ _hands.TryDrop(entity, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands);
}
}
+
+ _minds.WipeMind(mindId, mind);
+ QueueDel(entity);
+
+ if (_playerManager.TryGetSessionById(uid, out var session))
+ _gameTicker.SpawnObserver(session);
}
- _minds.WipeMind(player);
- QueueDel(entity);
-
- _gameTicker.SpawnObserver(player);
- }
-
private void OnSessionPlayTimeUpdated(ICommonSession session)
{
UpdatePlayerList(session);
diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs
index 05a04786a8..4016fb588d 100644
--- a/Content.Server/Administration/Systems/AdminVerbSystem.cs
+++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs
@@ -34,15 +34,15 @@ using Robust.Shared.Timing;
using Robust.Shared.Toolshed;
using Robust.Shared.Utility;
using System.Linq;
-using System.Numerics;
using Content.Server.Silicons.Laws;
using Content.Shared.Silicons.Laws;
using Content.Shared.Silicons.Laws.Components;
using Robust.Server.Player;
+using Content.Shared.Mind;
using Robust.Shared.Physics.Components;
using static Content.Shared.Configurable.ConfigurationComponent;
-using Content.Shared._Impstation.Thaven.Components; // DeltaV
-using Content.Server._Impstation.Thaven; // DeltaV
+using Content.Shared._Impstation.Thaven.Components;
+using Content.Server._Impstation.Thaven;
namespace Content.Server.Administration.Systems
{
@@ -76,7 +76,7 @@ namespace Content.Server.Administration.Systems
[Dependency] private readonly AdminFrozenSystem _freeze = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly SiliconLawSystem _siliconLawSystem = default!;
- [Dependency] private readonly ThavenMoodsSystem _moods = default!; // DeltaV
+ [Dependency] private readonly ThavenMoodsSystem _moods = default!; // DeltaV
private readonly Dictionary> _openSolutionUis = new();
@@ -140,87 +140,6 @@ namespace Content.Server.Administration.Systems
prayerVerb.Impact = LogImpact.Low;
args.Verbs.Add(prayerVerb);
- // Freeze
- var frozen = TryComp(args.Target, out var frozenComp);
- var frozenAndMuted = frozenComp?.Muted ?? false;
-
- 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-freeze"),
- Category = VerbCategory.Admin,
- Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
- Act = () =>
- {
- EnsureComp(args.Target);
- },
- 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
- {
- Text = Loc.GetString("admin-verbs-erase"),
- Message = Loc.GetString("admin-verbs-erase-description"),
- Category = VerbCategory.Admin,
- Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png")),
- Act = () =>
- {
- _adminSystem.Erase(targetActor.PlayerSession);
- },
- Impact = LogImpact.Extreme,
- ConfirmationPopup = true
- });
-
- // Respawn
- args.Verbs.Add(new Verb()
- {
- Text = Loc.GetString("admin-player-actions-respawn"),
- Category = VerbCategory.Admin,
- Act = () =>
- {
- _console.ExecuteCommand(player, $"respawn {targetActor.PlayerSession.Name}");
- },
- ConfirmationPopup = true,
- // No logimpact as the command does it internally.
- });
-
// Spawn - Like respawn but on the spot.
args.Verbs.Add(new Verb()
{
@@ -242,7 +161,7 @@ namespace Content.Server.Administration.Systems
if (targetMind != null) // AGHOSTS DON'T HAVE A FUCKING MINDDDDD
{
- _mindSystem.TransferTo(targetMind.Value, mobUid);
+ _mindSystem.TransferTo(targetMind.Value, mobUid, true);
}
},
ConfirmationPopup = true,
@@ -281,6 +200,92 @@ namespace Content.Server.Administration.Systems
});
}
+ if (_mindSystem.TryGetMind(args.Target, out _, out var mind) && mind.UserId != null)
+ {
+ // Erase
+ args.Verbs.Add(new Verb
+ {
+ Text = Loc.GetString("admin-verbs-erase"),
+ Message = Loc.GetString("admin-verbs-erase-description"),
+ Category = VerbCategory.Admin,
+ Icon = new SpriteSpecifier.Texture(
+ new("/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png")),
+ Act = () =>
+ {
+ _adminSystem.Erase(mind.UserId.Value);
+ },
+ Impact = LogImpact.Extreme,
+ ConfirmationPopup = true
+ });
+
+ // Respawn
+ args.Verbs.Add(new Verb
+ {
+ Text = Loc.GetString("admin-player-actions-respawn"),
+ Category = VerbCategory.Admin,
+ Act = () =>
+ {
+ _console.ExecuteCommand(player, $"respawn \"{mind.UserId}\"");
+ },
+ ConfirmationPopup = true,
+ // No logimpact as the command does it internally.
+ });
+ }
+
+ // Freeze
+ var frozen = TryComp(args.Target, out var frozenComp);
+ var frozenAndMuted = frozenComp?.Muted ?? false;
+
+ 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-freeze"),
+ Category = VerbCategory.Admin,
+ Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
+ Act = () =>
+ {
+ EnsureComp(args.Target);
+ },
+ 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,
+ });
+ }
+
+
// Admin Logs
if (_adminManager.HasAdminFlag(player, AdminFlags.Logs))
{
@@ -367,7 +372,7 @@ namespace Content.Server.Administration.Systems
}
// Begin DeltaV Additions - thaven moods
- if (TryComp(args.Target, out var moods))
+ if (TryComp(args.Target, out var moods))
{
args.Verbs.Add(new Verb()
{
@@ -382,7 +387,7 @@ namespace Content.Server.Administration.Systems
_euiManager.OpenEui(ui, session);
ui.UpdateMoods(moods, args.Target);
},
- Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Actions/actions_borg.rsi"), "state-laws"),
+ Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Actions/actions_borg.rsi"), "state-laws"),
});
}
}
diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs
index be76999cd3..0d21350f52 100644
--- a/Content.Server/Chat/Managers/ChatManager.cs
+++ b/Content.Server/Chat/Managers/ChatManager.cs
@@ -85,10 +85,10 @@ internal sealed partial class ChatManager : IChatManager
DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-admin-ooc-chat-enabled-message" : "chat-manager-admin-ooc-chat-disabled-message"));
}
- public void DeleteMessagesBy(ICommonSession player)
- {
- if (!_players.TryGetValue(player.UserId, out var user))
- return;
+ public void DeleteMessagesBy(NetUserId uid)
+ {
+ if (!_players.TryGetValue(uid, out var user))
+ return;
var msg = new MsgDeleteChatMessagesBy { Key = user.Key, Entities = user.Entities };
_netManager.ServerSendToAll(msg);
diff --git a/Content.Server/Chat/Managers/IChatManager.cs b/Content.Server/Chat/Managers/IChatManager.cs
index e941093e1d..e9588204a5 100644
--- a/Content.Server/Chat/Managers/IChatManager.cs
+++ b/Content.Server/Chat/Managers/IChatManager.cs
@@ -36,7 +36,7 @@ namespace Content.Server.Chat.Managers
bool MessageCharacterLimit(ICommonSession player, string message);
- void DeleteMessagesBy(ICommonSession player);
+ void DeleteMessagesBy(NetUserId uid);
[return: NotNullIfNotNull(nameof(author))]
ChatUser? EnsurePlayer(NetUserId? author);
diff --git a/Content.Server/GameTicking/Commands/RespawnCommand.cs b/Content.Server/GameTicking/Commands/RespawnCommand.cs
index 4f101d0939..f7ea11baf1 100644
--- a/Content.Server/GameTicking/Commands/RespawnCommand.cs
+++ b/Content.Server/GameTicking/Commands/RespawnCommand.cs
@@ -1,4 +1,6 @@
-using Content.Shared.Mind;
+using System.Linq;
+using Content.Server.Administration;
+using Content.Server.Mind;
using Content.Shared.Players;
using Robust.Server.Player;
using Robust.Shared.Console;
@@ -6,57 +8,72 @@ using Robust.Shared.Network;
namespace Content.Server.GameTicking.Commands
{
- sealed class RespawnCommand : IConsoleCommand
+ sealed class RespawnCommand : LocalizedEntityCommands
{
- public string Command => "respawn";
- public string Description => "Respawns a player, kicking them back to the lobby.";
- public string Help => "respawn [player]";
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IPlayerLocator _locator = default!;
+ [Dependency] private readonly GameTicker _gameTicker = default!;
+ [Dependency] private readonly MindSystem _mind = default!;
- public void Execute(IConsoleShell shell, string argStr, string[] args)
+ public override string Command => "respawn";
+
+ public override async void Execute(IConsoleShell shell, string argStr, string[] args)
{
var player = shell.Player;
if (args.Length > 1)
{
- shell.WriteLine("Must provide <= 1 argument.");
+ shell.WriteError(Loc.GetString("cmd-respawn-invalid-args"));
return;
}
- var playerMgr = IoCManager.Resolve();
- var sysMan = IoCManager.Resolve();
- var ticker = sysMan.GetEntitySystem();
- var mind = sysMan.GetEntitySystem();
-
NetUserId userId;
if (args.Length == 0)
{
if (player == null)
{
- shell.WriteLine("If not a player, an argument must be given.");
+ shell.WriteError(Loc.GetString("cmd-respawn-no-player"));
return;
}
userId = player.UserId;
}
- else if (!playerMgr.TryGetUserId(args[0], out userId))
+ else
{
- shell.WriteLine("Unknown player");
- return;
- }
+ var located = await _locator.LookupIdByNameOrIdAsync(args[0]);
- if (!playerMgr.TryGetSessionById(userId, out var targetPlayer))
- {
- if (!playerMgr.TryGetPlayerData(userId, out var data))
+ if (located == null)
{
- shell.WriteLine("Unknown player");
+ shell.WriteError(Loc.GetString("cmd-respawn-unknown-player"));
return;
}
- mind.WipeMind(data.ContentData()?.Mind);
- shell.WriteLine("Player is not currently online, but they will respawn if they come back online");
+ userId = located.UserId;
+ }
+
+ if (!_player.TryGetSessionById(userId, out var targetPlayer))
+ {
+ if (!_player.TryGetPlayerData(userId, out var data))
+ {
+ shell.WriteError(Loc.GetString("cmd-respawn-unknown-player"));
+ return;
+ }
+
+ _mind.WipeMind(data.ContentData()?.Mind);
+ shell.WriteError(Loc.GetString("cmd-respawn-player-not-online"));
return;
}
- ticker.Respawn(targetPlayer);
+ _gameTicker.Respawn(targetPlayer);
+ }
+
+ public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
+ {
+ if (args.Length != 1)
+ return CompletionResult.Empty;
+
+ var options = _player.Sessions.OrderBy(c => c.Name).Select(c => c.Name).ToArray();
+
+ return CompletionResult.FromHintOptions(options, Loc.GetString("cmd-respawn-player-completion"));
}
}
}
diff --git a/Content.Server/Traits/TraitSystem.cs b/Content.Server/Traits/TraitSystem.cs
index 97fa2b09f3..71267440ef 100644
--- a/Content.Server/Traits/TraitSystem.cs
+++ b/Content.Server/Traits/TraitSystem.cs
@@ -157,7 +157,7 @@ public sealed class TraitSystem : EntitySystem
///
private void VaporizeCheater (Robust.Shared.Player.ICommonSession targetPlayer)
{
- _adminSystem.Erase(targetPlayer);
+ _adminSystem.Erase(targetPlayer.UserId);
var feedbackMessage = $"[font size=24][color=#ff0000]{"You have spawned in with an illegal trait point total. If this was a result of cheats, then your nonexistence is a skill issue. Otherwise, feel free to click 'Return To Lobby', and fix your trait selections."}[/color][/font]";
_chatManager.ChatMessageToOne(
diff --git a/Resources/Locale/en-US/administration/commands/erase.ftl b/Resources/Locale/en-US/administration/commands/erase.ftl
new file mode 100644
index 0000000000..e9f995f03e
--- /dev/null
+++ b/Resources/Locale/en-US/administration/commands/erase.ftl
@@ -0,0 +1,7 @@
+# erase
+cmd-erase-desc = Erase a player's entity if it exists and all their chat messages
+cmd-erase-help = erase
+cmd-erase-invalid-args = Invalid number of arguments
+cmd-erase-player-not-found = Player not found
+
+cmd-erase-player-completion =
diff --git a/Resources/Locale/en-US/administration/commands/respawn.ftl b/Resources/Locale/en-US/administration/commands/respawn.ftl
new file mode 100644
index 0000000000..6aab854ee4
--- /dev/null
+++ b/Resources/Locale/en-US/administration/commands/respawn.ftl
@@ -0,0 +1,9 @@
+cmd-respawn-desc = Respawns a player, kicking them back to the lobby.
+cmd-respawn-help = respawn [player or UserId]
+
+cmd-respawn-invalid-args = Must provide <= 1 argument.
+cmd-respawn-no-player = If not a player, an argument must be given.
+cmd-respawn-unknown-player = Unknown player
+cmd-respawn-player-not-online = Player is not currently online, but they will respawn if they come back online
+
+cmd-respawn-player-completion =