Refactor stations to properly use entity prototypes. (stationsv3) (#16570)

* Update StationSpawningSystem.cs

Web-edit to allow feeding in an existing entity.

* Update StationSpawningSystem.cs

value type moment

* Update StationSpawningSystem.cs

* Oh goddamnit this is a refactor now.

* awawawa

* aaaaaaaaaaa

* ee

* forgot records.

* no records? no records.

* What's in a name?

* Sloth forcing me to do the refactor properly smh.

* e

* optional evac in test.

* tests pls work

* awa

---------

Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
This commit is contained in:
Moony
2023-05-19 15:45:09 -05:00
committed by GitHub
parent 0d9b9e113e
commit e92a8fedab
77 changed files with 1176 additions and 987 deletions

View File

@@ -227,14 +227,20 @@ namespace Content.IntegrationTests.Tests
// Test shuttle can dock. // Test shuttle can dock.
// This is done inside gamemap test because loading the map takes ages and we already have it. // This is done inside gamemap test because loading the map takes ages and we already have it.
var station = entManager.GetComponent<StationMemberComponent>(targetGrid!.Value).Station; var station = entManager.GetComponent<StationMemberComponent>(targetGrid!.Value).Station;
var stationConfig = entManager.GetComponent<StationDataComponent>(station).StationConfig; if (entManager.TryGetComponent<StationEmergencyShuttleComponent>(station, out var stationEvac))
Assert.IsNotNull(stationConfig, $"{entManager.ToPrettyString(station)} had null StationConfig."); {
var shuttlePath = stationConfig.EmergencyShuttlePath.ToString(); var shuttlePath = stationEvac.EmergencyShuttlePath;
var shuttle = mapLoader.LoadGrid(shuttleMap, shuttlePath); var shuttle = mapLoader.LoadGrid(shuttleMap, shuttlePath.ToString());
Assert.That(shuttle != null && shuttleSystem.TryFTLDock(shuttle.Value, entManager.GetComponent<ShuttleComponent>(shuttle.Value), targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}"); Assert.That(
shuttle != null && shuttleSystem.TryFTLDock(shuttle.Value,
entManager.GetComponent<ShuttleComponent>(shuttle.Value), targetGrid.Value),
$"Unable to dock {shuttlePath} to {mapProto}");
}
mapManager.DeleteMap(shuttleMap); mapManager.DeleteMap(shuttleMap);
if (entManager.HasComponent<StationJobsComponent>(station))
{
// Test that the map has valid latejoin spawn points // Test that the map has valid latejoin spawn points
if (!NoSpawnMaps.Contains(mapProto)) if (!NoSpawnMaps.Contains(mapProto))
{ {
@@ -256,6 +262,7 @@ namespace Content.IntegrationTests.Tests
Assert.That(lateSpawns, Is.GreaterThan(0), $"Found no latejoin spawn points on {mapProto}"); Assert.That(lateSpawns, Is.GreaterThan(0), $"Found no latejoin spawn points on {mapProto}");
} }
// Test all availableJobs have spawnPoints // Test all availableJobs have spawnPoints
// This is done inside gamemap test because loading the map takes ages and we already have it. // This is done inside gamemap test because loading the map takes ages and we already have it.
var jobList = entManager.GetComponent<StationJobsComponent>(station).RoundStartJobList var jobList = entManager.GetComponent<StationJobsComponent>(station).RoundStartJobList
@@ -271,7 +278,10 @@ namespace Content.IntegrationTests.Tests
if (protoManager.Index<JobPrototype>(spawnpoint).SetPreference) if (protoManager.Index<JobPrototype>(spawnpoint).SetPreference)
missingSpawnPoints.Add(spawnpoint); missingSpawnPoints.Add(spawnpoint);
} }
Assert.That(missingSpawnPoints.Count() == 0, $"There is no spawnpoint for {String.Join(", ", missingSpawnPoints)} on {mapProto}.");
Assert.That(missingSpawnPoints.Count() == 0,
$"There is no spawnpoint for {String.Join(", ", missingSpawnPoints)} on {mapProto}.");
}
try try
{ {

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Maps; using Content.Server.Maps;
using Content.Server.Station.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Roles; using Content.Shared.Roles;
@@ -32,6 +33,9 @@ public sealed class StationJobsTest
stations: stations:
Station: Station:
mapNameTemplate: FooStation mapNameTemplate: FooStation
stationProto: StandardNanotrasenStation
components:
- type: StationJobs
overflowJobs: overflowJobs:
- Assistant - Assistant
availableJobs: availableJobs:
@@ -213,7 +217,10 @@ public sealed class StationJobsTest
{ {
foreach (var (stationId, station) in gameMap.Stations) foreach (var (stationId, station) in gameMap.Stations)
{ {
foreach (var job in station.AvailableJobs.Keys) if (!station.StationComponentOverrides.TryGetComponent("StationJobs", out var comp))
continue;
foreach (var (job, _) in ((StationJobsComponent)comp).SetupAvailableJobs)
{ {
Assert.That(invalidJobs.Contains(job), Is.False, $"Station {stationId} contains job prototype {job} which cannot be present roundstart."); Assert.That(invalidJobs.Contains(job), Is.False, $"Station {stationId} contains job prototype {job} which cannot be present roundstart.");
} }

View File

@@ -1,3 +1,4 @@
using Content.Server.Station.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Roles; using Content.Shared.Roles;
@@ -9,6 +10,10 @@ namespace Content.Server.Administration.Commands.Station;
[AdminCommand(AdminFlags.Round)] [AdminCommand(AdminFlags.Round)]
public sealed class AdjustStationJobCommand : IConsoleCommand public sealed class AdjustStationJobCommand : IConsoleCommand
{ {
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntitySystemManager _entSysManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public string Command => "adjstationjob"; public string Command => "adjstationjob";
public string Description => "Adjust the job manifest on a station."; public string Description => "Adjust the job manifest on a station.";
@@ -23,19 +28,15 @@ public sealed class AdjustStationJobCommand : IConsoleCommand
return; return;
} }
var prototypeManager = IoCManager.Resolve<IPrototypeManager>(); var stationJobs = _entSysManager.GetEntitySystem<StationJobsSystem>();
var stationSystem = EntitySystem.Get<StationSystem>();
var stationJobs = EntitySystem.Get<StationJobsSystem>();
if (!int.TryParse(args[0], out var stationInt) || !stationSystem.Stations.Contains(new EntityUid(stationInt))) if (!EntityUid.TryParse(args[0], out var station) || _entityManager.HasComponent<StationDataComponent>(station))
{ {
shell.WriteError(Loc.GetString("shell-argument-station-id-invalid", ("index", 1))); shell.WriteError(Loc.GetString("shell-argument-station-id-invalid", ("index", 1)));
return; return;
} }
var station = new EntityUid(stationInt); if (!_prototypeManager.TryIndex<JobPrototype>(args[1], out var job))
if (!prototypeManager.TryIndex<JobPrototype>(args[1], out var job))
{ {
shell.WriteError(Loc.GetString("shell-argument-must-be-prototype", shell.WriteError(Loc.GetString("shell-argument-must-be-prototype",
("index", 2), ("prototypeName", nameof(JobPrototype)))); ("index", 2), ("prototypeName", nameof(JobPrototype))));

View File

@@ -1,3 +1,4 @@
using Content.Server.Station.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Shared.Console; using Robust.Shared.Console;
@@ -7,6 +8,9 @@ namespace Content.Server.Administration.Commands.Station;
[AdminCommand(AdminFlags.Admin)] [AdminCommand(AdminFlags.Admin)]
public sealed class ListStationJobsCommand : IConsoleCommand public sealed class ListStationJobsCommand : IConsoleCommand
{ {
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntitySystemManager _entSysManager = default!;
public string Command => "lsstationjobs"; public string Command => "lsstationjobs";
public string Description => "Lists all jobs on the given station."; public string Description => "Lists all jobs on the given station.";
@@ -21,16 +25,16 @@ public sealed class ListStationJobsCommand : IConsoleCommand
return; return;
} }
var stationSystem = EntitySystem.Get<StationSystem>(); var stationSystem = _entSysManager.GetEntitySystem<StationSystem>();
var stationJobs = EntitySystem.Get<StationJobsSystem>(); var stationJobs = _entSysManager.GetEntitySystem<StationJobsSystem>();
if (!int.TryParse(args[0], out var station) || !stationSystem.Stations.Contains(new EntityUid(station))) if (!EntityUid.TryParse(args[0], out var station) || !_entityManager.HasComponent<StationJobsComponent>(station))
{ {
shell.WriteError(Loc.GetString("shell-argument-station-id-invalid", ("index", 1))); shell.WriteError(Loc.GetString("shell-argument-station-id-invalid", ("index", 1)));
return; return;
} }
foreach (var (job, amount) in stationJobs.GetJobs(new EntityUid(station))) foreach (var (job, amount) in stationJobs.GetJobs(station))
{ {
var amountText = amount is null ? "Infinite" : amount.ToString(); var amountText = amount is null ? "Infinite" : amount.ToString();
shell.WriteLine($"{job}: {amountText}"); shell.WriteLine($"{job}: {amountText}");

View File

@@ -1,3 +1,4 @@
using Content.Server.Station.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Shared.Console; using Robust.Shared.Console;
@@ -17,7 +18,9 @@ public sealed class ListStationsCommand : IConsoleCommand
public void Execute(IConsoleShell shell, string argStr, string[] args) public void Execute(IConsoleShell shell, string argStr, string[] args)
{ {
foreach (var station in EntitySystem.Get<StationSystem>().Stations) var query = _entityManager.EntityQueryEnumerator<StationDataComponent>();
while (query.MoveNext(out var station, out _))
{ {
var name = _entityManager.GetComponent<MetaDataComponent>(station).EntityName; var name = _entityManager.GetComponent<MetaDataComponent>(station).EntityName;
shell.WriteLine($"{station, -10} | {name}"); shell.WriteLine($"{station, -10} | {name}");

View File

@@ -1,3 +1,4 @@
using Content.Server.Station.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Shared.Console; using Robust.Shared.Console;
@@ -7,6 +8,9 @@ namespace Content.Server.Administration.Commands.Station;
[AdminCommand(AdminFlags.Admin)] [AdminCommand(AdminFlags.Admin)]
public sealed class RenameStationCommand : IConsoleCommand public sealed class RenameStationCommand : IConsoleCommand
{ {
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntitySystemManager _entSysManager = default!;
public string Command => "renamestation"; public string Command => "renamestation";
public string Description => "Renames the given station"; public string Description => "Renames the given station";
@@ -21,14 +25,14 @@ public sealed class RenameStationCommand : IConsoleCommand
return; return;
} }
var stationSystem = EntitySystem.Get<StationSystem>(); var stationSystem = _entSysManager.GetEntitySystem<StationSystem>();
if (!int.TryParse(args[0], out var station) || !stationSystem.Stations.Contains(new EntityUid(station))) if (!EntityUid.TryParse(args[0], out var station) || _entityManager.HasComponent<StationDataComponent>(station))
{ {
shell.WriteError(Loc.GetString("shell-argument-station-id-invalid", ("index", 1))); shell.WriteError(Loc.GetString("shell-argument-station-id-invalid", ("index", 1)));
return; return;
} }
stationSystem.RenameStation(new EntityUid(station), args[1]); stationSystem.RenameStation(station, args[1]);
} }
} }

View File

@@ -1,3 +1,5 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.AlertLevel; namespace Content.Server.AlertLevel;
/// <summary> /// <summary>
@@ -14,7 +16,7 @@ public sealed class AlertLevelComponent : Component
public AlertLevelPrototype? AlertLevels; public AlertLevelPrototype? AlertLevels;
// Once stations are a prototype, this should be used. // Once stations are a prototype, this should be used.
[DataField("alertLevelPrototype")] [DataField("alertLevelPrototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<AlertLevelPrototype>))]
public string AlertLevelPrototype = default!; public string AlertLevelPrototype = default!;
/// <summary> /// <summary>

View File

@@ -32,13 +32,10 @@ public sealed class AlertLevelSystem : EntitySystem
public override void Update(float time) public override void Update(float time)
{ {
foreach (var station in _stationSystem.Stations) var query = EntityQueryEnumerator<AlertLevelComponent>();
{
if (!TryComp(station, out AlertLevelComponent? alert))
{
continue;
}
while (query.MoveNext(out var station, out var alert))
{
if (alert.CurrentDelay <= 0) if (alert.CurrentDelay <= 0)
{ {
if (alert.ActiveDelay) if (alert.ActiveDelay)
@@ -55,9 +52,10 @@ public sealed class AlertLevelSystem : EntitySystem
private void OnStationInitialize(StationInitializedEvent args) private void OnStationInitialize(StationInitializedEvent args)
{ {
var alertLevelComponent = AddComp<AlertLevelComponent>(args.Station); if (!TryComp<AlertLevelComponent>(args.Station, out var alertLevelComponent))
return;
if (!_prototypeManager.TryIndex(DefaultAlertLevelSet, out AlertLevelPrototype? alerts)) if (!_prototypeManager.TryIndex(alertLevelComponent.AlertLevelPrototype, out AlertLevelPrototype? alerts))
{ {
return; return;
} }

View File

@@ -22,7 +22,6 @@ public sealed partial class CargoSystem : SharedCargoSystem
InitializeConsole(); InitializeConsole();
InitializeShuttle(); InitializeShuttle();
InitializeTelepad(); InitializeTelepad();
SubscribeLocalEvent<StationInitializedEvent>(OnStationInit);
} }
public override void Shutdown() public override void Shutdown()
@@ -32,12 +31,6 @@ public sealed partial class CargoSystem : SharedCargoSystem
CleanupShuttle(); CleanupShuttle();
} }
private void OnStationInit(StationInitializedEvent ev)
{
EnsureComp<StationBankAccountComponent>(ev.Station);
EnsureComp<StationCargoOrderDatabaseComponent>(ev.Station);
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);

View File

@@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using Content.Server.Administration; using Content.Server.Administration;
using Content.Server.EUI; using Content.Server.EUI;
using Content.Server.Station.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Server.StationRecords; using Content.Server.StationRecords;
using Content.Server.StationRecords.Systems; using Content.Server.StationRecords.Systems;
@@ -270,13 +271,12 @@ public sealed class CrewManifestCommand : IConsoleCommand
} }
var stations = _entityManager var stations = _entityManager
.System<StationSystem>() .EntityQuery<StationDataComponent>()
.Stations .Select(stationData =>
.Select(station =>
{ {
var meta = _entityManager.GetComponent<MetaDataComponent>(station); var meta = _entityManager.GetComponent<MetaDataComponent>(stationData.Owner);
return new CompletionOption(station.ToString(), meta.EntityName); return new CompletionOption(stationData.Owner.ToString(), meta.EntityName);
}); });
return CompletionResult.FromHintOptions(stations, null); return CompletionResult.FromHintOptions(stations, null);

View File

@@ -31,10 +31,13 @@ public sealed partial class DragonSystem
{ {
base.Started(uid, component, gameRule, args); base.Started(uid, component, gameRule, args);
if (!_station.Stations.Any()) var eligible = EntityQuery<StationEventEligibleComponent>().Select(x => x.Owner).ToList();
if (!eligible.Any())
return; return;
var station = _random.Pick(_station.Stations); var station = _random.Pick(eligible);
if (_station.GetLargestGrid(EntityManager.GetComponent<StationDataComponent>(station)) is not { } grid) if (_station.GetLargestGrid(EntityManager.GetComponent<StationDataComponent>(station)) is not { } grid)
return; return;

View File

@@ -174,7 +174,6 @@ namespace Content.Server.Entry
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
_playTimeTracking?.Shutdown(); _playTimeTracking?.Shutdown();
_sysMan?.GetEntitySystemOrNull<StationSystem>()?.OnServerDispose();
_dbManager?.Shutdown(); _dbManager?.Shutdown();
} }

View File

@@ -52,23 +52,22 @@ namespace Content.Server.GameTicking
var playerCount = $"{_playerManager.PlayerCount}"; var playerCount = $"{_playerManager.PlayerCount}";
var readyCount = _playerGameStatuses.Values.Count(x => x == PlayerGameStatus.ReadyToPlay); var readyCount = _playerGameStatuses.Values.Count(x => x == PlayerGameStatus.ReadyToPlay);
StringBuilder stationNames = new StringBuilder(); var stationNames = new StringBuilder();
if (_stationSystem.Stations.Count != 0) var query =
{ EntityQueryEnumerator<StationJobsComponent, StationSpawningComponent, MetaDataComponent>();
foreach (EntityUid entUID in _stationSystem.Stations)
{ var foundOne = false;
StationDataComponent? stationData = null;
MetaDataComponent? metaData = null; while (query.MoveNext(out _, out _, out var meta))
if (Resolve(entUID, ref stationData, ref metaData, logMissing: true))
{ {
foundOne = true;
if (stationNames.Length > 0) if (stationNames.Length > 0)
stationNames.Append('\n'); stationNames.Append('\n');
stationNames.Append(metaData.EntityName); stationNames.Append(meta.EntityName);
} }
}
} if (!foundOne)
else
{ {
stationNames.Append(Loc.GetString("game-ticker-no-map-selected")); stationNames.Append(Loc.GetString("game-ticker-no-map-selected"));
} }

View File

@@ -50,7 +50,9 @@ namespace Content.Server.GameTicking
foreach (var (player, _) in profiles) foreach (var (player, _) in profiles)
{ {
if (playerNetIds.Contains(player)) continue; if (playerNetIds.Contains(player))
continue;
toRemove.Add(player); toRemove.Add(player);
} }
@@ -60,12 +62,14 @@ namespace Content.Server.GameTicking
} }
} }
var assignedJobs = _stationJobs.AssignJobs(profiles, _stationSystem.Stations.ToList()); var spawnableStations = EntityQuery<StationJobsComponent, StationSpawningComponent>().Select(x => x.Item1.Owner).ToList();
_stationJobs.AssignOverflowJobs(ref assignedJobs, playerNetIds, profiles, _stationSystem.Stations.ToList()); var assignedJobs = _stationJobs.AssignJobs(profiles, spawnableStations);
_stationJobs.AssignOverflowJobs(ref assignedJobs, playerNetIds, profiles, spawnableStations);
// Calculate extended access for stations. // Calculate extended access for stations.
var stationJobCounts = _stationSystem.Stations.ToDictionary(e => e, _ => 0); var stationJobCounts = spawnableStations.ToDictionary(e => e, _ => 0);
foreach (var (netUser, (job, station)) in assignedJobs) foreach (var (netUser, (job, station)) in assignedJobs)
{ {
if (job == null) if (job == null)
@@ -117,7 +121,7 @@ namespace Content.Server.GameTicking
if (station == EntityUid.Invalid) if (station == EntityUid.Invalid)
{ {
var stations = _stationSystem.Stations.ToList(); var stations = EntityQuery<StationJobsComponent, StationSpawningComponent>().Select(x => x.Item1.Owner).ToList();
_robustRandom.Shuffle(stations); _robustRandom.Shuffle(stations);
if (stations.Count == 0) if (stations.Count == 0)
station = EntityUid.Invalid; station = EntityUid.Invalid;

View File

@@ -1,3 +1,4 @@
using Content.Server.NPC.Components;
using Content.Server.StationEvents.Events; using Content.Server.StationEvents.Events;
using Content.Shared.Dataset; using Content.Shared.Dataset;
using Content.Shared.Humanoid.Prototypes; using Content.Shared.Humanoid.Prototypes;
@@ -7,6 +8,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Server.GameTicking.Rules.Components; namespace Content.Server.GameTicking.Rules.Components;
@@ -119,6 +121,9 @@ public sealed class NukeopsRuleComponent : Component
/// todo: don't store sessions, dingus /// todo: don't store sessions, dingus
[DataField("operativePlayers")] [DataField("operativePlayers")]
public readonly Dictionary<string, IPlayerSession> OperativePlayers = new(); public readonly Dictionary<string, IPlayerSession> OperativePlayers = new();
[DataField("faction", customTypeSerializer: typeof(PrototypeIdSerializer<FactionPrototype>), required: true)]
public string Faction = default!;
} }
public enum WinType : byte public enum WinType : byte

View File

@@ -6,6 +6,7 @@ using Content.Server.Ghost.Roles.Components;
using Content.Server.Ghost.Roles.Events; using Content.Server.Ghost.Roles.Events;
using Content.Server.Humanoid; using Content.Server.Humanoid;
using Content.Server.Mind.Components; using Content.Server.Mind.Components;
using Content.Server.NPC.Components;
using Content.Server.NPC.Systems; using Content.Server.NPC.Systems;
using Content.Server.Nuke; using Content.Server.Nuke;
using Content.Server.Preferences.Managers; using Content.Server.Preferences.Managers;
@@ -175,12 +176,16 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
// we can only currently guarantee that NT stations are the only station to // we can only currently guarantee that NT stations are the only station to
// exist in the base game. // exist in the base game.
component.TargetStation = _stationSystem.Stations.FirstOrNull(); var eligible = EntityQuery<StationEventEligibleComponent, FactionComponent>()
.Where(x =>
_faction.IsFactionHostile(component.Faction, x.Item2.Owner, x.Item2))
.Select(x => x.Item1.Owner)
.ToList();
if (component.TargetStation == null) if (!eligible.Any())
{
return; return;
}
component.TargetStation = _random.Pick(eligible);
var filter = Filter.Empty(); var filter = Filter.Empty();
var query = EntityQueryEnumerator<NukeOperativeComponent, ActorComponent>(); var query = EntityQueryEnumerator<NukeOperativeComponent, ActorComponent>();

View File

@@ -144,8 +144,8 @@ public sealed class PiratesRuleSystem : GameRuleSystem<PiratesRuleComponent>
var map = "/Maps/Shuttles/pirate.yml"; var map = "/Maps/Shuttles/pirate.yml";
var xformQuery = GetEntityQuery<TransformComponent>(); var xformQuery = GetEntityQuery<TransformComponent>();
var aabbs = _stationSystem.Stations.SelectMany(x => var aabbs = EntityQuery<StationDataComponent>().SelectMany(x =>
Comp<StationDataComponent>(x).Grids.Select(x => x.Grids.Select(x =>
xformQuery.GetComponent(x).WorldMatrix.TransformBox(_mapManager.GetGridComp(x).LocalAABB))) xformQuery.GetComponent(x).WorldMatrix.TransformBox(_mapManager.GetGridComp(x).LocalAABB)))
.ToArray(); .ToArray();

View File

@@ -11,7 +11,7 @@ namespace Content.Server.Jobs
[DataField("components")] [DataField("components")]
[AlwaysPushInheritance] [AlwaysPushInheritance]
public EntityPrototype.ComponentRegistry Components { get; } = new(); public ComponentRegistry Components { get; } = new();
public override void AfterEquip(EntityUid mob) public override void AfterEquip(EntityUid mob)
{ {

View File

@@ -13,7 +13,7 @@ public sealed class ChangeComponentsSpellEvent : EntityTargetActionEvent, ISpeak
[DataField("toAdd")] [DataField("toAdd")]
[AlwaysPushInheritance] [AlwaysPushInheritance]
public EntityPrototype.ComponentRegistry ToAdd = new(); public ComponentRegistry ToAdd = new();
[DataField("toRemove")] [DataField("toRemove")]
[AlwaysPushInheritance] [AlwaysPushInheritance]

View File

@@ -8,5 +8,5 @@ namespace Content.Server.NPC.Queries.Queries;
public sealed class ComponentQuery : UtilityQuery public sealed class ComponentQuery : UtilityQuery
{ {
[DataField("components", required: true)] [DataField("components", required: true)]
public EntityPrototype.ComponentRegistry Components = default!; public ComponentRegistry Components = default!;
} }

View File

@@ -5,5 +5,5 @@ namespace Content.Server.NPC.Queries.Queries;
public sealed class NearbyComponentsQuery : UtilityQuery public sealed class NearbyComponentsQuery : UtilityQuery
{ {
[DataField("components")] [DataField("components")]
public EntityPrototype.ComponentRegistry Component = default!; public ComponentRegistry Component = default!;
} }

View File

@@ -153,7 +153,7 @@ public sealed class FactionSystem : EntitySystem
} }
} }
public bool IsFriendly(EntityUid uidA, EntityUid uidB, FactionComponent? factionA = null, FactionComponent? factionB = null) public bool IsEntityFriendly(EntityUid uidA, EntityUid uidB, FactionComponent? factionA = null, FactionComponent? factionB = null)
{ {
if (!Resolve(uidA, ref factionA, false) || !Resolve(uidB, ref factionB, false)) if (!Resolve(uidA, ref factionA, false) || !Resolve(uidB, ref factionB, false))
return false; return false;
@@ -161,6 +161,39 @@ public sealed class FactionSystem : EntitySystem
return factionA.Factions.Overlaps(factionB.Factions) || factionA.FriendlyFactions.Overlaps(factionB.Factions); return factionA.Factions.Overlaps(factionB.Factions) || factionA.FriendlyFactions.Overlaps(factionB.Factions);
} }
public bool IsFactionFriendly(string target, string with)
{
return _factions[target].Friendly.Contains(with) && _factions[with].Friendly.Contains(target);
}
public bool IsFactionFriendly(string target, EntityUid with, FactionComponent? factionWith = null)
{
if (!Resolve(with, ref factionWith, false))
return false;
return factionWith.Factions.All(x => IsFactionFriendly(target, x)) ||
factionWith.FriendlyFactions.Contains(target);
}
public bool IsFactionHostile(string target, string with)
{
return _factions[target].Hostile.Contains(with) && _factions[with].Hostile.Contains(target);
}
public bool IsFactionHostile(string target, EntityUid with, FactionComponent? factionWith = null)
{
if (!Resolve(with, ref factionWith, false))
return false;
return factionWith.Factions.All(x => IsFactionHostile(target, x)) ||
factionWith.HostileFactions.Contains(target);
}
public bool IsFactionNeutral(string target, string with)
{
return !IsFactionFriendly(target, with) && !IsFactionHostile(target, with);
}
/// <summary> /// <summary>
/// Makes the source faction friendly to the target faction, 1-way. /// Makes the source faction friendly to the target faction, 1-way.
/// </summary> /// </summary>

View File

@@ -470,7 +470,7 @@ public sealed partial class NPCSteeringSystem
(mask & otherBody.CollisionLayer) == 0x0 && (mask & otherBody.CollisionLayer) == 0x0 &&
(layer & otherBody.CollisionMask) == 0x0 || (layer & otherBody.CollisionMask) == 0x0 ||
!factionQuery.TryGetComponent(ent, out var otherFaction) || !factionQuery.TryGetComponent(ent, out var otherFaction) ||
!_faction.IsFriendly(uid, ent, ourFaction, otherFaction) || !_faction.IsEntityFriendly(uid, ent, ourFaction, otherFaction) ||
// Use <= 0 so we ignore stationary friends in case. // Use <= 0 so we ignore stationary friends in case.
Vector2.Dot(otherBody.LinearVelocity, ourVelocity) <= 0f) Vector2.Dot(otherBody.LinearVelocity, ourVelocity) <= 0f)
{ {

View File

@@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using Content.Server.Administration; using Content.Server.Administration;
using Content.Server.Station.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.Administration; using Content.Shared.Administration;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -47,13 +48,12 @@ namespace Content.Server.Nuke.Commands
} }
var stations = _entityManager var stations = _entityManager
.System<StationSystem>() .EntityQuery<StationDataComponent>()
.Stations .Select(stationData =>
.Select(station =>
{ {
var meta = _entityManager.GetComponent<MetaDataComponent>(station); var meta = _entityManager.GetComponent<MetaDataComponent>(stationData.Owner);
return new CompletionOption(station.ToString(), meta.EntityName); return new CompletionOption(stationData.Owner.ToString(), meta.EntityName);
}); });
return CompletionResult.FromHintOptions(stations, null); return CompletionResult.FromHintOptions(stations, null);

View File

@@ -1,4 +1,5 @@
using Content.Server.Objectives.Interfaces; using Content.Server.Objectives.Interfaces;
using Content.Server.Shuttles.Components;
using Content.Server.Station.Components; using Content.Server.Station.Components;
using Content.Shared.Cuffs.Components; using Content.Shared.Cuffs.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -61,7 +62,7 @@ namespace Content.Server.Objectives.Conditions
agentIsEscaping = false; agentIsEscaping = false;
// Any emergency shuttle counts for this objective. // Any emergency shuttle counts for this objective.
foreach (var stationData in entMan.EntityQuery<StationDataComponent>()) foreach (var stationData in entMan.EntityQuery<StationEmergencyShuttleComponent>())
{ {
if (IsAgentOnShuttle(xform, stationData.EmergencyShuttle)) { if (IsAgentOnShuttle(xform, stationData.EmergencyShuttle)) {
shuttleContainsAgent = true; shuttleContainsAgent = true;

View File

@@ -28,8 +28,6 @@ public sealed partial class SalvageSystem
private void InitializeExpeditions() private void InitializeExpeditions()
{ {
SubscribeLocalEvent<StationInitializedEvent>(OnSalvageExpStationInit);
SubscribeLocalEvent<SalvageExpeditionConsoleComponent, ComponentInit>(OnSalvageConsoleInit); SubscribeLocalEvent<SalvageExpeditionConsoleComponent, ComponentInit>(OnSalvageConsoleInit);
SubscribeLocalEvent<SalvageExpeditionConsoleComponent, EntParentChangedMessage>(OnSalvageConsoleParent); SubscribeLocalEvent<SalvageExpeditionConsoleComponent, EntParentChangedMessage>(OnSalvageConsoleParent);
SubscribeLocalEvent<SalvageExpeditionConsoleComponent, ClaimSalvageMessage>(OnSalvageClaimMessage); SubscribeLocalEvent<SalvageExpeditionConsoleComponent, ClaimSalvageMessage>(OnSalvageClaimMessage);
@@ -115,11 +113,6 @@ public sealed partial class SalvageSystem
component.EndTime += args.PausedTime; component.EndTime += args.PausedTime;
} }
private void OnSalvageExpStationInit(StationInitializedEvent ev)
{
EnsureComp<SalvageExpeditionDataComponent>(ev.Station);
}
private void UpdateExpeditions() private void UpdateExpeditions()
{ {
var currentTime = _timing.CurTime; var currentTime = _timing.CurTime;

View File

@@ -0,0 +1,24 @@
using Content.Server.Shuttles.Systems;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Utility;
namespace Content.Server.Shuttles.Components;
/// <summary>
/// This is used for controlling evacuation for a station.
/// </summary>
[RegisterComponent]
public sealed class StationEmergencyShuttleComponent : Component
{
/// <summary>
/// The emergency shuttle assigned to this station.
/// </summary>
[ViewVariables, Access(typeof(ShuttleSystem), typeof(EmergencyShuttleSystem), Friend = AccessPermissions.ReadWrite)]
public EntityUid? EmergencyShuttle;
/// <summary>
/// Emergency shuttle map path for this station.
/// </summary>
[DataField("emergencyShuttlePath", customTypeSerializer: typeof(ResPathSerializer))]
public ResPath EmergencyShuttlePath { get; set; } = new("/Maps/Shuttles/emergency.yml");
}

View File

@@ -57,7 +57,6 @@ public sealed class ArrivalsSystem : EntitySystem
SubscribeLocalEvent<ArrivalsShuttleComponent, EntityUnpausedEvent>(OnShuttleUnpaused); SubscribeLocalEvent<ArrivalsShuttleComponent, EntityUnpausedEvent>(OnShuttleUnpaused);
SubscribeLocalEvent<ArrivalsShuttleComponent, FTLTagEvent>(OnShuttleTag); SubscribeLocalEvent<ArrivalsShuttleComponent, FTLTagEvent>(OnShuttleTag);
SubscribeLocalEvent<StationInitializedEvent>(OnStationInit);
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStarting); SubscribeLocalEvent<RoundStartingEvent>(OnRoundStarting);
SubscribeLocalEvent<ArrivalsShuttleComponent, FTLStartedEvent>(OnArrivalsFTL); SubscribeLocalEvent<ArrivalsShuttleComponent, FTLStartedEvent>(OnArrivalsFTL);
@@ -207,17 +206,15 @@ public sealed class ArrivalsSystem : EntitySystem
} }
} }
private void OnStationInit(StationInitializedEvent ev)
{
EnsureComp<StationArrivalsComponent>(ev.Station);
}
private void OnPlayerSpawn(PlayerSpawningEvent ev) private void OnPlayerSpawn(PlayerSpawningEvent ev)
{ {
// Only works on latejoin even if enabled. // Only works on latejoin even if enabled.
if (!Enabled || _ticker.RunLevel != GameRunLevel.InRound) if (!Enabled || _ticker.RunLevel != GameRunLevel.InRound)
return; return;
if (!HasComp<StationArrivalsComponent>(ev.Station))
return;
var points = EntityQuery<SpawnPointComponent, TransformComponent>().ToList(); var points = EntityQuery<SpawnPointComponent, TransformComponent>().ToList();
_random.Shuffle(points); _random.Shuffle(points);
TryGetArrivals(out var arrivals); TryGetArrivals(out var arrivals);

View File

@@ -156,7 +156,7 @@ public sealed partial class EmergencyShuttleSystem
if (CentComMap != null) if (CentComMap != null)
{ {
var dataQuery = AllEntityQuery<StationDataComponent>(); var dataQuery = AllEntityQuery<StationEmergencyShuttleComponent>();
while (dataQuery.MoveNext(out var comp)) while (dataQuery.MoveNext(out var comp))
{ {

View File

@@ -73,7 +73,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
// Don't immediately invoke as roundstart will just handle it. // Don't immediately invoke as roundstart will just handle it.
_configManager.OnValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled); _configManager.OnValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled);
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart); SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
SubscribeLocalEvent<StationDataComponent, ComponentStartup>(OnStationStartup); SubscribeLocalEvent<StationEmergencyShuttleComponent, ComponentStartup>(OnStationStartup);
SubscribeNetworkEvent<EmergencyShuttleRequestPositionMessage>(OnShuttleRequestPosition); SubscribeNetworkEvent<EmergencyShuttleRequestPositionMessage>(OnShuttleRequestPosition);
InitializeEmergencyConsole(); InitializeEmergencyConsole();
} }
@@ -115,19 +115,22 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
return; return;
var player = args.SenderSession.AttachedEntity; var player = args.SenderSession.AttachedEntity;
if (player is null)
return;
if (player == null || var station = _station.GetOwningStation(player.Value);
!TryComp<StationDataComponent>(_station.GetOwningStation(player.Value), out var stationData) ||
!HasComp<ShuttleComponent>(stationData.EmergencyShuttle)) if (!TryComp<StationEmergencyShuttleComponent>(station, out var stationShuttle) ||
!HasComp<ShuttleComponent>(stationShuttle.EmergencyShuttle))
{ {
return; return;
} }
var targetGrid = _station.GetLargestGrid(stationData); var targetGrid = _station.GetLargestGrid(Comp<StationDataComponent>(station.Value));
if (targetGrid == null) if (targetGrid == null)
return; return;
var config = _dock.GetDockingConfig(stationData.EmergencyShuttle.Value, targetGrid.Value, DockTag); var config = _dock.GetDockingConfig(stationShuttle.EmergencyShuttle.Value, targetGrid.Value, DockTag);
if (config == null) if (config == null)
return; return;
@@ -143,14 +146,14 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
/// </summary> /// </summary>
public void CallEmergencyShuttle(EntityUid? stationUid) public void CallEmergencyShuttle(EntityUid? stationUid)
{ {
if (!TryComp<StationDataComponent>(stationUid, out var stationData) || if (!TryComp<StationEmergencyShuttleComponent>(stationUid, out var stationShuttle) ||
!TryComp<TransformComponent>(stationData.EmergencyShuttle, out var xform) || !TryComp<TransformComponent>(stationShuttle.EmergencyShuttle, out var xform) ||
!TryComp<ShuttleComponent>(stationData.EmergencyShuttle, out var shuttle)) !TryComp<ShuttleComponent>(stationShuttle.EmergencyShuttle, out var shuttle))
{ {
return; return;
} }
var targetGrid = _station.GetLargestGrid(stationData); var targetGrid = _station.GetLargestGrid(Comp<StationDataComponent>(stationUid.Value));
// UHH GOOD LUCK // UHH GOOD LUCK
if (targetGrid == null) if (targetGrid == null)
@@ -164,11 +167,11 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
var xformQuery = GetEntityQuery<TransformComponent>(); var xformQuery = GetEntityQuery<TransformComponent>();
if (_shuttle.TryFTLDock(stationData.EmergencyShuttle.Value, shuttle, targetGrid.Value, DockTag)) if (_shuttle.TryFTLDock(stationShuttle.EmergencyShuttle.Value, shuttle, targetGrid.Value, DockTag))
{ {
if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform)) if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
{ {
var angle = _dock.GetAngle(stationData.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
_chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false); _chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false);
} }
@@ -180,7 +183,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
{ {
if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform)) if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
{ {
var angle = _dock.GetAngle(stationData.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
_chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), playDefaultSound: false); _chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), playDefaultSound: false);
} }
@@ -190,7 +193,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
} }
} }
private void OnStationStartup(EntityUid uid, StationDataComponent component, ComponentStartup args) private void OnStationStartup(EntityUid uid, StationEmergencyShuttleComponent component, ComponentStartup args)
{ {
AddEmergencyShuttle(component); AddEmergencyShuttle(component);
} }
@@ -254,24 +257,23 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
_sawmill.Info("No CentCom map found, skipping setup."); _sawmill.Info("No CentCom map found, skipping setup.");
} }
foreach (var comp in EntityQuery<StationDataComponent>(true)) foreach (var comp in EntityQuery<StationEmergencyShuttleComponent>(true))
{ {
AddEmergencyShuttle(comp); AddEmergencyShuttle(comp);
} }
} }
private void AddEmergencyShuttle(StationDataComponent component) private void AddEmergencyShuttle(StationEmergencyShuttleComponent component)
{ {
if (!_emergencyShuttleEnabled if (!_emergencyShuttleEnabled
|| CentComMap == null || CentComMap == null
|| component.EmergencyShuttle != null || component.EmergencyShuttle != null)
|| component.StationConfig == null)
{ {
return; return;
} }
// Load escape shuttle // Load escape shuttle
var shuttlePath = component.StationConfig.EmergencyShuttlePath; var shuttlePath = component.EmergencyShuttlePath;
var shuttle = _map.LoadGrid(CentComMap.Value, shuttlePath.ToString(), new MapLoadOptions() var shuttle = _map.LoadGrid(CentComMap.Value, shuttlePath.ToString(), new MapLoadOptions()
{ {
// Should be far enough... right? I'm too lazy to bounds check CentCom rn. // Should be far enough... right? I'm too lazy to bounds check CentCom rn.

View File

@@ -1,15 +0,0 @@
using Content.Server.GameTicking;
namespace Content.Server.Station.Components;
/// <summary>
/// Added to grids saved in maps to designate them as 'part of a station' and not main grids. I.e. ancillary
/// shuttles for multi-grid stations.
/// </summary>
[RegisterComponent, Access(typeof(GameTicker)), Obsolete("Performs the exact same function as BecomesStationComponent.")]
public sealed class PartOfStationComponent : Component
{
[DataField("id", required: true)]
[ViewVariables(VVAccess.ReadWrite)]
public string Id = default!;
}

View File

@@ -1,5 +1,7 @@
using Content.Server.Shuttles.Systems; using Content.Server.Shuttles.Systems;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Utility;
namespace Content.Server.Station.Components; namespace Content.Server.Station.Components;
@@ -19,15 +21,6 @@ public sealed class StationDataComponent : Component
/// <summary> /// <summary>
/// List of all grids this station is part of. /// List of all grids this station is part of.
/// </summary> /// </summary>
/// <remarks>
/// You should not mutate this yourself, go through StationSystem so the appropriate events get fired.
/// </remarks>
[DataField("grids")] [DataField("grids")]
public readonly HashSet<EntityUid> Grids = new(); public readonly HashSet<EntityUid> Grids = new();
/// <summary>
/// The emergency shuttle assigned to this station.
/// </summary>
[ViewVariables, Access(typeof(ShuttleSystem), typeof(EmergencyShuttleSystem), Friend = AccessPermissions.ReadWrite)]
public EntityUid? EmergencyShuttle;
} }

View File

@@ -0,0 +1,10 @@
namespace Content.Server.Station.Components;
/// <summary>
/// This is used for event eligibility.
/// </summary>
[RegisterComponent]
public sealed class StationEventEligibleComponent : Component
{
}

View File

@@ -32,6 +32,16 @@ public sealed class StationJobsComponent : Component
/// </summary> /// </summary>
[DataField("extendedAccess")] public bool ExtendedAccess; [DataField("extendedAccess")] public bool ExtendedAccess;
/// <summary>
/// If there are less than or equal this amount of players in the game at round start,
/// people get extended access levels from job prototypes.
/// </summary>
/// <remarks>
/// Set to -1 to disable extended access.
/// </remarks>
[DataField("extendedAccessThreshold")]
public int ExtendedAccessThreshold { get; set; } = 15;
/// <summary> /// <summary>
/// The percentage of jobs remaining. /// The percentage of jobs remaining.
/// </summary> /// </summary>
@@ -62,5 +72,10 @@ public sealed class StationJobsComponent : Component
/// <summary> /// <summary>
/// Overflow jobs that round-start can spawn infinitely many of. /// Overflow jobs that round-start can spawn infinitely many of.
/// </summary> /// </summary>
[DataField("overflowJobs", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<JobPrototype>))] public HashSet<string> OverflowJobs = new(); [DataField("overflowJobs", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<JobPrototype>))]
public HashSet<string> OverflowJobs = new();
[DataField("availableJobs", required: true,
customTypeSerializer: typeof(PrototypeIdDictionarySerializer<List<int?>, JobPrototype>))]
public readonly Dictionary<string, List<int?>> SetupAvailableJobs = default!;
} }

