Skip to content

Commit

Permalink
Add Load/SpawnCharacter back (DeltaV-Station#198)
Browse files Browse the repository at this point in the history
* Add Load/SpawnCharacter back

Funny admin QoL

* Rider automatic code cleanup

* Update Admin.yml

* Implement intention actions

Thanks Rider :clueless:
  • Loading branch information
DebugOk authored Oct 19, 2023
1 parent 8adc0a3 commit dbfdda0
Show file tree
Hide file tree
Showing 4 changed files with 310 additions and 0 deletions.
169 changes: 169 additions & 0 deletions Content.Server/DeltaV/Administration/Commands/LoadCharacter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
using System.Linq;
using Content.Server.Administration;
using Content.Server.GameTicking;
using Content.Server.Players;
using Content.Server.Preferences.Managers;
using Content.Server.Station.Systems;
using Content.Shared.Administration;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;

// This literally only exists because haha felinid oni
namespace Content.Server.DeltaV.Administration.Commands;

[AdminCommand(AdminFlags.Admin)]
public sealed class LoadCharacter : IConsoleCommand
{
[Dependency] private readonly IEntitySystemManager _entitySys = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IServerPreferencesManager _prefs = default!;

public string Command => "loadcharacter";
public string Description => Loc.GetString("loadcharacter-command-description");
public string Help => Loc.GetString("loadcharacter-command-help");

public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (shell.Player is not IPlayerSession player)
{
shell.WriteError(Loc.GetString("shell-only-players-can-run-this-command"));
return;
}

var data = player.ContentData();

if (data == null)
{
shell.WriteError(Loc.GetString("shell-entity-is-not-mob")); // No mind specific errors? :(
return;
}

EntityUid target;

if (args.Length >= 1)
{
if (!EntityUid.TryParse(args.First(), out var uid))
{
shell.WriteLine(Loc.GetString("shell-entity-uid-must-be-number"));
return;
}

target = uid;
}
else
{
if (player.AttachedEntity == null ||
!_entityManager.HasComponent<HumanoidAppearanceComponent>(player.AttachedEntity.Value))
{
shell.WriteError(Loc.GetString("shell-must-be-attached-to-entity"));
return;
}

target = player.AttachedEntity.Value;
}

if (!target.IsValid() || !_entityManager.EntityExists(target))
{
shell.WriteLine(Loc.GetString("shell-invalid-entity-id"));
return;
}

if (!_entityManager.TryGetComponent<HumanoidAppearanceComponent>(target, out var humanoidAppearance))
{
shell.WriteError(Loc.GetString("shell-entity-with-uid-lacks-component", ("uid", target.ToString()),
("componentName", nameof(HumanoidAppearanceComponent))));
return;
}

HumanoidCharacterProfile character;

if (args.Length >= 2)
{
// This seems like a bad way to go about it, but it works so eh?
var name = string.Join(" ", args.Skip(1).ToArray());
shell.WriteLine(Loc.GetString("loadcharacter-command-fetching", ("name", name)));

if (!FetchCharacters(data.UserId, out var characters))
{
shell.WriteError(Loc.GetString("loadcharacter-command-failed-fetching"));
return;
}

var selectedCharacter = characters.FirstOrDefault(c => c.Name == name);

if (selectedCharacter == null)
{
shell.WriteError(Loc.GetString("loadcharacter-command-failed-fetching"));
return;
}

character = selectedCharacter;
}
else
character = (HumanoidCharacterProfile) _prefs.GetPreferences(data.UserId).SelectedCharacter;

// This shouldn't ever fail considering the previous checks
if (!_prototypeManager.TryIndex(humanoidAppearance.Species, out var speciesPrototype) ||
!_prototypeManager.TryIndex<SpeciesPrototype>(character.Species, out var entPrototype))
return;

if (speciesPrototype != entPrototype)
shell.WriteLine(Loc.GetString("loadcharacter-command-mismatch"));

var coordinates = player.AttachedEntity != null
? _entityManager.GetComponent<TransformComponent>(player.AttachedEntity.Value).Coordinates
: _entitySys.GetEntitySystem<GameTicker>().GetObserverSpawnPoint();

_entityManager.System<StationSpawningSystem>()
.SpawnPlayerMob(coordinates, profile: character, entity: target, job: null, station: null);

shell.WriteLine(Loc.GetString("loadcharacter-command-complete"));
}

public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
switch (args.Length)
{
case 1:
return CompletionResult.FromHint(Loc.GetString("shell-argument-uid"));
case 2:
{
var player = shell.Player as IPlayerSession;
if (player == null)
return CompletionResult.Empty;

var data = player.ContentData();
var mind = data?.Mind;

if (mind == null || data == null)
return CompletionResult.Empty;

return FetchCharacters(data.UserId, out var characters)
? CompletionResult.FromOptions(characters.Select(c => c.Name))
: CompletionResult.Empty;
}
default:
return CompletionResult.Empty;
}
}

