Skip to content

Commit

Permalink
Public Transit (#850)
Browse files Browse the repository at this point in the history
* public transportation

* priority dock prototypes

* map changes

* bug fixes

* comments and cleanup
  • Loading branch information
Cheackraze authored Jan 11, 2024
1 parent c666120 commit b0a4729
Show file tree
Hide file tree
Showing 10 changed files with 3,903 additions and 3,758 deletions.
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

0 comments on commit b0a4729

Please sign in to comment.