#define HIGHLIGHT_DYNAMIC_TRANSIT 0 #define MAX_TRANSIT_REQUEST_RETRIES 10 SUBSYSTEM_DEF(shuttle) name = "Shuttle" wait = 10 init_order = INIT_ORDER_SHUTTLE flags = SS_KEEP_TIMING|SS_NO_TICK_CHECK runlevels = RUNLEVEL_SETUP | RUNLEVEL_GAME var/list/mobile = list() var/list/stationary = list() var/list/transit = list() var/list/turf/transit_turfs = list() var/list/transit_requesters = list() var/list/transit_request_failures = list() var/clear_transit = FALSE //emergency shuttle stuff var/obj/docking_port/mobile/emergency/emergency var/obj/docking_port/mobile/arrivals/arrivals var/obj/docking_port/mobile/emergency/backup/backup_shuttle var/emergencyCallTime = 6000 //time taken for emergency shuttle to reach the station when called (in deciseconds) var/emergencyDockTime = 1800 //time taken for emergency shuttle to leave again once it has docked (in deciseconds) var/emergencyEscapeTime = 1200 //time taken for emergency shuttle to reach a safe distance after leaving station (in deciseconds) var/area/emergencyLastCallLoc var/emergencyCallAmount = 0 //how many times the escape shuttle was called var/emergencyNoEscape var/emergencyNoRecall = FALSE var/list/hostileEnvironments = list() //supply shuttle stuff var/obj/docking_port/mobile/supply/supply var/ordernum = 1 //order number given to next order var/points = 5000 //number of trade-points we have var/centcom_message = "" //Remarks from CentCom on how well you checked the last order. var/list/discoveredPlants = list() //Typepaths for unusual plants we've already sent CentCom, associated with their potencies var/list/supply_packs = list() var/list/shoppinglist = list() var/list/requestlist = list() var/list/orderhistory = list() var/datum/round_event/shuttle_loan/shuttle_loan var/shuttle_purchased = FALSE //If the station has purchased a replacement escape shuttle this round var/list/shuttle_purchase_requirements_met = list() //For keeping track of ingame events that would unlock new shuttles, such as defeating a boss or discovering a secret item var/lockdown = FALSE //disallow transit after nuke goes off var/auto_call = 99000 //time before in deciseconds in which the shuttle is auto called. Default is 2½ hours plus 15 for the shuttle. So total is 3. /datum/controller/subsystem/shuttle/Initialize(timeofday) if(!arrivals) WARNING("No /obj/docking_port/mobile/arrivals placed on the map!") if(!emergency) WARNING("No /obj/docking_port/mobile/emergency placed on the map!") if(!backup_shuttle) WARNING("No /obj/docking_port/mobile/emergency/backup placed on the map!") if(!supply) WARNING("No /obj/docking_port/mobile/supply placed on the map!") ordernum = rand(1, 9000) for(var/pack in subtypesof(/datum/supply_pack)) var/datum/supply_pack/P = new pack() if(!P.contains) continue supply_packs[P.type] = P if(!transit_turfs.len) setup_transit_zone() initial_move() #ifdef HIGHLIGHT_DYNAMIC_TRANSIT color_space() #endif ..() /datum/controller/subsystem/shuttle/proc/setup_transit_zone() // transit zone var/turf/A = get_turf(locate(SHUTTLE_TRANSIT_BORDER,SHUTTLE_TRANSIT_BORDER,ZLEVEL_TRANSIT)) var/turf/B = get_turf(locate(world.maxx - SHUTTLE_TRANSIT_BORDER,world.maxy - SHUTTLE_TRANSIT_BORDER,ZLEVEL_TRANSIT)) for(var/i in block(A, B)) var/turf/T = i T.ChangeTurf(/turf/open/space) transit_turfs += T T.flags_1 |= UNUSED_TRANSIT_TURF_1 #ifdef HIGHLIGHT_DYNAMIC_TRANSIT /datum/controller/subsystem/shuttle/proc/color_space() var/turf/A = get_turf(locate(SHUTTLE_TRANSIT_BORDER,SHUTTLE_TRANSIT_BORDER,ZLEVEL_TRANSIT)) var/turf/B = get_turf(locate(world.maxx - SHUTTLE_TRANSIT_BORDER,world.maxy - SHUTTLE_TRANSIT_BORDER,ZLEVEL_TRANSIT)) for(var/i in block(A, B)) var/turf/T = i // Only dying the "pure" space, not the transit tiles if(istype(T, /turf/open/space/transit) || !isspaceturf(T)) continue if((T.x == A.x) || (T.x == B.x) || (T.y == A.y) || (T.y == B.y)) T.color = "#ffff00" else T.color = "#00ffff" #endif /datum/controller/subsystem/shuttle/fire() for(var/thing in mobile) if(!thing) mobile.Remove(thing) continue var/obj/docking_port/mobile/P = thing P.check() var/changed_transit = FALSE for(var/thing in transit) var/obj/docking_port/stationary/transit/T = thing if(!T.owner) qdel(T, force=TRUE) changed_transit = TRUE // This next one removes transit docks/zones that aren't // immediately being used. This will mean that the zone creation // code will be running a lot. var/obj/docking_port/mobile/owner = T.owner if(owner) var/idle = owner.mode == SHUTTLE_IDLE var/not_centcom_evac = owner.launch_status == NOLAUNCH var/not_in_use = (!T.get_docked()) if(idle && not_centcom_evac && not_in_use) qdel(T, force=TRUE) changed_transit = TRUE if(clear_transit) transit_requesters.Cut() for(var/i in transit) qdel(i, force=TRUE) setup_transit_zone() clear_transit = FALSE changed_transit = TRUE #ifdef HIGHLIGHT_DYNAMIC_TRANSIT if(changed_transit) color_space() #endif CheckAutoEvac() while(transit_requesters.len) var/requester = popleft(transit_requesters) var/success = generate_transit_dock(requester) if(!success) // BACK OF THE QUEUE transit_request_failures[requester]++ if(transit_request_failures[requester] < MAX_TRANSIT_REQUEST_RETRIES) transit_requesters += requester else var/obj/docking_port/mobile/M = requester M.transit_failure() if(MC_TICK_CHECK) break /datum/controller/subsystem/shuttle/proc/CheckAutoEvac() if(emergencyNoEscape || emergencyNoRecall || !emergency) return var/threshold = CONFIG_GET(number/emergency_shuttle_autocall_threshold) if(!threshold) return var/alive = 0 for(var/I in GLOB.player_list) var/mob/M = I if(M.stat != DEAD) ++alive var/total = GLOB.joined_player_list.len if(alive / total <= threshold) var/msg = "Automatically dispatching shuttle due to crew death." message_admins(msg) log_game("[msg] Alive: [alive], Roundstart: [total], Threshold: [threshold]") emergencyNoRecall = TRUE priority_announce("Catastrophic casualties detected: crisis shuttle protocols activated - jamming recall signals across all frequencies.") if(emergency.timeLeft(1) > emergencyCallTime * 0.4) emergency.request(null, set_coefficient = 0.4) /datum/controller/subsystem/shuttle/proc/getShuttle(id) for(var/obj/docking_port/mobile/M in mobile) if(M.id == id) return M WARNING("couldn't find shuttle with id: [id]") /datum/controller/subsystem/shuttle/proc/getDock(id) for(var/obj/docking_port/stationary/S in stationary) if(S.id == id) return S WARNING("couldn't find dock with id: [id]") /datum/controller/subsystem/shuttle/proc/requestEvac(mob/user, call_reason) if(!emergency) WARNING("requestEvac(): There is no emergency shuttle, but the \ shuttle was called. Using the backup shuttle instead.") if(!backup_shuttle) throw EXCEPTION("requestEvac(): There is no emergency shuttle, \ or backup shuttle! The game will be unresolvable. This is \ possibly a mapping error, more likely a bug with the shuttle \ manipulation system, or badminry. It is possible to manually \ resolve this problem by loading an emergency shuttle template \ manually, and then calling register() on the mobile docking port. \ Good luck.") return emergency = backup_shuttle var/srd = CONFIG_GET(number/shuttle_refuel_delay) if(world.time - SSticker.round_start_time < srd) to_chat(user, "The emergency shuttle is refueling. Please wait [DisplayTimeText(srd - (world.time - SSticker.round_start_time))] before trying again.") return switch(emergency.mode) if(SHUTTLE_RECALL) to_chat(user, "The emergency shuttle may not be called while returning to CentCom.") return if(SHUTTLE_CALL) to_chat(user, "The emergency shuttle is already on its way.") return if(SHUTTLE_DOCKED) to_chat(user, "The emergency shuttle is already here.") return if(SHUTTLE_IGNITING) to_chat(user, "The emergency shuttle is firing its engines to leave.") return if(SHUTTLE_ESCAPE) to_chat(user, "The emergency shuttle is moving away to a safe distance.") return if(SHUTTLE_STRANDED) to_chat(user, "The emergency shuttle has been disabled by CentCom.") return call_reason = trim(html_encode(call_reason)) if(length(call_reason) < CALL_SHUTTLE_REASON_LENGTH && seclevel2num(get_security_level()) > SEC_LEVEL_GREEN) to_chat(user, "You must provide a reason.") return var/area/signal_origin = get_area(user) var/emergency_reason = "\nNature of emergency:\n\n[call_reason]" var/security_num = seclevel2num(get_security_level()) switch(security_num) if(SEC_LEVEL_RED,SEC_LEVEL_DELTA) emergency.request(null, signal_origin, html_decode(emergency_reason), 1) //There is a serious threat we gotta move no time to give them five minutes. else emergency.request(null, signal_origin, html_decode(emergency_reason), 0) log_game("[key_name(user)] has called the shuttle.") if(call_reason) SSblackbox.add_details("shuttle_reason", call_reason) log_game("Shuttle call reason: [call_reason]") message_admins("[key_name_admin(user)] has called the shuttle. (TRIGGER CENTCOM RECALL)") /datum/controller/subsystem/shuttle/proc/centcom_recall(old_timer, admiral_message) if(emergency.mode != SHUTTLE_CALL || emergency.timer != old_timer) return emergency.cancel() if(!admiral_message) admiral_message = pick(GLOB.admiral_messages) var/intercepttext = "NanoTrasen Update: Request For Shuttle.
\ To whom it may concern:

