Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Content.Client/Station/StationSpawningSystem.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using Content.Shared.Station;
using Robust.Shared.Prototypes; // Frontier

namespace Content.Client.Station;

public sealed class StationSpawningSystem : SharedStationSpawningSystem
{
protected override void EquipPdaCartridgesIfPossible(EntityUid entity, List<EntProtoId> encryptionKeys) { } // Frontier: PDA equipment

}
112 changes: 107 additions & 5 deletions Content.Server/Station/Systems/StationSpawningSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
using Content.Server.CartridgeLoader; // Frontier
using Content.Shared.CartridgeLoader; // Frontier
using Robust.Server.GameObjects; // Frontier
using Robust.Shared.Containers; // Frontier
using Content.Shared.Radio.Components; // Frontier
using Content.Shared.Implants; // Frontier
using Content.Shared.Implants.Components; // Frontier

namespace Content.Server.Station.Systems;

Expand All @@ -54,6 +58,8 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
[Dependency] private readonly BankSystem _bank = default!; // Frontier
[Dependency] private readonly CartridgeLoaderSystem _cartridgeLoader = default!; // Frontier
[Dependency] private readonly TransformSystem _xformSystem = default!; // Frontier
[Dependency] private readonly SharedContainerSystem _container = default!; // Frontier
[Dependency] private readonly SharedImplanterSystem _implanter = default!; // Frontier

/// <summary>
/// Attempts to spawn a player character onto the given station.
Expand Down Expand Up @@ -184,6 +190,11 @@ public EntityUid SpawnPlayerMob(
hasBalance = true;
}

// Frontier: A final loadout applied at the end of everything else.
// Right now it's just being used for special auto-equips,
// but maybe it could be used in the future to equip all loadouts in a single pass?
LoadoutPrototype loadoutLast = new();

