using Content.Server.Access.Systems;
using Content.Server.CharacterAppearance.Systems;
using Content.Server.DetailExaminable;
using Content.Server.Hands.Components;
using Content.Server.Hands.Systems;
using Content.Server.PDA;
using Content.Server.Roles;
using Content.Server.Station.Components;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.CCVar;
using Content.Shared.Inventory;
using Content.Shared.PDA;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.Species;
using JetBrains.Annotations;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Station.Systems;
///
/// Manages spawning into the game, tracking available spawn points.
/// Also provides helpers for spawning in the player's mob.
///
[PublicAPI]
public sealed class StationSpawningSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly HandsSystem _handsSystem = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearanceSystem = default!;
[Dependency] private readonly IdCardSystem _cardSystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly PDASystem _pdaSystem = default!;
[Dependency] private readonly AccessSystem _accessSystem = default!;
///
public override void Initialize()
{
SubscribeLocalEvent(OnStationInitialized);
}
private void OnStationInitialized(StationInitializedEvent ev)
{
AddComp(ev.Station);
}
///
/// Attempts to spawn a player character onto the given station.
///
/// Station to spawn onto.
/// The job to assign, if any.
/// The character profile to use, if any.
/// Resolve pattern, the station spawning component for the station.
/// The resulting player character, if any.
/// Thrown when the given station is not a station.
///
/// This only spawns the character, and does none of the mind-related setup you'd need for it to be playable.
///
public EntityUid? SpawnPlayerCharacterOnStation(EntityUid? station, Job? job, HumanoidCharacterProfile? profile, StationSpawningComponent? stationSpawning = null)
{
if (station != null && !Resolve(station.Value, ref stationSpawning))
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
var ev = new PlayerSpawningEvent(job, profile, station);
RaiseLocalEvent(ev);
DebugTools.Assert(ev.SpawnResult is {Valid: true} or null);
return ev.SpawnResult;
}
//TODO: Figure out if everything in the player spawning region belongs somewhere else.
#region Player spawning helpers
///
/// Spawns in a player's mob according to their job and character information at the given coordinates.
/// Used by systems that need to handle spawning players.
///
/// Coordinates to spawn the character at.
/// Job to assign to the character, if any.
/// Appearance profile to use for the character.
/// The station this player is being spawned on.
/// The spawned entity
public EntityUid SpawnPlayerMob(
EntityCoordinates coordinates,
Job? job,
HumanoidCharacterProfile? profile,
EntityUid? station)
{
var entity = EntityManager.SpawnEntity(
_prototypeManager.Index(profile?.Species ?? SpeciesManager.DefaultSpecies).Prototype,
coordinates);
if (job?.StartingGear != null)
{
var startingGear = _prototypeManager.Index(job.StartingGear);
EquipStartingGear(entity, startingGear, profile);
if (profile != null)
EquipIdCard(entity, profile.Name, job.Prototype, station);
}
if (profile != null)
{
_humanoidAppearanceSystem.UpdateFromProfile(entity, profile);
EntityManager.GetComponent(entity).EntityName = profile.Name;
if (profile.FlavorText != "" && _configurationManager.GetCVar(CCVars.FlavorText))
{
EntityManager.AddComponent(entity).Content = profile.FlavorText;
}
}
foreach (var jobSpecial in job?.Prototype.Special ?? Array.Empty())
{
jobSpecial.AfterEquip(entity);
}
return entity;
}
///
/// Equips starting gear onto the given entity.
///
/// Entity to load out.
/// Starting gear to use.
/// Character profile to use, if any.
public void EquipStartingGear(EntityUid entity, StartingGearPrototype startingGear, HumanoidCharacterProfile? profile)
{
if (_inventorySystem.TryGetSlots(entity, out var slotDefinitions))
{
foreach (var slot in slotDefinitions)
{
var equipmentStr = startingGear.GetGear(slot.Name, profile);
if (!string.IsNullOrEmpty(equipmentStr))
{
var equipmentEntity = EntityManager.SpawnEntity(equipmentStr, EntityManager.GetComponent(entity).Coordinates);
_inventorySystem.TryEquip(entity, equipmentEntity, slot.Name, true);
}
}
}
if (!TryComp(entity, out HandsComponent? handsComponent))
return;
var inhand = startingGear.Inhand;
var coords = EntityManager.GetComponent(entity).Coordinates;
foreach (var (hand, prototype) in inhand)
{
var inhandEntity = EntityManager.SpawnEntity(prototype, coords);
_handsSystem.TryPickup(entity, inhandEntity, hand, checkActionBlocker: false, handsComp: handsComponent);
}
}
///
/// Equips an ID card and PDA onto the given entity.
///
/// Entity to load out.
/// Character name to use for the ID.
/// Job prototype to use for the PDA and ID.
/// The station this player is being spawned on.
public void EquipIdCard(EntityUid entity, string characterName, JobPrototype jobPrototype, EntityUid? station)
{
if (!_inventorySystem.TryGetSlotEntity(entity, "id", out var idUid))
return;
if (!EntityManager.TryGetComponent(idUid, out PDAComponent? pdaComponent) || pdaComponent.ContainedID == null)
return;
var card = pdaComponent.ContainedID;
var cardId = card.Owner;
_cardSystem.TryChangeFullName(cardId, characterName, card);
_cardSystem.TryChangeJobTitle(cardId, jobPrototype.Name, card);
var extendedAccess = false;
if (station != null)
{
var data = Comp(station.Value);
extendedAccess = data.ExtendedAccess;
}
_accessSystem.SetAccessToJob(cardId, jobPrototype, extendedAccess);
_pdaSystem.SetOwner(pdaComponent, characterName);
}
#endregion Player spawning helpers
}
///
/// Ordered broadcast event fired on any spawner eligible to attempt to spawn a player.
/// This event's success is measured by if SpawnResult is not null.
/// You should not make this event's success rely on random chance.
/// This event is designed to use ordered handling. You probably want SpawnPointSystem to be the last handler.
///
[PublicAPI]
public sealed class PlayerSpawningEvent : EntityEventArgs
{
///
/// The entity spawned, if any. You should set this if you succeed at spawning the character, and leave it alone if it's not null.
///
public EntityUid? SpawnResult;
///
/// The job to use, if any.
///
public readonly Job? Job;
///
/// The profile to use, if any.
///
public readonly HumanoidCharacterProfile? HumanoidCharacterProfile;
///
/// The target station, if any.
///
public readonly EntityUid? Station;
public PlayerSpawningEvent(Job? job, HumanoidCharacterProfile? humanoidCharacterProfile, EntityUid? station)
{
Job = job;
HumanoidCharacterProfile = humanoidCharacterProfile;
Station = station;
}
}