\ We have taken note of the situation upon [station_name()] and have come to the \ conclusion that it does not warrant the abandonment of the station.
\ If you do not agree with our opinion we suggest that you open a direct \ line with us and explain the nature of your crisis.

\ This message has been automatically generated based upon readings from long \ range diagnostic tools. To assure the quality of your request every finalized report \ is reviewed by an on-call rear admiral.
\ Rear Admiral's Notes: \ [admiral_message]" print_command_report(intercepttext, announce = TRUE) // Called when an emergency shuttle mobile docking port is // destroyed, which will only happen with admin intervention /datum/controller/subsystem/shuttle/proc/emergencyDeregister() // When a new emergency shuttle is created, it will override the // backup shuttle. src.emergency = src.backup_shuttle /datum/controller/subsystem/shuttle/proc/cancelEvac(mob/user) if(canRecall()) emergency.cancel(get_area(user)) log_game("[key_name(user)] has recalled the shuttle.") message_admins("[key_name_admin(user)] has recalled the shuttle.") return 1 /datum/controller/subsystem/shuttle/proc/canRecall() if(!emergency || emergency.mode != SHUTTLE_CALL) return if(SSticker.mode.name == "meteor") return var/security_num = seclevel2num(get_security_level()) switch(security_num) if(SEC_LEVEL_GREEN) if(emergency.timeLeft(1) < emergencyCallTime) return if(SEC_LEVEL_BLUE) if(emergency.timeLeft(1) < emergencyCallTime * 0.5) return else if(emergency.timeLeft(1) < emergencyCallTime * 0.25) return return 1 /datum/controller/subsystem/shuttle/proc/autoEvac() var/callShuttle = 1 for(var/thing in GLOB.shuttle_caller_list) if(isAI(thing)) var/mob/living/silicon/ai/AI = thing if(AI.deployed_shell && !AI.deployed_shell.client) continue if(AI.stat || !AI.client) continue else if(istype(thing, /obj/machinery/computer/communications)) var/obj/machinery/computer/communications/C = thing if(C.stat & BROKEN) continue var/turf/T = get_turf(thing) if(T && (T.z in GLOB.station_z_levels)) callShuttle = 0 break if(callShuttle) if(EMERGENCY_IDLE_OR_RECALLED) emergency.request(null, set_coefficient = 2.5) log_game("There is no means of calling the shuttle anymore. Shuttle automatically called.") message_admins("All the communications consoles were destroyed and all AIs are inactive. Shuttle called.") /datum/controller/subsystem/shuttle/proc/registerHostileEnvironment(datum/bad) hostileEnvironments[bad] = TRUE checkHostileEnvironment() /datum/controller/subsystem/shuttle/proc/clearHostileEnvironment(datum/bad) hostileEnvironments -= bad checkHostileEnvironment() /datum/controller/subsystem/shuttle/proc/checkHostileEnvironment() for(var/datum/d in hostileEnvironments) if(!istype(d) || QDELETED(d)) hostileEnvironments -= d emergencyNoEscape = hostileEnvironments.len if(emergencyNoEscape && (emergency.mode == SHUTTLE_IGNITING)) emergency.mode = SHUTTLE_STRANDED emergency.timer = null emergency.sound_played = FALSE priority_announce("Hostile environment detected. \ Departure has been postponed indefinitely pending \ conflict resolution.", null, 'sound/misc/notice1.ogg', "Priority") if(!emergencyNoEscape && (emergency.mode == SHUTTLE_STRANDED)) emergency.mode = SHUTTLE_DOCKED emergency.setTimer(emergencyDockTime) priority_announce("Hostile environment resolved. \ You have 3 minutes to board the Emergency Shuttle.", null, 'sound/AI/shuttledock.ogg', "Priority") //try to move/request to dockHome if possible, otherwise dockAway. Mainly used for admin buttons /datum/controller/subsystem/shuttle/proc/toggleShuttle(shuttleId, dockHome, dockAway, timed) var/obj/docking_port/mobile/M = getShuttle(shuttleId) if(!M) return 1 var/obj/docking_port/stationary/dockedAt = M.get_docked() var/destination = dockHome if(dockedAt && dockedAt.id == dockHome) destination = dockAway if(timed) if(M.request(getDock(destination))) return 2 else if(M.dock(getDock(destination)) != DOCKING_SUCCESS) return 2 return 0 //dock successful /datum/controller/subsystem/shuttle/proc/moveShuttle(shuttleId, dockId, timed) var/obj/docking_port/mobile/M = getShuttle(shuttleId) var/obj/docking_port/stationary/D = getDock(dockId) if(!M) return 1 if(timed) if(M.request(D)) return 2 else if(M.dock(D) != DOCKING_SUCCESS) return 2 return 0 //dock successful /datum/controller/subsystem/shuttle/proc/request_transit_dock(obj/docking_port/mobile/M) if(!istype(M)) throw EXCEPTION("[M] is not a mobile docking port") if(M.assigned_transit) return else if(!(M in transit_requesters)) transit_requesters += M /datum/controller/subsystem/shuttle/proc/autoEnd() if(world.time > auto_call && EMERGENCY_IDLE_OR_RECALLED) //3 hours SSshuttle.emergency.request(null, 1.5) priority_announce("The shift has come to an end and the shuttle called.") log_game("Round time limit reached. Shuttle has been auto-called.") message_admins("Round time limit reached. Shuttle called.") /datum/controller/subsystem/shuttle/proc/generate_transit_dock(obj/docking_port/mobile/M) // First, determine the size of the needed zone // Because of shuttle rotation, the "width" of the shuttle is not // always x. var/travel_dir = M.preferred_direction // Remember, the direction is the direction we appear to be // coming from var/dock_angle = dir2angle(M.preferred_direction) + dir2angle(M.port_direction) + 180 var/dock_dir = angle2dir(dock_angle) var/transit_width = SHUTTLE_TRANSIT_BORDER * 2 var/transit_height = SHUTTLE_TRANSIT_BORDER * 2 // Shuttles travelling on their side have their dimensions swapped // from our perspective switch(dock_dir) if(NORTH, SOUTH) transit_width += M.width transit_height += M.height if(EAST, WEST) transit_width += M.height transit_height += M.width /* to_chat(world, "The attempted transit dock will be [transit_width] width, and \) [transit_height] in height. The travel dir is [travel_dir]." */ // Then find a place to put the zone var/list/proposed_zone base: for(var/i in transit_turfs) CHECK_TICK var/turf/topleft = i if(!(topleft.flags_1 & UNUSED_TRANSIT_TURF_1)) continue var/turf/bottomright = locate(topleft.x + transit_width, topleft.y + transit_height, topleft.z) if(!bottomright) continue if(!(bottomright.flags_1 & UNUSED_TRANSIT_TURF_1)) continue proposed_zone = block(topleft, bottomright) if(!proposed_zone) continue for(var/j in proposed_zone) var/turf/T = j if(!T) continue base if(!(T.flags_1 & UNUSED_TRANSIT_TURF_1)) continue base break base if((!proposed_zone) || (!proposed_zone.len)) return FALSE var/turf/topleft = proposed_zone[1] // Then create a transit docking port in the middle var/coords = M.return_coords(0, 0, dock_dir) /* 0------2 | | | | | x | 3------1 */ var/x0 = coords[1] var/y0 = coords[2] var/x1 = coords[3] var/y1 = coords[4] // Then we want the point closest to -infinity,-infinity var/x2 = min(x0, x1) var/y2 = min(y0, y1) /* var/lowx = topleft.x + SHUTTLE_TRANSIT_BORDER var/lowy = topleft.y + SHUTTLE_TRANSIT_BORDER var/turf/low_point = locate(lowx, lowy, topleft.z) new /obj/effect/landmark/stationary(low_point) to_chat(world, "Starting at the low point, we go [x2],[y2]") */ // Then invert the numbers var/transit_x = topleft.x + SHUTTLE_TRANSIT_BORDER + abs(x2) var/transit_y = topleft.y + SHUTTLE_TRANSIT_BORDER + abs(y2) var/transit_path = /turf/open/space/transit switch(travel_dir) if(NORTH) transit_path = /turf/open/space/transit/north if(SOUTH) transit_path = /turf/open/space/transit/south if(EAST) transit_path = /turf/open/space/transit/east if(WEST) transit_path = /turf/open/space/transit/west var/turf/midpoint = locate(transit_x, transit_y, topleft.z) if(!midpoint) return FALSE var/area/shuttle/transit/A = new() A.parallax_movedir = travel_dir A.contents = proposed_zone var/obj/docking_port/stationary/transit/new_transit_dock = new(midpoint) new_transit_dock.assigned_turfs = proposed_zone new_transit_dock.name = "Transit for [M.id]/[M.name]" new_transit_dock.turf_type = transit_path new_transit_dock.owner = M new_transit_dock.assigned_area = A // Add 180, because ports point inwards, rather than outwards new_transit_dock.setDir(angle2dir(dock_angle)) for(var/i in new_transit_dock.assigned_turfs) var/turf/T = i T.ChangeTurf(transit_path) T.flags_1 &= ~(UNUSED_TRANSIT_TURF_1) M.assigned_transit = new_transit_dock return TRUE /datum/controller/subsystem/shuttle/proc/initial_move() for(var/obj/docking_port/mobile/M in mobile) if(!M.roundstart_move) continue M.dockRoundstart() M.roundstart_move = FALSE CHECK_TICK /datum/controller/subsystem/shuttle/Recover() if (istype(SSshuttle.mobile)) mobile = SSshuttle.mobile if (istype(SSshuttle.stationary)) stationary = SSshuttle.stationary if (istype(SSshuttle.transit)) transit = SSshuttle.transit if (istype(SSshuttle.transit_turfs)) transit_turfs = SSshuttle.transit_turfs if (istype(SSshuttle.transit_requesters)) transit_requesters = SSshuttle.transit_requesters if (istype(SSshuttle.transit_request_failures)) transit_request_failures = SSshuttle.transit_request_failures if (istype(SSshuttle.emergency)) emergency = SSshuttle.emergency if (istype(SSshuttle.arrivals)) arrivals = SSshuttle.arrivals if (istype(SSshuttle.backup_shuttle)) backup_shuttle = SSshuttle.backup_shuttle if (istype(SSshuttle.emergencyLastCallLoc)) emergencyLastCallLoc = SSshuttle.emergencyLastCallLoc if (istype(SSshuttle.hostileEnvironments)) hostileEnvironments = SSshuttle.hostileEnvironments if (istype(SSshuttle.supply)) supply = SSshuttle.supply if (istype(SSshuttle.discoveredPlants)) discoveredPlants = SSshuttle.discoveredPlants if (istype(SSshuttle.shoppinglist)) shoppinglist = SSshuttle.shoppinglist if (istype(SSshuttle.requestlist)) requestlist = SSshuttle.requestlist if (istype(SSshuttle.orderhistory)) orderhistory = SSshuttle.orderhistory if (istype(SSshuttle.shuttle_loan)) shuttle_loan = SSshuttle.shuttle_loan if (istype(SSshuttle.shuttle_purchase_requirements_met)) shuttle_purchase_requirements_met = SSshuttle.shuttle_purchase_requirements_met if (clear_transit) WARNING("The shuttle subsystem crashed and was recovered while clearing transit.") centcom_message = SSshuttle.centcom_message ordernum = SSshuttle.ordernum points = SSshuttle.points emergencyNoEscape = SSshuttle.emergencyNoEscape emergencyCallAmount = SSshuttle.emergencyCallAmount shuttle_purchased = SSshuttle.shuttle_purchased lockdown = SSshuttle.lockdown /datum/controller/subsystem/shuttle/proc/is_in_shuttle_bounds(atom/A) var/area/current = get_area(A) if(istype(current, /area/shuttle) && !istype(current, /area/shuttle/transit)) return TRUE for(var/obj/docking_port/mobile/M in mobile) if(M.is_in_shuttle_bounds(A)) return TRUE /datum/controller/subsystem/shuttle/proc/get_containing_shuttle(atom/A) for(var/obj/docking_port/mobile/M in mobile) if(M.is_in_shuttle_bounds(A)) return M