// Order loadout selections by the order they appear on the prototype.
foreach (var group in loadout.SelectedLoadouts.OrderBy(x => roleProto!.Groups.FindIndex(e => e == x.Key)))
{
Expand All @@ -205,6 +216,7 @@ public EntityUid SpawnPlayerMob(
{
bankBalance -= int.Max(0, loadoutProto.Price); // Treat negatives as zero.
EquipStartingGear(entity.Value, loadoutProto, raiseEvent: false);
CollectLoadout(loadoutProto, ref loadoutLast);
equippedItems.Add(loadoutProto.ID);
}
}
Expand Down Expand Up @@ -234,6 +246,7 @@ public EntityUid SpawnPlayerMob(
}

EquipStartingGear(entity.Value, loadoutProto, raiseEvent: false);
CollectLoadout(loadoutProto, ref loadoutLast);
equippedItems.Add(fallback);
// Minimum number of items equipped, no need to load more prototypes.
if (equippedItems.Count >= groupPrototype.MinLimit)
Expand All @@ -244,8 +257,14 @@ public EntityUid SpawnPlayerMob(

// Frontier: do not re-equip roleLoadout, make sure we equip job startingGear,
// and deduct loadout costs from a bank account if we have one.
if (prototype?.StartingGear is not null)
EquipStartingGear(entity.Value, prototype.StartingGear, raiseEvent: false);
if (_prototypeManager.TryIndex(prototype?.StartingGear, out var startingGear))
{
EquipStartingGear(entity.Value, startingGear, raiseEvent: false);
CollectLoadout(startingGear, ref loadoutLast);
}

// Frontier: Attempt auto-equip for implants, encryption keys, and PDA cartridges
TryAutoEquipMisc(entity.Value, loadoutLast);

var bankComp = EnsureComp<BankAccountComponent>(entity.Value);

Expand Down Expand Up @@ -322,8 +341,45 @@ public void SetPdaAndIdCardData(EntityUid entity, string characterName, JobProto


#endregion Player spawning helpers
// Frontier: equip cartridges
protected override void EquipPdaCartridgesIfPossible(EntityUid entity, List<EntProtoId> pdaCartridges)
// Frontier: extra loadout fields
/// <summary>
/// Function to equip an entity with encryption keys.
/// If not possible, will delete them.
/// </summary>
/// <param name="entity">The entity to receive equipment.</param>
/// <param name="encryptionKeys">The encryption key prototype IDs to equip.</param>
private void EquipEncryptionKeysIfPossible(EntityUid entity, List<EntProtoId> encryptionKeys)
{
if (!InventorySystem.TryGetSlotEntity(entity, "ears", out var slotEnt))
{
DebugTools.Assert(false, $"Entity {entity} has a non-empty encryption key loadout, but doesn't have a headset!");
return;
}
if (!_container.TryGetContainer(slotEnt.Value, EncryptionKeyHolderComponent.KeyContainerName, out var keyContainer))
{
DebugTools.Assert(false, $"Entity {entity} has a non-empty encryption key loadout, but their headset doesn't have an encryption key container!");
return;
}
var coords = _xformSystem.GetMapCoordinates(entity);
foreach (var entProto in encryptionKeys)
{
Log.Debug($"Entity {entity} auto-inserting loadout encryption key {entProto} into headset {keyContainer}.");
var spawnedEntity = Spawn(entProto, coords);
if (!_container.Insert(spawnedEntity, keyContainer))
{
QueueDel(spawnedEntity);
DebugTools.Assert(false, $"Entity {entity} could not insert their loadout encryption key {entProto} into their headset!");
}
}
}

/// <summary>
/// Function to equip an entity with PDA cartridges.
/// If not possible, will delete them.
/// </summary>
/// <param name="entity">The entity to receive equipment.</param>
/// <param name="pdaCartridges">The PDA cartridge prototype IDs to equip.</param>
private void EquipPdaCartridgesIfPossible(EntityUid entity, List<EntProtoId> pdaCartridges)
{
if (!InventorySystem.TryGetSlotEntity(entity, "id", out var slotEnt))
{
Expand All @@ -338,14 +394,60 @@ protected override void EquipPdaCartridgesIfPossible(EntityUid entity, List<EntP
var coords = _xformSystem.GetMapCoordinates(entity);
foreach (var entProto in pdaCartridges)
{
Log.Debug($"Entity {entity} auto-installing cartridge {entProto} into PDA {slotEnt.Value}.");
var spawnedEntity = Spawn(entProto, coords);
if (!_cartridgeLoader.InstallCartridge(slotEnt.Value, spawnedEntity, cartridgeLoader))
DebugTools.Assert(false, $"Entity {entity} could not install cartridge {entProto} into their PDA {slotEnt.Value}!");

QueueDel(spawnedEntity);
}
}
// End Frontier: equip cartridges

/// <summary>
/// Function to equip an entity with implants.
/// If not possible, will delete them.
/// </summary>
/// <param name="entity">The entity to receive equipment.</param>
/// <param name="implants">The implant prototype IDs to equip.</param>
private void EquipImplantsIfPossible(EntityUid entity, List<EntProtoId> implants)
{
var coords = _xformSystem.GetMapCoordinates(entity);
foreach (var entProto in implants)
{
var spawnedEntity = Spawn(entProto, coords);
if (TryComp<ImplanterComponent>(spawnedEntity, out var implanter))
_implanter.Implant(entity, entity, spawnedEntity, implanter);
else
DebugTools.Assert(false, $"Entity has an implant for {entProto}, which doesn't have an implanter component!");
QueueDel(spawnedEntity);
}
}

public void TryAutoEquipMisc(EntityUid entity, LoadoutPrototype loadout)
{
if (loadout.Implants.Count > 0)
EquipImplantsIfPossible(entity, loadout.Implants);

if (loadout.EncryptionKeys.Count > 0)
EquipEncryptionKeysIfPossible(entity, loadout.EncryptionKeys);

if (loadout.Cartridges.Count > 0)
EquipPdaCartridgesIfPossible(entity, loadout.Cartridges);
}

/// <summary>
/// Function to collect and store encryption keys and cartridges.
/// Does not handle any equip logic.
/// </summary>
/// <param name="loadoutProto">The loadout prototype to collect from.</param>
/// <param name="collectorLoadout">Reference to the loadout to collect to.</param>
private void CollectLoadout(IEquipmentLoadout loadoutProto, ref LoadoutPrototype collectorLoadout)
{
collectorLoadout.EncryptionKeys.AddRange(loadoutProto.EncryptionKeys);
collectorLoadout.Cartridges.AddRange(loadoutProto.Cartridges);
collectorLoadout.Implants.AddRange(loadoutProto.Implants);
}
// End Frontier: extra loadout fields
}

/// <summary>
Expand Down
16 changes: 16 additions & 0 deletions Content.Server/_NF/GameTicking/GameTicker.NFSpawning.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Numerics;
using Content.Server.Players.PlayTimeTracking;
using Content.Shared.Preferences.Loadouts;
using Content.Server.Radio.EntitySystems;
using Content.Shared._NF.CCVar;
using Content.Shared.Radio;
Expand All @@ -18,12 +19,14 @@ public sealed partial class GameTicker
private ProtoId<RadioChannelPrototype> _newPlayerRadioChannel = "Service";
private EntProtoId _greetingRadioSource = "GreetingRadioSource";
private EntityUid _greetingEntity = EntityUid.Invalid;
private LoadoutPrototype? _newPlayerLoadoutPrototype = null;

public void NFInitialize()
{
Subs.CVar(_cfg, NFCCVars.NewPlayerRadioGreetingEnabled, e => _newPlayerGreetingEnabled = e, true);
Subs.CVar(_cfg, NFCCVars.NewPlayerRadioGreetingMaxPlaytime, e => _newPlayerGreetingMaxTime = TimeSpan.FromMinutes(e), true);
Subs.CVar(_cfg, NFCCVars.NewPlayerRadioGreetingChannel, SetChannel, true);
Subs.CVar(_cfg, NFCCVars.NewPlayerStarterLoadout, SetLoadout, true);
}

private void SetChannel(string channel)
Expand All @@ -32,6 +35,11 @@ private void SetChannel(string channel)
_newPlayerRadioChannel = channel;
}

private void SetLoadout(string loadout)
{
_prototypeManager.TryIndex<LoadoutPrototype>(loadout, out _newPlayerLoadoutPrototype);
}

private void NFRoundStarted()
{
_greetingEntity = Spawn(_greetingRadioSource, new MapCoordinates(Vector2.Zero, DefaultMap));
Expand Down Expand Up @@ -63,6 +71,14 @@ private void HandleGreetingMessage(ICommonSession session, EntityUid mob, Entity

if (playtime < _newPlayerGreetingMaxTime)
{
// Equip new player loadout if one is specified
// Ordered before the radio message so the new player can see it, thus communicating that it exists
if (_newPlayerLoadoutPrototype is not null)
{
_stationSpawning.EquipStartingGear(mob, _newPlayerLoadoutPrototype, false);
_stationSpawning.TryAutoEquipMisc(mob, _newPlayerLoadoutPrototype);
}

_radio.SendRadioMessage(_greetingEntity, Loc.GetString("latejoin-arrival-new-player-announcement",
("character", MetaData(mob).EntityName),
("station", station)),
Expand Down
77 changes: 0 additions & 77 deletions Content.Shared/Station/SharedStationSpawningSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Content.Shared.Implants; // Frontier
using Content.Shared.Implants.Components; // Frontier
using Content.Shared.Radio.Components; // Frontier
using Robust.Shared.Containers; // Frontier
using Robust.Shared.Network; // Frontier

namespace Content.Shared.Station;

Expand All @@ -29,9 +24,6 @@ public abstract class SharedStationSpawningSystem : EntitySystem
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedStorageSystem _storage = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly INetManager _net = default!; // Frontier
[Dependency] private readonly SharedContainerSystem _container = default!; // Frontier
[Dependency] private readonly SharedImplanterSystem _implanter = default!; // Frontier

private EntityQuery<HandsComponent> _handsQuery;
private EntityQuery<InventoryComponent> _inventoryQuery;
Expand Down Expand Up @@ -215,34 +207,6 @@ public void EquipStartingGear(EntityUid entity, IEquipmentLoadout? startingGear,
}
}

// Frontier: extra fields
// Implants must run on server, container initialization only runs on server, and lobby dummies don't work.
if (_net.IsServer && startingGear.Implants.Count > 0)
{
var coords = _xformSystem.GetMapCoordinates(entity);
foreach (var entProto in startingGear.Implants)
{
var spawnedEntity = Spawn(entProto, coords);
if (TryComp<ImplanterComponent>(spawnedEntity, out var implanter))
_implanter.Implant(entity, entity, spawnedEntity, implanter);
else
DebugTools.Assert(false, $"Entity has an implant for {entProto}, which doesn't have an implanter component!");
QueueDel(spawnedEntity);
}
}

if (startingGear.EncryptionKeys.Count > 0)
{
EquipEncryptionKeysIfPossible(entity, startingGear.EncryptionKeys);
}

// PDA cartridges must run on server, installation logic exists server-side.
if (_net.IsServer && startingGear.Cartridges.Count > 0)
{
EquipPdaCartridgesIfPossible(entity, startingGear.Cartridges);
}
// End Frontier

if (raiseEvent)
{
var ev = new StartingGearEquippedEvent(entity);
Expand Down Expand Up @@ -279,45 +243,4 @@ public void EquipStartingGear(EntityUid entity, IEquipmentLoadout? startingGear,

return null;
}

// Frontier: extra loadout fields
/// Function to equip an entity with encryption keys.
/// If not possible, will delete them.
/// Only called in practice server-side.
/// </summary>
/// <param name="entity">The entity to receive equipment.</param>
/// <param name="encryptionKeys">The encryption key prototype IDs to equip.</param>
protected void EquipEncryptionKeysIfPossible(EntityUid entity, List<EntProtoId> encryptionKeys)
{
if (!InventorySystem.TryGetSlotEntity(entity, "ears", out var slotEnt))
{
DebugTools.Assert(false, $"Entity {entity} has a non-empty encryption key loadout, but doesn't have a headset!");
return;
}
if (!_container.TryGetContainer(slotEnt.Value, EncryptionKeyHolderComponent.KeyContainerName, out var keyContainer))
{
DebugTools.Assert(false, $"Entity {entity} has a non-empty encryption key loadout, but their headset doesn't have an encryption key container!");
return;
}
var coords = _xformSystem.GetMapCoordinates(entity);
foreach (var entProto in encryptionKeys)
{
var spawnedEntity = Spawn(entProto, coords);
if (!_container.Insert(spawnedEntity, keyContainer))
{
QueueDel(spawnedEntity);
DebugTools.Assert(false, $"Entity {entity} could not insert their loadout encryption key {entProto} into their headset!");
}
}
}

/// <summary>
/// Function to equip an entity with PDA cartridges.
/// If not possible, will delete them.
/// Only called in practice server-side.
/// </summary>
/// <param name="entity">The entity to receive equipment.</param>
/// <param name="encryptionKeys">The PDA cartridge prototype IDs to equip.</param>
protected abstract void EquipPdaCartridgesIfPossible(EntityUid entity, List<EntProtoId> encryptionKeys);
// End Frontier: extra loadout fields
}
10 changes: 8 additions & 2 deletions Content.Shared/_NF/CCVar/NFCCVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,11 +279,17 @@ public sealed class NFCCVars
/// The maximum playtime, in minutes, for a new player radio message to be sent.
/// </summary>
public static readonly CVarDef<int> NewPlayerRadioGreetingMaxPlaytime =
CVarDef.Create("nf14.greeting.max_playtime", 180, CVar.REPLICATED); // Three hours
CVarDef.Create("nf14.greeting.max_playtime", 600, CVar.REPLICATED); // Ten hours

/// <summary>
/// The channel the radio message should be sent off on.
/// </summary>
public static readonly CVarDef<string> NewPlayerRadioGreetingChannel =
CVarDef.Create("nf14.greeting.channel", "Service", CVar.REPLICATED);
CVarDef.Create("nf14.greeting.channel", "Greeting", CVar.REPLICATED);

/// <summary>
/// A starter loadout prototype given to new players.
/// </summary>
public static readonly CVarDef<string> NewPlayerStarterLoadout =
CVarDef.Create("nf14.greeting.loadout", "NFGreenhornLoadout", CVar.REPLICATED);
}
4 changes: 4 additions & 0 deletions Resources/Prototypes/_NF/Loadouts/misc_loadouts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- type: loadout
id: NFGreenhornLoadout
encryptionKeys:
- EncryptionKeyGreeting
Loading