View File

@@ -0,0 +1,23 @@
using Content.Server.Maps.NameGenerators;
namespace Content.Server.Station.Components;
/// <summary>
/// This is used for setting up a station's name.
/// </summary>
[RegisterComponent]
public sealed class StationNameSetupComponent : Component
{
/// <summary>
/// The name template to use for the station.
/// If there's a name generator this should follow it's required format.
/// </summary>
[DataField("mapNameTemplate", required: true)]
public string StationNameTemplate { get; } = default!;
/// <summary>
/// Name generator to use for the station, if any.
/// </summary>
[DataField("nameGenerator")]
public StationNameGenerator? NameGenerator { get; }
}

View File

@@ -1,38 +0,0 @@
using Content.Shared.Roles;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server.Station;
public sealed partial class StationConfig
{
[DataField("overflowJobs", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<JobPrototype>))]
private readonly List<string> _overflowJobs = default!;
/// <summary>
/// Jobs used at round start should the station run out of job slots.
/// Doesn't necessarily mean the station has infinite slots for the given jobs mid-round!
/// </summary>
public IReadOnlyList<string> OverflowJobs => _overflowJobs;
[DataField("availableJobs", required: true,
customTypeSerializer: typeof(PrototypeIdDictionarySerializer<List<int?>, JobPrototype>))]
private readonly Dictionary<string, List<int?>> _availableJobs = default!;
/// <summary>
/// Index of all jobs available on the station, of form
/// job name: [round-start, mid-round]
/// </summary>
public IReadOnlyDictionary<string, List<int?>> AvailableJobs => _availableJobs;
/// <summary>
/// If there are less than or equal this amount of players in the game at round start,
/// people get extended access levels from job prototypes.
/// </summary>
/// <remarks>
/// Set to -1 to disable extended access.
/// </remarks>
[DataField("extendedAccessThreshold")]
public int ExtendedAccessThreshold { get; set; } = 15;
}

