#define MAX_TRANSIT_REQUEST_RETRIES 10 /// How many turfs to allow before we stop blocking transit requests #define MAX_TRANSIT_TILE_COUNT (150 ** 2) /// How many turfs to allow before we start freeing up existing "soft reserved" transit docks /// If we're under load we want to allow for cycling, but if not we want to preserve already generated docks for use #define SOFT_TRANSIT_RESERVATION_THRESHOLD (100 ** 2) SUBSYSTEM_DEF(shuttle) name = "Shuttle" wait = 1 SECONDS init_order = INIT_ORDER_SHUTTLE flags = SS_KEEP_TIMING runlevels = RUNLEVEL_SETUP | RUNLEVEL_GAME loading_points = 4.9 SECONDS // Yogs -- loading times /// A list of all the mobile docking ports. var/list/mobile_docking_ports = list() /// A list of all the stationary docking ports. var/list/stationary_docking_ports = list() /// A list of all the beacons that can be docked to. var/list/beacon_list = list() /// A list of all the transit docking ports. var/list/transit_docking_ports = list() /// Now it's only for ID generation in /obj/docking_port/mobile/register() var/list/assoc_mobile = list() /// Now it's only for ID generation in /obj/docking_port/stationary/register() var/list/assoc_stationary = list() /// A list of all the mobile docking ports currently requesting a spot in hyperspace. var/list/transit_requesters = list() /// An associative list of the mobile docking ports that have failed a transit request, with the amount of times they've actually failed that transit request, up to MAX_TRANSIT_REQUEST_RETRIES var/list/transit_request_failures = list() /// How many turfs our shuttles are currently utilizing in reservation space var/transit_utilized = 0 /** * Emergency shuttle stuff */ /// The mobile docking port of the emergency shuttle. var/obj/docking_port/mobile/emergency/emergency /// The mobile docking port of the arrivals shuttle. var/obj/docking_port/mobile/arrivals/arrivals /// The mobile docking port of the backup emergency shuttle. var/obj/docking_port/mobile/emergency/backup/backup_shuttle /// Time taken for emergency shuttle to reach the station when called (in deciseconds). var/emergency_call_time = 10 MINUTES /// Time taken for emergency shuttle to leave again once it has docked (in deciseconds). var/emergency_dock_time = 3 MINUTES /// Time taken for emergency shuttle to reach a safe distance after leaving station (in deciseconds). var/emergency_escape_time = 2 MINUTES /// Where was the emergency shuttle last called from? var/area/emergency_last_call_loc /// How many times was the escape shuttle called? var/emergencyCallAmount = 0 /// Is the departure of the shuttle currently prevented? FALSE for no, any other number for yes (thanks shuttle code). var/emergency_no_escape = FALSE /// Do we prevent the recall of the shuttle? var/emergency_no_recall = FALSE /// Did admins force-prevent the recall of the shuttle? var/admin_emergency_no_recall = FALSE /// Things blocking escape shuttle from leaving. var/list/hostile_environments = list() /** * Supply shuttle stuff */ /// The current cargo shuttle's mobile docking port. var/obj/docking_port/mobile/supply/supply /// Order number given to next order. var/order_number = 1 /// Number of trade-points we have (basically money). var/points = 5000 /// Remarks from CentCom on how well you checked the last order. var/centcom_message = "" /// Typepaths for unusual plants we've already sent CentCom, associated with their potencies. var/list/discovered_plants = list() /// Things blocking the cargo shuttle from leaving. var/list/trade_blockade = list() /// Is the cargo shuttle currently blocked from leaving? var/supply_blocked = FALSE /// All of the possible supply packs that can be purchased by cargo. var/list/supply_packs = list() /// Queued supplies to be purchased for the chef. var/list/chef_groceries = list() /// Queued supply packs to be purchased. var/list/shopping_list = list() /// Wishlist items made by crew for cargo to purchase at their leisure. var/list/request_list = list() var/list/order_history = list() /// A list of job accesses that are able to purchase any shuttles. var/list/has_purchase_shuttle_access /// All turfs hidden from navigation computers associated with a list containing the image hiding them and the type of the turf they are pretending to be var/list/hidden_shuttle_turfs = list() /// Only the images from the [/datum/controller/subsystem/shuttle/hidden_shuttle_turfs] list. var/list/hidden_shuttle_turf_images = list() /// The current shuttle loan event, if any. var/datum/round_event/shuttle_loan/shuttle_loan /// If the event happens where the crew can purchase shuttle insurance, catastrophe can't run. var/shuttle_insurance = FALSE // If the station has purchased a replacement escape shuttle this round. var/shuttle_purchased = SHUTTLEPURCHASE_PURCHASABLE /// For keeping track of ingame events that would unlock new shuttles, such as defeating a boss or discovering a secret item. var/list/shuttle_purchase_requirements_met = list() // If the traitors have purchased a replacement escape shuttle this round var/emag_shuttle_purchased = FALSE //disallow transit after nuke goes off var/lockdown = FALSE /// The currently selected shuttle map_template in the shuttle manipulator's template viewer. var/datum/map_template/shuttle/selected /// The existing shuttle associated with the selected shuttle map_template. var/obj/docking_port/mobile/existing_shuttle /// The shuttle map_template of the shuttle we want to preview. var/datum/map_template/shuttle/preview_template /// The docking port associated to the preview_template that's currently being previewed. var/obj/docking_port/mobile/preview_shuttle /// The turf reservation for the current previewed shuttle. var/datum/turf_reservation/preview_reservation /// Are we currently in the process of loading a shuttle? Useful to ensure we don't load more than one at once, to avoid weird inconsistencies and possible runtimes. var/shuttle_loading /// Did the supermatter start a cascade event? var/supermatter_cascade = FALSE /datum/controller/subsystem/shuttle/Initialize(timeofday) order_number = 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 setup_shuttles(stationary_docking_ports) has_purchase_shuttle_access = init_has_purchase_shuttle_access() 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!") return SS_INIT_SUCCESS /datum/controller/subsystem/shuttle/proc/setup_shuttles(list/stationary) for(var/obj/docking_port/stationary/port as anything in stationary) port.load_roundstart() CHECK_TICK /datum/controller/subsystem/shuttle/fire() for(var/thing in mobile_docking_ports) if(!thing) mobile_docking_ports.Remove(thing) continue var/obj/docking_port/mobile/port = thing port.check() for(var/thing in transit_docking_ports) var/obj/docking_port/stationary/transit/T = thing if(!T.owner) qdel(T, force=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. // If we're below the soft reservation threshold, don't clear the old space // We're better off holding onto it for now if(transit_utilized < SOFT_TRANSIT_RESERVATION_THRESHOLD) continue 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) CheckAutoEvac() if(!SSmapping.clearing_reserved_turfs) while(transit_requesters.len) var/requester = popleft(transit_requesters) var/success = null // Do not try and generate any transit if we're using more then our max already if(transit_utilized < MAX_TRANSIT_TILE_COUNT) 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(emergency_no_escape || admin_emergency_no_recall || emergency_no_recall || !emergency || !SSticker.HasRoundStarted()) 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(total <= 0) return //no players no autoevac 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]") emergency_no_recall = TRUE priority_announce("Catastrophic casualties detected: crisis shuttle protocols activated - jamming recall signals across all frequencies.") if(emergency.timeLeft(1) > emergency_no_recall * 0.4) emergency.request(null, set_coefficient = 0.4) /datum/controller/subsystem/shuttle/proc/block_recall(lockout_timer) if(isnull(lockout_timer)) CRASH("Emergency shuttle block was called, but missing a value for the lockout duration") emergency_no_recall = TRUE addtimer(CALLBACK(src, PROC_REF(unblock_recall)), lockout_timer) /datum/controller/subsystem/shuttle/proc/unblock_recall() emergency_no_recall = FALSE /datum/controller/subsystem/shuttle/proc/getShuttle(id) for(var/obj/docking_port/mobile/M in mobile_docking_ports) if(M.shuttle_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_docking_ports) if(S.shuttle_id == id) return S WARNING("couldn't find dock with id: [id]") /// Check if we can call the evac shuttle. /// Returns TRUE if we can. Otherwise, returns a string detailing the problem. /datum/controller/subsystem/shuttle/proc/canEvac(mob/user) var/srd = CONFIG_GET(number/shuttle_refuel_delay) if(world.time - SSticker.round_start_time < srd) return "The emergency shuttle is refueling. Please wait [DisplayTimeText(srd - (world.time - SSticker.round_start_time))] before attempting to call." switch(emergency.mode) if(SHUTTLE_RECALL) return "The emergency shuttle may not be called while returning to CentCom." if(SHUTTLE_CALL) return "The emergency shuttle is already on its way." if(SHUTTLE_DOCKED) return "The emergency shuttle is already here." if(SHUTTLE_IGNITING) return "The emergency shuttle is firing its engines to leave." if(SHUTTLE_ESCAPE) return "The emergency shuttle is moving away to a safe distance." if(SHUTTLE_STRANDED) return "The emergency shuttle has been disabled by CentCom." return TRUE /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) CRASH("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.") emergency = backup_shuttle var/pretty_input = replacetext(call_reason, "\n", " ") if(isnotpretty(pretty_input)) to_chat(user, "Your fingers slip. See rule 0.1.") var/log_message = "[key_name(user)] just tripped a pretty filter: '[call_reason]'." message_admins(log_message) log_say(log_message) return var/can_evac_or_fail_reason = SSshuttle.canEvac(user) if(can_evac_or_fail_reason != TRUE) to_chat(user, span_alert("[can_evac_or_fail_reason]")) 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 && SSsecurity_level.current_security_level.require_call_reason) to_chat(user, "You must provide a reason.") return var/area/signal_origin = get_area(user) emergency.request(null, signal_origin, call_reason) var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) if(!frequency) return var/datum/signal/status_signal = new(list("command" = "update")) // Start processing shuttle-mode displays to display the timer frequency.post_signal(src, status_signal) var/area/A = get_area(user) log_game("[key_name(user)] has called the shuttle.") deadchat_broadcast(" has called the shuttle at [span_name("[A.name]")].", span_name("[user.real_name]"), user) if(call_reason) SSblackbox.record_feedback("text", "shuttle_reason", 1, "[call_reason]") log_game("Shuttle call reason: [call_reason]") message_admins("[ADMIN_LOOKUPFLW(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("[ADMIN_LOOKUPFLW(user)] has recalled the shuttle.") deadchat_broadcast(" has recalled the shuttle from [span_name("[get_area_name(user, TRUE)]")].", span_name("[user.real_name]"), user) return 1 /datum/controller/subsystem/shuttle/proc/canRecall() if(!emergency || emergency.mode != SHUTTLE_CALL || admin_emergency_no_recall || emergency_no_recall) return var/security_num = SSsecurity_level.get_current_level_as_number() switch(security_num) if(SEC_LEVEL_GREEN) if(emergency.timeLeft(1) < emergency_call_time) return if(SEC_LEVEL_BLUE) if(emergency.timeLeft(1) < emergency_call_time * 0.5) return else if(emergency.timeLeft(1) < emergency_call_time * 0.25) return return 1 /datum/controller/subsystem/shuttle/proc/autoEvac() if (!SSticker.IsRoundInProgress()) return 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 && is_station_level(T.z)) 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) hostile_environments[bad] = TRUE checkHostileEnvironment() /datum/controller/subsystem/shuttle/proc/clearHostileEnvironment(datum/bad) hostile_environments -= bad checkHostileEnvironment() /datum/controller/subsystem/shuttle/proc/registerTradeBlockade(datum/bad) trade_blockade[bad] = TRUE checkTradeBlockade() /datum/controller/subsystem/shuttle/proc/clearTradeBlockade(datum/bad) trade_blockade -= bad checkTradeBlockade() /datum/controller/subsystem/shuttle/proc/checkTradeBlockade() for(var/datum/d in trade_blockade) if(!istype(d) || QDELETED(d)) trade_blockade -= d supply_blocked = trade_blockade.len if(supply_blocked && (supply.mode == SHUTTLE_IGNITING)) supply.mode = SHUTTLE_STRANDED supply.timer = null //Make all cargo consoles speak up if(!supply_blocked && (supply.mode == SHUTTLE_STRANDED)) supply.mode = SHUTTLE_DOCKED //Make all cargo consoles speak up /datum/controller/subsystem/shuttle/proc/checkHostileEnvironment() for(var/datum/hostile_environment_source in hostile_environments) if(!istype(hostile_environment_source) || QDELETED(hostile_environment_source)) hostile_environments -= hostile_environment_source emergency_no_escape = hostile_environments.len if(emergency_no_escape && (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(!emergency_no_escape && (emergency.mode == SHUTTLE_STRANDED)) emergency.mode = SHUTTLE_DOCKED emergency.setTimer(emergency_dock_time) priority_announce("Hostile environment resolved. \ You have 3 minutes to board the Emergency Shuttle.", null, ANNOUNCER_SHUTTLEDOCK, "Priority") //try to move/request to dock_home if possible, otherwise dock_away. Mainly used for admin buttons /datum/controller/subsystem/shuttle/proc/toggleShuttle(shuttle_id, dock_home, dock_away, timed) var/obj/docking_port/mobile/shuttle_port = getShuttle(shuttle_id) if(!shuttle_port) return DOCKING_BLOCKED var/obj/docking_port/stationary/docked_at = shuttle_port.get_docked() var/destination = dock_home if(docked_at && docked_at.shuttle_id == dock_home) destination = dock_away if(timed) if(shuttle_port.request(getDock(destination))) return DOCKING_IMMOBILIZED else if(shuttle_port.initiate_docking(getDock(destination)) != DOCKING_SUCCESS) return DOCKING_IMMOBILIZED return DOCKING_SUCCESS //dock successful /** * Moves a shuttle to a new location * * Arguments: * * shuttle_id - The ID of the shuttle (mobile docking port) to move * * dock_id - The ID of the destination (stationary docking port) to move to * * timed - If true, have the shuttle follow normal spool-up, jump, dock process. If false, immediately move to the new location. */ /datum/controller/subsystem/shuttle/proc/moveShuttle(shuttle_id, dock_id, timed) var/obj/docking_port/mobile/shuttle_port = getShuttle(shuttle_id) var/obj/docking_port/stationary/docking_target = getDock(dock_id) if(!shuttle_port) return DOCKING_NULL_SOURCE if(timed) if(shuttle_port.request(docking_target)) return DOCKING_IMMOBILIZED else if(shuttle_port.initiate_docking(docking_target) != DOCKING_SUCCESS) return DOCKING_IMMOBILIZED return DOCKING_SUCCESS //dock successful /datum/controller/subsystem/shuttle/proc/request_transit_dock(obj/docking_port/mobile/M) if(!istype(M)) CRASH("[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/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]." */ 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/datum/turf_reservation/proposal = SSmapping.request_turf_block_reservation( transit_width, transit_height, z_size = 1, //if this is changed the turf uncontain code below has to be updated to support multiple zs reservation_type = /datum/turf_reservation/transit, turf_type_override = transit_path, ) if(!istype(proposal)) return FALSE var/turf/bottomleft = proposal.bottom_left_turfs[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) // Then invert the numbers var/transit_x = bottomleft.x + SHUTTLE_TRANSIT_BORDER + abs(x2) var/transit_y = bottomleft.y + SHUTTLE_TRANSIT_BORDER + abs(y2) var/turf/midpoint = locate(transit_x, transit_y, bottomleft.z) if(!midpoint) qdel(proposal) return FALSE var/area/old_area = midpoint.loc old_area.turfs_to_uncontain += proposal.reserved_turfs var/area/shuttle/transit/new_area = new() new_area.parallax_movedir = travel_dir new_area.contents = proposal.reserved_turfs new_area.contained_turfs = proposal.reserved_turfs var/obj/docking_port/stationary/transit/new_transit_dock = new(midpoint) new_transit_dock.reserved_area = proposal new_transit_dock.name = "Transit for [M.shuttle_id]/[M.name]" new_transit_dock.owner = M new_transit_dock.assigned_area = new_area // Add 180, because ports point inwards, rather than outwards new_transit_dock.setDir(angle2dir(dock_angle)) // Proposals use 2 extra hidden tiles of space, from the cordons that surround them transit_utilized += (proposal.width + 2) * (proposal.height + 2) M.assigned_transit = new_transit_dock RegisterSignal(proposal, COMSIG_QDELETING, PROC_REF(transit_space_clearing)) return new_transit_dock /// Gotta manage our space brother /datum/controller/subsystem/shuttle/proc/transit_space_clearing(datum/turf_reservation/source) SIGNAL_HANDLER transit_utilized -= (source.width + 2) * (source.height + 2) /datum/controller/subsystem/shuttle/Recover() initialized = SSshuttle.initialized if (istype(SSshuttle.mobile_docking_ports)) mobile_docking_ports = SSshuttle.mobile_docking_ports if (istype(SSshuttle.stationary_docking_ports)) stationary_docking_ports = SSshuttle.stationary_docking_ports if (istype(SSshuttle.transit_docking_ports)) transit_docking_ports = SSshuttle.transit_docking_ports 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.emergency_last_call_loc)) emergency_last_call_loc = SSshuttle.emergency_last_call_loc if (istype(SSshuttle.hostile_environments)) hostile_environments = SSshuttle.hostile_environments if (istype(SSshuttle.supply)) supply = SSshuttle.supply if (istype(SSshuttle.discovered_plants)) discovered_plants = SSshuttle.discovered_plants if (istype(SSshuttle.shopping_list)) shopping_list = SSshuttle.shopping_list if (istype(SSshuttle.request_list)) request_list = SSshuttle.request_list if (istype(SSshuttle.order_history)) order_history = SSshuttle.order_history 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 (istype(SSshuttle.shuttle_loan)) shuttle_loan = SSshuttle.shuttle_loan var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) centcom_message = SSshuttle.centcom_message order_number = SSshuttle.order_number points = D.account_balance emergency_no_escape = SSshuttle.emergency_no_escape emergencyCallAmount = SSshuttle.emergencyCallAmount shuttle_purchased = SSshuttle.shuttle_purchased lockdown = SSshuttle.lockdown selected = SSshuttle.selected existing_shuttle = SSshuttle.existing_shuttle preview_shuttle = SSshuttle.preview_shuttle preview_template = SSshuttle.preview_template preview_reservation = SSshuttle.preview_reservation /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_docking_ports) if(M.is_in_shuttle_bounds(A)) return TRUE /datum/controller/subsystem/shuttle/proc/get_containing_shuttle(atom/A) var/list/mobile_cache = mobile_docking_ports for(var/i in 1 to mobile_cache.len) var/obj/docking_port/port = mobile_cache[i] if(port.is_in_shuttle_bounds(A)) return port /datum/controller/subsystem/shuttle/proc/get_containing_dock(atom/A) . = list() var/list/stationary_docking_ports_cache = stationary_docking_ports for(var/i in 1 to stationary_docking_ports_cache.len) var/obj/docking_port/port = stationary_docking_ports_cache[i] if(port.is_in_shuttle_bounds(A)) . += port /datum/controller/subsystem/shuttle/proc/get_dock_overlap(x0, y0, x1, y1, z) . = list() var/list/stationary_docking_ports_cache = stationary_docking_ports for(var/i in 1 to stationary_docking_ports_cache.len) var/obj/docking_port/port = stationary_docking_ports_cache[i] if(!port || port.z != z) continue var/list/bounds = port.return_coords() var/list/overlap = get_overlap(x0, y0, x1, y1, bounds[1], bounds[2], bounds[3], bounds[4]) var/list/xs = overlap[1] var/list/ys = overlap[2] if(xs.len && ys.len) .[port] = overlap /datum/controller/subsystem/shuttle/proc/update_hidden_docking_ports(list/remove_turfs, list/add_turfs) var/list/remove_images = list() var/list/add_images = list() if(remove_turfs) for(var/T in remove_turfs) var/list/L = hidden_shuttle_turfs[T] if(L) remove_images += L[1] hidden_shuttle_turfs -= remove_turfs if(add_turfs) for(var/V in add_turfs) var/turf/T = V var/image/I if(remove_images.len) //we can just reuse any images we are about to delete instead of making new ones I = remove_images[1] remove_images.Cut(1, 2) I.loc = T else I = image(loc = T) add_images += I I.appearance = T.appearance I.override = TRUE hidden_shuttle_turfs[T] = list(I, T.type) hidden_shuttle_turf_images -= remove_images hidden_shuttle_turf_images += add_images for(var/obj/machinery/computer/camera_advanced/shuttle_docker/docking_computer \ as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/computer/camera_advanced/shuttle_docker)) docking_computer.update_hidden_docking_ports(remove_images, add_images) QDEL_LIST(remove_images) /** * Loads a shuttle template and sends it to a given destination port, optionally replacing the existing shuttle * * Arguments: * * loading_template - The shuttle template to load * * destination_port - The station docking port to send the shuttle to once loaded * * replace - Whether to replace the shuttle or create a new one */ /datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port, replace = FALSE) // Check for an existing preview if(preview_shuttle && (loading_template != preview_template)) preview_shuttle.jumpToNullSpace() preview_shuttle = null preview_template = null QDEL_NULL(preview_reservation) if(!preview_shuttle) load_template(loading_template) preview_template = loading_template // get the existing shuttle information, if any var/timer = 0 var/mode = SHUTTLE_IDLE var/obj/docking_port/stationary/dest_dock if(istype(destination_port)) dest_dock = destination_port else if(existing_shuttle && replace) timer = existing_shuttle.timer mode = existing_shuttle.mode dest_dock = existing_shuttle.get_docked() if(!dest_dock) dest_dock = generate_transit_dock(preview_shuttle) if(!dest_dock) CRASH("No dock found for preview shuttle ([preview_template.name]), aborting.") var/result = preview_shuttle.canDock(dest_dock) // truthy value means that it cannot dock for some reason // but we can ignore the someone else docked error because we'll // be moving into their place shortly if((result != SHUTTLE_CAN_DOCK) && (result != SHUTTLE_SOMEONE_ELSE_DOCKED)) CRASH("Template shuttle [preview_shuttle] cannot dock at [dest_dock] ([result]).") if(existing_shuttle && replace) existing_shuttle.jumpToNullSpace() preview_shuttle.register(replace) var/list/force_memory = preview_shuttle.movement_force preview_shuttle.movement_force = list("KNOCKDOWN" = 0, "THROW" = 0) preview_shuttle.mode = SHUTTLE_PREARRIVAL//No idle shuttle moving. Transit dock get removed if shuttle moves too long. preview_shuttle.initiate_docking(dest_dock) preview_shuttle.movement_force = force_memory . = preview_shuttle // Shuttle state involves a mode and a timer based on world.time, so // plugging the existing shuttles old values in works fine. preview_shuttle.timer = timer preview_shuttle.mode = mode preview_shuttle.postregister(replace) // TODO indicate to the user that success happened, rather than just // blanking the modification tab preview_shuttle = null preview_template = null existing_shuttle = null selected = null QDEL_NULL(preview_reservation) /** * Loads a shuttle template into the transit Z level, usually referred to elsewhere in the code as a shuttle preview. * Does not register the shuttle so it can't be used yet, that's handled in action_load() * * Arguments: * * loading_template - The shuttle template to load */ /datum/controller/subsystem/shuttle/proc/load_template(datum/map_template/shuttle/loading_template) . = FALSE // Load shuttle template to a fresh block reservation. preview_reservation = SSmapping.request_turf_block_reservation( loading_template.width, loading_template.height, 1, reservation_type = /datum/turf_reservation/transit, ) if(!preview_reservation) CRASH("failed to reserve an area for shuttle template loading") var/turf/bottom_left = preview_reservation.bottom_left_turfs[1] loading_template.load(bottom_left, centered = FALSE, register = FALSE) var/affected = loading_template.get_affected_turfs(bottom_left, centered=FALSE) var/found = 0 // Search the turfs for docking ports // - We need to find the mobile docking port because that is the heart of // the shuttle. // - We need to check that no additional ports have slipped in from the // template, because that causes unintended behaviour. for(var/affected_turfs in affected) for(var/obj/docking_port/port in affected_turfs) if(istype(port, /obj/docking_port/mobile)) found++ if(found > 1) qdel(port, force=TRUE) log_mapping("Shuttle Template [loading_template.mappath] has multiple mobile docking ports.") else preview_shuttle = port if(istype(port, /obj/docking_port/stationary)) log_mapping("Shuttle Template [loading_template.mappath] has a stationary docking port.") if(!found) var/msg = "load_template(): Shuttle Template [loading_template.mappath] has no mobile docking port. Aborting import." for(var/affected_turfs in affected) var/turf/T0 = affected_turfs T0.empty() message_admins(msg) WARNING(msg) return //Everything fine loading_template.post_load(preview_shuttle) return TRUE /** * Removes the preview_shuttle from the transit Z-level */ /datum/controller/subsystem/shuttle/proc/unload_preview() if(preview_shuttle) preview_shuttle.jumpToNullSpace() preview_shuttle = null if(preview_reservation) QDEL_NULL(preview_reservation) /datum/controller/subsystem/shuttle/ui_state(mob/user) return GLOB.admin_state /datum/controller/subsystem/shuttle/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "ShuttleManipulator") ui.open() /datum/controller/subsystem/shuttle/ui_data(mob/user) var/list/data = list() data["tabs"] = list("Status", "Templates", "Modification") // Templates panel data["templates"] = list() var/list/templates = data["templates"] data["templates_tabs"] = list() data["selected"] = list() for(var/shuttle_id in SSmapping.shuttle_templates) var/datum/map_template/shuttle/S = SSmapping.shuttle_templates[shuttle_id] if(!templates[S.port_id]) data["templates_tabs"] += S.port_id templates[S.port_id] = list( "port_id" = S.port_id, "templates" = list()) var/list/L = list() L["name"] = S.name L["shuttle_id"] = S.shuttle_id L["port_id"] = S.port_id L["description"] = S.description L["admin_notes"] = S.admin_notes if(selected == S) data["selected"] = L templates[S.port_id]["templates"] += list(L) data["templates_tabs"] = sortList(data["templates_tabs"]) data["existing_shuttle"] = null // Status panel data["shuttles"] = list() for(var/i in mobile_docking_ports) var/obj/docking_port/mobile/M = i var/timeleft = M.timeLeft(1) var/list/L = list() L["name"] = M.name L["id"] = M.shuttle_id L["timer"] = M.timer L["timeleft"] = M.getTimerStr() if (timeleft > 1 HOURS) L["timeleft"] = "Infinity" L["can_fast_travel"] = M.timer && timeleft >= 50 L["can_fly"] = TRUE if(istype(M, /obj/docking_port/mobile/emergency)) L["can_fly"] = FALSE else if(!M.destination) L["can_fast_travel"] = FALSE if (M.mode != SHUTTLE_IDLE) L["mode"] = capitalize(M.mode) L["status"] = M.getDbgStatusText() if(M == existing_shuttle) data["existing_shuttle"] = L data["shuttles"] += list(L) return data /datum/controller/subsystem/shuttle/ui_act(action, params) if(..()) return var/mob/user = usr // Preload some common parameters var/shuttle_id = params["shuttle_id"] var/datum/map_template/shuttle/S = SSmapping.shuttle_templates[shuttle_id] switch(action) if("select_template") if(S) existing_shuttle = getShuttle(S.port_id) selected = S . = TRUE if("jump_to") if(params["type"] == "mobile") for(var/i in mobile_docking_ports) var/obj/docking_port/mobile/M = i if(M.shuttle_id == params["id"]) user.forceMove(get_turf(M)) . = TRUE break if("fly") for(var/i in mobile_docking_ports) var/obj/docking_port/mobile/M = i if(M.shuttle_id == params["id"]) . = TRUE M.admin_fly_shuttle(user) break if("fast_travel") for(var/i in mobile_docking_ports) var/obj/docking_port/mobile/M = i if(M.shuttle_id == params["id"] && M.timer && M.timeLeft(1) >= 50) M.setTimer(50) . = TRUE message_admins("[key_name_admin(usr)] fast travelled [M]") log_admin("[key_name(usr)] fast travelled [M]") SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[M.name]") break if("preview") if(S) . = TRUE unload_preview() load_template(S) if(preview_shuttle) preview_template = S user.forceMove(get_turf(preview_shuttle)) if("load") if(existing_shuttle == backup_shuttle) // TODO make the load button disabled WARNING("The shuttle that the selected shuttle will replace \ is the backup shuttle. Backup shuttle is required to be \ intact for round sanity.") else if(S) . = TRUE // If successful, returns the mobile docking port var/obj/docking_port/mobile/mdp = action_load(S, replace = TRUE) if(mdp) user.forceMove(get_turf(mdp)) message_admins("[key_name_admin(usr)] loaded [mdp] with the shuttle manipulator.") log_admin("[key_name(usr)] loaded [mdp] with the shuttle manipulator.") SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[mdp.name]") /datum/controller/subsystem/shuttle/proc/init_has_purchase_shuttle_access() var/list/has_purchase_shuttle_access = list() for (var/shuttle_id in SSmapping.shuttle_templates) var/datum/map_template/shuttle/shuttle_template = SSmapping.shuttle_templates[shuttle_id] if (!isnull(shuttle_template.who_can_purchase)) has_purchase_shuttle_access |= shuttle_template.who_can_purchase return has_purchase_shuttle_access #undef MAX_TRANSIT_REQUEST_RETRIES #undef MAX_TRANSIT_TILE_COUNT #undef SOFT_TRANSIT_RESERVATION_THRESHOLD