diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index 245fad4b33..76cb1e0da9 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -20,7 +20,6 @@ public sealed partial class AdminVerbSystem [Dependency] private readonly PiratesRuleSystem _piratesRule = default!; [Dependency] private readonly RevolutionaryRuleSystem _revolutionaryRule = default!; [Dependency] private readonly SharedMindSystem _minds = default!; - [Dependency] private readonly GameTicker _gameTicker = default!; // All antag verbs have names so invokeverb works. private void AddAntagVerbs(GetVerbsEvent args) diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs index 5ac4bc4171..2664913c9a 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs @@ -5,10 +5,12 @@ using Content.Server.Administration.UI; using Content.Server.Disposal.Tube; using Content.Server.Disposal.Tube.Components; using Content.Server.EUI; +using Content.Server.GameTicking; using Content.Server.Ghost.Roles; using Content.Server.Mind; using Content.Server.Mind.Commands; using Content.Server.Prayer; +using Content.Server.Station.Systems; using Content.Server.Xenoarchaeology.XenoArtifacts; using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; using Content.Shared.Administration; @@ -50,6 +52,7 @@ namespace Content.Server.Administration.Systems [Dependency] private readonly AdminSystem _adminSystem = default!; [Dependency] private readonly DisposalTubeSystem _disposalTubes = default!; [Dependency] private readonly EuiManager _euiManager = default!; + [Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly GhostRoleSystem _ghostRoleSystem = default!; [Dependency] private readonly ArtifactSystem _artifactSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; @@ -59,6 +62,8 @@ namespace Content.Server.Administration.Systems [Dependency] private readonly ToolshedManager _toolshed = default!; [Dependency] private readonly RejuvenateSystem _rejuvenate = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly StationSystem _stations = default!; + [Dependency] private readonly StationSpawningSystem _spawning = default!; private readonly Dictionary _openSolutionUis = new(); @@ -169,6 +174,56 @@ namespace Content.Server.Administration.Systems ConfirmationPopup = true, // No logimpact as the command does it internally. }); + + // Spawn - Like respawn but on the spot. + args.Verbs.Add(new Verb() + { + Text = Loc.GetString("admin-player-actions-spawn"), + Category = VerbCategory.Admin, + Act = () => + { + if (!_transformSystem.TryGetMapOrGridCoordinates(args.Target, out var coords)) + { + _popup.PopupEntity(Loc.GetString("admin-player-spawn-failed"), args.User, args.User); + return; + } + + var stationUid = _stations.GetOwningStation(args.Target); + + var profile = _ticker.GetPlayerProfile(targetActor.PlayerSession); + var mobUid = _spawning.SpawnPlayerMob(coords.Value, null, profile, stationUid); + var targetMind = _mindSystem.GetMind(args.Target); + + if (targetMind != null) + { + _mindSystem.TransferTo(targetMind.Value, mobUid); + } + }, + ConfirmationPopup = true, + Impact = LogImpact.High, + }); + + // Clone - Spawn but without the mind transfer, also spawns at the user's coordinates not the target's + args.Verbs.Add(new Verb() + { + Text = Loc.GetString("admin-player-actions-clone"), + Category = VerbCategory.Admin, + Act = () => + { + if (!_transformSystem.TryGetMapOrGridCoordinates(args.User, out var coords)) + { + _popup.PopupEntity(Loc.GetString("admin-player-spawn-failed"), args.User, args.User); + return; + } + + var stationUid = _stations.GetOwningStation(args.Target); + + var profile = _ticker.GetPlayerProfile(targetActor.PlayerSession); + _spawning.SpawnPlayerMob(coords.Value, null, profile, stationUid); + }, + ConfirmationPopup = true, + Impact = LogImpact.High, + }); } // Admin Logs diff --git a/Content.Server/GameTicking/GameTicker.Player.cs b/Content.Server/GameTicking/GameTicker.Player.cs index a333e68d05..5fccee1d11 100644 --- a/Content.Server/GameTicking/GameTicker.Player.cs +++ b/Content.Server/GameTicking/GameTicker.Player.cs @@ -153,7 +153,7 @@ namespace Content.Server.GameTicking } } - private HumanoidCharacterProfile GetPlayerProfile(ICommonSession p) + public HumanoidCharacterProfile GetPlayerProfile(ICommonSession p) { return (HumanoidCharacterProfile) _prefsManager.GetPreferences(p.UserId).SelectedCharacter; } diff --git a/Resources/Locale/en-US/administration/ui/actions.ftl b/Resources/Locale/en-US/administration/ui/actions.ftl index 86a5e15039..0433a724c1 100644 --- a/Resources/Locale/en-US/administration/ui/actions.ftl +++ b/Resources/Locale/en-US/administration/ui/actions.ftl @@ -4,5 +4,9 @@ admin-player-actions-kick = Kick admin-player-actions-ban = Ban admin-player-actions-ahelp = AHelp admin-player-actions-respawn = Respawn +admin-player-actions-spawn = Spawn here +admin-player-spawn-failed = Failed to find valid coordinates + +admin-player-actions-clone = Clone admin-player-actions-teleport = Teleport To admin-player-actions-confirm = Are you sure?