View File

@@ -1,13 +0,0 @@
using Robust.Shared.Utility;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
namespace Content.Server.Station;
public sealed partial class StationConfig
{
/// <summary>
/// Emergency shuttle map path for this station.
/// </summary>
[DataField("emergencyShuttlePath", customTypeSerializer: typeof(ResPathSerializer))]
public ResPath EmergencyShuttlePath { get; set; } = new("/Maps/Shuttles/emergency.yml");
}

View File

@@ -1,30 +1,19 @@
using Content.Server.Maps.NameGenerators; using Content.Server.Maps.NameGenerators;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Station; namespace Content.Server.Station;
/// <summary> /// <summary>
/// A config for a station. Specifies name and job slots. /// A config for a station. Specifies name and component modifications.
/// This is the only part of stations a downstream should ideally need to modify directly.
/// </summary> /// </summary>
/// <remarks>
/// Forks should not directly edit existing parts of this class.
/// Make a new partial for your fancy new feature, it'll save you time later.
/// </remarks>
[DataDefinition, PublicAPI] [DataDefinition, PublicAPI]
public sealed partial class StationConfig public sealed class StationConfig
{ {
/// <summary> [DataField("stationProto", required: true)]
/// The name template to use for the station. public string StationPrototype = default!;
/// If there's a name generator this should follow it's required format.
/// </summary>
[DataField("mapNameTemplate", required: true)]
public string StationNameTemplate { get; } = default!;
/// <summary> [DataField("components", required: true)]
/// Name generator to use for the station, if any. public ComponentRegistry StationComponentOverrides = default!;
/// </summary>
[DataField("nameGenerator")]
public StationNameGenerator? NameGenerator { get; }
} }

