Allow for respawn and erase verb to be used on offline players. Also minor rewrite on respawn command and new erase command (#30433)

* Localize respawn command, allow for it to use userids, and make it use [Dependency] attributes

* Make respawn verb available for offline players

* Make erase available for offline players

A thousand admins rejoice

* Reorder verbs in code

* Add erase command

* Fix localisation for erase command

* Address reviews and add completion to respawn command

* Complete reviews which I forgor
This commit is contained in:
nikthechampiongr
2024-08-31 11:38:03 +00:00
committed by GitHub
parent 4435ccd29f
commit 9a63144636
9 changed files with 175 additions and 88 deletions

View File

@@ -132,13 +132,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)
{

View File

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

View File

@@ -15,7 +15,9 @@ using Content.Shared.GameTicking;
using Content.Shared.Hands.Components;
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;
@@ -383,24 +385,26 @@ public sealed class AdminSystem : EntitySystem
/// chat messages and showing a popup to other players.
/// Their items are dropped on the ground.
/// </summary>
public void Erase(ICommonSession player)
public void Erase(NetUserId uid)
{
var entity = player.AttachedEntity;
_chat.DeleteMessagesBy(player);
_chat.DeleteMessagesBy(uid);
if (entity != null && !TerminatingOrDeleted(entity.Value))
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))
{
if (TryComp(entity.Value, 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/Effects/pop_high.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) &&
@@ -424,28 +428,28 @@ 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(player);
_minds.WipeMind(mindId, mind);
QueueDel(entity);
_gameTicker.SpawnObserver(player);
if (_playerManager.TryGetSessionById(uid, out var session))
_gameTicker.SpawnObserver(session);
}
private void OnSessionPlayTimeUpdated(ICommonSession session)

View File

@@ -34,11 +34,11 @@ 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;
@@ -137,34 +137,6 @@ namespace Content.Server.Administration.Systems
prayerVerb.Impact = LogImpact.Low;
args.Verbs.Add(prayerVerb);
// 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()
{
@@ -225,6 +197,38 @@ 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<AdminFrozenComponent>(args.Target, out var frozenComp);
var frozenAndMuted = frozenComp?.Muted ?? false;

View File

@@ -81,9 +81,9 @@ 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)
public void DeleteMessagesBy(NetUserId uid)
{
if (!_players.TryGetValue(player.UserId, out var user))
if (!_players.TryGetValue(uid, out var user))
return;
var msg = new MsgDeleteChatMessagesBy { Key = user.Key, Entities = user.Entities };

View File

@@ -41,7 +41,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);

View File

@@ -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<IPlayerManager>();
var sysMan = IoCManager.Resolve<IEntitySystemManager>();
var ticker = sysMan.GetEntitySystem<GameTicker>();
var mind = sysMan.GetEntitySystem<SharedMindSystem>();
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");
var located = await _locator.LookupIdByNameOrIdAsync(args[0]);
if (located == null)
{
shell.WriteError(Loc.GetString("cmd-respawn-unknown-player"));
return;
}
if (!playerMgr.TryGetSessionById(userId, out var targetPlayer))
userId = located.UserId;
}
if (!_player.TryGetSessionById(userId, out var targetPlayer))
{
if (!playerMgr.TryGetPlayerData(userId, out var data))
if (!_player.TryGetPlayerData(userId, out var data))
{
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");
_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"));
}
}
}

View File

@@ -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 <Username of User Id>
cmd-erase-invalid-args = Invalid number of arguments
cmd-erase-player-not-found = Player not found
cmd-erase-player-completion = <Username>

View File

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