Files
Bubberstation/code/controllers/subsystem/transport.dm
lessthanthree 8246e3cf8e Tram malfunction adjustments (#83706)
## About The Pull Request

Adjusts tram malfunction and the tram malfunction event to be more
straightforward and similar to other telecoms machinery.

- Tram malfunction now requires engineering, borg, or AI to visit either
the tram or the tram controller in telecoms to press 'reset'
(previously: open the tram panel and find the appropriate tool to repair
it)
- Tram malfunction event only stops the tram once per event roll
- Adjust announcement to include message that engineers need to reset
the tram
- Removed loud priority_announce when the tram is fixed. Players know
the tram is fixed because they are using it again
- Tram doors no longer force crush during malfunction, since engineers
have no method to fix this
- EMP/communications blackout event will temporarily degrade crossing
signals, otherwise the tram continues to function normally
- Loss of telecoms power will degrade crossing signals, otherwise the
tram continues to function normally

Frequency of tram malfunction event is reduced to be in line with
communications blackout and grav gen blackout, and a maximum of 3
occurrences per round

Signal failure:
Previously: Signals fail until reset has physically been done on the
controller
New: Signal failure is moved to be part of the Communications Blackout
event, lasting the duration of EMP blast that hits telecoms

Communication loss:
Previously: For the duration of the Tram Malfunction event there is a 5%
chance the tram does a degraded stop, ending when the controller is
repaired or the timer ends
New: A guaranteed 1 degraded stop per event roll. To balance the
increase in occurrences it's been made easier to reset on the
controller. If the tram is not reset by the end of the timer, it is done
automatically

New telecoms room tram controller:

![dreamseeker_AGHpKvyunZ](https://github.com/tgstation/tgstation/assets/83487515/e8269607-11c9-407a-8f6e-d20f3f1b5f56)

## Why It's Good For The Game

Making the tram friendlier for engineering to pick up/understand, it
will function more predictably in the tram malfunction and
communications blackout event.

## Changelog

🆑 LT3
add: Telecoms now has a central tram controller
add: Tram controller is now included in communications blackout event
qol: Tram malfunction event only stops tram once, requiring engineering
to reset (no tools required)
qol: Tram malfunction event no longer sends a Central Command
announcement when fixed
del: Tram doors no longer force crush you during tram malfunction event
del: Silicons can no longer control the tram when communication is lost
/🆑
2024-06-09 14:51:11 +01:00

233 lines
12 KiB
Plaintext

PROCESSING_SUBSYSTEM_DEF(transport)
name = "Transport"
wait = 0.05 SECONDS
/// only used on maps with trams, so only enabled by such.
can_fire = FALSE
///associative list of the form: list(lift_id = list(all transport_controller datums attached to lifts of that type))
var/list/transports_by_type = list()
var/list/nav_beacons = list()
var/list/crossing_signals = list()
var/list/sensors = list()
var/list/doors = list()
var/list/displays = list()
///how much time a tram can take per movement before we notify admins and slow down the tram. in milliseconds
var/max_time = 15
///how many times the tram can move costing over max_time milliseconds before it gets slowed down
var/max_exceeding_moves = 5
///how many times the tram can move costing less than half max_time milliseconds before we speed it back up again.
///is only used if the tram has been slowed down for exceeding max_time
var/max_cheap_moves = 5
/**
* Registers the subsystem to listen for incoming requests from paired devices
*
* When a new device (such as a button, tram, signal etc) comes online
* it calls this proc with the subsystem enabling two-way communication using
* signals.
*
* Arguments: new_unit: the starting point to find a beacon
* unit_name: the friendly name of this device
* id_tag: a unique identifier for this device, set on init
*/
/datum/controller/subsystem/processing/transport/proc/hello(atom/new_unit, unit_name, id_tag)
RegisterSignal(new_unit, COMSIG_TRANSPORT_REQUEST, PROC_REF(incoming_request))
log_transport("Sub: Registered new transport component [unit_name] [id_tag].")
/datum/controller/subsystem/processing/transport/Recover()
_listen_lookup = SStransport._listen_lookup
/**
* Performs the request received from a registered transport device
*
* Currently the only supported request type is tram dispatch
* so there's no var for what type of request it is
*
* The subsystem will validate and process, then send a success
* or fail response to the device that made the request,
* with info relevant to the request such as destination
* or error details (if the request is rejected/fails)
*
* Arguments: source: the device sending the request
* transport_id: the transport this request is for, such as tram line 1 or 2
* platform: the requested destination to dispatch the tram
* options: additional flags for the request (ie: bypass doors, emagged request)
*/
/datum/controller/subsystem/processing/transport/proc/incoming_request(obj/source, transport_id, platform, options)
SIGNAL_HANDLER
log_transport("Sub: Received request from [source.name] [source.id_tag]. Contents: [transport_id] [platform] [options]")
var/relevant
var/request_flags = options
var/datum/transport_controller/linear/tram/transport_controller
var/obj/effect/landmark/transport/nav_beacon/tram/platform/destination
for(var/datum/transport_controller/linear/tram/candidate_controller as anything in transports_by_type[TRANSPORT_TYPE_TRAM])
if(candidate_controller.specific_transport_id == transport_id)
transport_controller = candidate_controller
break
// We make a list of relevant devices (that should act/respond to this request) for when we send the signal at the end
LAZYADD(relevant, source)
// Check for various failure states
// The transport controller datum is qdel'd
if(isnull(transport_controller))
log_transport("Sub: Transport [transport_id] has no controller datum! Someone deleted it or something catastrophic happened.")
SEND_TRANSPORT_SIGNAL(COMSIG_TRANSPORT_RESPONSE, relevant, REQUEST_FAIL, BROKEN_BEYOND_REPAIR)
log_transport("Sub: Sending response to [source.id_tag]. Contents: [REQUEST_FAIL] [INTERNAL_ERROR]. Info: [SUB_TS_STATUS].")
return
// Non operational (such as power loss) or the controls cabinet is missing/destroyed
if(!transport_controller.controller_operational || !transport_controller.paired_cabinet)
SEND_TRANSPORT_SIGNAL(COMSIG_TRANSPORT_RESPONSE, relevant, REQUEST_FAIL, NOT_IN_SERVICE)
log_transport("Sub: Sending response to [source.id_tag]. Contents: [REQUEST_FAIL] [NOT_IN_SERVICE]. Info: TC-[!transport_controller][!transport_controller.controller_operational][!transport_controller.paired_cabinet].")
return
// Someone emergency stopped the tram, or something went wrong and it needs to reset its landmarks.
if(transport_controller.controller_status & SYSTEM_FAULT || transport_controller.controller_status & EMERGENCY_STOP)
SEND_TRANSPORT_SIGNAL(COMSIG_TRANSPORT_RESPONSE, relevant, REQUEST_FAIL, INTERNAL_ERROR)
log_transport("Sub: Sending response to [source.id_tag]. Contents: [REQUEST_FAIL] [INTERNAL_ERROR]. Info: [SUB_TS_STATUS].")
return
// Controller is 'active' (not accepting requests right now) someone already pushed button, hit by a rod, etc.
if(transport_controller.controller_active)
SEND_TRANSPORT_SIGNAL(COMSIG_TRANSPORT_RESPONSE, relevant, REQUEST_FAIL, TRANSPORT_IN_USE)
log_transport("Sub: Sending response to [source.id_tag]. Contents: [REQUEST_FAIL] [TRANSPORT_IN_USE]. Info: [TC_TA_INFO].")
return
// We've made it this far, tram is physically fine so let's trip plan
// This is based on the destination nav beacon, the logical location
// If Something Happens and the location the controller thinks it's at
// gets out of sync with it's actual physical location, it can be reset
// Since players can set the platform ID themselves, make sure it's a valid platform we're aware of
var/network = LAZYACCESS(nav_beacons, transport_id)
for(var/obj/effect/landmark/transport/nav_beacon/tram/platform/potential_destination in network)
if(potential_destination.platform_code == platform)
destination = potential_destination
break
// The platform in the request doesn't exist (ie: Can't send a tram to East Wing when the map is Birdshot)
if(!destination)
SEND_TRANSPORT_SIGNAL(COMSIG_TRANSPORT_RESPONSE, relevant, REQUEST_FAIL, INVALID_PLATFORM)
log_transport("Sub: Sending response to [source.id_tag]. Contents: [REQUEST_FAIL] [INVALID_PLATFORM]. Info: RD0.")
return
// The controller thinks the tram is already there
if(transport_controller.idle_platform == destination) //did you even look?
SEND_TRANSPORT_SIGNAL(COMSIG_TRANSPORT_RESPONSE, relevant, REQUEST_FAIL, NO_CALL_REQUIRED)
log_transport("Sub: Sending response to [source.id_tag]. Contents: [REQUEST_FAIL] [NO_CALL_REQUIRED]. Info: RD1.")
return
// Calculate the trip data, which will be stored on the controller datum, passed to the transport modules making up the tram
// If for some reason the controller can't determine the distance/direction it needs to go, send a failure message
if(!transport_controller.calculate_route(destination))
SEND_TRANSPORT_SIGNAL(COMSIG_TRANSPORT_RESPONSE, relevant, REQUEST_FAIL, INTERNAL_ERROR)
log_transport("Sub: Sending response to [source.id_tag]. Contents: [REQUEST_FAIL] [INTERNAL_ERROR]. Info: NV0.")
return
// At this point we're sending the tram somewhere, so send a success response to the devices
SEND_TRANSPORT_SIGNAL(COMSIG_TRANSPORT_RESPONSE, relevant, REQUEST_SUCCESS, destination.name)
log_transport("Sub: Sending response to [source.id_tag]. Contents: [REQUEST_SUCCESS] [destination.name].")
// Since this is a signal and we're done with the request, do the rest async
INVOKE_ASYNC(src, PROC_REF(dispatch_transport), transport_controller, request_flags)
/**
* Dispatches the transport on a validated trip
*
* The subsystem at this point has confirmed a valid trip
* Start the transport, wake up machinery running on
* the subsystem (signals, etc.)
*
* Make tram go, basically.
*
* Arguments: transport_controller: the transport controller datum we're giving orders to
* destination: destination we're sending it to
* request_flags: additional flags for the request (ie: bypass doors, emagged request)
*/
/datum/controller/subsystem/processing/transport/proc/dispatch_transport(datum/transport_controller/linear/tram/transport_controller, destination, request_flags)
log_transport("Sub: Sending dispatch request to [transport_controller.specific_transport_id]. [request_flags ? "Contents: [request_flags]." : "No request flags."]")
// This will generally be caught in the request validation, however an admin may try to force move the tram, or other actions bypassing the request process.
if(transport_controller.idle_platform == transport_controller.destination_platform)
log_transport("Sub: [transport_controller.specific_transport_id] dispatch failed. Info: DE-1 Transport Controller idle and destination are the same.")
return
// Set active, so no more requests will be accepted until we're in a safe state to change destination.
transport_controller.set_active(TRUE)
pre_departure(transport_controller, request_flags)
/**
* Pre-departure checks for the tram
*
* We do things slighly different based on the request_flags such as
* door crushing, emag related things
*
* Arguments: transport_controller: the transport controller datum we're giving orders to
* request_flags: additional flags for the request (ie: bypass doors, emagged request)
*/
/datum/controller/subsystem/processing/transport/proc/pre_departure(datum/transport_controller/linear/tram/transport_controller, request_flags)
log_transport("Sub: [transport_controller.specific_transport_id] start pre-departure. Info: [SUB_TS_STATUS]")
// Lock the physical controls of the tram
transport_controller.set_status_code(PRE_DEPARTURE, TRUE)
transport_controller.set_status_code(CONTROLS_LOCKED, TRUE)
// Tram door actions
log_transport("Sub: [transport_controller.specific_transport_id] requested door close. Info: [SUB_TS_STATUS].")
if(request_flags & RAPID_MODE || request_flags & BYPASS_SENSORS || transport_controller.controller_status & BYPASS_SENSORS) // bypass for unsafe, rapid departure
transport_controller.cycle_doors(CYCLE_CLOSED, BYPASS_DOOR_CHECKS)
if(request_flags & RAPID_MODE)
log_transport("Sub: [transport_controller.specific_transport_id] rapid mode enabled, bypassing validation.")
transport_controller.dispatch_transport()
return
else
transport_controller.set_status_code(DOORS_READY, FALSE)
transport_controller.cycle_doors(CYCLE_CLOSED)
addtimer(CALLBACK(src, PROC_REF(validate_and_dispatch), transport_controller), 3 SECONDS)
/**
* Operational checks, then start moving
*
* Some check failures aren't worth halting the tram for, like no blocking the doors forever
* Crush them instead!
*
* Arguments: transport_controller: the transport controller datum we're giving orders to
* attempt: how many attempts to start moving we've made
*/
/datum/controller/subsystem/processing/transport/proc/validate_and_dispatch(datum/transport_controller/linear/tram/transport_controller, attempt)
log_transport("Sub: [transport_controller.specific_transport_id] start pre-departure validation. Attempts: [attempt ? attempt : 0].")
var/current_attempt
if(attempt)
current_attempt = attempt
else
current_attempt = 0
if(current_attempt >= 4)
log_transport("Sub: [transport_controller.specific_transport_id] pre-departure validation failed, but dispatching tram anyways. Info: [SUB_TS_STATUS].")
transport_controller.dispatch_transport()
return
current_attempt++
transport_controller.update_status()
if(!(transport_controller.controller_status & DOORS_READY))
addtimer(CALLBACK(src, PROC_REF(validate_and_dispatch), transport_controller, current_attempt), 3 SECONDS)
return
else
transport_controller.dispatch_transport()
log_transport("Sub: [transport_controller.specific_transport_id] pre-departure passed.")
/// Give a list of destinations to the tram controls
/datum/controller/subsystem/processing/transport/proc/detailed_destination_list(specific_transport_id)
. = list()
for(var/obj/effect/landmark/transport/nav_beacon/tram/platform/destination as anything in SStransport.nav_beacons[specific_transport_id])
var/list/this_destination = list()
this_destination["name"] = destination.name
this_destination["dest_icons"] = destination.tgui_icons
this_destination["id"] = destination.platform_code
. += list(this_destination)