View File

@@ -318,9 +318,8 @@ public sealed partial class StationJobsSystem
foreach (var (station, count) in jobsCount) foreach (var (station, count) in jobsCount)
{ {
var jobs = Comp<StationJobsComponent>(station); var jobs = Comp<StationJobsComponent>(station);
var data = Comp<StationDataComponent>(station);
var thresh = data.StationConfig?.ExtendedAccessThreshold ?? -1; var thresh = jobs.ExtendedAccessThreshold;
jobs.ExtendedAccess = count <= thresh; jobs.ExtendedAccess = count <= thresh;

View File

@@ -53,30 +53,32 @@ public sealed partial class StationJobsSystem : EntitySystem
private void OnStationInitialized(StationInitializedEvent msg) private void OnStationInitialized(StationInitializedEvent msg)
{ {
var stationJobs = AddComp<StationJobsComponent>(msg.Station); if (!TryComp<StationJobsComponent>(msg.Station, out var stationJobs))
var stationData = Comp<StationDataComponent>(msg.Station);
if (stationData.StationConfig == null)
return; return;
var mapJobList = stationData.StationConfig.AvailableJobs; var mapJobList = stationJobs.SetupAvailableJobs;
stationJobs.RoundStartTotalJobs = mapJobList.Values.Where(x => x[0] is not null && x[0] > 0).Sum(x => x[0]!.Value); stationJobs.RoundStartTotalJobs = mapJobList.Values.Where(x => x[0] is not null && x[0] > 0).Sum(x => x[0]!.Value);
stationJobs.MidRoundTotalJobs = mapJobList.Values.Where(x => x[1] is not null && x[1] > 0).Sum(x => x[1]!.Value); stationJobs.MidRoundTotalJobs = mapJobList.Values.Where(x => x[1] is not null && x[1] > 0).Sum(x => x[1]!.Value);
stationJobs.TotalJobs = stationJobs.MidRoundTotalJobs; stationJobs.TotalJobs = stationJobs.MidRoundTotalJobs;
stationJobs.JobList = mapJobList.ToDictionary(x => x.Key, x => stationJobs.JobList = mapJobList.ToDictionary(x => x.Key, x =>
{ {
if (x.Value[1] <= -1) if (x.Value[1] <= -1)
return null; return null;
return (uint?) x.Value[1]; return (uint?) x.Value[1];
}); });
stationJobs.RoundStartJobList = mapJobList.ToDictionary(x => x.Key, x => stationJobs.RoundStartJobList = mapJobList.ToDictionary(x => x.Key, x =>
{ {
if (x.Value[0] <= -1) if (x.Value[0] <= -1)
return null; return null;
return (uint?) x.Value[0]; return (uint?) x.Value[0];
}); });
stationJobs.OverflowJobs = stationData.StationConfig.OverflowJobs.ToHashSet();
stationJobs.OverflowJobs = stationJobs.OverflowJobs.ToHashSet();
UpdateJobsAvailable(); UpdateJobsAvailable();
} }
@@ -464,9 +466,11 @@ public sealed partial class StationJobsSystem : EntitySystem
var jobs = new Dictionary<EntityUid, Dictionary<string, uint?>>(); var jobs = new Dictionary<EntityUid, Dictionary<string, uint?>>();
var stationNames = new Dictionary<EntityUid, string>(); var stationNames = new Dictionary<EntityUid, string>();
foreach (var station in _stationSystem.Stations) var query = EntityQueryEnumerator<StationJobsComponent>();
while (query.MoveNext(out var station, out var comp))
{ {
var list = Comp<StationJobsComponent>(station).JobList.ToDictionary(x => x.Key, x => x.Value); var list = comp.JobList.ToDictionary(x => x.Key, x => x.Value);
jobs.Add(station, list); jobs.Add(station, list);
stationNames.Add(station, Name(station)); stationNames.Add(station, Name(station));
} }

View File

@@ -0,0 +1,35 @@
using Content.Server.Station.Components;
namespace Content.Server.Station.Systems;
/// <summary>
/// This handles naming stations.
/// </summary>
public sealed class StationNameSystem : EntitySystem
{
[Dependency] private readonly StationSystem _station = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<StationNameSetupComponent, ComponentInit>(OnStationNameSetupInit);
}
private void OnStationNameSetupInit(EntityUid uid, StationNameSetupComponent component, ComponentInit args)
{
if (!HasComp<StationDataComponent>(uid))
return;
_station.RenameStation(uid, GenerateStationName(component), false);
}
/// <summary>
/// Generates a station name from the given config.
/// </summary>
private static string GenerateStationName(StationNameSetupComponent config)
{
return config.NameGenerator is not null
? config.NameGenerator.FormatName(config.StationNameTemplate)
: config.StationNameTemplate;
}
}

View File

@@ -50,15 +50,9 @@ public sealed class StationSpawningSystem : EntitySystem
/// <inheritdoc/> /// <inheritdoc/>
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<StationInitializedEvent>(OnStationInitialized);
_configurationManager.OnValueChanged(CCVars.ICRandomCharacters, e => _randomizeCharacters = e, true); _configurationManager.OnValueChanged(CCVars.ICRandomCharacters, e => _randomizeCharacters = e, true);
} }
private void OnStationInitialized(StationInitializedEvent ev)
{
AddComp<StationSpawningComponent>(ev.Station);
}
/// <summary> /// <summary>
/// Attempts to spawn a player character onto the given station. /// Attempts to spawn a player character onto the given station.
/// </summary> /// </summary>
@@ -95,16 +89,19 @@ public sealed class StationSpawningSystem : EntitySystem
/// <param name="job">Job to assign to the character, if any.</param> /// <param name="job">Job to assign to the character, if any.</param>
/// <param name="profile">Appearance profile to use for the character.</param> /// <param name="profile">Appearance profile to use for the character.</param>
/// <param name="station">The station this player is being spawned on.</param> /// <param name="station">The station this player is being spawned on.</param>
/// <param name="entity">The entity to use, if one already exists.</param>
/// <returns>The spawned entity</returns> /// <returns>The spawned entity</returns>
public EntityUid SpawnPlayerMob( public EntityUid SpawnPlayerMob(
EntityCoordinates coordinates, EntityCoordinates coordinates,
Job? job, Job? job,
HumanoidCharacterProfile? profile, HumanoidCharacterProfile? profile,
EntityUid? station) EntityUid? station,
EntityUid? entity = null)
{ {
// If we're not spawning a humanoid, we're gonna exit early without doing all the humanoid stuff. // If we're not spawning a humanoid, we're gonna exit early without doing all the humanoid stuff.
if (job?.JobEntity != null) if (job?.JobEntity != null)
{ {
DebugTools.Assert(entity is null);
var jobEntity = EntityManager.SpawnEntity(job.JobEntity, coordinates); var jobEntity = EntityManager.SpawnEntity(job.JobEntity, coordinates);
MakeSentientCommand.MakeSentient(jobEntity, EntityManager); MakeSentientCommand.MakeSentient(jobEntity, EntityManager);
DoJobSpecials(job, jobEntity); DoJobSpecials(job, jobEntity);
@@ -131,7 +128,7 @@ public sealed class StationSpawningSystem : EntitySystem
if (!_prototypeManager.TryIndex<SpeciesPrototype>(speciesId, out var species)) if (!_prototypeManager.TryIndex<SpeciesPrototype>(speciesId, out var species))
throw new ArgumentException($"Invalid species prototype was used: {speciesId}"); throw new ArgumentException($"Invalid species prototype was used: {speciesId}");
var entity = Spawn(species.Prototype, coordinates); entity ??= Spawn(species.Prototype, coordinates);
if (_randomizeCharacters) if (_randomizeCharacters)
{ {
@@ -141,24 +138,24 @@ public sealed class StationSpawningSystem : EntitySystem
if (job?.StartingGear != null) if (job?.StartingGear != null)
{ {
var startingGear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear); var startingGear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
EquipStartingGear(entity, startingGear, profile); EquipStartingGear(entity.Value, startingGear, profile);
if (profile != null) if (profile != null)
EquipIdCard(entity, profile.Name, job.Prototype, station); EquipIdCard(entity.Value, profile.Name, job.Prototype, station);
} }
if (profile != null) if (profile != null)
{ {
_humanoidSystem.LoadProfile(entity, profile); _humanoidSystem.LoadProfile(entity.Value, profile);
MetaData(entity).EntityName = profile.Name; MetaData(entity.Value).EntityName = profile.Name;
if (profile.FlavorText != "" && _configurationManager.GetCVar(CCVars.FlavorText)) if (profile.FlavorText != "" && _configurationManager.GetCVar(CCVars.FlavorText))
{ {
AddComp<DetailExaminableComponent>(entity).Content = profile.FlavorText; AddComp<DetailExaminableComponent>(entity.Value).Content = profile.FlavorText;
} }
} }
DoJobSpecials(job, entity); DoJobSpecials(job, entity.Value);
_identity.QueueIdentityUpdate(entity); _identity.QueueIdentityUpdate(entity.Value);
return entity; return entity.Value;
} }
private void DoJobSpecials(Job? job, EntityUid entity) private void DoJobSpecials(Job? job, EntityUid entity)

View File

@@ -13,6 +13,7 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Station.Systems; namespace Content.Server.Station.Systems;
@@ -35,16 +36,6 @@ public sealed class StationSystem : EntitySystem
private ISawmill _sawmill = default!; private ISawmill _sawmill = default!;
private readonly HashSet<EntityUid> _stations = new();
/// <summary>
/// All stations that currently exist.
/// </summary>
/// <remarks>
/// I'd have this just invoke an entity query, but I want this to be a hashset for convenience and it allocating on use would be lame.
/// </remarks>
public IReadOnlySet<EntityUid> Stations => _stations;
private bool _randomStationOffset; private bool _randomStationOffset;
private bool _randomStationRotation; private bool _randomStationRotation;
private float _maxRandomStationOffset; private float _maxRandomStationOffset;
@@ -57,9 +48,8 @@ public sealed class StationSystem : EntitySystem
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRoundEnd); SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRoundEnd);
SubscribeLocalEvent<PreGameMapLoad>(OnPreGameMapLoad); SubscribeLocalEvent<PreGameMapLoad>(OnPreGameMapLoad);
SubscribeLocalEvent<PostGameMapLoad>(OnPostGameMapLoad); SubscribeLocalEvent<PostGameMapLoad>(OnPostGameMapLoad);
SubscribeLocalEvent<StationDataComponent, ComponentAdd>(OnStationAdd); SubscribeLocalEvent<StationDataComponent, ComponentStartup>(OnStationAdd);
SubscribeLocalEvent<StationDataComponent, ComponentShutdown>(OnStationDeleted); SubscribeLocalEvent<StationDataComponent, ComponentShutdown>(OnStationDeleted);
SubscribeLocalEvent<StationDataComponent, EntParentChangedMessage>(OnParentChanged);
SubscribeLocalEvent<StationMemberComponent, ComponentShutdown>(OnStationGridDeleted); SubscribeLocalEvent<StationMemberComponent, ComponentShutdown>(OnStationGridDeleted);
SubscribeLocalEvent<StationMemberComponent, PostGridSplitEvent>(OnStationSplitEvent); SubscribeLocalEvent<StationMemberComponent, PostGridSplitEvent>(OnStationSplitEvent);
@@ -89,68 +79,34 @@ public sealed class StationSystem : EntitySystem
_player.PlayerStatusChanged -= OnPlayerStatusChanged; _player.PlayerStatusChanged -= OnPlayerStatusChanged;
} }
/// <summary>
/// Called when the server shuts down or restarts to avoid uneccesarily logging mid-round station deletion errors.
/// </summary>
public void OnServerDispose()
{
_stations.Clear();
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{ {
if (e.NewStatus == SessionStatus.Connected) if (e.NewStatus == SessionStatus.Connected)
{ {
RaiseNetworkEvent(new StationsUpdatedEvent(_stations), e.Session); RaiseNetworkEvent(new StationsUpdatedEvent(GetStationsSet()), e.Session);
} }
} }
#region Event handlers #region Event handlers
private void OnStationAdd(EntityUid uid, StationDataComponent component, ComponentAdd args) private void OnStationAdd(EntityUid uid, StationDataComponent component, ComponentStartup args)
{ {
_stations.Add(uid); RaiseNetworkEvent(new StationsUpdatedEvent(GetStationsSet()), Filter.Broadcast());
var metaData = MetaData(uid);
RaiseLocalEvent(new StationInitializedEvent(uid));
_sawmill.Info($"Set up station {metaData.EntityName} ({uid}).");
RaiseNetworkEvent(new StationsUpdatedEvent(_stations), Filter.Broadcast());
} }
private void OnStationDeleted(EntityUid uid, StationDataComponent component, ComponentShutdown args) private void OnStationDeleted(EntityUid uid, StationDataComponent component, ComponentShutdown args)
{ {
if (_stations.Contains(uid) && // Was not deleted via DeleteStation()
_gameTicker.RunLevel == GameRunLevel.InRound && // And not due to a round restart
_gameTicker.LobbyEnabled) // If there isn't a lobby, this is probably sandbox, single player, or a test
{
// printing a stack trace, rather than throwing an exception so that entity deletion continues as normal.
Logger.Error($"Station entity {ToPrettyString(uid)} is getting deleted mid-round. Trace: {Environment.StackTrace}");
}
foreach (var grid in component.Grids) foreach (var grid in component.Grids)
{ {
RemComp<StationMemberComponent>(grid); RemComp<StationMemberComponent>(grid);
} }
_stations.Remove(uid); RaiseNetworkEvent(new StationsUpdatedEvent(GetStationsSet()), Filter.Broadcast());
RaiseNetworkEvent(new StationsUpdatedEvent(_stations), Filter.Broadcast());
}
/// <summary>
/// If a station data entity is getting re-parented mid-round, this will log an error.
/// </summary>
/// <remarks>
/// This doesn't really achieve anything, it just for debugging any future station data bugs.
/// </remarks>
private void OnParentChanged(EntityUid uid, StationDataComponent component, ref EntParentChangedMessage args)
{
if (_gameTicker.RunLevel != GameRunLevel.InRound ||
MetaData(uid).EntityLifeStage >= EntityLifeStage.MapInitialized ||
component.LifeStage <= ComponentLifeStage.Initializing)
{
return;
}
// Yeah this doesn't actually stop the parent change..... it just ineffectually yells about it.
// STOP RIGHT THERE CRIMINAL SCUM
_sawmill.Error($"Station entity {ToPrettyString(uid)} is getting reparented from {ToPrettyString(args.OldParent ?? EntityUid.Invalid)} to {ToPrettyString(args.Transform.ParentUid)}");
} }
private void OnPreGameMapLoad(PreGameMapLoad ev) private void OnPreGameMapLoad(PreGameMapLoad ev)
@@ -199,23 +155,18 @@ public sealed class StationSystem : EntitySystem
_sawmill.Error($"There were no station grids for {ev.GameMap.ID}!"); _sawmill.Error($"There were no station grids for {ev.GameMap.ID}!");
} }
// Iterate over all PartOfStation
// TODO: Remove this whenever pillar finally gets replaced. It's the sole user.
foreach (var grid in ev.Grids)
{
if (!TryComp<PartOfStationComponent>(grid, out var partOfStation))
continue;
AddGrid(partOfStation.Id, grid);
}
foreach (var (id, gridIds) in dict) foreach (var (id, gridIds) in dict)
{ {
StationConfig? stationConfig = null; StationConfig stationConfig;
if (ev.GameMap.Stations.ContainsKey(id)) if (ev.GameMap.Stations.ContainsKey(id))
stationConfig = ev.GameMap.Stations[id]; stationConfig = ev.GameMap.Stations[id];
else else
{
_sawmill.Error($"The station {id} in map {ev.GameMap.ID} does not have an associated station config!"); _sawmill.Error($"The station {id} in map {ev.GameMap.ID} does not have an associated station config!");
continue;
}
InitializeNewStation(stationConfig, gridIds, ev.StationName); InitializeNewStation(stationConfig, gridIds, ev.StationName);
} }
} }
@@ -225,9 +176,10 @@ public sealed class StationSystem : EntitySystem
if (eventArgs.New != GameRunLevel.PreRoundLobby) if (eventArgs.New != GameRunLevel.PreRoundLobby)
return; return;
foreach (var entity in _stations) var query = EntityQueryEnumerator<StationDataComponent>();
while (query.MoveNext(out var station, out _))
{ {
DeleteStation(entity); QueueDel(station);
} }
} }
@@ -326,47 +278,26 @@ public sealed class StationSystem : EntitySystem
return filter; return filter;
} }
/// <summary>
/// Generates a station name from the given config.
/// </summary>
public static string GenerateStationName(StationConfig config)
{
return config.NameGenerator is not null
? config.NameGenerator.FormatName(config.StationNameTemplate)
: config.StationNameTemplate;
}
/// <summary> /// <summary>
/// Initializes a new station with the given information. /// Initializes a new station with the given information.
/// </summary> /// </summary>
/// <param name="stationConfig">The game map prototype used, if any.</param> /// <param name="stationConfig">The game map prototype used, if any.</param>
/// <param name="gridIds">All grids that should be added to the station.</param> /// <param name="gridIds">All grids that should be added to the station.</param>
/// <param name="name">Optional override for the station name.</param> /// <param name="name">Optional override for the station name.</param>
/// <remarks>This is for ease of use, manually spawning the entity works just fine.</remarks>
/// <returns>The initialized station.</returns> /// <returns>The initialized station.</returns>
public EntityUid InitializeNewStation(StationConfig? stationConfig, IEnumerable<EntityUid>? gridIds, string? name = null) public EntityUid InitializeNewStation(StationConfig stationConfig, IEnumerable<EntityUid>? gridIds, string? name = null)
{ {
var station = Spawn(null, MapCoordinates.Nullspace); // Use overrides for setup.
var station = EntityManager.SpawnEntity(stationConfig.StationPrototype, MapCoordinates.Nullspace, stationConfig.StationComponentOverrides);
// TODO SERIALIZATION The station data needs to be saveable somehow, but when a map gets saved, this entity if (name is not null)
// won't be included because its in null-space. Also, what happens to shuttles on other maps? RenameStation(station, name, false);
var data = AddComp<StationDataComponent>(station); DebugTools.Assert(HasComp<StationDataComponent>(station), "Stations should have StationData in their prototype.");
var metaData = MetaData(station);
data.StationConfig = stationConfig;
if (stationConfig is not null && name is null) var data = Comp<StationDataComponent>(station);
{ name ??= MetaData(station).EntityName;
name = GenerateStationName(stationConfig);
}
else if (name is null)
{
_sawmill.Error($"When setting up station {station}, was unable to find a valid name in the config and no name was provided.");
name = "unnamed station";
}
metaData.EntityName = name;
RaiseLocalEvent(new StationInitializedEvent(station));
_sawmill.Info($"Set up station {metaData.EntityName} ({station}).");
foreach (var grid in gridIds ?? Array.Empty<EntityUid>()) foreach (var grid in gridIds ?? Array.Empty<EntityUid>())
{ {
@@ -397,11 +328,11 @@ public sealed class StationSystem : EntitySystem
var stationMember = AddComp<StationMemberComponent>(mapGrid); var stationMember = AddComp<StationMemberComponent>(mapGrid);
stationMember.Station = station; stationMember.Station = station;
stationData.Grids.Add(gridComponent.Owner); stationData.Grids.Add(mapGrid);
RaiseLocalEvent(station, new StationGridAddedEvent(gridComponent.Owner, false), true); RaiseLocalEvent(station, new StationGridAddedEvent(mapGrid, false), true);
_sawmill.Info($"Adding grid {mapGrid}:{gridComponent.Owner} to station {Name(station)} ({station})"); _sawmill.Info($"Adding grid {mapGrid} to station {Name(station)} ({station})");
} }
/// <summary> /// <summary>
@@ -420,10 +351,10 @@ public sealed class StationSystem : EntitySystem
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
RemComp<StationMemberComponent>(mapGrid); RemComp<StationMemberComponent>(mapGrid);
stationData.Grids.Remove(gridComponent.Owner); stationData.Grids.Remove(mapGrid);
RaiseLocalEvent(station, new StationGridRemovedEvent(gridComponent.Owner), true); RaiseLocalEvent(station, new StationGridRemovedEvent(mapGrid), true);
_sawmill.Info($"Removing grid {mapGrid}:{gridComponent.Owner} from station {Name(station)} ({station})"); _sawmill.Info($"Removing grid {mapGrid} from station {Name(station)} ({station})");
} }
/// <summary> /// <summary>
@@ -462,9 +393,7 @@ public sealed class StationSystem : EntitySystem
if (!Resolve(station, ref stationData)) if (!Resolve(station, ref stationData))
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
// component shutdown will error if the station was not removed from _stations prior to deletion. QueueDel(station);
_stations.Remove(station);
Del(station);
} }
/// <summary> /// <summary>
@@ -501,6 +430,16 @@ public sealed class StationSystem : EntitySystem
return CompOrNull<StationMemberComponent>(xform.GridUid)?.Station; return CompOrNull<StationMemberComponent>(xform.GridUid)?.Station;
} }
public List<EntityUid> GetStations()
{
return EntityQuery<StationDataComponent>().Select(x => x.Owner).ToList();
}
public HashSet<EntityUid> GetStationsSet()
{
return EntityQuery<StationDataComponent>().Select(x => x.Owner).ToHashSet();
}
} }
/// <summary> /// <summary>

