diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm index 759121e3b8dd8..12f15ab1e68dc 100644 --- a/code/__DEFINES/shuttles.dm +++ b/code/__DEFINES/shuttles.dm @@ -62,6 +62,7 @@ #define ENGINE_COEFF_MIN 0.5 #define ENGINE_COEFF_MAX 2 #define ENGINE_DEFAULT_MAXSPEED_ENGINES 5 +#define ENGINE_START_TIME 100 // Alert level related #define ALERT_COEFF_AUTOEVAC_NORMAL 2.5 @@ -120,3 +121,12 @@ #define SHUTTLE_EVENT_MISS_SHUTTLE 1 << 0 ///spawned stuff should hit the shuttle #define SHUTTLE_EVENT_HIT_SHUTTLE 1 << 1 + +// Hijack stages + +#define HIJACK_NOT_BEGUN 0 +#define HIJACK_STAGE_1 1 +#define HIJACK_STAGE_2 2 +#define HIJACK_STAGE_3 3 +#define HIJACK_STAGE_4 4 +#define HIJACK_COMPLETED 5 diff --git a/code/__HELPERS/shuttle.dm b/code/__HELPERS/shuttle.dm new file mode 100644 index 0000000000000..4f866e22384dd --- /dev/null +++ b/code/__HELPERS/shuttle.dm @@ -0,0 +1,52 @@ +/// Helper proc that tests to ensure all whiteship templates can spawn at their docking port, and logs their sizes +/// This should be a unit test, but too much of our other code breaks during shuttle movement, so not yet, not yet. +/proc/test_whiteship_sizes() + var/obj/docking_port/stationary/port_type = /obj/docking_port/stationary/picked/whiteship + var/datum/turf_reservation/docking_yard = SSmapping.request_turf_block_reservation( + initial(port_type.width), + initial(port_type.height), + 1, + ) + var/turf/bottom_left = docking_yard.bottom_left_turfs[1] + var/turf/spawnpoint = locate( + bottom_left.x + initial(port_type.dwidth), + bottom_left.y + initial(port_type.dheight), + bottom_left.z, + ) + + var/obj/docking_port/stationary/picked/whiteship/port = new(spawnpoint) + var/list/ids = port.shuttlekeys + var/height = 0 + var/width = 0 + var/dheight = 0 + var/dwidth = 0 + var/delta_height = 0 + var/delta_width = 0 + for(var/id in ids) + var/datum/map_template/shuttle/our_template = SSmapping.shuttle_templates[id] + // We do a standard load here so any errors will properly runtimes + var/obj/docking_port/mobile/ship = SSshuttle.action_load(our_template, port) + if(ship) + ship.jumpToNullSpace() + ship = null + // Yes this is very hacky, but we need to both allow loading a template that's too big to be an error state + // And actually get the sizing information from every shuttle + SSshuttle.load_template(our_template) + var/obj/docking_port/mobile/theoretical_ship = SSshuttle.preview_shuttle + if(theoretical_ship) + height = max(theoretical_ship.height, height) + width = max(theoretical_ship.width, width) + dheight = max(theoretical_ship.dheight, dheight) + dwidth = max(theoretical_ship.dwidth, dwidth) + delta_height = max(theoretical_ship.height - theoretical_ship.dheight, delta_height) + delta_width = max(theoretical_ship.width - theoretical_ship.dwidth, delta_width) + theoretical_ship.jumpToNullSpace() + qdel(port, TRUE) + log_world("Whiteship sizing information. Use this to set the docking port, and the map size\n\ + Max Height: [height] \n\ + Max Width: [width] \n\ + Max DHeight: [dheight] \n\ + Max DWidth: [dwidth] \n\ + The following are the safest bet for map sizing. Anything smaller then this could in the worst case not fit in the docking port\n\ + Max Combined Width: [height + dheight] \n\ + Max Combinded Height [width + dwidth]") diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm deleted file mode 100644 index 2753dfc65f4df..0000000000000 --- a/code/modules/shuttle/emergency.dm +++ /dev/null @@ -1,888 +0,0 @@ -#define TIME_LEFT (SSshuttle.emergency.timeLeft()) -#define ENGINES_START_TIME 100 -#define ENGINES_STARTED (SSshuttle.emergency.mode == SHUTTLE_IGNITING) -#define IS_DOCKED (SSshuttle.emergency.mode == SHUTTLE_DOCKED || (ENGINES_STARTED)) -#define SHUTTLE_CONSOLE_ACTION_DELAY (5 SECONDS) - -#define NOT_BEGUN 0 -#define STAGE_1 1 -#define STAGE_2 2 -#define STAGE_3 3 -#define STAGE_4 4 -#define HIJACKED 5 - -/obj/machinery/computer/emergency_shuttle - name = "emergency shuttle console" - desc = "For shuttle control." - icon_screen = "shuttle" - icon_keyboard = "tech_key" - resistance_flags = INDESTRUCTIBLE - var/auth_need = 3 - var/list/authorized = list() - var/list/acted_recently = list() - var/hijack_last_stage_increase = 0 SECONDS - var/hijack_stage_time = 5 SECONDS - var/hijack_stage_cooldown = 5 SECONDS - var/hijack_flight_time_increase = 30 SECONDS - var/hijack_completion_flight_time_set = 10 SECONDS //How long in deciseconds to set shuttle's timer after hijack is done. - var/hijack_hacking = FALSE - var/hijack_announce = TRUE - -/obj/machinery/computer/emergency_shuttle/examine(mob/user) - . = ..() - if(hijack_announce) - . += span_danger("Security systems present on console. Any unauthorized tampering will result in an emergency announcement.") - if(user?.mind?.get_hijack_speed()) - . += span_danger("Alt click on this to attempt to hijack the shuttle. This will take multiple tries (current: stage [SSshuttle.emergency.hijack_status]/[HIJACKED]).") - . += span_notice("It will take you [(hijack_stage_time * user.mind.get_hijack_speed()) / 10] seconds to reprogram a stage of the shuttle's navigational firmware, and the console will undergo automated timed lockout for [hijack_stage_cooldown/10] seconds after each stage.") - if(hijack_announce) - . += span_warning("It is probably best to fortify your position as to be uninterrupted during the attempt, given the automatic announcements..") - -/obj/machinery/computer/emergency_shuttle/attackby(obj/item/I, mob/user,params) - if(isidcard(I)) - say("Please equip your ID card into your ID slot to authenticate.") - . = ..() - -/obj/machinery/computer/emergency_shuttle/ui_state(mob/user) - return GLOB.human_adjacent_state - -/obj/machinery/computer/emergency_shuttle/ui_interact(mob/user, datum/tgui/ui) - . = ..() - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "EmergencyShuttleConsole", name) - ui.open() - -/obj/machinery/computer/emergency_shuttle/ui_data(user) - var/list/data = list() - - data["timer_str"] = SSshuttle.emergency.getTimerStr() - data["engines_started"] = ENGINES_STARTED - data["authorizations_remaining"] = max((auth_need - authorized.len), 0) - var/list/A = list() - for(var/i in authorized) - var/obj/item/card/id/ID = i - var/name = ID.registered_name - var/job = ID.assignment - - if(obj_flags & EMAGGED) - name = Gibberish(name) - job = Gibberish(job) - A += list(list("name" = name, "job" = job)) - data["authorizations"] = A - - data["enabled"] = (IS_DOCKED && !ENGINES_STARTED) && !(user in acted_recently) - data["emagged"] = obj_flags & EMAGGED ? 1 : 0 - return data - -/obj/machinery/computer/emergency_shuttle/ui_act(action, params, datum/tgui/ui) - . = ..() - if(.) - return - if(ENGINES_STARTED) // past the point of no return - return - if(!IS_DOCKED) // shuttle computer only has uses when onstation - return - if(SSshuttle.emergency.mode == SHUTTLE_DISABLED) // admins have disabled the shuttle. - return - if(!isliving(usr)) - return - - var/area/my_area = get_area(src) - if(!istype(my_area, /area/shuttle/escape)) - say("Error - Network connectivity: Console has lost connection to the shuttle.") - return - - var/mob/living/user = usr - . = FALSE - - var/obj/item/card/id/ID = user.get_idcard(TRUE) - - if(!ID) - to_chat(user, span_warning("You don't have an ID.")) - return - - if(!(ACCESS_COMMAND in ID.access)) - to_chat(user, span_warning("The access level of your card is not high enough.")) - return - - if (user in acted_recently) - return - - var/old_len = authorized.len - addtimer(CALLBACK(src, PROC_REF(clear_recent_action), user), SHUTTLE_CONSOLE_ACTION_DELAY) - - switch(action) - if("authorize") - . = authorize(user) - - if("repeal") - authorized -= ID - - if("abort") - if(authorized.len) - // Abort. The action for when heads are fighting over whether - // to launch early. - authorized.Cut() - . = TRUE - - if((old_len != authorized.len) && !ENGINES_STARTED) - var/alert = (authorized.len > old_len) - var/repeal = (authorized.len < old_len) - var/remaining = max(0, auth_need - authorized.len) - if(authorized.len && remaining) - minor_announce("[remaining] authorizations needed until shuttle is launched early", null, alert) - if(repeal) - minor_announce("Early launch authorization revoked, [remaining] authorizations needed") - - acted_recently += user - SStgui.update_user_uis(user, src) - -/obj/machinery/computer/emergency_shuttle/proc/authorize(mob/living/user, source) - var/obj/item/card/id/ID = user.get_idcard(TRUE) - - if(ID in authorized) - return FALSE - for(var/i in authorized) - var/obj/item/card/id/other = i - if(other.registered_name == ID.registered_name) - return FALSE // No using IDs with the same name - - authorized += ID - - message_admins("[ADMIN_LOOKUPFLW(user)] has authorized early shuttle launch") - log_shuttle("[key_name(user)] has authorized early shuttle launch in [COORD(src)]") - // Now check if we're on our way - . = TRUE - process(SSMACHINES_DT) - -/obj/machinery/computer/emergency_shuttle/proc/clear_recent_action(mob/user) - acted_recently -= user - if (!QDELETED(user)) - SStgui.update_user_uis(user, src) - -/obj/machinery/computer/emergency_shuttle/process() - // Launch check is in process in case auth_need changes for some reason - // probably external. - . = FALSE - if(!SSshuttle.emergency) - return - - if(SSshuttle.emergency.mode == SHUTTLE_STRANDED) - authorized.Cut() - obj_flags &= ~(EMAGGED) - - if(ENGINES_STARTED || (!IS_DOCKED)) - return . - - // Check to see if we've reached criteria for early launch - if((authorized.len >= auth_need) || (obj_flags & EMAGGED)) - // shuttle timers use 1/10th seconds internally - SSshuttle.emergency.setTimer(ENGINES_START_TIME) - var/system_error = obj_flags & EMAGGED ? "SYSTEM ERROR:" : null - minor_announce("The emergency shuttle will launch in \ - [TIME_LEFT] seconds", system_error, alert=TRUE) - . = TRUE - -/obj/machinery/computer/emergency_shuttle/proc/increase_hijack_stage() - var/obj/docking_port/mobile/emergency/shuttle = SSshuttle.emergency - // Begin loading this early, prevents a delay when the shuttle goes to land - INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, lazy_load_template), LAZY_TEMPLATE_KEY_NUKIEBASE) - - shuttle.hijack_status++ - if(hijack_announce) - announce_hijack_stage() - hijack_last_stage_increase = world.time - say("Navigational protocol error! Rebooting systems.") - if(shuttle.mode == SHUTTLE_ESCAPE) - if(shuttle.hijack_status == HIJACKED) - shuttle.setTimer(hijack_completion_flight_time_set) - else - shuttle.setTimer(shuttle.timeLeft(1) + hijack_flight_time_increase) //give the guy more time to hijack if it's already in flight. - return shuttle.hijack_status - -/obj/machinery/computer/emergency_shuttle/click_alt(mob/living/user) - if(!isliving(user)) - return NONE - attempt_hijack_stage(user) - return CLICK_ACTION_SUCCESS - -/obj/machinery/computer/emergency_shuttle/proc/attempt_hijack_stage(mob/living/user) - if(!user.CanReach(src)) - return - if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) - to_chat(user, span_warning("You need your hands free before you can manipulate [src].")) - return - var/area/my_area = get_area(src) - if(!istype(my_area, /area/shuttle/escape)) - say("Error - Network connectivity: Console has lost connection to the shuttle.") - return - if(!user?.mind?.get_hijack_speed()) - to_chat(user, span_warning("You manage to open a user-mode shell on [src], and hundreds of lines of debugging output fly through your vision. It is probably best to leave this alone.")) - return - if(!EMERGENCY_AT_LEAST_DOCKED) // prevent advancing hijack stages on BYOS shuttles until the shuttle has "docked" - to_chat(user, span_warning("The flight plans for the shuttle haven't been loaded yet, you can't hack this right now.")) - return - if(hijack_hacking == TRUE) - return - if(SSshuttle.emergency.hijack_status >= HIJACKED) - to_chat(user, span_warning("The emergency shuttle is already loaded with a corrupt navigational payload. What more do you want from it?")) - return - if(hijack_last_stage_increase >= world.time - hijack_stage_cooldown) - say("Error - Catastrophic software error detected. Input is currently on timeout.") - return - hijack_hacking = TRUE - to_chat(user, span_boldwarning("You [SSshuttle.emergency.hijack_status == NOT_BEGUN? "begin" : "continue"] to override [src]'s navigational protocols.")) - say("Software override initiated.") - var/turf/console_hijack_turf = get_turf(src) - message_admins("[src] is being overriden for hijack by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(console_hijack_turf)]") - user.log_message("is hijacking [src].", LOG_GAME) - . = FALSE - if(do_after(user, hijack_stage_time * (1 / user.mind.get_hijack_speed()), target = src)) - increase_hijack_stage() - console_hijack_turf = get_turf(src) - message_admins("[ADMIN_LOOKUPFLW(user)] has hijacked [src] in [ADMIN_VERBOSEJMP(console_hijack_turf)]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACKED].") - user.log_message("has hijacked [src]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACKED].", LOG_GAME) - . = TRUE - to_chat(user, span_notice("You reprogram some of [src]'s programming, putting it on timeout for [hijack_stage_cooldown/10] seconds.")) - visible_message( - span_warning("[user.name] appears to be tampering with [src]."), - blind_message = span_hear("You hear someone tapping computer keys."), - vision_distance = COMBAT_MESSAGE_RANGE, - ignored_mobs = user - ) - hijack_hacking = FALSE - -/obj/machinery/computer/emergency_shuttle/proc/announce_hijack_stage() - var/msg - switch(SSshuttle.emergency.hijack_status) - if(NOT_BEGUN) - return - if(STAGE_1) - msg = "AUTHENTICATING - FAIL. AUTHENTICATING - FAIL. AUTHENTICATING - FAI###### Welcome, technician JOHN DOE." - if(STAGE_2) - msg = "Warning: Navigational route fails \"IS_AUTHORIZED\". Please try againNN[scramble_message_replace_chars("againagainagainagainagain", 70)]." - if(STAGE_3) - msg = "CRC mismatch at ~h~ in calculated route buffer. Full reset initiated of FTL_NAVIGATION_SERVICES. Memory decrypted for automatic repair." - if(STAGE_4) - msg = "~ACS_directive module_load(cyberdyne.exploit.nanotrasen.shuttlenav)... NT key mismatch. Confirm load? Y...###Reboot complete. $SET transponder_state = 0; System link initiated with connected engines..." - if(HIJACKED) - msg = "SYSTEM OVERRIDE - Resetting course to \[[scramble_message_replace_chars("###########", 100)]\] \ - ([scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]) \ - {AUTH - ROOT (uid: 0)}.\ - [SSshuttle.emergency.mode == SHUTTLE_ESCAPE ? "Diverting from existing route - Bluespace exit in \ - [hijack_completion_flight_time_set >= INFINITY ? "[scramble_message_replace_chars("\[ERROR\]")]" : hijack_completion_flight_time_set/10] seconds." : ""]" - minor_announce(scramble_message_replace_chars(msg, replaceprob = 10), "Emergency Shuttle", TRUE) - -/obj/machinery/computer/emergency_shuttle/emag_act(mob/user, obj/item/card/emag/emag_card) - // How did you even get on the shuttle before it go to the station? - if(!IS_DOCKED) - return FALSE - - if((obj_flags & EMAGGED) || ENGINES_STARTED) //SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LAUNCH IN 10 SECONDS - balloon_alert(user, "shuttle already about to launch!") - return FALSE - - var/time = TIME_LEFT - if (user) - message_admins("[ADMIN_LOOKUPFLW(user)] has emagged the emergency shuttle [time] seconds before launch.") - log_shuttle("[key_name(user)] has emagged the emergency shuttle in [COORD(src)] [time] seconds before launch.") - else - message_admins("The emergency shuttle was emagged [time] seconds before launch, with no emagger.") - log_shuttle("The emergency shuttle was emagged in [COORD(src)] [time] seconds before launch, with no emagger.") - - obj_flags |= EMAGGED - SSshuttle.emergency.movement_force = list("KNOCKDOWN" = 60, "THROW" = 20)//YOUR PUNY SEATBELTS can SAVE YOU NOW, MORTAL - for(var/i in 1 to 10) - // the shuttle system doesn't know who these people are, but they - // must be important, surely - var/obj/item/card/id/ID = new(src) - var/datum/job/J = pick(SSjob.joinable_occupations) - ID.registered_name = generate_random_name_species_based(species_type = /datum/species/human) - ID.assignment = J.title - - authorized += ID - - process(SSMACHINES_DT) - return TRUE - -/obj/machinery/computer/emergency_shuttle/Destroy() - // Our fake IDs that the emag generated are just there for colour - // They're not supposed to be accessible - - for(var/obj/item/card/id/ID in src) - qdel(ID) - if(authorized?.len) - authorized.Cut() - authorized = null - - . = ..() - -/obj/docking_port/mobile/emergency - name = "emergency shuttle" - shuttle_id = "emergency" - dir = EAST - port_direction = WEST - var/sound_played = 0 //If the launch sound has been sent to all players on the shuttle itself - var/hijack_status = NOT_BEGUN - -/obj/docking_port/mobile/emergency/Initialize(mapload) - . = ..() - - setup_shuttle_events() - -/obj/docking_port/mobile/emergency/canDock(obj/docking_port/stationary/S) - return SHUTTLE_CAN_DOCK //If the emergency shuttle can't move, the whole game breaks, so it will force itself to land even if it has to crush a few departments in the process - -/obj/docking_port/mobile/emergency/register() - . = ..() - SSshuttle.emergency = src - -/obj/docking_port/mobile/emergency/Destroy(force) - if(force) - // This'll make the shuttle subsystem use the backup shuttle. - if(src == SSshuttle.emergency) - // If we're the selected emergency shuttle - SSshuttle.emergencyDeregister() - - . = ..() - -/obj/docking_port/mobile/emergency/request(obj/docking_port/stationary/S, area/signal_origin, reason, red_alert, set_coefficient=null) - if(!isnum(set_coefficient)) - set_coefficient = SSsecurity_level.current_security_level.shuttle_call_time_mod - alert_coeff = set_coefficient - var/call_time = SSshuttle.emergency_call_time * alert_coeff * engine_coeff - switch(mode) - // The shuttle can not normally be called while "recalling", so - // if this proc is called, it's via admin fiat - if(SHUTTLE_RECALL, SHUTTLE_IDLE, SHUTTLE_CALL) - mode = SHUTTLE_CALL - setTimer(call_time) - else - return - - SSshuttle.emergencyCallAmount++ - - if(prob(70)) - SSshuttle.emergency_last_call_loc = signal_origin - else - SSshuttle.emergency_last_call_loc = null - - priority_announce( - text = "The emergency shuttle has been called. [red_alert ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [(timeLeft(60 SECONDS))] minutes.[reason][SSshuttle.emergency_last_call_loc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ][SSshuttle.admin_emergency_no_recall ? "\n\nWarning: Shuttle recall subroutines disabled; Recall not possible." : ""]", - title = "Emergency Shuttle Dispatched", - sound = ANNOUNCER_SHUTTLECALLED, - sender_override = "Emergency Shuttle Uplink Alert", - color_override = "orange", - ) - -/obj/docking_port/mobile/emergency/cancel(area/signalOrigin) - if(mode != SHUTTLE_CALL) - return - if(SSshuttle.emergency_no_recall) - return - - invertTimer() - mode = SHUTTLE_RECALL - - if(prob(70)) - SSshuttle.emergency_last_call_loc = signalOrigin - else - SSshuttle.emergency_last_call_loc = null - priority_announce( - text = "The emergency shuttle has been recalled.[SSshuttle.emergency_last_call_loc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]", - title = "Emergency Shuttle Recalled", - sound = ANNOUNCER_SHUTTLERECALLED, - sender_override = "Emergency Shuttle Uplink Alert", - color_override = "orange", - ) - - SSticker.emergency_reason = null - -/** - * Proc that handles checking if the emergency shuttle was successfully hijacked via being the only people present on the shuttle for the elimination hijack or highlander objective - * - * Checks for all mobs on the shuttle, checks their status, and checks if they're - * borgs or simple animals. Depending on the args, certain mobs may be ignored, - * and the presence of other antags may or may not invalidate a hijack. - * Args: - * filter_by_human, default TRUE, tells the proc that only humans should block a hijack. Borgs and animals are ignored and will not block if this is TRUE. - * solo_hijack, default FALSE, tells the proc to fail with multiple hijackers, such as for Highlander mode. - */ -/obj/docking_port/mobile/emergency/proc/elimination_hijack(filter_by_human = TRUE, solo_hijack = FALSE) - var/has_people = FALSE - var/hijacker_count = 0 - for(var/mob/living/player in GLOB.player_list) - if(player.mind) - if(player.stat != DEAD) - if(issilicon(player) && filter_by_human) //Borgs are technically dead anyways - continue - if(isanimal_or_basicmob(player) && filter_by_human) //animals don't count - continue - if(isbrain(player)) //also technically dead - continue - if(shuttle_areas[get_area(player)]) - has_people = TRUE - var/location = get_area(player.mind.current) - //Non-antag present. Can't hijack. - if(!(player.mind.has_antag_datum(/datum/antagonist)) && !istype(location, /area/shuttle/escape/brig)) - return FALSE - //Antag present, doesn't stop but let's see if we actually want to hijack - var/prevent = FALSE - for(var/datum/antagonist/A in player.mind.antag_datums) - if(A.can_elimination_hijack == ELIMINATION_ENABLED) - hijacker_count += 1 - prevent = FALSE - break //If we have both prevent and hijacker antags assume we want to hijack. - else if(A.can_elimination_hijack == ELIMINATION_PREVENT) - prevent = TRUE - if(prevent) - return FALSE - - //has people AND either there's only one hijacker or there's any but solo_hijack is disabled - return has_people && ((hijacker_count == 1) || (hijacker_count && !solo_hijack)) - -/obj/docking_port/mobile/emergency/proc/is_hijacked() - return hijack_status == HIJACKED - -/obj/docking_port/mobile/emergency/proc/ShuttleDBStuff() - set waitfor = FALSE - if(!SSdbcore.Connect()) - return - var/datum/db_query/query_round_shuttle_name = SSdbcore.NewQuery({" - UPDATE [format_table_name("round")] SET shuttle_name = :name WHERE id = :round_id - "}, list("name" = name, "round_id" = GLOB.round_id)) - query_round_shuttle_name.Execute() - qdel(query_round_shuttle_name) - -/obj/docking_port/mobile/emergency/check() - if(!timer) - return - var/time_left = timeLeft(1) - - // The emergency shuttle doesn't work like others so this - // ripple check is slightly different - if(!ripples.len && (time_left <= SHUTTLE_RIPPLE_TIME) && ((mode == SHUTTLE_CALL) || (mode == SHUTTLE_ESCAPE))) - var/destination - if(mode == SHUTTLE_CALL) - destination = SSshuttle.getDock("emergency_home") - else if(mode == SHUTTLE_ESCAPE) - destination = SSshuttle.getDock("emergency_away") - create_ripples(destination) - - switch(mode) - if(SHUTTLE_RECALL) - if(time_left <= 0) - mode = SHUTTLE_IDLE - timer = 0 - if(SHUTTLE_CALL) - if(time_left <= 0) - //move emergency shuttle to station - if(initiate_docking(SSshuttle.getDock("emergency_home")) != DOCKING_SUCCESS) - setTimer(20) - return - mode = SHUTTLE_DOCKED - setTimer(SSshuttle.emergency_dock_time) - send2adminchat("Server", "The Emergency Shuttle has docked with the station.") - priority_announce( - text = "[SSshuttle.emergency] has docked with the station. You have [DisplayTimeText(SSshuttle.emergency_dock_time)] to board the emergency shuttle.", - title = "Emergency Shuttle Arrival", - sound = ANNOUNCER_SHUTTLEDOCK, - sender_override = "Emergency Shuttle Uplink Alert", - color_override = "orange", - ) - ShuttleDBStuff() - addtimer(CALLBACK(src, PROC_REF(announce_shuttle_events)), 20 SECONDS) - - - if(SHUTTLE_DOCKED) - if(time_left <= ENGINES_START_TIME) - mode = SHUTTLE_IGNITING - SSshuttle.checkHostileEnvironment() - if(mode == SHUTTLE_STRANDED) - return - for(var/A in SSshuttle.mobile_docking_ports) - var/obj/docking_port/mobile/M = A - if(M.launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to. - M.check_transit_zone() - - if(SHUTTLE_IGNITING) - var/success = TRUE - SSshuttle.checkHostileEnvironment() - if(mode == SHUTTLE_STRANDED) - return - - success &= (check_transit_zone() == TRANSIT_READY) - for(var/A in SSshuttle.mobile_docking_ports) - var/obj/docking_port/mobile/M = A - if(M.launch_status == UNLAUNCHED) - success &= (M.check_transit_zone() == TRANSIT_READY) - if(!success) - setTimer(ENGINES_START_TIME) - - if(time_left <= 50 && !sound_played) //4 seconds left:REV UP THOSE ENGINES BOYS. - should sync up with the launch - sound_played = 1 //Only rev them up once. - var/list/areas = list() - for(var/area/shuttle/escape/E in GLOB.areas) - areas += E - hyperspace_sound(HYPERSPACE_WARMUP, areas) - - if(time_left <= 0 && !SSshuttle.emergency_no_escape) - //move each escape pod (or applicable spaceship) to its corresponding transit dock - for(var/A in SSshuttle.mobile_docking_ports) - var/obj/docking_port/mobile/M = A - M.on_emergency_launch() - - //now move the actual emergency shuttle to its transit dock - var/list/areas = list() - for(var/area/shuttle/escape/E in GLOB.areas) - areas += E - hyperspace_sound(HYPERSPACE_LAUNCH, areas) - enterTransit() - - //Tell the events we're starting, so they can time their spawns or do some other stuff - for(var/datum/shuttle_event/event as anything in event_list) - event.start_up_event(SSshuttle.emergency_escape_time * engine_coeff) - - mode = SHUTTLE_ESCAPE - launch_status = ENDGAME_LAUNCHED - setTimer(SSshuttle.emergency_escape_time * engine_coeff) - priority_announce( - text = "The emergency shuttle has left the station. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].", - title = "Emergency Shuttle Departure", - sender_override = "Emergency Shuttle Uplink Alert", - color_override = "orange", - ) - INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker, poll_hearts)) - INVOKE_ASYNC(SSvote, TYPE_PROC_REF(/datum/controller/subsystem/vote, initiate_vote), /datum/vote/map_vote, vote_initiator_name = "Map Rotation", forced = TRUE) - - if(!is_reserved_level(z)) - CRASH("Emergency shuttle did not move to transit z-level!") - - if(SHUTTLE_STRANDED, SHUTTLE_DISABLED) - SSshuttle.checkHostileEnvironment() - - - if(SHUTTLE_ESCAPE) - if(sound_played && time_left <= HYPERSPACE_END_TIME) - var/list/areas = list() - for(var/area/shuttle/escape/E in GLOB.areas) - areas += E - hyperspace_sound(HYPERSPACE_END, areas) - if(time_left <= PARALLAX_LOOP_TIME) - var/area_parallax = FALSE - for(var/place in shuttle_areas) - var/area/shuttle/shuttle_area = place - if(shuttle_area.parallax_movedir) - area_parallax = TRUE - break - if(area_parallax) - parallax_slowdown() - for(var/A in SSshuttle.mobile_docking_ports) - var/obj/docking_port/mobile/M = A - if(M.launch_status == ENDGAME_LAUNCHED) - if(istype(M, /obj/docking_port/mobile/pod)) - M.parallax_slowdown() - - process_events() - - if(time_left <= 0) - //move each escape pod to its corresponding escape dock - for(var/obj/docking_port/mobile/port as anything in SSshuttle.mobile_docking_ports) - port.on_emergency_dock() - - // now move the actual emergency shuttle to centcom - // unless the shuttle is "hijacked" - var/destination_dock = "emergency_away" - if(is_hijacked() || elimination_hijack()) - // just double check - SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NUKIEBASE) - destination_dock = "emergency_syndicate" - minor_announce("Corruption detected in \ - shuttle navigation protocols. Please contact your \ - supervisor.", "SYSTEM ERROR:", sound_override = 'sound/announcer/announcement/announce_syndi.ogg') - - dock_id(destination_dock) - mode = SHUTTLE_ENDGAME - timer = 0 - -/obj/docking_port/mobile/emergency/transit_failure() - ..() - message_admins("Moving emergency shuttle directly to centcom dock to prevent deadlock.") - - mode = SHUTTLE_ESCAPE - launch_status = ENDGAME_LAUNCHED - setTimer(SSshuttle.emergency_escape_time) - priority_announce( - text = "The emergency shuttle is preparing for direct jump. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].", - title = "Emergency Shuttle Transit Failure", - sender_override = "Emergency Shuttle Uplink Alert", - color_override = "orange", - ) - -///Generate a list of events to run during the departure -/obj/docking_port/mobile/emergency/proc/setup_shuttle_events() - var/list/names = list() - for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event)) - if(prob(initial(event.event_probability))) - add_shuttle_event(event) - names += initial(event.name) - if(LAZYLEN(names)) - log_game("[capitalize(name)] has selected the following shuttle events: [english_list(names)].") - -/obj/docking_port/mobile/monastery - name = "monastery pod" - shuttle_id = "mining_common" //set so mining can call it down - launch_status = UNLAUNCHED //required for it to launch as a pod. - -/obj/docking_port/mobile/monastery/on_emergency_dock() - if(launch_status == ENDGAME_LAUNCHED) - initiate_docking(SSshuttle.getDock("pod_away")) //docks our shuttle as any pod would - mode = SHUTTLE_ENDGAME - -/obj/docking_port/mobile/pod - name = "escape pod" - shuttle_id = "pod" - launch_status = UNLAUNCHED - -/obj/docking_port/mobile/pod/request(obj/docking_port/stationary/S) - var/obj/machinery/computer/shuttle/connected_computer = get_control_console() - if(!istype(connected_computer, /obj/machinery/computer/shuttle/pod)) - return FALSE - if(!(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED) && !(connected_computer.obj_flags & EMAGGED)) - to_chat(usr, span_warning("Escape pods will only launch during \"Code Red\" security alert.")) - return FALSE - if(launch_status == UNLAUNCHED) - launch_status = EARLY_LAUNCHED - return ..() - -/obj/docking_port/mobile/pod/cancel() - return - -/obj/machinery/computer/shuttle/pod - name = "pod control computer" - locked = TRUE - possible_destinations = "pod_asteroid" - icon = 'icons/obj/machines/wallmounts.dmi' - icon_state = "pod_off" - circuit = /obj/item/circuitboard/computer/emergency_pod - light_color = LIGHT_COLOR_BLUE - density = FALSE - icon_keyboard = null - icon_screen = "pod_on" - -/obj/machinery/computer/shuttle/pod/Initialize(mapload) - . = ..() - RegisterSignal(SSsecurity_level, COMSIG_SECURITY_LEVEL_CHANGED, PROC_REF(check_lock)) - -/obj/machinery/computer/shuttle/pod/emag_act(mob/user, obj/item/card/emag/emag_card) - if(obj_flags & EMAGGED) - return FALSE - obj_flags |= EMAGGED - locked = FALSE - balloon_alert(user, "alert level checking disabled") - icon_screen = "emagged_general" - update_appearance() - return TRUE - -/obj/machinery/computer/shuttle/pod/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) - . = ..() - if(port) - //Checks if the computer has already added the shuttle destination with the initial id - //This has to be done because connect_to_shuttle is called again after its ID is updated - //due to conflicting id names - var/base_shuttle_destination = ";[initial(port.shuttle_id)]_lavaland" - var/shuttle_destination = ";[port.shuttle_id]_lavaland" - - var/position = findtext(possible_destinations, base_shuttle_destination) - if(position) - if(base_shuttle_destination == shuttle_destination) - return - possible_destinations = splicetext(possible_destinations, position, position + length(base_shuttle_destination), shuttle_destination) - return - - possible_destinations += shuttle_destination - -/** - * Signal handler for checking if we should lock or unlock escape pods accordingly to a newly set security level - * - * Arguments: - * * source The datum source of the signal - * * new_level The new security level that is in effect - */ -/obj/machinery/computer/shuttle/pod/proc/check_lock(datum/source, new_level) - SIGNAL_HANDLER - - if(obj_flags & EMAGGED) - return - locked = (new_level < SEC_LEVEL_RED) - -/obj/docking_port/stationary/random - name = "escape pod" - shuttle_id = "pod" - hidden = TRUE - override_can_dock_checks = TRUE - /// The area the pod tries to land at - var/target_area = /area/lavaland/surface/outdoors - /// Minimal distance from the map edge, setting this too low can result in shuttle landing on the edge and getting "sliced" - var/edge_distance = 16 - -/obj/docking_port/stationary/random/Initialize(mapload) - . = ..() - if(!mapload) - return - - var/list/turfs = get_area_turfs(target_area) - var/original_len = turfs.len - while(turfs.len) - var/turf/picked_turf = pick(turfs) - if(picked_turf.x stationary_dock.dwidth) + return SHUTTLE_DWIDTH_TOO_LARGE + + if(width-dwidth > stationary_dock.width-stationary_dock.dwidth) + return SHUTTLE_WIDTH_TOO_LARGE + + if(dheight > stationary_dock.dheight) + return SHUTTLE_DHEIGHT_TOO_LARGE + + if(height-dheight > stationary_dock.height-stationary_dock.dheight) + return SHUTTLE_HEIGHT_TOO_LARGE + + //check the dock isn't occupied + var/currently_docked = stationary_dock.get_docked() + if(currently_docked) + // by someone other than us + if(currently_docked != src) + return SHUTTLE_SOMEONE_ELSE_DOCKED + else + // This isn't an error, per se, but we can't let the shuttle code + // attempt to move us where we currently are, it will get weird. + return SHUTTLE_ALREADY_DOCKED + + return SHUTTLE_CAN_DOCK + +/obj/docking_port/mobile/proc/check_dock(obj/docking_port/stationary/S, silent = FALSE) + var/status = canDock(S) + if(status == SHUTTLE_CAN_DOCK) + return TRUE + else + if(status != SHUTTLE_ALREADY_DOCKED && !silent) // SHUTTLE_ALREADY_DOCKED is no cause for error + message_admins("Shuttle [src] cannot dock at [S], error: [status]") + // We're already docked there, don't need to do anything. + // Triggering shuttle movement code in place is weird + return FALSE + +/obj/docking_port/mobile/proc/transit_failure() + message_admins("Shuttle [src] repeatedly failed to create transit zone.") + +/** + * Calls the shuttle to the destination port, respecting its ignition and call timers + * + * Arguments: + * * destination_port - Stationary docking port to move the shuttle to + */ +/obj/docking_port/mobile/proc/request(obj/docking_port/stationary/destination_port) + if(!check_dock(destination_port)) + testing("check_dock failed on request for [src]") + return + + if(mode == SHUTTLE_IGNITING && destination == destination_port) + return + + switch(mode) + if(SHUTTLE_CALL) + if(destination_port == destination) + if(timeLeft(1) < callTime * engine_coeff) + setTimer(callTime * engine_coeff) + else + destination = destination_port + setTimer(callTime * engine_coeff) + if(SHUTTLE_RECALL) + if(destination_port == destination) + setTimer(callTime * engine_coeff - timeLeft(1)) + else + destination = destination_port + setTimer(callTime * engine_coeff) + mode = SHUTTLE_CALL + if(SHUTTLE_IDLE, SHUTTLE_IGNITING) + destination = destination_port + mode = SHUTTLE_IGNITING + setTimer(ignitionTime) + +//recall the shuttle to where it was previously +/obj/docking_port/mobile/proc/cancel() + if(mode != SHUTTLE_CALL) + return + + remove_ripples() + + invertTimer() + mode = SHUTTLE_RECALL + +/obj/docking_port/mobile/proc/enterTransit() + if((SSshuttle.lockdown && is_station_level(z)) || !canMove()) //emp went off, no escape + mode = SHUTTLE_IDLE + return + previous = null + if(!destination) + // sent to transit with no destination -> unlimited timer + timer = INFINITY + var/obj/docking_port/stationary/S0 = get_docked() + var/obj/docking_port/stationary/S1 = assigned_transit + if(S1) + if(initiate_docking(S1) != DOCKING_SUCCESS) + WARNING("shuttle \"[shuttle_id]\" could not enter transit space. Docked at [S0 ? S0.shuttle_id : "null"]. Transit dock [S1 ? S1.shuttle_id : "null"].") + else if(S0) + if(S0.delete_after) + qdel(S0, TRUE) + else + previous = S0 + else + WARNING("shuttle \"[shuttle_id]\" could not enter transit space. S0=[S0 ? S0.shuttle_id : "null"] S1=[S1 ? S1.shuttle_id : "null"]") + + +/obj/docking_port/mobile/proc/jumpToNullSpace() + // Destroys the docking port and the shuttle contents. + // Not in a fancy way, it just ceases. + var/obj/docking_port/stationary/current_dock = get_docked() + + var/underlying_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA + // If the shuttle is docked to a stationary port, restore its normal + // "empty" area and turf + if(current_dock?.area_type) + underlying_area_type = current_dock.area_type + + var/list/old_turfs = return_ordered_turfs(x, y, z, dir) + + var/area/underlying_area = GLOB.areas_by_type[underlying_area_type] + if(!underlying_area) + underlying_area = new underlying_area_type(null) + + for(var/i in 1 to old_turfs.len) + var/turf/oldT = old_turfs[i] + if(!oldT || !istype(oldT.loc, area_type)) + continue + oldT.change_area(oldT.loc, underlying_area) + oldT.empty(FALSE) + + // Here we locate the bottommost shuttle boundary and remove all turfs above it + var/shuttle_tile_depth = oldT.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle) + if (!isnull(shuttle_tile_depth)) + oldT.ScrapeAway(shuttle_tile_depth) + + qdel(src, force=TRUE) + +/** + * Ghosts and marks as escaped (for greentext purposes) all mobs, then deletes the shuttle. + * Used by the Shuttle Manipulator + */ +/obj/docking_port/mobile/proc/intoTheSunset() + // Loop over mobs + for(var/turf/turfs as anything in return_turfs()) + for(var/mob/living/sunset_mobs in turfs.get_all_contents()) + // If they have a mind and they're not in the brig, they escaped + if(sunset_mobs.mind && !istype(get_area(sunset_mobs), /area/shuttle/escape/brig)) + sunset_mobs.mind.force_escaped = TRUE + // Ghostize them and put them in nullspace stasis (for stat & possession checks) + ADD_TRAIT(sunset_mobs, TRAIT_NO_TRANSFORM, REF(src)) + sunset_mobs.ghostize(FALSE) + sunset_mobs.moveToNullspace() + + // Now that mobs are stowed, delete the shuttle + jumpToNullSpace() + +/obj/docking_port/mobile/proc/create_ripples(obj/docking_port/stationary/S1, animate_time) + var/list/turfs = ripple_area(S1) + for(var/t in turfs) + ripples += new /obj/effect/abstract/ripple(t, animate_time) + +/obj/docking_port/mobile/proc/remove_ripples() + QDEL_LIST(ripples) + +/obj/docking_port/mobile/proc/ripple_area(obj/docking_port/stationary/S1) + var/list/L0 = return_ordered_turfs(x, y, z, dir) + var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir) + + var/list/ripple_turfs = list() + var/stop = min(L0.len, L1.len) + for(var/i in 1 to stop) + var/turf/T0 = L0[i] + var/turf/T1 = L1[i] + if(!istype(T0.loc, area_type) || istype(T0.loc, /area/shuttle/transit)) + continue // not part of the shuttle + ripple_turfs += T1 + + return ripple_turfs + +/obj/docking_port/mobile/proc/check_poddoors() + for(var/obj/machinery/door/poddoor/shuttledock/pod as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor/shuttledock)) + pod.check() + +/obj/docking_port/mobile/proc/dock_id(id) + var/port = SSshuttle.getDock(id) + if(port) + . = initiate_docking(port) + else + . = null + +//used by shuttle subsystem to check timers +/obj/docking_port/mobile/proc/check() + check_effects() + //process_events() if you were to add events to non-escape shuttles, uncomment this + + if(mode == SHUTTLE_IGNITING) + check_transit_zone() + + if(timeLeft(1) > 0) + return + // If we can't dock or we don't have a transit slot, wait for 20 ds, + // then try again + switch(mode) + if(SHUTTLE_CALL, SHUTTLE_PREARRIVAL) + if(prearrivalTime && mode != SHUTTLE_PREARRIVAL) + mode = SHUTTLE_PREARRIVAL + setTimer(prearrivalTime) + return + var/error = initiate_docking(destination, preferred_direction) + if(error && error & (DOCKING_NULL_DESTINATION | DOCKING_NULL_SOURCE)) + var/msg = "A mobile dock in transit exited initiate_docking() with an error. This is most likely a mapping problem: Error: [error], ([src]) ([previous][ADMIN_JMP(previous)] -> [destination][ADMIN_JMP(destination)])" + WARNING(msg) + message_admins(msg) + mode = SHUTTLE_IDLE + return + else if(error) + setTimer(20) + return + if(rechargeTime) + mode = SHUTTLE_RECHARGING + setTimer(rechargeTime) + return + if(SHUTTLE_RECALL) + if(initiate_docking(previous) != DOCKING_SUCCESS) + setTimer(20) + return + if(SHUTTLE_IGNITING) + if(check_transit_zone() != TRANSIT_READY) + setTimer(20) + return + else + mode = SHUTTLE_CALL + setTimer(callTime * engine_coeff) + enterTransit() + return + + mode = SHUTTLE_IDLE + timer = 0 + destination = null + +/obj/docking_port/mobile/proc/check_effects() + if(!ripples.len) + if((mode == SHUTTLE_CALL) || (mode == SHUTTLE_RECALL)) + var/tl = timeLeft(1) + if(tl <= SHUTTLE_RIPPLE_TIME) + create_ripples(destination, tl) + + var/obj/docking_port/stationary/S0 = get_docked() + if(istype(S0, /obj/docking_port/stationary/transit) && timeLeft(1) <= PARALLAX_LOOP_TIME) + for(var/place in shuttle_areas) + var/area/shuttle/shuttle_area = place + if(shuttle_area.parallax_movedir) + parallax_slowdown() + +/obj/docking_port/mobile/proc/parallax_slowdown() + for(var/place in shuttle_areas) + var/area/shuttle/shuttle_area = place + shuttle_area.parallax_movedir = FALSE + if(assigned_transit?.assigned_area) + assigned_transit.assigned_area.parallax_movedir = FALSE + var/list/L0 = return_ordered_turfs(x, y, z, dir) + for (var/thing in L0) + var/turf/T = thing + if(!T || !istype(T.loc, area_type)) + continue + for (var/atom/movable/movable as anything in T) + if (movable.client_mobs_in_contents) + movable.update_parallax_contents() + +/obj/docking_port/mobile/proc/check_transit_zone() + if(assigned_transit) + return TRANSIT_READY + else + SSshuttle.request_transit_dock(src) + +/obj/docking_port/mobile/proc/setTimer(wait) + timer = world.time + wait + last_timer_length = wait + +/obj/docking_port/mobile/proc/modTimer(multiple) + var/time_remaining = timer - world.time + if(time_remaining < 0 || !last_timer_length) + return + time_remaining *= multiple + last_timer_length *= multiple + setTimer(time_remaining) + +/obj/docking_port/mobile/proc/alert_coeff_change(new_coeff) + if(isnull(new_coeff)) + return + + var/time_multiplier = new_coeff / alert_coeff + var/time_remaining = timer - world.time + if(time_remaining < 0 || !last_timer_length) + return + + time_remaining *= time_multiplier + last_timer_length *= time_multiplier + alert_coeff = new_coeff + setTimer(time_remaining) + +/obj/docking_port/mobile/proc/invertTimer() + if(!last_timer_length) + return + var/time_remaining = timer - world.time + if(time_remaining > 0) + var/time_passed = last_timer_length - time_remaining + setTimer(time_passed) + +//returns timeLeft +/obj/docking_port/mobile/proc/timeLeft(divisor) + if(divisor <= 0) + divisor = 10 + + var/ds_remaining + if(!timer) + ds_remaining = callTime * engine_coeff + else + ds_remaining = max(0, timer - world.time) + + . = round(ds_remaining / divisor, 1) + +// returns 3-letter mode string, used by status screens and mob status panel +/obj/docking_port/mobile/proc/getModeStr() + switch(mode) + if(SHUTTLE_IGNITING) + return "IGN" + if(SHUTTLE_RECALL) + return "RCL" + if(SHUTTLE_CALL) + return "ETA" + if(SHUTTLE_DOCKED) + return "ETD" + if(SHUTTLE_ESCAPE) + return "ESC" + if(SHUTTLE_STRANDED) + return "ERR" + if(SHUTTLE_RECHARGING) + return "RCH" + if(SHUTTLE_PREARRIVAL) + return "LDN" + if(SHUTTLE_DISABLED) + return "DIS" + return "" + +// returns 5-letter timer string, used by status screens and mob status panel +/obj/docking_port/mobile/proc/getTimerStr() + if(mode == SHUTTLE_STRANDED || mode == SHUTTLE_DISABLED) + return "--:--" + + var/timeleft = timeLeft() + if(timeleft > 1 HOURS) + return "--:--" + else if(timeleft > 0) + return "[add_leading(num2text((timeleft / 60) % 60), 2, "0")]:[add_leading(num2text(timeleft % 60), 2, "0")]" + else + return "00:00" + +/** + * Gets shuttle location status in a form of string for tgui interfaces + */ +/obj/docking_port/mobile/proc/get_status_text_tgui() + var/obj/docking_port/stationary/dockedAt = get_docked() + var/docked_at = dockedAt?.name || "Unknown" + if(!istype(dockedAt, /obj/docking_port/stationary/transit)) + return docked_at + if(timeLeft() > 1 HOURS) + return "Hyperspace" + else + var/obj/docking_port/stationary/dst = (mode == SHUTTLE_RECALL) ? previous : destination + return "In transit to [dst?.name || "unknown location"]" + +/obj/docking_port/mobile/proc/getStatusText() + var/obj/docking_port/stationary/dockedAt = get_docked() + var/docked_at = dockedAt?.name || "unknown" + if(istype(dockedAt, /obj/docking_port/stationary/transit)) + if (timeLeft() > 1 HOURS) + return "hyperspace" + else + var/obj/docking_port/stationary/dst + if(mode == SHUTTLE_RECALL) + dst = previous + else + dst = destination + . = "transit towards [dst?.name || "unknown location"] ([getTimerStr()])" + else if(mode == SHUTTLE_RECHARGING) + return "[docked_at], recharging [getTimerStr()]" + else + return docked_at + +/obj/docking_port/mobile/proc/getDbgStatusText() + var/obj/docking_port/stationary/dockedAt = get_docked() + . = (dockedAt?.name) ? dockedAt.name : "unknown" + if(istype(dockedAt, /obj/docking_port/stationary/transit)) + var/obj/docking_port/stationary/dst + if(mode == SHUTTLE_RECALL) + dst = previous + else + dst = destination + if(dst) + . = "(transit to) [dst.name || dst.shuttle_id]" + else + . = "(transit to) nowhere" + else if(dockedAt) + . = dockedAt.name || dockedAt.shuttle_id + else + . = "unknown" + + +// attempts to locate /obj/machinery/computer/shuttle with matching ID inside the shuttle +/obj/docking_port/mobile/proc/get_control_console() + for(var/area/shuttle/shuttle_area as anything in shuttle_areas) + var/obj/machinery/computer/shuttle/shuttle_computer = locate(/obj/machinery/computer/shuttle) in shuttle_area + if(!shuttle_computer) + continue + if(shuttle_computer.shuttleId == shuttle_id) + return shuttle_computer + return null + +/obj/docking_port/mobile/proc/hyperspace_sound(phase, list/areas) + var/selected_sound + switch(phase) + if(HYPERSPACE_WARMUP) + selected_sound = "hyperspace_begin" + if(HYPERSPACE_LAUNCH) + selected_sound = "hyperspace_progress" + if(HYPERSPACE_END) + selected_sound = "hyperspace_end" + else + CRASH("Invalid hyperspace sound phase: [phase]") + // This previously was played from each door at max volume, and was one of the worst things I had ever seen. + // Now it's instead played from the nearest engine if close, or the first engine in the list if far since it doesn't really matter. + // Or a door if for some reason the shuttle has no engine, fuck oh hi daniel fuck it + var/range = (engine_coeff * max(width, height)) + var/long_range = range * 2.5 + var/atom/distant_source + + if(engine_list.len) + distant_source = engine_list[1] + else + for(var/our_area in areas) + distant_source = locate(/obj/machinery/door) in our_area + if(distant_source) + break + + if(!distant_source) + return + for(var/mob/zlevel_mobs as anything in SSmobs.clients_by_zlevel[z]) + var/dist_far = get_dist(zlevel_mobs, distant_source) + if(dist_far <= long_range && dist_far > range) + zlevel_mobs.playsound_local(distant_source, "sound/runtime/hyperspace/[selected_sound]_distance.ogg", 100) + else if(dist_far <= range) + var/source + if(!engine_list.len) + source = distant_source + else + var/closest_dist = 10000 + for(var/obj/machinery/power/shuttle_engine/engines as anything in engine_list) + var/dist_near = get_dist(zlevel_mobs, engines) + if(dist_near < closest_dist) + source = engines + closest_dist = dist_near + zlevel_mobs.playsound_local(source, "sound/runtime/hyperspace/[selected_sound].ogg", 100) + +// Losing all initial engines should get you 2 +// Adding another set of engines at 0.5 time +/obj/docking_port/mobile/proc/alter_engines(mod) + if(!mod) + return + var/old_coeff = engine_coeff + engine_coeff = get_engine_coeff(mod) + current_engine_power = max(0, current_engine_power + mod) + if(in_flight()) + var/delta_coeff = engine_coeff / old_coeff + modTimer(delta_coeff) + +// Double initial engines to get to 0.5 minimum +// Lose all initial engines to get to 2 +//For 0 engine shuttles like BYOS 5 engines to get to doublespeed +/obj/docking_port/mobile/proc/get_engine_coeff(engine_mod) + var/new_value = max(0, current_engine_power + engine_mod) + if(new_value == initial_engine_power) + return 1 + if(new_value > initial_engine_power) + var/delta = new_value - initial_engine_power + var/change_per_engine = (1 - ENGINE_COEFF_MIN) / ENGINE_DEFAULT_MAXSPEED_ENGINES // 5 by default + if(initial_engine_power > 0) + change_per_engine = (1 - ENGINE_COEFF_MIN) / initial_engine_power // or however many it had + return clamp(1 - delta * change_per_engine,ENGINE_COEFF_MIN, ENGINE_COEFF_MAX) + if(new_value < initial_engine_power) + var/delta = initial_engine_power - new_value + var/change_per_engine = 1 //doesn't really matter should not be happening for 0 engine shuttles + if(initial_engine_power > 0) + change_per_engine = (ENGINE_COEFF_MAX - 1) / initial_engine_power //just linear drop to max delay + return clamp(1 + delta * change_per_engine, ENGINE_COEFF_MIN, ENGINE_COEFF_MAX) + + +/obj/docking_port/mobile/proc/in_flight() + switch(mode) + if(SHUTTLE_CALL,SHUTTLE_RECALL,SHUTTLE_PREARRIVAL) + return TRUE + if(SHUTTLE_IDLE,SHUTTLE_IGNITING) + return FALSE + return FALSE // hmm + +/obj/docking_port/mobile/emergency/in_flight() + switch(mode) + if(SHUTTLE_ESCAPE) + return TRUE + if(SHUTTLE_STRANDED,SHUTTLE_ENDGAME) + return FALSE + return ..() + +//Called when emergency shuttle leaves the station +/obj/docking_port/mobile/proc/on_emergency_launch() + if(launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to. + launch_status = ENDGAME_LAUNCHED + enterTransit() + +///Let people know shits about to go down +/obj/docking_port/mobile/proc/announce_shuttle_events() + for(var/datum/shuttle_event/event as anything in event_list) + notify_ghosts("The [name] has selected: [event.name]") + +/obj/docking_port/mobile/emergency/on_emergency_launch() + return + +//Called when emergency shuttle docks at centcom +/obj/docking_port/mobile/proc/on_emergency_dock() + // Mapping a new docking point for each ship mappers could potentially want docking with centcom would take up lots of space, + // just let them keep flying off "into the sunset" for their greentext. + if(launch_status == ENDGAME_LAUNCHED) + launch_status = ENDGAME_TRANSIT + +/obj/docking_port/mobile/pod/on_emergency_dock() + if(launch_status == ENDGAME_LAUNCHED) + initiate_docking(SSshuttle.getDock("[shuttle_id]_away")) //Escape pods dock at centcom + mode = SHUTTLE_ENDGAME + +/obj/docking_port/mobile/emergency/on_emergency_dock() + return + +///Process all the shuttle events for every shuttle tick we get +/obj/docking_port/mobile/proc/process_events() + var/list/removees + for(var/datum/shuttle_event/event as anything in event_list) + if(event.event_process() == SHUTTLE_EVENT_CLEAR) //if we return SHUTTLE_EVENT_CLEAR, we clean them up + LAZYADD(removees, event) + for(var/item in removees) + event_list.Remove(item) + +/// Give a typepath of a shuttle event to add to the shuttle. If added during endgame transit, will insta start the event +/obj/docking_port/mobile/proc/add_shuttle_event(typepath) + var/datum/shuttle_event/event = new typepath (src) + event_list.Add(event) + if(launch_status == ENDGAME_LAUNCHED) + event.start_up_event(0) + return event diff --git a/code/modules/shuttle/docking.dm b/code/modules/shuttle/mobile_port/shuttle_move.dm similarity index 98% rename from code/modules/shuttle/docking.dm rename to code/modules/shuttle/mobile_port/shuttle_move.dm index 32a1ca4950afa..b7e125826dce2 100644 --- a/code/modules/shuttle/docking.dm +++ b/code/modules/shuttle/mobile_port/shuttle_move.dm @@ -1,4 +1,5 @@ -/// This is the main proc. It instantly moves our mobile port to stationary port `new_dock`. +/// This is the main proc. Despite what the name suggests, +/// it instantly moves our mobile port to stationary port `new_dock`. /obj/docking_port/mobile/proc/initiate_docking(obj/docking_port/stationary/new_dock, movement_direction, force=FALSE) // Crashing this ship with NO SURVIVORS diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm similarity index 100% rename from code/modules/shuttle/on_move.dm rename to code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm diff --git a/code/modules/shuttle/shuttle_rotate.dm b/code/modules/shuttle/mobile_port/shuttle_rotate_callbacks.dm similarity index 100% rename from code/modules/shuttle/shuttle_rotate.dm rename to code/modules/shuttle/mobile_port/shuttle_rotate_callbacks.dm diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/mobile_port/variants/arrivals.dm similarity index 100% rename from code/modules/shuttle/arrivals.dm rename to code/modules/shuttle/mobile_port/variants/arrivals.dm diff --git a/code/modules/shuttle/assault_pod.dm b/code/modules/shuttle/mobile_port/variants/assault_pod.dm similarity index 100% rename from code/modules/shuttle/assault_pod.dm rename to code/modules/shuttle/mobile_port/variants/assault_pod.dm diff --git a/code/modules/shuttle/battlecruiser_starfury.dm b/code/modules/shuttle/mobile_port/variants/battlecruiser_starfury.dm similarity index 100% rename from code/modules/shuttle/battlecruiser_starfury.dm rename to code/modules/shuttle/mobile_port/variants/battlecruiser_starfury.dm diff --git a/code/modules/shuttle/elevator.dm b/code/modules/shuttle/mobile_port/variants/elevator.dm similarity index 100% rename from code/modules/shuttle/elevator.dm rename to code/modules/shuttle/mobile_port/variants/elevator.dm diff --git a/code/modules/shuttle/mobile_port/variants/emergency/emergency.dm b/code/modules/shuttle/mobile_port/variants/emergency/emergency.dm new file mode 100644 index 0000000000000..b162620cf4815 --- /dev/null +++ b/code/modules/shuttle/mobile_port/variants/emergency/emergency.dm @@ -0,0 +1,311 @@ +/obj/docking_port/mobile/emergency + name = "emergency shuttle" + shuttle_id = "emergency" + dir = EAST + port_direction = WEST + var/sound_played = 0 //If the launch sound has been sent to all players on the shuttle itself + var/hijack_status = HIJACK_NOT_BEGUN + +/obj/docking_port/mobile/emergency/Initialize(mapload) + . = ..() + + setup_shuttle_events() + +/obj/docking_port/mobile/emergency/canDock(obj/docking_port/stationary/S) + return SHUTTLE_CAN_DOCK //If the emergency shuttle can't move, the whole game breaks, so it will force itself to land even if it has to crush a few departments in the process + +/obj/docking_port/mobile/emergency/register() + . = ..() + SSshuttle.emergency = src + +/obj/docking_port/mobile/emergency/Destroy(force) + if(force) + // This'll make the shuttle subsystem use the backup shuttle. + if(src == SSshuttle.emergency) + // If we're the selected emergency shuttle + SSshuttle.emergencyDeregister() + + . = ..() + +/obj/docking_port/mobile/emergency/request(obj/docking_port/stationary/S, area/signal_origin, reason, red_alert, set_coefficient=null) + if(!isnum(set_coefficient)) + set_coefficient = SSsecurity_level.current_security_level.shuttle_call_time_mod + alert_coeff = set_coefficient + var/call_time = SSshuttle.emergency_call_time * alert_coeff * engine_coeff + switch(mode) + // The shuttle can not normally be called while "recalling", so + // if this proc is called, it's via admin fiat + if(SHUTTLE_RECALL, SHUTTLE_IDLE, SHUTTLE_CALL) + mode = SHUTTLE_CALL + setTimer(call_time) + else + return + + SSshuttle.emergencyCallAmount++ + + if(prob(70)) + SSshuttle.emergency_last_call_loc = signal_origin + else + SSshuttle.emergency_last_call_loc = null + + priority_announce( + text = "The emergency shuttle has been called. [red_alert ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [(timeLeft(60 SECONDS))] minutes.[reason][SSshuttle.emergency_last_call_loc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ][SSshuttle.admin_emergency_no_recall ? "\n\nWarning: Shuttle recall subroutines disabled; Recall not possible." : ""]", + title = "Emergency Shuttle Dispatched", + sound = ANNOUNCER_SHUTTLECALLED, + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "orange", + ) + +/obj/docking_port/mobile/emergency/cancel(area/signalOrigin) + if(mode != SHUTTLE_CALL) + return + if(SSshuttle.emergency_no_recall) + return + + invertTimer() + mode = SHUTTLE_RECALL + + if(prob(70)) + SSshuttle.emergency_last_call_loc = signalOrigin + else + SSshuttle.emergency_last_call_loc = null + priority_announce( + text = "The emergency shuttle has been recalled.[SSshuttle.emergency_last_call_loc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]", + title = "Emergency Shuttle Recalled", + sound = ANNOUNCER_SHUTTLERECALLED, + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "orange", + ) + + SSticker.emergency_reason = null + +/** + * Proc that handles checking if the emergency shuttle was successfully hijacked via being the only people present on the shuttle for the elimination hijack or highlander objective + * + * Checks for all mobs on the shuttle, checks their status, and checks if they're + * borgs or simple animals. Depending on the args, certain mobs may be ignored, + * and the presence of other antags may or may not invalidate a hijack. + * Args: + * filter_by_human, default TRUE, tells the proc that only humans should block a hijack. Borgs and animals are ignored and will not block if this is TRUE. + * solo_hijack, default FALSE, tells the proc to fail with multiple hijackers, such as for Highlander mode. + */ +/obj/docking_port/mobile/emergency/proc/elimination_hijack(filter_by_human = TRUE, solo_hijack = FALSE) + var/has_people = FALSE + var/hijacker_count = 0 + for(var/mob/living/player in GLOB.player_list) + if(player.mind) + if(player.stat != DEAD) + if(issilicon(player) && filter_by_human) //Borgs are technically dead anyways + continue + if(isanimal_or_basicmob(player) && filter_by_human) //animals don't count + continue + if(isbrain(player)) //also technically dead + continue + if(shuttle_areas[get_area(player)]) + has_people = TRUE + var/location = get_area(player.mind.current) + //Non-antag present. Can't hijack. + if(!(player.mind.has_antag_datum(/datum/antagonist)) && !istype(location, /area/shuttle/escape/brig)) + return FALSE + //Antag present, doesn't stop but let's see if we actually want to hijack + var/prevent = FALSE + for(var/datum/antagonist/A in player.mind.antag_datums) + if(A.can_elimination_hijack == ELIMINATION_ENABLED) + hijacker_count += 1 + prevent = FALSE + break //If we have both prevent and hijacker antags assume we want to hijack. + else if(A.can_elimination_hijack == ELIMINATION_PREVENT) + prevent = TRUE + if(prevent) + return FALSE + + //has people AND either there's only one hijacker or there's any but solo_hijack is disabled + return has_people && ((hijacker_count == 1) || (hijacker_count && !solo_hijack)) + +/obj/docking_port/mobile/emergency/proc/is_hijacked() + return hijack_status == HIJACK_COMPLETED + +/obj/docking_port/mobile/emergency/proc/ShuttleDBStuff() + set waitfor = FALSE + if(!SSdbcore.Connect()) + return + var/datum/db_query/query_round_shuttle_name = SSdbcore.NewQuery({" + UPDATE [format_table_name("round")] SET shuttle_name = :name WHERE id = :round_id + "}, list("name" = name, "round_id" = GLOB.round_id)) + query_round_shuttle_name.Execute() + qdel(query_round_shuttle_name) + +/obj/docking_port/mobile/emergency/check() + if(!timer) + return + var/time_left = timeLeft(1) + + // The emergency shuttle doesn't work like others so this + // ripple check is slightly different + if(!ripples.len && (time_left <= SHUTTLE_RIPPLE_TIME) && ((mode == SHUTTLE_CALL) || (mode == SHUTTLE_ESCAPE))) + var/destination + if(mode == SHUTTLE_CALL) + destination = SSshuttle.getDock("emergency_home") + else if(mode == SHUTTLE_ESCAPE) + destination = SSshuttle.getDock("emergency_away") + create_ripples(destination) + + switch(mode) + if(SHUTTLE_RECALL) + if(time_left <= 0) + mode = SHUTTLE_IDLE + timer = 0 + if(SHUTTLE_CALL) + if(time_left <= 0) + //move emergency shuttle to station + if(initiate_docking(SSshuttle.getDock("emergency_home")) != DOCKING_SUCCESS) + setTimer(20) + return + mode = SHUTTLE_DOCKED + setTimer(SSshuttle.emergency_dock_time) + send2adminchat("Server", "The Emergency Shuttle has docked with the station.") + priority_announce( + text = "[SSshuttle.emergency] has docked with the station. You have [DisplayTimeText(SSshuttle.emergency_dock_time)] to board the emergency shuttle.", + title = "Emergency Shuttle Arrival", + sound = ANNOUNCER_SHUTTLEDOCK, + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "orange", + ) + ShuttleDBStuff() + addtimer(CALLBACK(src, PROC_REF(announce_shuttle_events)), 20 SECONDS) + + + if(SHUTTLE_DOCKED) + if(time_left <= ENGINE_START_TIME) + mode = SHUTTLE_IGNITING + SSshuttle.checkHostileEnvironment() + if(mode == SHUTTLE_STRANDED) + return + for(var/A in SSshuttle.mobile_docking_ports) + var/obj/docking_port/mobile/M = A + if(M.launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to. + M.check_transit_zone() + + if(SHUTTLE_IGNITING) + var/success = TRUE + SSshuttle.checkHostileEnvironment() + if(mode == SHUTTLE_STRANDED) + return + + success &= (check_transit_zone() == TRANSIT_READY) + for(var/A in SSshuttle.mobile_docking_ports) + var/obj/docking_port/mobile/M = A + if(M.launch_status == UNLAUNCHED) + success &= (M.check_transit_zone() == TRANSIT_READY) + if(!success) + setTimer(ENGINE_START_TIME) + + if(time_left <= 50 && !sound_played) //4 seconds left:REV UP THOSE ENGINES BOYS. - should sync up with the launch + sound_played = 1 //Only rev them up once. + var/list/areas = list() + for(var/area/shuttle/escape/E in GLOB.areas) + areas += E + hyperspace_sound(HYPERSPACE_WARMUP, areas) + + if(time_left <= 0 && !SSshuttle.emergency_no_escape) + //move each escape pod (or applicable spaceship) to its corresponding transit dock + for(var/A in SSshuttle.mobile_docking_ports) + var/obj/docking_port/mobile/M = A + M.on_emergency_launch() + + //now move the actual emergency shuttle to its transit dock + var/list/areas = list() + for(var/area/shuttle/escape/E in GLOB.areas) + areas += E + hyperspace_sound(HYPERSPACE_LAUNCH, areas) + enterTransit() + + //Tell the events we're starting, so they can time their spawns or do some other stuff + for(var/datum/shuttle_event/event as anything in event_list) + event.start_up_event(SSshuttle.emergency_escape_time * engine_coeff) + + mode = SHUTTLE_ESCAPE + launch_status = ENDGAME_LAUNCHED + setTimer(SSshuttle.emergency_escape_time * engine_coeff) + priority_announce( + text = "The emergency shuttle has left the station. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].", + title = "Emergency Shuttle Departure", + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "orange", + ) + INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker, poll_hearts)) + INVOKE_ASYNC(SSvote, TYPE_PROC_REF(/datum/controller/subsystem/vote, initiate_vote), /datum/vote/map_vote, vote_initiator_name = "Map Rotation", forced = TRUE) + + if(!is_reserved_level(z)) + CRASH("Emergency shuttle did not move to transit z-level!") + + if(SHUTTLE_STRANDED, SHUTTLE_DISABLED) + SSshuttle.checkHostileEnvironment() + + + if(SHUTTLE_ESCAPE) + if(sound_played && time_left <= HYPERSPACE_END_TIME) + var/list/areas = list() + for(var/area/shuttle/escape/E in GLOB.areas) + areas += E + hyperspace_sound(HYPERSPACE_END, areas) + if(time_left <= PARALLAX_LOOP_TIME) + var/area_parallax = FALSE + for(var/place in shuttle_areas) + var/area/shuttle/shuttle_area = place + if(shuttle_area.parallax_movedir) + area_parallax = TRUE + break + if(area_parallax) + parallax_slowdown() + for(var/A in SSshuttle.mobile_docking_ports) + var/obj/docking_port/mobile/M = A + if(M.launch_status == ENDGAME_LAUNCHED) + if(istype(M, /obj/docking_port/mobile/pod)) + M.parallax_slowdown() + + process_events() + + if(time_left <= 0) + //move each escape pod to its corresponding escape dock + for(var/obj/docking_port/mobile/port as anything in SSshuttle.mobile_docking_ports) + port.on_emergency_dock() + + // now move the actual emergency shuttle to centcom + // unless the shuttle is "hijacked" + var/destination_dock = "emergency_away" + if(is_hijacked() || elimination_hijack()) + // just double check + SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NUKIEBASE) + destination_dock = "emergency_syndicate" + minor_announce("Corruption detected in \ + shuttle navigation protocols. Please contact your \ + supervisor.", "SYSTEM ERROR:", sound_override = 'sound/announcer/announcement/announce_syndi.ogg') + + dock_id(destination_dock) + mode = SHUTTLE_ENDGAME + timer = 0 + +/obj/docking_port/mobile/emergency/transit_failure() + ..() + message_admins("Moving emergency shuttle directly to centcom dock to prevent deadlock.") + + mode = SHUTTLE_ESCAPE + launch_status = ENDGAME_LAUNCHED + setTimer(SSshuttle.emergency_escape_time) + priority_announce( + text = "The emergency shuttle is preparing for direct jump. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].", + title = "Emergency Shuttle Transit Failure", + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "orange", + ) + +///Generate a list of events to run during the departure +/obj/docking_port/mobile/emergency/proc/setup_shuttle_events() + var/list/names = list() + for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event)) + if(prob(initial(event.event_probability))) + add_shuttle_event(event) + names += initial(event.name) + if(LAZYLEN(names)) + log_game("[capitalize(name)] has selected the following shuttle events: [english_list(names)].") diff --git a/code/modules/shuttle/mobile_port/variants/emergency/emergency_console.dm b/code/modules/shuttle/mobile_port/variants/emergency/emergency_console.dm new file mode 100644 index 0000000000000..b46bfff274307 --- /dev/null +++ b/code/modules/shuttle/mobile_port/variants/emergency/emergency_console.dm @@ -0,0 +1,316 @@ +#define ENGINES_STARTED (SSshuttle.emergency.mode == SHUTTLE_IGNITING) +#define IS_DOCKED (SSshuttle.emergency.mode == SHUTTLE_DOCKED || (ENGINES_STARTED)) +#define SHUTTLE_CONSOLE_ACTION_DELAY (5 SECONDS) +#define TIME_LEFT (SSshuttle.emergency.timeLeft()) + +/obj/machinery/computer/emergency_shuttle + name = "emergency shuttle console" + desc = "For shuttle control." + icon_screen = "shuttle" + icon_keyboard = "tech_key" + resistance_flags = INDESTRUCTIBLE + var/auth_need = 3 + var/list/authorized = list() + var/list/acted_recently = list() + var/hijack_last_stage_increase = 0 SECONDS + var/hijack_stage_time = 5 SECONDS + var/hijack_stage_cooldown = 5 SECONDS + var/hijack_flight_time_increase = 30 SECONDS + var/hijack_completion_flight_time_set = 10 SECONDS //How long in deciseconds to set shuttle's timer after hijack is done. + var/hijack_hacking = FALSE + var/hijack_announce = TRUE + +/obj/machinery/computer/emergency_shuttle/Destroy() + // Our fake IDs that the emag generated are just there for colour + // They're not supposed to be accessible + + for(var/obj/item/card/id/ID in src) + qdel(ID) + if(authorized?.len) + authorized.Cut() + authorized = null + + . = ..() + +/obj/machinery/computer/emergency_shuttle/examine(mob/user) + . = ..() + if(hijack_announce) + . += span_danger("Security systems present on console. Any unauthorized tampering will result in an emergency announcement.") + if(user?.mind?.get_hijack_speed()) + . += span_danger("Alt click on this to attempt to hijack the shuttle. This will take multiple tries (current: stage [SSshuttle.emergency.hijack_status]/[HIJACK_COMPLETED]).") + . += span_notice("It will take you [(hijack_stage_time * user.mind.get_hijack_speed()) / 10] seconds to reprogram a stage of the shuttle's navigational firmware, and the console will undergo automated timed lockout for [hijack_stage_cooldown/10] seconds after each stage.") + if(hijack_announce) + . += span_warning("It is probably best to fortify your position as to be uninterrupted during the attempt, given the automatic announcements..") + +/obj/machinery/computer/emergency_shuttle/attackby(obj/item/I, mob/user,params) + if(isidcard(I)) + say("Please equip your ID card into your ID slot to authenticate.") + . = ..() + +/obj/machinery/computer/emergency_shuttle/ui_state(mob/user) + return GLOB.human_adjacent_state + +/obj/machinery/computer/emergency_shuttle/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "EmergencyShuttleConsole", name) + ui.open() + +/obj/machinery/computer/emergency_shuttle/ui_data(user) + var/list/data = list() + + data["timer_str"] = SSshuttle.emergency.getTimerStr() + data["engines_started"] = ENGINES_STARTED + data["authorizations_remaining"] = max((auth_need - authorized.len), 0) + var/list/A = list() + for(var/i in authorized) + var/obj/item/card/id/ID = i + var/name = ID.registered_name + var/job = ID.assignment + + if(obj_flags & EMAGGED) + name = Gibberish(name) + job = Gibberish(job) + A += list(list("name" = name, "job" = job)) + data["authorizations"] = A + + data["enabled"] = (IS_DOCKED && !ENGINES_STARTED) && !(user in acted_recently) + data["emagged"] = obj_flags & EMAGGED ? 1 : 0 + return data + +/obj/machinery/computer/emergency_shuttle/ui_act(action, params, datum/tgui/ui) + . = ..() + if(.) + return + if(ENGINES_STARTED) // past the point of no return + return + if(!IS_DOCKED) // shuttle computer only has uses when onstation + return + if(SSshuttle.emergency.mode == SHUTTLE_DISABLED) // admins have disabled the shuttle. + return + if(!isliving(usr)) + return + + var/area/my_area = get_area(src) + if(!istype(my_area, /area/shuttle/escape)) + say("Error - Network connectivity: Console has lost connection to the shuttle.") + return + + var/mob/living/user = usr + . = FALSE + + var/obj/item/card/id/ID = user.get_idcard(TRUE) + + if(!ID) + to_chat(user, span_warning("You don't have an ID.")) + return + + if(!(ACCESS_COMMAND in ID.access)) + to_chat(user, span_warning("The access level of your card is not high enough.")) + return + + if (user in acted_recently) + return + + var/old_len = authorized.len + addtimer(CALLBACK(src, PROC_REF(clear_recent_action), user), SHUTTLE_CONSOLE_ACTION_DELAY) + + switch(action) + if("authorize") + . = authorize(user) + + if("repeal") + authorized -= ID + + if("abort") + if(authorized.len) + // Abort. The action for when heads are fighting over whether + // to launch early. + authorized.Cut() + . = TRUE + + if((old_len != authorized.len) && !ENGINES_STARTED) + var/alert = (authorized.len > old_len) + var/repeal = (authorized.len < old_len) + var/remaining = max(0, auth_need - authorized.len) + if(authorized.len && remaining) + minor_announce("[remaining] authorizations needed until shuttle is launched early", null, alert) + if(repeal) + minor_announce("Early launch authorization revoked, [remaining] authorizations needed") + + acted_recently += user + SStgui.update_user_uis(user, src) + +/obj/machinery/computer/emergency_shuttle/proc/authorize(mob/living/user, source) + var/obj/item/card/id/ID = user.get_idcard(TRUE) + + if(ID in authorized) + return FALSE + for(var/i in authorized) + var/obj/item/card/id/other = i + if(other.registered_name == ID.registered_name) + return FALSE // No using IDs with the same name + + authorized += ID + + message_admins("[ADMIN_LOOKUPFLW(user)] has authorized early shuttle launch") + log_shuttle("[key_name(user)] has authorized early shuttle launch in [COORD(src)]") + // Now check if we're on our way + . = TRUE + process(SSMACHINES_DT) + +/obj/machinery/computer/emergency_shuttle/proc/clear_recent_action(mob/user) + acted_recently -= user + if (!QDELETED(user)) + SStgui.update_user_uis(user, src) + +/obj/machinery/computer/emergency_shuttle/process() + // Launch check is in process in case auth_need changes for some reason + // probably external. + . = FALSE + if(!SSshuttle.emergency) + return + + if(SSshuttle.emergency.mode == SHUTTLE_STRANDED) + authorized.Cut() + obj_flags &= ~(EMAGGED) + + if(ENGINES_STARTED || (!IS_DOCKED)) + return . + + // Check to see if we've reached criteria for early launch + if((authorized.len >= auth_need) || (obj_flags & EMAGGED)) + // shuttle timers use 1/10th seconds internally + SSshuttle.emergency.setTimer(ENGINE_START_TIME) + var/system_error = obj_flags & EMAGGED ? "SYSTEM ERROR:" : null + minor_announce("The emergency shuttle will launch in \ + [TIME_LEFT] seconds", system_error, alert=TRUE) + . = TRUE + +/obj/machinery/computer/emergency_shuttle/proc/increase_hijack_stage() + var/obj/docking_port/mobile/emergency/shuttle = SSshuttle.emergency + // Begin loading this early, prevents a delay when the shuttle goes to land + INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, lazy_load_template), LAZY_TEMPLATE_KEY_NUKIEBASE) + + shuttle.hijack_status++ + if(hijack_announce) + announce_hijack_stage() + hijack_last_stage_increase = world.time + say("Navigational protocol error! Rebooting systems.") + if(shuttle.mode == SHUTTLE_ESCAPE) + if(shuttle.hijack_status == HIJACK_COMPLETED) + shuttle.setTimer(hijack_completion_flight_time_set) + else + shuttle.setTimer(shuttle.timeLeft(1) + hijack_flight_time_increase) //give the guy more time to hijack if it's already in flight. + return shuttle.hijack_status + +/obj/machinery/computer/emergency_shuttle/click_alt(mob/living/user) + if(!isliving(user)) + return NONE + attempt_hijack_stage(user) + return CLICK_ACTION_SUCCESS + +/obj/machinery/computer/emergency_shuttle/proc/attempt_hijack_stage(mob/living/user) + if(!user.CanReach(src)) + return + if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) + to_chat(user, span_warning("You need your hands free before you can manipulate [src].")) + return + var/area/my_area = get_area(src) + if(!istype(my_area, /area/shuttle/escape)) + say("Error - Network connectivity: Console has lost connection to the shuttle.") + return + if(!user?.mind?.get_hijack_speed()) + to_chat(user, span_warning("You manage to open a user-mode shell on [src], and hundreds of lines of debugging output fly through your vision. It is probably best to leave this alone.")) + return + if(!EMERGENCY_AT_LEAST_DOCKED) // prevent advancing hijack stages on BYOS shuttles until the shuttle has "docked" + to_chat(user, span_warning("The flight plans for the shuttle haven't been loaded yet, you can't hack this right now.")) + return + if(hijack_hacking == TRUE) + return + if(SSshuttle.emergency.hijack_status >= HIJACK_COMPLETED) + to_chat(user, span_warning("The emergency shuttle is already loaded with a corrupt navigational payload. What more do you want from it?")) + return + if(hijack_last_stage_increase >= world.time - hijack_stage_cooldown) + say("Error - Catastrophic software error detected. Input is currently on timeout.") + return + hijack_hacking = TRUE + to_chat(user, span_boldwarning("You [SSshuttle.emergency.hijack_status == HIJACK_NOT_BEGUN? "begin" : "continue"] to override [src]'s navigational protocols.")) + say("Software override initiated.") + var/turf/console_hijack_turf = get_turf(src) + message_admins("[src] is being overriden for hijack by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(console_hijack_turf)]") + user.log_message("is hijacking [src].", LOG_GAME) + . = FALSE + if(do_after(user, hijack_stage_time * (1 / user.mind.get_hijack_speed()), target = src)) + increase_hijack_stage() + console_hijack_turf = get_turf(src) + message_admins("[ADMIN_LOOKUPFLW(user)] has hijacked [src] in [ADMIN_VERBOSEJMP(console_hijack_turf)]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACK_COMPLETED].") + user.log_message("has hijacked [src]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACK_COMPLETED].", LOG_GAME) + . = TRUE + to_chat(user, span_notice("You reprogram some of [src]'s programming, putting it on timeout for [hijack_stage_cooldown/10] seconds.")) + visible_message( + span_warning("[user.name] appears to be tampering with [src]."), + blind_message = span_hear("You hear someone tapping computer keys."), + vision_distance = COMBAT_MESSAGE_RANGE, + ignored_mobs = user + ) + hijack_hacking = FALSE + +/obj/machinery/computer/emergency_shuttle/proc/announce_hijack_stage() + var/msg + switch(SSshuttle.emergency.hijack_status) + if(HIJACK_NOT_BEGUN) + return + if(HIJACK_STAGE_1) + msg = "AUTHENTICATING - FAIL. AUTHENTICATING - FAIL. AUTHENTICATING - FAI###### Welcome, technician JOHN DOE." + if(HIJACK_STAGE_2) + msg = "Warning: Navigational route fails \"IS_AUTHORIZED\". Please try againNN[scramble_message_replace_chars("againagainagainagainagain", 70)]." + if(HIJACK_STAGE_3) + msg = "CRC mismatch at ~h~ in calculated route buffer. Full reset initiated of FTL_NAVIGATION_SERVICES. Memory decrypted for automatic repair." + if(HIJACK_STAGE_4) + msg = "~ACS_directive module_load(cyberdyne.exploit.nanotrasen.shuttlenav)... NT key mismatch. Confirm load? Y...###Reboot complete. $SET transponder_state = 0; System link initiated with connected engines..." + if(HIJACK_COMPLETED) + msg = "SYSTEM OVERRIDE - Resetting course to \[[scramble_message_replace_chars("###########", 100)]\] \ + ([scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]) \ + {AUTH - ROOT (uid: 0)}.\ + [SSshuttle.emergency.mode == SHUTTLE_ESCAPE ? "Diverting from existing route - Bluespace exit in \ + [hijack_completion_flight_time_set >= INFINITY ? "[scramble_message_replace_chars("\[ERROR\]")]" : hijack_completion_flight_time_set/10] seconds." : ""]" + minor_announce(scramble_message_replace_chars(msg, replaceprob = 10), "Emergency Shuttle", TRUE) + +/obj/machinery/computer/emergency_shuttle/emag_act(mob/user, obj/item/card/emag/emag_card) + // How did you even get on the shuttle before it go to the station? + if(!IS_DOCKED) + return FALSE + + if((obj_flags & EMAGGED) || ENGINES_STARTED) //SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LAUNCH IN 10 SECONDS + balloon_alert(user, "shuttle already about to launch!") + return FALSE + + var/time = TIME_LEFT + if (user) + message_admins("[ADMIN_LOOKUPFLW(user)] has emagged the emergency shuttle [time] seconds before launch.") + log_shuttle("[key_name(user)] has emagged the emergency shuttle in [COORD(src)] [time] seconds before launch.") + else + message_admins("The emergency shuttle was emagged [time] seconds before launch, with no emagger.") + log_shuttle("The emergency shuttle was emagged in [COORD(src)] [time] seconds before launch, with no emagger.") + + obj_flags |= EMAGGED + SSshuttle.emergency.movement_force = list("KNOCKDOWN" = 60, "THROW" = 20)//YOUR PUNY SEATBELTS can SAVE YOU NOW, MORTAL + for(var/i in 1 to 10) + // the shuttle system doesn't know who these people are, but they + // must be important, surely + var/obj/item/card/id/ID = new(src) + var/datum/job/J = pick(SSjob.joinable_occupations) + ID.registered_name = generate_random_name_species_based(species_type = /datum/species/human) + ID.assignment = J.title + + authorized += ID + + process(SSMACHINES_DT) + return TRUE + +#undef TIME_LEFT +#undef ENGINES_STARTED +#undef IS_DOCKED +#undef SHUTTLE_CONSOLE_ACTION_DELAY diff --git a/code/modules/shuttle/mobile_port/variants/emergency/emergency_types.dm b/code/modules/shuttle/mobile_port/variants/emergency/emergency_types.dm new file mode 100644 index 0000000000000..6030999698b00 --- /dev/null +++ b/code/modules/shuttle/mobile_port/variants/emergency/emergency_types.dm @@ -0,0 +1,39 @@ +/// Fallback shuttle +/obj/docking_port/mobile/emergency/backup + name = "backup shuttle" + shuttle_id = "backup" + dir = EAST + +/obj/docking_port/mobile/emergency/backup/Initialize(mapload) + // We want to be a valid emergency shuttle + // but not be the main one, keep whatever's set + // valid. + // backup shuttle ignores `timid` because THERE SHOULD BE NO TOUCHING IT + var/current_emergency = SSshuttle.emergency + . = ..() + SSshuttle.emergency = current_emergency + SSshuttle.backup_shuttle = src + +/obj/docking_port/mobile/emergency/backup/Destroy(force) + if(SSshuttle.backup_shuttle == src) + SSshuttle.backup_shuttle = null + return ..() + +/// Monastery shuttle +/obj/docking_port/mobile/monastery + name = "monastery pod" + shuttle_id = "mining_common" //set so mining can call it down + launch_status = UNLAUNCHED //required for it to launch as a pod. + +/obj/docking_port/mobile/monastery/on_emergency_dock() + if(launch_status == ENDGAME_LAUNCHED) + initiate_docking(SSshuttle.getDock("pod_away")) //docks our shuttle as any pod would + mode = SHUTTLE_ENDGAME + +/// Build Your Own Shuttle (BYOS) kit +/obj/docking_port/mobile/emergency/shuttle_build + +/obj/docking_port/mobile/emergency/shuttle_build/postregister() + . = ..() + initiate_docking(SSshuttle.getDock("emergency_home")) + diff --git a/code/modules/shuttle/mobile_port/variants/emergency/pods.dm b/code/modules/shuttle/mobile_port/variants/emergency/pods.dm new file mode 100644 index 0000000000000..1d8e1bae6bc03 --- /dev/null +++ b/code/modules/shuttle/mobile_port/variants/emergency/pods.dm @@ -0,0 +1,211 @@ +// THIS FILE CONTAINS: Pod mobile/stationary docking port, pod control console, pod storage and pod items + +/obj/docking_port/mobile/pod + name = "escape pod" + shuttle_id = "pod" + launch_status = UNLAUNCHED + +/obj/docking_port/mobile/pod/request(obj/docking_port/stationary/S) + var/obj/machinery/computer/shuttle/connected_computer = get_control_console() + if(!istype(connected_computer, /obj/machinery/computer/shuttle/pod)) + return FALSE + if(!(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED) && !(connected_computer.obj_flags & EMAGGED)) + to_chat(usr, span_warning("Escape pods will only launch during \"Code Red\" security alert.")) + return FALSE + if(launch_status == UNLAUNCHED) + launch_status = EARLY_LAUNCHED + return ..() + +/obj/docking_port/mobile/pod/cancel() + return + +/obj/machinery/computer/shuttle/pod + name = "pod control computer" + locked = TRUE + possible_destinations = "pod_asteroid" + icon = 'icons/obj/machines/wallmounts.dmi' + icon_state = "pod_off" + circuit = /obj/item/circuitboard/computer/emergency_pod + light_color = LIGHT_COLOR_BLUE + density = FALSE + icon_keyboard = null + icon_screen = "pod_on" + +/obj/machinery/computer/shuttle/pod/Initialize(mapload) + . = ..() + RegisterSignal(SSsecurity_level, COMSIG_SECURITY_LEVEL_CHANGED, PROC_REF(check_lock)) + +/obj/machinery/computer/shuttle/pod/emag_act(mob/user, obj/item/card/emag/emag_card) + if(obj_flags & EMAGGED) + return FALSE + obj_flags |= EMAGGED + locked = FALSE + balloon_alert(user, "alert level checking disabled") + icon_screen = "emagged_general" + update_appearance() + return TRUE + +/obj/machinery/computer/shuttle/pod/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) + . = ..() + if(port) + //Checks if the computer has already added the shuttle destination with the initial id + //This has to be done because connect_to_shuttle is called again after its ID is updated + //due to conflicting id names + var/base_shuttle_destination = ";[initial(port.shuttle_id)]_lavaland" + var/shuttle_destination = ";[port.shuttle_id]_lavaland" + + var/position = findtext(possible_destinations, base_shuttle_destination) + if(position) + if(base_shuttle_destination == shuttle_destination) + return + possible_destinations = splicetext(possible_destinations, position, position + length(base_shuttle_destination), shuttle_destination) + return + + possible_destinations += shuttle_destination + +/** + * Signal handler for checking if we should lock or unlock escape pods accordingly to a newly set security level + * + * Arguments: + * * source The datum source of the signal + * * new_level The new security level that is in effect + */ +/obj/machinery/computer/shuttle/pod/proc/check_lock(datum/source, new_level) + SIGNAL_HANDLER + + if(obj_flags & EMAGGED) + return + locked = (new_level < SEC_LEVEL_RED) + +/obj/docking_port/stationary/random + name = "escape pod" + shuttle_id = "pod" + hidden = TRUE + override_can_dock_checks = TRUE + /// The area the pod tries to land at + var/target_area = /area/lavaland/surface/outdoors + /// Minimal distance from the map edge, setting this too low can result in shuttle landing on the edge and getting "sliced" + var/edge_distance = 16 + +/obj/docking_port/stationary/random/Initialize(mapload) + . = ..() + if(!mapload) + return + + var/list/turfs = get_area_turfs(target_area) + var/original_len = turfs.len + while(turfs.len) + var/turf/picked_turf = pick(turfs) + if(picked_turf.x stationary_dock.dwidth) - return SHUTTLE_DWIDTH_TOO_LARGE - - if(width-dwidth > stationary_dock.width-stationary_dock.dwidth) - return SHUTTLE_WIDTH_TOO_LARGE - - if(dheight > stationary_dock.dheight) - return SHUTTLE_DHEIGHT_TOO_LARGE - - if(height-dheight > stationary_dock.height-stationary_dock.dheight) - return SHUTTLE_HEIGHT_TOO_LARGE - - //check the dock isn't occupied - var/currently_docked = stationary_dock.get_docked() - if(currently_docked) - // by someone other than us - if(currently_docked != src) - return SHUTTLE_SOMEONE_ELSE_DOCKED - else - // This isn't an error, per se, but we can't let the shuttle code - // attempt to move us where we currently are, it will get weird. - return SHUTTLE_ALREADY_DOCKED - - return SHUTTLE_CAN_DOCK - -/obj/docking_port/mobile/proc/check_dock(obj/docking_port/stationary/S, silent = FALSE) - var/status = canDock(S) - if(status == SHUTTLE_CAN_DOCK) - return TRUE - else - if(status != SHUTTLE_ALREADY_DOCKED && !silent) // SHUTTLE_ALREADY_DOCKED is no cause for error - message_admins("Shuttle [src] cannot dock at [S], error: [status]") - // We're already docked there, don't need to do anything. - // Triggering shuttle movement code in place is weird - return FALSE - -/obj/docking_port/mobile/proc/transit_failure() - message_admins("Shuttle [src] repeatedly failed to create transit zone.") - -/** - * Calls the shuttle to the destination port, respecting its ignition and call timers - * - * Arguments: - * * destination_port - Stationary docking port to move the shuttle to - */ -/obj/docking_port/mobile/proc/request(obj/docking_port/stationary/destination_port) - if(!check_dock(destination_port)) - testing("check_dock failed on request for [src]") - return - - if(mode == SHUTTLE_IGNITING && destination == destination_port) - return - - switch(mode) - if(SHUTTLE_CALL) - if(destination_port == destination) - if(timeLeft(1) < callTime * engine_coeff) - setTimer(callTime * engine_coeff) - else - destination = destination_port - setTimer(callTime * engine_coeff) - if(SHUTTLE_RECALL) - if(destination_port == destination) - setTimer(callTime * engine_coeff - timeLeft(1)) - else - destination = destination_port - setTimer(callTime * engine_coeff) - mode = SHUTTLE_CALL - if(SHUTTLE_IDLE, SHUTTLE_IGNITING) - destination = destination_port - mode = SHUTTLE_IGNITING - setTimer(ignitionTime) - -//recall the shuttle to where it was previously -/obj/docking_port/mobile/proc/cancel() - if(mode != SHUTTLE_CALL) - return - - remove_ripples() - - invertTimer() - mode = SHUTTLE_RECALL - -/obj/docking_port/mobile/proc/enterTransit() - if((SSshuttle.lockdown && is_station_level(z)) || !canMove()) //emp went off, no escape - mode = SHUTTLE_IDLE - return - previous = null - if(!destination) - // sent to transit with no destination -> unlimited timer - timer = INFINITY - var/obj/docking_port/stationary/S0 = get_docked() - var/obj/docking_port/stationary/S1 = assigned_transit - if(S1) - if(initiate_docking(S1) != DOCKING_SUCCESS) - WARNING("shuttle \"[shuttle_id]\" could not enter transit space. Docked at [S0 ? S0.shuttle_id : "null"]. Transit dock [S1 ? S1.shuttle_id : "null"].") - else if(S0) - if(S0.delete_after) - qdel(S0, TRUE) - else - previous = S0 - else - WARNING("shuttle \"[shuttle_id]\" could not enter transit space. S0=[S0 ? S0.shuttle_id : "null"] S1=[S1 ? S1.shuttle_id : "null"]") - - -/obj/docking_port/mobile/proc/jumpToNullSpace() - // Destroys the docking port and the shuttle contents. - // Not in a fancy way, it just ceases. - var/obj/docking_port/stationary/current_dock = get_docked() - - var/underlying_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA - // If the shuttle is docked to a stationary port, restore its normal - // "empty" area and turf - if(current_dock?.area_type) - underlying_area_type = current_dock.area_type - - var/list/old_turfs = return_ordered_turfs(x, y, z, dir) - - var/area/underlying_area = GLOB.areas_by_type[underlying_area_type] - if(!underlying_area) - underlying_area = new underlying_area_type(null) - - for(var/i in 1 to old_turfs.len) - var/turf/oldT = old_turfs[i] - if(!oldT || !istype(oldT.loc, area_type)) - continue - oldT.change_area(oldT.loc, underlying_area) - oldT.empty(FALSE) - - // Here we locate the bottommost shuttle boundary and remove all turfs above it - var/shuttle_tile_depth = oldT.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle) - if (!isnull(shuttle_tile_depth)) - oldT.ScrapeAway(shuttle_tile_depth) - - qdel(src, force=TRUE) - -/** - * Ghosts and marks as escaped (for greentext purposes) all mobs, then deletes the shuttle. - * Used by the Shuttle Manipulator - */ -/obj/docking_port/mobile/proc/intoTheSunset() - // Loop over mobs - for(var/turf/turfs as anything in return_turfs()) - for(var/mob/living/sunset_mobs in turfs.get_all_contents()) - // If they have a mind and they're not in the brig, they escaped - if(sunset_mobs.mind && !istype(get_area(sunset_mobs), /area/shuttle/escape/brig)) - sunset_mobs.mind.force_escaped = TRUE - // Ghostize them and put them in nullspace stasis (for stat & possession checks) - ADD_TRAIT(sunset_mobs, TRAIT_NO_TRANSFORM, REF(src)) - sunset_mobs.ghostize(FALSE) - sunset_mobs.moveToNullspace() - - // Now that mobs are stowed, delete the shuttle - jumpToNullSpace() - -/obj/docking_port/mobile/proc/create_ripples(obj/docking_port/stationary/S1, animate_time) - var/list/turfs = ripple_area(S1) - for(var/t in turfs) - ripples += new /obj/effect/abstract/ripple(t, animate_time) - -/obj/docking_port/mobile/proc/remove_ripples() - QDEL_LIST(ripples) - -/obj/docking_port/mobile/proc/ripple_area(obj/docking_port/stationary/S1) - var/list/L0 = return_ordered_turfs(x, y, z, dir) - var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir) - - var/list/ripple_turfs = list() - var/stop = min(L0.len, L1.len) - for(var/i in 1 to stop) - var/turf/T0 = L0[i] - var/turf/T1 = L1[i] - if(!istype(T0.loc, area_type) || istype(T0.loc, /area/shuttle/transit)) - continue // not part of the shuttle - ripple_turfs += T1 - - return ripple_turfs - -/obj/docking_port/mobile/proc/check_poddoors() - for(var/obj/machinery/door/poddoor/shuttledock/pod as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor/shuttledock)) - pod.check() - -/obj/docking_port/mobile/proc/dock_id(id) - var/port = SSshuttle.getDock(id) - if(port) - . = initiate_docking(port) - else - . = null - -//used by shuttle subsystem to check timers -/obj/docking_port/mobile/proc/check() - check_effects() - //process_events() if you were to add events to non-escape shuttles, uncomment this - - if(mode == SHUTTLE_IGNITING) - check_transit_zone() - - if(timeLeft(1) > 0) - return - // If we can't dock or we don't have a transit slot, wait for 20 ds, - // then try again - switch(mode) - if(SHUTTLE_CALL, SHUTTLE_PREARRIVAL) - if(prearrivalTime && mode != SHUTTLE_PREARRIVAL) - mode = SHUTTLE_PREARRIVAL - setTimer(prearrivalTime) - return - var/error = initiate_docking(destination, preferred_direction) - if(error && error & (DOCKING_NULL_DESTINATION | DOCKING_NULL_SOURCE)) - var/msg = "A mobile dock in transit exited initiate_docking() with an error. This is most likely a mapping problem: Error: [error], ([src]) ([previous][ADMIN_JMP(previous)] -> [destination][ADMIN_JMP(destination)])" - WARNING(msg) - message_admins(msg) - mode = SHUTTLE_IDLE - return - else if(error) - setTimer(20) - return - if(rechargeTime) - mode = SHUTTLE_RECHARGING - setTimer(rechargeTime) - return - if(SHUTTLE_RECALL) - if(initiate_docking(previous) != DOCKING_SUCCESS) - setTimer(20) - return - if(SHUTTLE_IGNITING) - if(check_transit_zone() != TRANSIT_READY) - setTimer(20) - return - else - mode = SHUTTLE_CALL - setTimer(callTime * engine_coeff) - enterTransit() - return - - mode = SHUTTLE_IDLE - timer = 0 - destination = null - -/obj/docking_port/mobile/proc/check_effects() - if(!ripples.len) - if((mode == SHUTTLE_CALL) || (mode == SHUTTLE_RECALL)) - var/tl = timeLeft(1) - if(tl <= SHUTTLE_RIPPLE_TIME) - create_ripples(destination, tl) - - var/obj/docking_port/stationary/S0 = get_docked() - if(istype(S0, /obj/docking_port/stationary/transit) && timeLeft(1) <= PARALLAX_LOOP_TIME) - for(var/place in shuttle_areas) - var/area/shuttle/shuttle_area = place - if(shuttle_area.parallax_movedir) - parallax_slowdown() - -/obj/docking_port/mobile/proc/parallax_slowdown() - for(var/place in shuttle_areas) - var/area/shuttle/shuttle_area = place - shuttle_area.parallax_movedir = FALSE - if(assigned_transit?.assigned_area) - assigned_transit.assigned_area.parallax_movedir = FALSE - var/list/L0 = return_ordered_turfs(x, y, z, dir) - for (var/thing in L0) - var/turf/T = thing - if(!T || !istype(T.loc, area_type)) - continue - for (var/atom/movable/movable as anything in T) - if (movable.client_mobs_in_contents) - movable.update_parallax_contents() - -/obj/docking_port/mobile/proc/check_transit_zone() - if(assigned_transit) - return TRANSIT_READY - else - SSshuttle.request_transit_dock(src) - -/obj/docking_port/mobile/proc/setTimer(wait) - timer = world.time + wait - last_timer_length = wait - -/obj/docking_port/mobile/proc/modTimer(multiple) - var/time_remaining = timer - world.time - if(time_remaining < 0 || !last_timer_length) - return - time_remaining *= multiple - last_timer_length *= multiple - setTimer(time_remaining) - -/obj/docking_port/mobile/proc/alert_coeff_change(new_coeff) - if(isnull(new_coeff)) - return - - var/time_multiplier = new_coeff / alert_coeff - var/time_remaining = timer - world.time - if(time_remaining < 0 || !last_timer_length) - return - - time_remaining *= time_multiplier - last_timer_length *= time_multiplier - alert_coeff = new_coeff - setTimer(time_remaining) - -/obj/docking_port/mobile/proc/invertTimer() - if(!last_timer_length) - return - var/time_remaining = timer - world.time - if(time_remaining > 0) - var/time_passed = last_timer_length - time_remaining - setTimer(time_passed) - -//returns timeLeft -/obj/docking_port/mobile/proc/timeLeft(divisor) - if(divisor <= 0) - divisor = 10 - - var/ds_remaining - if(!timer) - ds_remaining = callTime * engine_coeff - else - ds_remaining = max(0, timer - world.time) - - . = round(ds_remaining / divisor, 1) - -// returns 3-letter mode string, used by status screens and mob status panel -/obj/docking_port/mobile/proc/getModeStr() - switch(mode) - if(SHUTTLE_IGNITING) - return "IGN" - if(SHUTTLE_RECALL) - return "RCL" - if(SHUTTLE_CALL) - return "ETA" - if(SHUTTLE_DOCKED) - return "ETD" - if(SHUTTLE_ESCAPE) - return "ESC" - if(SHUTTLE_STRANDED) - return "ERR" - if(SHUTTLE_RECHARGING) - return "RCH" - if(SHUTTLE_PREARRIVAL) - return "LDN" - if(SHUTTLE_DISABLED) - return "DIS" - return "" - -// returns 5-letter timer string, used by status screens and mob status panel -/obj/docking_port/mobile/proc/getTimerStr() - if(mode == SHUTTLE_STRANDED || mode == SHUTTLE_DISABLED) - return "--:--" - - var/timeleft = timeLeft() - if(timeleft > 1 HOURS) - return "--:--" - else if(timeleft > 0) - return "[add_leading(num2text((timeleft / 60) % 60), 2, "0")]:[add_leading(num2text(timeleft % 60), 2, "0")]" - else - return "00:00" - -/** - * Gets shuttle location status in a form of string for tgui interfaces - */ -/obj/docking_port/mobile/proc/get_status_text_tgui() - var/obj/docking_port/stationary/dockedAt = get_docked() - var/docked_at = dockedAt?.name || "Unknown" - if(!istype(dockedAt, /obj/docking_port/stationary/transit)) - return docked_at - if(timeLeft() > 1 HOURS) - return "Hyperspace" - else - var/obj/docking_port/stationary/dst = (mode == SHUTTLE_RECALL) ? previous : destination - return "In transit to [dst?.name || "unknown location"]" - -/obj/docking_port/mobile/proc/getStatusText() - var/obj/docking_port/stationary/dockedAt = get_docked() - var/docked_at = dockedAt?.name || "unknown" - if(istype(dockedAt, /obj/docking_port/stationary/transit)) - if (timeLeft() > 1 HOURS) - return "hyperspace" - else - var/obj/docking_port/stationary/dst - if(mode == SHUTTLE_RECALL) - dst = previous - else - dst = destination - . = "transit towards [dst?.name || "unknown location"] ([getTimerStr()])" - else if(mode == SHUTTLE_RECHARGING) - return "[docked_at], recharging [getTimerStr()]" - else - return docked_at - -/obj/docking_port/mobile/proc/getDbgStatusText() - var/obj/docking_port/stationary/dockedAt = get_docked() - . = (dockedAt?.name) ? dockedAt.name : "unknown" - if(istype(dockedAt, /obj/docking_port/stationary/transit)) - var/obj/docking_port/stationary/dst - if(mode == SHUTTLE_RECALL) - dst = previous - else - dst = destination - if(dst) - . = "(transit to) [dst.name || dst.shuttle_id]" - else - . = "(transit to) nowhere" - else if(dockedAt) - . = dockedAt.name || dockedAt.shuttle_id - else - . = "unknown" - - -// attempts to locate /obj/machinery/computer/shuttle with matching ID inside the shuttle -/obj/docking_port/mobile/proc/get_control_console() - for(var/area/shuttle/shuttle_area as anything in shuttle_areas) - var/obj/machinery/computer/shuttle/shuttle_computer = locate(/obj/machinery/computer/shuttle) in shuttle_area - if(!shuttle_computer) - continue - if(shuttle_computer.shuttleId == shuttle_id) - return shuttle_computer - return null - -/obj/docking_port/mobile/proc/hyperspace_sound(phase, list/areas) - var/selected_sound - switch(phase) - if(HYPERSPACE_WARMUP) - selected_sound = "hyperspace_begin" - if(HYPERSPACE_LAUNCH) - selected_sound = "hyperspace_progress" - if(HYPERSPACE_END) - selected_sound = "hyperspace_end" - else - CRASH("Invalid hyperspace sound phase: [phase]") - // This previously was played from each door at max volume, and was one of the worst things I had ever seen. - // Now it's instead played from the nearest engine if close, or the first engine in the list if far since it doesn't really matter. - // Or a door if for some reason the shuttle has no engine, fuck oh hi daniel fuck it - var/range = (engine_coeff * max(width, height)) - var/long_range = range * 2.5 - var/atom/distant_source - - if(engine_list.len) - distant_source = engine_list[1] - else - for(var/our_area in areas) - distant_source = locate(/obj/machinery/door) in our_area - if(distant_source) - break - - if(!distant_source) - return - for(var/mob/zlevel_mobs as anything in SSmobs.clients_by_zlevel[z]) - var/dist_far = get_dist(zlevel_mobs, distant_source) - if(dist_far <= long_range && dist_far > range) - zlevel_mobs.playsound_local(distant_source, "sound/runtime/hyperspace/[selected_sound]_distance.ogg", 100) - else if(dist_far <= range) - var/source - if(!engine_list.len) - source = distant_source - else - var/closest_dist = 10000 - for(var/obj/machinery/power/shuttle_engine/engines as anything in engine_list) - var/dist_near = get_dist(zlevel_mobs, engines) - if(dist_near < closest_dist) - source = engines - closest_dist = dist_near - zlevel_mobs.playsound_local(source, "sound/runtime/hyperspace/[selected_sound].ogg", 100) - -// Losing all initial engines should get you 2 -// Adding another set of engines at 0.5 time -/obj/docking_port/mobile/proc/alter_engines(mod) - if(!mod) - return - var/old_coeff = engine_coeff - engine_coeff = get_engine_coeff(mod) - current_engine_power = max(0, current_engine_power + mod) - if(in_flight()) - var/delta_coeff = engine_coeff / old_coeff - modTimer(delta_coeff) - -// Double initial engines to get to 0.5 minimum -// Lose all initial engines to get to 2 -//For 0 engine shuttles like BYOS 5 engines to get to doublespeed -/obj/docking_port/mobile/proc/get_engine_coeff(engine_mod) - var/new_value = max(0, current_engine_power + engine_mod) - if(new_value == initial_engine_power) - return 1 - if(new_value > initial_engine_power) - var/delta = new_value - initial_engine_power - var/change_per_engine = (1 - ENGINE_COEFF_MIN) / ENGINE_DEFAULT_MAXSPEED_ENGINES // 5 by default - if(initial_engine_power > 0) - change_per_engine = (1 - ENGINE_COEFF_MIN) / initial_engine_power // or however many it had - return clamp(1 - delta * change_per_engine,ENGINE_COEFF_MIN, ENGINE_COEFF_MAX) - if(new_value < initial_engine_power) - var/delta = initial_engine_power - new_value - var/change_per_engine = 1 //doesn't really matter should not be happening for 0 engine shuttles - if(initial_engine_power > 0) - change_per_engine = (ENGINE_COEFF_MAX - 1) / initial_engine_power //just linear drop to max delay - return clamp(1 + delta * change_per_engine, ENGINE_COEFF_MIN, ENGINE_COEFF_MAX) - - -/obj/docking_port/mobile/proc/in_flight() - switch(mode) - if(SHUTTLE_CALL,SHUTTLE_RECALL,SHUTTLE_PREARRIVAL) - return TRUE - if(SHUTTLE_IDLE,SHUTTLE_IGNITING) - return FALSE - return FALSE // hmm - -/obj/docking_port/mobile/emergency/in_flight() - switch(mode) - if(SHUTTLE_ESCAPE) - return TRUE - if(SHUTTLE_STRANDED,SHUTTLE_ENDGAME) - return FALSE - return ..() - -//Called when emergency shuttle leaves the station -/obj/docking_port/mobile/proc/on_emergency_launch() - if(launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to. - launch_status = ENDGAME_LAUNCHED - enterTransit() - -///Let people know shits about to go down -/obj/docking_port/mobile/proc/announce_shuttle_events() - for(var/datum/shuttle_event/event as anything in event_list) - notify_ghosts("The [name] has selected: [event.name]") - -/obj/docking_port/mobile/emergency/on_emergency_launch() - return - -//Called when emergency shuttle docks at centcom -/obj/docking_port/mobile/proc/on_emergency_dock() - // Mapping a new docking point for each ship mappers could potentially want docking with centcom would take up lots of space, - // just let them keep flying off "into the sunset" for their greentext. - if(launch_status == ENDGAME_LAUNCHED) - launch_status = ENDGAME_TRANSIT - -/obj/docking_port/mobile/pod/on_emergency_dock() - if(launch_status == ENDGAME_LAUNCHED) - initiate_docking(SSshuttle.getDock("[shuttle_id]_away")) //Escape pods dock at centcom - mode = SHUTTLE_ENDGAME - -/obj/docking_port/mobile/emergency/on_emergency_dock() - return - -///Process all the shuttle events for every shuttle tick we get -/obj/docking_port/mobile/proc/process_events() - var/list/removees - for(var/datum/shuttle_event/event as anything in event_list) - if(event.event_process() == SHUTTLE_EVENT_CLEAR) //if we return SHUTTLE_EVENT_CLEAR, we clean them up - LAZYADD(removees, event) - for(var/item in removees) - event_list.Remove(item) - -/// Give a typepath of a shuttle event to add to the shuttle. If added during endgame transit, will insta start the event -/obj/docking_port/mobile/proc/add_shuttle_event(typepath) - var/datum/shuttle_event/event = new typepath (src) - event_list.Add(event) - if(launch_status == ENDGAME_LAUNCHED) - event.start_up_event(0) - return event - -#ifdef TESTING -#undef DOCKING_PORT_HIGHLIGHT -#endif diff --git a/code/modules/shuttle/monastery.dm b/code/modules/shuttle/shuttle_consoles/monastery.dm similarity index 100% rename from code/modules/shuttle/monastery.dm rename to code/modules/shuttle/shuttle_consoles/monastery.dm diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm similarity index 100% rename from code/modules/shuttle/navigation_computer.dm rename to code/modules/shuttle/shuttle_consoles/navigation_computer.dm diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/shuttle_consoles/shuttle_console.dm similarity index 100% rename from code/modules/shuttle/computer.dm rename to code/modules/shuttle/shuttle_consoles/shuttle_console.dm diff --git a/code/modules/shuttle/syndicate.dm b/code/modules/shuttle/shuttle_consoles/syndicate.dm similarity index 100% rename from code/modules/shuttle/syndicate.dm rename to code/modules/shuttle/shuttle_consoles/syndicate.dm diff --git a/code/modules/shuttle/white_ship.dm b/code/modules/shuttle/shuttle_consoles/white_ship.dm similarity index 100% rename from code/modules/shuttle/white_ship.dm rename to code/modules/shuttle/shuttle_consoles/white_ship.dm diff --git a/code/modules/shuttle/stationary_port/port_types.dm b/code/modules/shuttle/stationary_port/port_types.dm new file mode 100644 index 0000000000000..047856566c2db --- /dev/null +++ b/code/modules/shuttle/stationary_port/port_types.dm @@ -0,0 +1,100 @@ +/// Subtype for escape pod ports so that we can give them trait behaviour +/obj/docking_port/stationary/escape_pod + name = "escape pod loader" + height = 5 + width = 3 + dwidth = 1 + roundstart_template = /datum/map_template/shuttle/escape_pod/default + /// Set to true if you have a snowflake escape pod dock which needs to always have the normal pod or some other one + var/enforce_specific_pod = FALSE + +/obj/docking_port/stationary/escape_pod/Initialize(mapload) + . = ..() + if (enforce_specific_pod) + return + + if (HAS_TRAIT(SSstation, STATION_TRAIT_SMALLER_PODS)) + roundstart_template = /datum/map_template/shuttle/escape_pod/cramped + return + if (HAS_TRAIT(SSstation, STATION_TRAIT_BIGGER_PODS)) + roundstart_template = /datum/map_template/shuttle/escape_pod/luxury + +// should fit the syndicate infiltrator, and smaller ships like the battlecruiser corvettes and fighters +/obj/docking_port/stationary/syndicate + name = "near the station" + dheight = 1 + dwidth = 12 + height = 17 + width = 23 + shuttle_id = "syndicate_nearby" + +/obj/docking_port/stationary/syndicate/northwest + name = "northwest of station" + shuttle_id = "syndicate_nw" + +/obj/docking_port/stationary/syndicate/northeast + name = "northeast of station" + shuttle_id = "syndicate_ne" + +/obj/docking_port/stationary/transit + name = "In Transit" + override_can_dock_checks = TRUE + /// The turf reservation returned by the transit area request + var/datum/turf_reservation/reserved_area + /// The area created during the transit area reservation + var/area/shuttle/transit/assigned_area + /// The mobile port that owns this transit port + var/obj/docking_port/mobile/owner + +/obj/docking_port/stationary/transit/Initialize(mapload) + . = ..() + SSshuttle.transit_docking_ports += src + +/obj/docking_port/stationary/transit/Destroy(force=FALSE) + if(force) + if(get_docked()) + log_world("A transit dock was destroyed while something was docked to it.") + SSshuttle.transit_docking_ports -= src + if(owner) + if(owner.assigned_transit == src) + owner.assigned_transit = null + owner = null + if(!QDELETED(reserved_area)) + qdel(reserved_area) + reserved_area = null + return ..() + +/obj/docking_port/stationary/picked + ///Holds a list of map name strings for the port to pick from + var/list/shuttlekeys + +/obj/docking_port/stationary/picked/Initialize(mapload) + . = ..() + if(!LAZYLEN(shuttlekeys)) + WARNING("Random docking port [shuttle_id] loaded with no shuttle keys") + return + var/selectedid = pick(shuttlekeys) + roundstart_template = SSmapping.shuttle_templates[selectedid] + +/obj/docking_port/stationary/picked/whiteship + name = "Deep Space" + shuttle_id = "whiteship_away" + height = 45 //Width and height need to remain in sync with the size of whiteshipdock.dmm, otherwise we'll get overflow + width = 44 + dheight = 18 + dwidth = 18 + dir = 2 + shuttlekeys = list( + "whiteship_meta", + "whiteship_pubby", + "whiteship_box", + "whiteship_cere", + "whiteship_kilo", + "whiteship_donut", + "whiteship_delta", + "whiteship_tram", + "whiteship_personalshuttle", + "whiteship_obelisk", + "whiteship_birdshot", + ) + diff --git a/code/modules/shuttle/stationary_port/stationary_port.dm b/code/modules/shuttle/stationary_port/stationary_port.dm new file mode 100644 index 0000000000000..49437730cb071 --- /dev/null +++ b/code/modules/shuttle/stationary_port/stationary_port.dm @@ -0,0 +1,91 @@ + +/obj/docking_port/stationary + name = "dock" + + var/last_dock_time + + /// Map template to load when the dock is loaded + var/datum/map_template/shuttle/roundstart_template + /// Used to check if the shuttle template is enabled in the config file + var/json_key + ///If true, the shuttle can always dock at this docking port, despite its area checks, or if something is already docked + var/override_can_dock_checks = FALSE + +/obj/docking_port/stationary/Initialize(mapload) + . = ..() + register() + if(!area_type) + var/area/place = get_area(src) + area_type = place?.type // We might be created in nullspace + + if(mapload) + for(var/turf/T in return_turfs()) + T.turf_flags |= NO_RUINS + + if(SSshuttle.initialized) + INVOKE_ASYNC(SSshuttle, TYPE_PROC_REF(/datum/controller/subsystem/shuttle, setup_shuttles), list(src)) + +#ifdef TESTING + highlight("#f00") +#endif + +/obj/docking_port/stationary/Destroy(force) + if(force) + unregister() + return ..() + +/obj/docking_port/stationary/register(replace = FALSE) + . = ..() + if(!shuttle_id) + shuttle_id = "dock" + else + port_destinations = shuttle_id + + if(!name) + name = "dock" + + var/counter = SSshuttle.assoc_stationary[shuttle_id] + if(!replace || !counter) + if(counter) + counter++ + SSshuttle.assoc_stationary[shuttle_id] = counter + shuttle_id = "[shuttle_id]_[counter]" + name = "[name] [counter]" + else + SSshuttle.assoc_stationary[shuttle_id] = 1 + + if(!port_destinations) + port_destinations = shuttle_id + + SSshuttle.stationary_docking_ports += src + +/obj/docking_port/stationary/unregister() + . = ..() + SSshuttle.stationary_docking_ports -= src + +/obj/docking_port/stationary/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) + . = ..() + if(area_type) // We already have one + return + var/area/newarea = get_area(src) + area_type = newarea?.type + +/obj/docking_port/stationary/proc/load_roundstart() + if(json_key) + var/sid = SSmapping.current_map.shuttles[json_key] + roundstart_template = SSmapping.shuttle_templates[sid] + if(!roundstart_template) + CRASH("json_key:[json_key] value \[[sid]\] resulted in a null shuttle template for [src]") + else if(roundstart_template) // passed a PATH + var/sid = "[initial(roundstart_template.port_id)]_[initial(roundstart_template.suffix)]" + + roundstart_template = SSmapping.shuttle_templates[sid] + if(!roundstart_template) + CRASH("Invalid path ([sid]/[roundstart_template]) passed to docking port.") + + if(roundstart_template) + SSshuttle.action_load(roundstart_template, src) + +//returns first-found touching shuttleport +/obj/docking_port/stationary/get_docked() + . = locate(/obj/docking_port/mobile) in loc diff --git a/tgstation.dme b/tgstation.dme index 8256e959611e4..135f7be0446e2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -466,6 +466,7 @@ #include "code\__HELPERS\screen_objs.dm" #include "code\__HELPERS\see_through_maps.dm" #include "code\__HELPERS\shell.dm" +#include "code\__HELPERS\shuttle.dm" #include "code\__HELPERS\spatial_info.dm" #include "code\__HELPERS\spawns.dm" #include "code\__HELPERS\stack_trace.dm" @@ -5937,28 +5938,32 @@ #include "code\modules\research\xenobiology\vatgrowing\samples\viruses\_virus.dm" #include "code\modules\security_levels\keycard_authentication.dm" #include "code\modules\security_levels\security_level_datums.dm" -#include "code\modules\shuttle\arrivals.dm" -#include "code\modules\shuttle\assault_pod.dm" -#include "code\modules\shuttle\battlecruiser_starfury.dm" -#include "code\modules\shuttle\computer.dm" -#include "code\modules\shuttle\docking.dm" -#include "code\modules\shuttle\elevator.dm" -#include "code\modules\shuttle\emergency.dm" -#include "code\modules\shuttle\ferry.dm" -#include "code\modules\shuttle\infiltrator.dm" -#include "code\modules\shuttle\manipulator.dm" -#include "code\modules\shuttle\medisim.dm" -#include "code\modules\shuttle\monastery.dm" -#include "code\modules\shuttle\navigation_computer.dm" -#include "code\modules\shuttle\on_move.dm" -#include "code\modules\shuttle\ripple.dm" #include "code\modules\shuttle\shuttle.dm" -#include "code\modules\shuttle\shuttle_rotate.dm" -#include "code\modules\shuttle\spaceship_navigation_beacon.dm" -#include "code\modules\shuttle\special.dm" -#include "code\modules\shuttle\supply.dm" -#include "code\modules\shuttle\syndicate.dm" -#include "code\modules\shuttle\white_ship.dm" +#include "code\modules\shuttle\misc\manipulator.dm" +#include "code\modules\shuttle\misc\medisim.dm" +#include "code\modules\shuttle\misc\ripple.dm" +#include "code\modules\shuttle\misc\spaceship_navigation_beacon.dm" +#include "code\modules\shuttle\misc\special.dm" +#include "code\modules\shuttle\mobile_port\mobile_port.dm" +#include "code\modules\shuttle\mobile_port\shuttle_move.dm" +#include "code\modules\shuttle\mobile_port\shuttle_move_callbacks.dm" +#include "code\modules\shuttle\mobile_port\shuttle_rotate_callbacks.dm" +#include "code\modules\shuttle\mobile_port\variants\arrivals.dm" +#include "code\modules\shuttle\mobile_port\variants\assault_pod.dm" +#include "code\modules\shuttle\mobile_port\variants\battlecruiser_starfury.dm" +#include "code\modules\shuttle\mobile_port\variants\elevator.dm" +#include "code\modules\shuttle\mobile_port\variants\ferry.dm" +#include "code\modules\shuttle\mobile_port\variants\infiltrator.dm" +#include "code\modules\shuttle\mobile_port\variants\supply.dm" +#include "code\modules\shuttle\mobile_port\variants\emergency\emergency.dm" +#include "code\modules\shuttle\mobile_port\variants\emergency\emergency_console.dm" +#include "code\modules\shuttle\mobile_port\variants\emergency\emergency_types.dm" +#include "code\modules\shuttle\mobile_port\variants\emergency\pods.dm" +#include "code\modules\shuttle\shuttle_consoles\monastery.dm" +#include "code\modules\shuttle\shuttle_consoles\navigation_computer.dm" +#include "code\modules\shuttle\shuttle_consoles\shuttle_console.dm" +#include "code\modules\shuttle\shuttle_consoles\syndicate.dm" +#include "code\modules\shuttle\shuttle_consoles\white_ship.dm" #include "code\modules\shuttle\shuttle_events\_shuttle_events.dm" #include "code\modules\shuttle\shuttle_events\blackhole.dm" #include "code\modules\shuttle\shuttle_events\carp.dm" @@ -5968,6 +5973,8 @@ #include "code\modules\shuttle\shuttle_events\player_controlled.dm" #include "code\modules\shuttle\shuttle_events\projectile.dm" #include "code\modules\shuttle\shuttle_events\turbulence.dm" +#include "code\modules\shuttle\stationary_port\port_types.dm" +#include "code\modules\shuttle\stationary_port\stationary_port.dm" #include "code\modules\spatial_grid\cell_tracker.dm" #include "code\modules\spells\spell.dm" #include "code\modules\spells\spell_types\madness_curse.dm"