private bool FetchCharacters(NetUserId player, out HumanoidCharacterProfile[] characters)
{
characters = null!;
if (!_prefs.TryGetCachedPreferences(player, out var prefs))
return false;

characters = prefs.Characters
.Where(kv => kv.Value is HumanoidCharacterProfile)
.Select(kv => (HumanoidCharacterProfile) kv.Value)
.ToArray();

return true;
}
}
124 changes: 124 additions & 0 deletions Content.Server/DeltaV/Administration/Commands/SpawnCharacter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System.Linq;
using Content.Server.Administration;
using Content.Server.GameTicking;
using Content.Server.Players;
using Content.Server.Preferences.Managers;
using Content.Server.Station.Systems;
using Content.Shared.Administration;
using Content.Shared.Mind;
using Content.Shared.Preferences;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.Network;

namespace Content.Server.DeltaV.Administration.Commands;

[AdminCommand(AdminFlags.Admin)]
public sealed class SpawnCharacter : IConsoleCommand
{
[Dependency] private readonly IEntitySystemManager _entitySys = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IServerPreferencesManager _prefs = default!;

public string Command => "spawncharacter";
public string Description => Loc.GetString("spawncharacter-command-description");
public string Help => Loc.GetString("spawncharacter-command-help");

public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (shell.Player is not IPlayerSession player)
{
shell.WriteError(Loc.GetString("shell-only-players-can-run-this-command"));
return;
}

var mindSystem = _entitySys.GetEntitySystem<SharedMindSystem>();

var data = player.ContentData();

if (data?.UserId == null)
{
shell.WriteError(Loc.GetString("shell-entity-is-not-mob"));
return;
}


HumanoidCharacterProfile character;

if (args.Length >= 1)
{
// This seems like a bad way to go about it, but it works so eh?
var name = string.Join(" ", args.ToArray());
shell.WriteLine(Loc.GetString("loadcharacter-command-fetching", ("name", name)));

if (!FetchCharacters(data.UserId, out var characters))
{
shell.WriteError(Loc.GetString("loadcharacter-command-failed-fetching"));
return;
}

var selectedCharacter = characters.FirstOrDefault(c => c.Name == name);

if (selectedCharacter == null)
{
shell.WriteError(Loc.GetString("loadcharacter-command-failed-fetching"));
return;
}

character = selectedCharacter;
}
else
character = (HumanoidCharacterProfile) _prefs.GetPreferences(data.UserId).SelectedCharacter;


var coordinates = player.AttachedEntity != null
? _entityManager.GetComponent<TransformComponent>(player.AttachedEntity.Value).Coordinates
: _entitySys.GetEntitySystem<GameTicker>().GetObserverSpawnPoint();

if (player.AttachedEntity == null ||
!mindSystem.TryGetMind(player.AttachedEntity.Value, out var mindId, out var mind))
return;


mindSystem.TransferTo(mindId, _entityManager.System<StationSpawningSystem>()
.SpawnPlayerMob(coordinates, profile: character, entity: null, job: null, station: null));

shell.WriteLine(Loc.GetString("spawncharacter-command-complete"));
}

public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
var player = shell.Player as IPlayerSession;
if (player == null)
return CompletionResult.Empty;

var data = player.ContentData();
var mind = data?.Mind;

if (mind == null || data == null)
return CompletionResult.Empty;

return FetchCharacters(data.UserId, out var characters)
? CompletionResult.FromOptions(characters.Select(c => c.Name))
: CompletionResult.Empty;
}

return CompletionResult.Empty;
}

private bool FetchCharacters(NetUserId player, out HumanoidCharacterProfile[] characters)
{
characters = null!;
if (!_prefs.TryGetCachedPreferences(player, out var prefs))
return false;

characters = prefs.Characters
.Where(kv => kv.Value is HumanoidCharacterProfile)
.Select(kv => (HumanoidCharacterProfile) kv.Value)
.ToArray();

return true;
}
}
5 changes: 5 additions & 0 deletions Resources/Changelog/Admin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,8 @@ Entries:
- {message: 'Add pop sound effect when using the erase admin verb.', type: Tweak}
id: 5
time: '2023-10-14T09:47:00.0000000+00:00'
- author: DebugOk
changes:
- {message: 'Add back the loadcharacter and spawncharacter commands.', type: Add}
id: 6
time: '2023-10-19T00:00:00.0000000+00:00'
12 changes: 12 additions & 0 deletions Resources/Locale/en-US/administration/commands/load-character.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
loadcharacter-command-description = Applies your currently selected character to an entity
loadcharacter-command-help = Usage: loadcharacter | loadcharacter <entityUid> | loadcharacter <entityUid> <characterName>
loadcharacter-command-mismatch = Species mismatch detected between character and selected entity, this may have unexpected results.
loadcharacter-command-complete = Character loaded.
loadcharacter-command-fetching = Fetching character data for {$name}...
loadcharacter-command-fetching-failed = Failed to fetch character data!
loadcharacter-command-failed-fetching = Profile fetching failed???
loadcharacter-command-hint-select = Select character
spawncharacter-command-description = Spawns your currently selected/specified character
spawncharacter-command-help = Usage: spawncharacter | spawncharacter <characterName>
spawncharacter-command-complete = Character spawned.

0 comments on commit dbfdda0

Please sign in to comment.