View File

@@ -24,9 +24,9 @@ public sealed class AnomalySpawnRule : StationEventSystem<AnomalySpawnRuleCompon
{ {
base.Started(uid, component, gameRule, args); base.Started(uid, component, gameRule, args);
if (StationSystem.Stations.Count == 0) if (!TryGetRandomStation(out var chosenStation))
return; // No stations return;
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
if (!TryComp<StationDataComponent>(chosenStation, out var stationData)) if (!TryComp<StationDataComponent>(chosenStation, out var stationData))
return; return;

View File

@@ -26,9 +26,8 @@ public sealed class BreakerFlipRule : StationEventSystem<BreakerFlipRuleComponen
{ {
base.Started(uid, component, gameRule, args); base.Started(uid, component, gameRule, args);
if (StationSystem.Stations.Count == 0) if (!TryGetRandomStation(out var chosenStation))
return; return;
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
var stationApcs = new List<ApcComponent>(); var stationApcs = new List<ApcComponent>();
foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>()) foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>())

View File

@@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using Content.Server.GameTicking.Rules.Components; using Content.Server.GameTicking.Rules.Components;
using Content.Server.Station.Components;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Server.StationEvents.Components; using Content.Server.StationEvents.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -16,10 +17,13 @@ public sealed class BureaucraticErrorRule : StationEventSystem<BureaucraticError
{ {
base.Started(uid, component, gameRule, args); base.Started(uid, component, gameRule, args);
if (StationSystem.Stations.Count == 0) if (!TryGetRandomStation(out var chosenStation, HasComp<StationJobsComponent>))
return; // No stations return;
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
var jobList = _stationJobs.GetJobs(chosenStation).Keys.ToList(); var jobList = _stationJobs.GetJobs(chosenStation.Value).Keys.ToList();
if (jobList.Count == 0)
return;
var mod = GetSeverityModifier(); var mod = GetSeverityModifier();
@@ -28,12 +32,12 @@ public sealed class BureaucraticErrorRule : StationEventSystem<BureaucraticError
if (RobustRandom.Prob(Math.Min(0.25f * MathF.Sqrt(mod), 1.0f))) if (RobustRandom.Prob(Math.Min(0.25f * MathF.Sqrt(mod), 1.0f)))
{ {
var chosenJob = RobustRandom.PickAndTake(jobList); var chosenJob = RobustRandom.PickAndTake(jobList);
_stationJobs.MakeJobUnlimited(chosenStation, chosenJob); // INFINITE chaos. _stationJobs.MakeJobUnlimited(chosenStation.Value, chosenJob); // INFINITE chaos.
foreach (var job in jobList) foreach (var job in jobList)
{ {
if (_stationJobs.IsJobUnlimited(chosenStation, job)) if (_stationJobs.IsJobUnlimited(chosenStation.Value, job))
continue; continue;
_stationJobs.TrySetJobSlot(chosenStation, job, 0); _stationJobs.TrySetJobSlot(chosenStation.Value, job, 0);
} }
} }
else else
@@ -45,10 +49,10 @@ public sealed class BureaucraticErrorRule : StationEventSystem<BureaucraticError
for (var i = 0; i < num; i++) for (var i = 0; i < num; i++)
{ {
var chosenJob = RobustRandom.PickAndTake(jobList); var chosenJob = RobustRandom.PickAndTake(jobList);
if (_stationJobs.IsJobUnlimited(chosenStation, chosenJob)) if (_stationJobs.IsJobUnlimited(chosenStation.Value, chosenJob))
continue; continue;
_stationJobs.TryAdjustJobSlot(chosenStation, chosenJob, RobustRandom.Next(-3, 6), clamp: true); _stationJobs.TryAdjustJobSlot(chosenStation.Value, chosenJob, RobustRandom.Next(-3, 6), clamp: true);
} }
} }
} }

