Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Public Transit #850

Merged
merged 5 commits into from
Jan 11, 2024
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Content.Server._NF.PublicTransit.Components;

/// <summary>
/// Added to a station that is available for public transit.
/// </summary>
[RegisterComponent, Access(typeof(PublicTransitSystem))]
public sealed partial class StationTransitComponent : Component
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;

namespace Content.Server._NF.PublicTransit.Components;

/// <summary>
/// Added to a grid to have it act as an automated public transit bus.
/// Public Transit system will add this procedurally to any grid designated as a 'bus' through the CVAR
/// Mappers may add it to their shuttle if they wish, but this is going to force it's use and function as a public transit bus
/// </summary>
[RegisterComponent, Access(typeof(PublicTransitSystem))]
public sealed partial class TransitShuttleComponent : Component
{
[DataField("nextStation")]
public EntityUid NextStation;

[DataField("nextTransfer", customTypeSerializer:typeof(TimeOffsetSerializer))]
public TimeSpan NextTransfer;
}
255 changes: 255 additions & 0 deletions Content.Server/_NF/PublicTransit/PublicTransitSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
using Content.Server._NF.PublicTransit.Components;
using Content.Server.GameTicking;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Server.Shuttles.Systems;
using Content.Shared.NF14.CCVar;
using Content.Shared.Shuttles.Components;
using Content.Shared.Tiles;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Timing;
using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;

namespace Content.Server._NF.PublicTransit;

/// <summary>
/// If enabled, spawns a public trasnport grid as definied by cvar, to act as an automatic transit shuttle between designated grids
/// </summary>
public sealed class PublicTransitSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly MapLoaderSystem _loader = default!;
[Dependency] private readonly ShuttleSystem _shuttles = default!;

/// <summary>
/// If enabled then spawns the bus and sets up the bus line.
/// </summary>
public bool Enabled { get; private set; }
public float FlyTime = 50f;
public int Counter = 0;
public List<EntityUid> StationList = new();

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<StationTransitComponent, ComponentStartup>(OnStationStartup);
SubscribeLocalEvent<StationTransitComponent, ComponentShutdown>(OnStationShutdown);
SubscribeLocalEvent<TransitShuttleComponent, ComponentStartup>(OnShuttleStartup);
SubscribeLocalEvent<TransitShuttleComponent, EntityUnpausedEvent>(OnShuttleUnpaused);
SubscribeLocalEvent<TransitShuttleComponent, FTLTagEvent>(OnShuttleTag);

Enabled = _cfgManager.GetCVar(NF14CVars.PublicTransit);
FlyTime = _cfgManager.GetCVar(NF14CVars.PublicTransitFlyTime);
Counter = 0;
StationList.Clear();
_cfgManager.OnValueChanged(NF14CVars.PublicTransit, SetTransit);
_cfgManager.OnValueChanged(NF14CVars.PublicTransitFlyTime, SetFly);
}

/// <summary>
/// Hardcoded snippit to intercept FTL events. It catches the transit shuttle and ensures its looking for the "DockTransit" priority dock.
/// </summary>
private void OnShuttleTag(EntityUid uid, TransitShuttleComponent component, ref FTLTagEvent args)
{
if (args.Handled)
return;

// Just saves mappers forgetting, or ensuring that a non-standard grid forced to be a bus will prioritize the "DockTransit" tagged docks
args.Handled = true;
args.Tag = "DockTransit";
}

public override void Shutdown()
{
base.Shutdown();
_cfgManager.UnsubValueChanged(NF14CVars.PublicTransitFlyTime, SetFly);
_cfgManager.UnsubValueChanged(NF14CVars.PublicTransit, SetTransit);
}

/// <summary>
/// Checks to make sure the grid is on the appropriate playfield, i.e., not in mapping space being worked on.
/// If so, adds the grid to the list of bus stops, but only if its not already there
/// </summary>
private void OnStationStartup(EntityUid uid, StationTransitComponent component, ComponentStartup args)
{
if (Transform(uid).MapID == _ticker.DefaultMap) //best solution i could find because of componentinit/mapinit race conditions
{
if (!StationList.Contains(uid)) //if the grid isnt already in
StationList.Add(uid); //add it to the list
if (Enabled) //and just in case this has been added dynamically mid-round, lets do a setup check
SetupPublicTransit();
}
}

/// <summary>
/// When a bus stop gets deleted in-game, we need to remove it from the list of bus stops, or else we get FTL problems
/// </summary>
private void OnStationShutdown(EntityUid uid, StationTransitComponent component, ComponentShutdown args)
{
if (StationList.Contains(uid))
StationList.Remove(uid);
}

/// <summary>
/// Again, this can and likely should be instructed to mappers to do, but just in case it was either forgotten or we are doing admemes,
/// we make sure that the bus is (mostly) griefer protected and that it cant be hijacked
/// </summary>
private void OnShuttleStartup(EntityUid uid, TransitShuttleComponent component, ComponentStartup args)
{
EnsureComp<PreventPilotComponent>(uid);
EnsureComp<ProtectedGridComponent>(uid);
}

/// <summary>
/// ensuring that pausing the shuttle for any reason doesnt mess up our timing
/// </summary>
private void OnShuttleUnpaused(EntityUid uid, TransitShuttleComponent component, ref EntityUnpausedEvent args)
{
component.NextTransfer += args.PausedTime;
}

/// <summary>
/// Here is our bus stop list handler. Theres probably a better way...
/// First, sets our output to null just in case
/// then, makes sure that our counter/index isnt out of range (reaching the end of the list will force you back to the beginning, like a loop)
/// Then, it checks to make sure that there even is anything in the list
/// and if so, we return the next station, and then increment our counter for the next time its ran
/// </summary>
private bool TryGetNextStation(out EntityUid? station)
{
station = null;

if (Counter >= StationList.Count)
Counter = 0;

if (!(StationList.Count > 0))
return false;

station = StationList[Counter];
Counter++;
return true;
}

/// <summary>
/// We check the current time every tick, and if its not yet time, we just ignore.
/// If the timer is ready, we send the shuttle on an FTL journey to the destination it has saved
/// then we check our bus list, and if it returns true with the next station, we cache it on the component and reset the timer
/// if it returns false or gives a bad grid, we are just going to FTL back to where we are and try again until theres a proper destination
/// This could cause unintended behavior, if a destination is deleted while it's next in the cache, the shuttle is going to be stuck in FTL space
/// However the timer is going to force it to FTL to the next bus stop
/// If it happens that all bus stops are deleted and we never get a valid stop again, we are going to be stuck FTL'ing forever in ftl space
/// but at that point, theres nowhere to return to anyway
/// </summary>
public override void Update(float frameTime)
{
base.Update(frameTime);

var query = EntityQueryEnumerator<TransitShuttleComponent, ShuttleComponent>();
var curTime = _timing.CurTime;

while (query.MoveNext(out var uid, out var comp, out var shuttle))
{
if (comp.NextTransfer > curTime)
continue;

_shuttles.FTLTravel(uid, shuttle, comp.NextStation, hyperspaceTime: FlyTime, dock: true);

if (TryGetNextStation(out var nextStation) && nextStation is {Valid : true} destination)
comp.NextStation = destination;

comp.NextTransfer += TimeSpan.FromSeconds(FlyTime + _cfgManager.GetCVar(NF14CVars.PublicTransitWaitTime));
}
}

/// <summary>
/// Here is handling a simple CVAR change to enable/disable the system
/// if the cvar is changed to enabled, we setup the transit system
/// if its changed to disabled, we delete any bus grids that exist
/// along with anyone/thing riding the bus
/// you've been warned
/// </summary>
private void SetTransit(bool obj)
{
Enabled = obj;

if (Enabled)
{
SetupPublicTransit();
}
else
{
var shuttleQuery = AllEntityQuery<TransitShuttleComponent>();

while (shuttleQuery.MoveNext(out var uid, out _))
{
QueueDel(uid);
}
}
}

/// <summary>
/// Simple cache reflection
/// </summary>
private void SetFly(float obj)
{
FlyTime = obj;
}

/// <summary>
/// Here is where we handle setting up the transit system, including sanity checks.
/// This is called multiple times, from a few different sources, to ensure that if the system is activated dynamically
/// it will still function as intended
/// </summary>
private void SetupPublicTransit()
{
// If a public bus alraedy exists, we simply return. No need to set up the system again.
var query = EntityQueryEnumerator<TransitShuttleComponent>();
while (query.MoveNext(out var euid, out _))
{
if (!Deleted(euid))
return;
}

// Spawn the bus onto a dummy map
var dummyMap = _mapManager.CreateMap();
var busMap = _cfgManager.GetCVar(NF14CVars.PublicTransitBusMap);
if (_loader.TryLoad(dummyMap, busMap, out var shuttleUids))
{
var shuttleComp = Comp<ShuttleComponent>(shuttleUids[0]);
// Here we are making sure that the shuttle has the TransitShuttle comp onto it, in case of dynamically changing the bus grid
var transitComp = EnsureComp<TransitShuttleComponent>(shuttleUids[0]);

//We run our bus station function to try to get a valid station to FTL to. If for some reason, there are no bus stops, we will instead just delete the shuttle
if (TryGetNextStation(out var station) && station is { Valid : true } destination)
{
//we set up a default in case the second time we call it fails for some reason
transitComp.NextStation = destination;
_shuttles.FTLTravel(shuttleUids[0], shuttleComp, destination, hyperspaceTime: 5f, dock: true);
transitComp.NextTransfer = _timing.CurTime + TimeSpan.FromSeconds(_cfgManager.GetCVar(NF14CVars.PublicTransitWaitTime));

//since the initial cached value of the next station is actually the one we are 'starting' from, we need to run the
//bus stop list code one more time so that our first trip isnt just Frontier - Frontier
if (TryGetNextStation(out var firstStop) && firstStop is { Valid : true } firstDestination)
transitComp.NextStation = firstDestination;
}
else
{
foreach (var shuttle in shuttleUids)
{
QueueDel(shuttle);
}
}
}

// the FTL sequence takes a few seconds to warm up and send the grid, so we give the temp dummy map
// some buffer time before calling a self-delete
var timer = AddComp<TimedDespawnComponent>(_mapManager.GetMapEntityId(dummyMap));
timer.Lifetime = 15f;
}
}
27 changes: 27 additions & 0 deletions Content.Shared/_NF/CCVars/CCVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,31 @@ public sealed class NF14CVars
/// </summary>
public static readonly CVarDef<float> CryoExpirationTime =
CVarDef.Create("nf14.uncryo.maxtime", 60 * 60f, CVar.SERVER | CVar.REPLICATED);

/*
* Public Transit
*/
/// <summary>
/// Whether public transit is enabled.
/// </summary>
public static readonly CVarDef<bool> PublicTransit =
CVarDef.Create("nf14.publictransit.enabled", true, CVar.SERVERONLY);

/// <summary>
/// The map to use for the public bus.
/// </summary>
public static readonly CVarDef<string> PublicTransitBusMap =
CVarDef.Create("nf14.publictransit.bus_map", "/Maps/_NF/Shuttles/publicts.yml", CVar.SERVERONLY);

/// <summary>
/// The amount of time the bus waits at a station.
/// </summary>
public static readonly CVarDef<float> PublicTransitWaitTime =
CVarDef.Create("nf14.publictransit.wait_time", 150f, CVar.SERVERONLY);

/// <summary>
/// The amount of time the flies through FTL space.
/// </summary>
public static readonly CVarDef<float> PublicTransitFlyTime =
CVarDef.Create("nf14.publictransit.fly_time", 145f, CVar.SERVERONLY);
}
Loading
Loading