View File

@@ -22,8 +22,9 @@ namespace Content.Server.StationEvents.Events
var mod = MathF.Sqrt(GetSeverityModifier()); var mod = MathF.Sqrt(GetSeverityModifier());
// Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there. // Essentially we'll pick out a target amount of gas to leak, then a rate to leak it at, then work out the duration from there.
if (TryFindRandomTile(out component.TargetTile, out component.TargetStation, out component.TargetGrid, out component.TargetCoords)) if (TryFindRandomTile(out component.TargetTile, out var target, out component.TargetGrid, out component.TargetCoords))
{ {
component.TargetStation = target.Value;
component.FoundTile = true; component.FoundTile = true;
component.LeakGas = RobustRandom.Pick(component.LeakableGases); component.LeakGas = RobustRandom.Pick(component.LeakableGases);

View File

@@ -23,9 +23,8 @@ namespace Content.Server.StationEvents.Events
{ {
base.Started(uid, component, gameRule, args); base.Started(uid, component, gameRule, args);
if (StationSystem.Stations.Count == 0) if (!TryGetRandomStation(out var chosenStation))
return; return;
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>(true)) foreach (var (apc, transform) in EntityQuery<ApcComponent, TransformComponent>(true))
{ {

View File

@@ -1,3 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
@@ -131,19 +133,37 @@ public abstract class StationEventSystem<T> : GameRuleSystem<T> where T : Compon
GameTicker.EndGameRule(uid, component); GameTicker.EndGameRule(uid, component);
} }
protected bool TryFindRandomTile(out Vector2i tile, out EntityUid targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords) protected bool TryGetRandomStation([NotNullWhen(true)] out EntityUid? station, Func<EntityUid, bool>? filter = null)
{
filter ??= _ => true;
// augh. sorry sloth there's no better API and my goal today isn't adding 50 entitymanager methods :waa:
var stations = EntityManager.GetAllComponents(typeof(StationEventEligibleComponent)).Select(x => x.Owner).Where(filter).ToArray();
if (stations.Length == 0)
{
station = null;
return false;
}
station = RobustRandom.Pick(stations);
return true;
}
protected bool TryFindRandomTile(out Vector2i tile, [NotNullWhen(true)] out EntityUid? targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords)
{ {
tile = default; tile = default;
targetCoords = EntityCoordinates.Invalid; targetCoords = EntityCoordinates.Invalid;
if (StationSystem.Stations.Count == 0) if (!TryGetRandomStation(out targetStation))
{ {
targetStation = EntityUid.Invalid; targetStation = EntityUid.Invalid;
targetGrid = EntityUid.Invalid; targetGrid = EntityUid.Invalid;
return false; return false;
} }
targetStation = RobustRandom.Pick(StationSystem.Stations); var possibleTargets = Comp<StationDataComponent>(targetStation.Value).Grids;
var possibleTargets = Comp<StationDataComponent>(targetStation).Grids;
if (possibleTargets.Count == 0) if (possibleTargets.Count == 0)
{ {
targetGrid = EntityUid.Invalid; targetGrid = EntityUid.Invalid;

View File

@@ -22,9 +22,8 @@ public sealed class VentClogRule : StationEventSystem<VentClogRuleComponent>
{ {
base.Started(uid, component, gameRule, args); base.Started(uid, component, gameRule, args);
if (StationSystem.Stations.Count == 0) if (!TryGetRandomStation(out var chosenStation))
return; return;
var chosenStation = RobustRandom.Pick(StationSystem.Stations.ToList());
// TODO: "safe random" for chems. Right now this includes admin chemicals. // TODO: "safe random" for chems. Right now this includes admin chemicals.
var allReagents = PrototypeManager.EnumeratePrototypes<ReagentPrototype>() var allReagents = PrototypeManager.EnumeratePrototypes<ReagentPrototype>()

View File

@@ -44,17 +44,14 @@ public sealed class StationRecordsSystem : EntitySystem
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<StationInitializedEvent>(OnStationInitialize);
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn); SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn);
} }
private void OnStationInitialize(StationInitializedEvent args)
{
AddComp<StationRecordsComponent>(args.Station);
}
private void OnPlayerSpawn(PlayerSpawnCompleteEvent args) private void OnPlayerSpawn(PlayerSpawnCompleteEvent args)
{ {
if (!HasComp<StationRecordsComponent>(args.Station))
return;
CreateGeneralRecord(args.Station, args.Mob, args.Profile, args.JobId); CreateGeneralRecord(args.Station, args.Mob, args.Profile, args.JobId);
} }

View File

@@ -41,7 +41,7 @@ public sealed class BiomePrototype : IPrototype, IInheritingPrototype
/// </summary> /// </summary>
[DataField("chunkComponents")] [DataField("chunkComponents")]
[AlwaysPushInheritance] [AlwaysPushInheritance]
public EntityPrototype.ComponentRegistry ChunkComponents { get; } = new(); public ComponentRegistry ChunkComponents { get; } = new();
//TODO: Get someone to make this a method on componentregistry that does it Correctly. //TODO: Get someone to make this a method on componentregistry that does it Correctly.
/// <summary> /// <summary>

View File

@@ -18,7 +18,7 @@ public sealed class WorldgenConfigPrototype : IPrototype
/// The components that get added to the target map. /// The components that get added to the target map.
/// </summary> /// </summary>
[DataField("components", required: true)] [DataField("components", required: true)]
public EntityPrototype.ComponentRegistry Components { get; } = default!; public ComponentRegistry Components { get; } = default!;
//TODO: Get someone to make this a method on componentregistry that does it Correctly. //TODO: Get someone to make this a method on componentregistry that does it Correctly.
/// <summary> /// <summary>

View File

@@ -33,5 +33,5 @@ public sealed class RandomHumanoidSettingsPrototype : IPrototype, IInheritingPro
/// Extra components to add to this entity. /// Extra components to add to this entity.
/// </summary> /// </summary>
[DataField("components")] [DataField("components")]
public EntityPrototype.ComponentRegistry? Components { get; } public ComponentRegistry? Components { get; }
} }

View File

@@ -29,7 +29,7 @@ public sealed class PayloadTriggerComponent : Component
/// List of components to add or remove from an entity when this trigger is (un)installed. /// List of components to add or remove from an entity when this trigger is (un)installed.
/// </summary> /// </summary>
[DataField("components", serverOnly:true, readOnly: true)] [DataField("components", serverOnly:true, readOnly: true)]
public readonly EntityPrototype.ComponentRegistry? Components = null; public readonly ComponentRegistry? Components = null;
/// <summary> /// <summary>
/// Keeps track of what components this trigger has granted to the payload case. /// Keeps track of what components this trigger has granted to the payload case.

View File

@@ -21,13 +21,13 @@ public sealed class ArtifactEffectPrototype : IPrototype
/// These are removed after the node is exited and the effect is changed. /// These are removed after the node is exited and the effect is changed.
/// </summary> /// </summary>
[DataField("components", serverOnly: true)] [DataField("components", serverOnly: true)]
public EntityPrototype.ComponentRegistry Components = new(); public ComponentRegistry Components = new();
/// <summary> /// <summary>
/// Components that are permanently added to an entity when the effect's node is entered. /// Components that are permanently added to an entity when the effect's node is entered.
/// </summary> /// </summary>
[DataField("permanentComponents")] [DataField("permanentComponents")]
public EntityPrototype.ComponentRegistry PermanentComponents = new(); public ComponentRegistry PermanentComponents = new();
//TODO: make this a list so we can have multiple target depths //TODO: make this a list so we can have multiple target depths
[DataField("targetDepth")] [DataField("targetDepth")]

View File

@@ -17,7 +17,7 @@ public sealed class ArtifactTriggerPrototype : IPrototype
public string ID { get; } = default!; public string ID { get; } = default!;
[DataField("components", serverOnly: true)] [DataField("components", serverOnly: true)]
public EntityPrototype.ComponentRegistry Components = new(); public ComponentRegistry Components = new();
[DataField("targetDepth")] [DataField("targetDepth")]
public int TargetDepth = 0; public int TargetDepth = 0;

View File

@@ -0,0 +1,57 @@
- type: entity
id: BaseStation
abstract: true
components:
- type: StationData
- type: entity
id: BaseStationCargo
abstract: true
components:
- type: StationBankAccount
- type: StationCargoOrderDatabase
- type: entity
id: BaseStationJobsSpawning
abstract: true
components:
- type: StationJobs
availableJobs: {}
- type: StationSpawning
- type: entity
id: BaseStationRecords
abstract: true
components:
- type: StationRecords
- type: entity
id: BaseStationArrivals
abstract: true
components:
- type: StationArrivals
- type: entity
id: BaseStationEvacuation
abstract: true
components:
- type: StationEmergencyShuttle
- type: entity
id: BaseStationAlertLevels
abstract: true
components:
- type: AlertLevel
alertLevelPrototype: stationAlerts
- type: entity
id: BaseStationExpeditions
abstract: true
components:
- type: SalvageExpeditionData
- type: entity
id: BaseStationAllEventsEligible
abstract: true
components:
- type: StationEventEligible # For when someone makes this more granular in the future.

View File

@@ -0,0 +1,33 @@
- type: entity
id: BaseStationNanotrasen
components:
- type: Faction
factions:
- NanoTrasen
- type: entity
id: StandardNanotrasenStation
parent:
- BaseStation
- BaseStationCargo
- BaseStationJobsSpawning
- BaseStationRecords
- BaseStationArrivals
- BaseStationEvacuation
- BaseStationAlertLevels
- BaseStationExpeditions
- BaseStationAllEventsEligible
- BaseStationNanotrasen
noSpawn: true
components:
- type: Transform
- type: entity
id: NanotrasenCentralCommand
parent:
- BaseStation
- BaseStationAlertLevels
- BaseStationNanotrasen
noSpawn: true
components:
- type: Transform

View File

@@ -36,6 +36,7 @@
noSpawn: true noSpawn: true
components: components:
- type: NukeopsRule - type: NukeopsRule
faction: Syndicate
- type: entity - type: entity
id: Pirates id: Pirates

View File

@@ -6,11 +6,16 @@
maxPlayers: 70 maxPlayers: 70
stations: stations:
Aspid: Aspid:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} NCS Aspid {1}' mapNameTemplate: '{0} NCS Aspid {1}'
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: '14' prefixCreator: '14'
- type: StationEmergencyShuttle
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -6,11 +6,16 @@
maxPlayers: 75 maxPlayers: 75
stations: stations:
Bagel: Bagel:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Bagel Station {1}' mapNameTemplate: '{0} Bagel Station {1}'
emergencyShuttlePath: /Maps/Shuttles/emergency_lox.yml
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: '14' prefixCreator: '14'
- type: StationEmergencyShuttle
emergencyShuttlePath: /Maps/Shuttles/emergency_lox.yml
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -6,11 +6,16 @@
maxPlayers: 70 maxPlayers: 70
stations: stations:
Barratry: Barratry:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Barratry {1}' mapNameTemplate: '{0} Barratry {1}'
emergencyShuttlePath: /Maps/Shuttles/emergency_raven.yml
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: '14' prefixCreator: '14'
- type: StationEmergencyShuttle
emergencyShuttlePath: /Maps/Shuttles/emergency_raven.yml
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -5,11 +5,16 @@
minPlayers: 50 minPlayers: 50
stations: stations:
Boxstation: Boxstation:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Box Station {1}' mapNameTemplate: '{0} Box Station {1}'
emergencyShuttlePath: /Maps/Shuttles/emergency_box.yml
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: 'TG' prefixCreator: 'TG'
- type: StationEmergencyShuttle
emergencyShuttlePath: /Maps/Shuttles/emergency_box.yml
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -5,11 +5,10 @@
minPlayers: 10 minPlayers: 10
stations: stations:
centcomm: centcomm:
stationProto: NanotrasenCentralCommand
components:
- type: StationNameSetup
mapNameTemplate: '{0} Central Command {1}' mapNameTemplate: '{0} Central Command {1}'
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: 'TG' prefixCreator: 'TG'
overflowJobs:
- Passenger
availableJobs:
Passenger: [ 0, 1 ]

View File

@@ -6,10 +6,14 @@
maxPlayers: 35 maxPlayers: 35
stations: stations:
Cluster: Cluster:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Cluster Station {1}' mapNameTemplate: '{0} Cluster Station {1}'
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: '14' prefixCreator: '14'
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -5,7 +5,11 @@
minPlayers: 0 minPlayers: 0
stations: stations:
Empty: Empty:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: "Empty" mapNameTemplate: "Empty"
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:
@@ -18,7 +22,11 @@
minPlayers: 0 minPlayers: 0
stations: stations:
Dev: Dev:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: "Dev" mapNameTemplate: "Dev"
- type: StationJobs
overflowJobs: overflowJobs:
- Captain - Captain
availableJobs: availableJobs:

View File

@@ -5,11 +5,16 @@
minPlayers: 70 minPlayers: 70
stations: stations:
Fland: Fland:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Fland Installation {1}' mapNameTemplate: '{0} Fland Installation {1}'
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: 'B' prefixCreator: 'B'
- type: StationEmergencyShuttle
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -1,14 +0,0 @@
- type: gameMap
id: Infiltrator
mapName: 'Syndicate Infiltrator'
mapPath: /Maps/infiltrator.yml
minPlayers: 0
stations:
Station: #TODO: Mapper, add a BecomesStation component to the primary grid of the map.
mapNameTemplate: '{0} Infiltrator {1}'
nameGenerator:
!type:NanotrasenNameGenerator
prefixCreator: '14'
overflowJobs: []
availableJobs:
Captain: [ 1, 1 ]

View File

@@ -5,11 +5,16 @@
minPlayers: 35 minPlayers: 35
stations: stations:
Kettle: Kettle:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Kettle {1}' mapNameTemplate: '{0} Kettle {1}'
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: '14' prefixCreator: '14'
- type: StationEmergencyShuttle
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -6,11 +6,16 @@
maxPlayers: 70 maxPlayers: 70
stations: stations:
Marathon: Marathon:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Marathon Station {1}' mapNameTemplate: '{0} Marathon Station {1}'
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: '14' prefixCreator: '14'
- type: StationEmergencyShuttle
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -5,11 +5,16 @@
minPlayers: 50 minPlayers: 50
stations: stations:
Meta: Meta:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Meta Station {1}' mapNameTemplate: '{0} Meta Station {1}'
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: 'TG' prefixCreator: 'TG'
- type: StationEmergencyShuttle
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -7,10 +7,14 @@
fallback: true fallback: true
stations: stations:
Moose: Moose:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Moose {1}' mapNameTemplate: '{0} Moose {1}'
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: '14' prefixCreator: '14'
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -6,10 +6,14 @@
maxPlayers: 35 maxPlayers: 35
stations: stations:
Omega: Omega:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Omega Station {1}' mapNameTemplate: '{0} Omega Station {1}'
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: 'TG' prefixCreator: 'TG'
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -5,11 +5,16 @@
minPlayers: 50 minPlayers: 50
stations: stations:
Origin: Origin:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Origin {1}' mapNameTemplate: '{0} Origin {1}'
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: '14' prefixCreator: '14'
- type: StationEmergencyShuttle
emergencyShuttlePath: /Maps/Shuttles/emergency_courser.yml
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs:

View File

@@ -7,10 +7,14 @@
fallback: true fallback: true
stations: stations:
Saltern: Saltern:
stationProto: StandardNanotrasenStation
components:
- type: StationNameSetup
mapNameTemplate: '{0} Saltern {1}' mapNameTemplate: '{0} Saltern {1}'
nameGenerator: nameGenerator:
!type:NanotrasenNameGenerator !type:NanotrasenNameGenerator
prefixCreator: '14' prefixCreator: '14'
- type: StationJobs
overflowJobs: overflowJobs:
- Passenger - Passenger
availableJobs: availableJobs: