From 5ec5eb80c47fdb549c27c7e87208492a80b5b03f Mon Sep 17 00:00:00 2001 From: Iajret Creature <122297233+Steals-The-PRs@users.noreply.github.com> Date: Thu, 29 Feb 2024 21:19:52 +0300 Subject: [PATCH] [MIRROR] General maintenance for frames (#2147) * General maintenance for frames (#81473) ## About The Pull Request Builds upon the changes in https://github.com/tgstation/tgstation/pull/81477 **1. Qol** - Adds detailed examines & screentips for building & deconstructing both machine & computer frames - Adding a circuitboard from a rped to a computer frame will automatically screw it in place like before **2. Code Improvements** - Merged procs like `update_path_names()` , `get_requested_amt()` etc into their required places to reduce proc calls - Autodocs procs like `add_cable()`, `add_glass()` etc. makes them private - Moved code for machine frame into its own file * General maintenance for frames --------- Co-authored-by: NovaBot <154629622+NovaBot13@users.noreply.github.com> Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> --- code/__DEFINES/construction/structures.dm | 2 +- .../game/machinery/computer/buildandrepair.dm | 233 ++++++--- code/game/machinery/constructable_frame.dm | 424 +--------------- code/game/machinery/machine_frame.dm | 474 ++++++++++++++++++ tgstation.dme | 1 + 5 files changed, 645 insertions(+), 489 deletions(-) create mode 100644 code/game/machinery/machine_frame.dm diff --git a/code/__DEFINES/construction/structures.dm b/code/__DEFINES/construction/structures.dm index 453de8ebf05..e52b82f248e 100644 --- a/code/__DEFINES/construction/structures.dm +++ b/code/__DEFINES/construction/structures.dm @@ -62,7 +62,7 @@ #define GEAR_SECURE 1 #define GEAR_LOOSE 2 -// Stationary gas tanks +//Stationary gas tanks #define TANK_FRAME 0 #define TANK_PLATING_UNSECURED 1 diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index 8446fb4a9d9..1274a6796eb 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -9,6 +9,92 @@ /obj/structure/frame/computer/Initialize(mapload) . = ..() AddComponent(/datum/component/simple_rotation) + register_context() + update_appearance(UPDATE_ICON_STATE) + +/obj/structure/frame/computer/deconstruct(disassembled = TRUE) + if(!(obj_flags & NO_DECONSTRUCTION)) + var/atom/drop_loc = drop_location() + if(state == FRAME_COMPUTER_STATE_GLASSED) + if(disassembled) + new /obj/item/stack/sheet/glass(drop_loc, 2) + else + new /obj/item/shard(drop_loc) + new /obj/item/shard(drop_loc) + if(state >= FRAME_COMPUTER_STATE_WIRED) + new /obj/item/stack/cable_coil(drop_loc, 5) + + return ..() + +/obj/structure/frame/computer/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = NONE + if(isnull(held_item)) + return + + switch(state) + if(FRAME_COMPUTER_STATE_EMPTY) + if(held_item.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor" + return CONTEXTUAL_SCREENTIP_SET + else if(anchored && istype(held_item, /obj/item/circuitboard/computer)) + context[SCREENTIP_CONTEXT_LMB] = "Install board" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_WELDER) + context[SCREENTIP_CONTEXT_LMB] = "Unweld frame" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "Disassemble frame" + return CONTEXTUAL_SCREENTIP_SET + if(FRAME_COMPUTER_STATE_BOARD_INSTALLED) + if(held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Pry out board" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "Secure board" + return CONTEXTUAL_SCREENTIP_SET + if(FRAME_COMPUTER_STATE_BOARD_SECURED) + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "Unsecure board" + return CONTEXTUAL_SCREENTIP_SET + else if(istype(held_item, /obj/item/stack/cable_coil)) + context[SCREENTIP_CONTEXT_LMB] = "Install cable" + return CONTEXTUAL_SCREENTIP_SET + if(FRAME_COMPUTER_STATE_WIRED) + if(held_item.tool_behaviour == TOOL_WIRECUTTER) + context[SCREENTIP_CONTEXT_LMB] = "Cut out cable" + return CONTEXTUAL_SCREENTIP_SET + else if(istype(held_item, /obj/item/stack/sheet/glass)) + context[SCREENTIP_CONTEXT_LMB] = "Install panel" + return CONTEXTUAL_SCREENTIP_SET + if(FRAME_COMPUTER_STATE_GLASSED) + if(held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Pry out glass" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "Complete frame" + return CONTEXTUAL_SCREENTIP_SET + +/obj/structure/frame/computer/examine(user) + . = ..() + + switch(state) + if(FRAME_STATE_EMPTY) + . += span_notice("It can be [EXAMINE_HINT("wrenched")] [anchored ? "loose" : "in place"].") + if(anchored) + . += span_warning("It's missing a circuit board.") + . += span_notice("It can be [EXAMINE_HINT("welded")] or [EXAMINE_HINT("screwed")] apart.") + if(FRAME_COMPUTER_STATE_BOARD_INSTALLED) + . += span_warning("An [circuit.name] is installed and should be [EXAMINE_HINT("screwed")] in place.") + . += span_notice("The circuit board can be [EXAMINE_HINT("pried")] out.") + if(FRAME_COMPUTER_STATE_BOARD_SECURED) + . += span_warning("Its requires [EXAMINE_HINT("5 cable")] pieces to wire it.") + . += span_notice("The circuit board can be [EXAMINE_HINT("screwed")] loose.") + if(FRAME_COMPUTER_STATE_WIRED) + . += span_notice("The wires can be cut out with [EXAMINE_HINT("wire cutters")].") + . += span_warning("Its requires [EXAMINE_HINT("2 glass")] sheets to complete the screen.") + if(FRAME_COMPUTER_STATE_GLASSED) + . += span_notice("The screen can be [EXAMINE_HINT("pried")] out.") + . += span_notice("The moniter can be [EXAMINE_HINT("screwed")] to complete it") /obj/structure/frame/computer/circuit_added(obj/item/circuitboard/added) state = FRAME_COMPUTER_STATE_BOARD_INSTALLED @@ -22,7 +108,45 @@ if(state != FRAME_COMPUTER_STATE_EMPTY) balloon_alert(user, "circuit already installed!") return FALSE - return ..() + if(!anchored && istype(board)) + balloon_alert(user, "frame must be anchored!") + return FALSE + . = ..() + if(. && !by_hand) // Installing via RPED auto-secures it + state = FRAME_COMPUTER_STATE_BOARD_SECURED + update_appearance(UPDATE_ICON_STATE) + return . + +/obj/structure/frame/computer/install_parts_from_part_replacer(mob/living/user, obj/item/storage/part_replacer/replacer, no_sound = FALSE) + switch(state) + if(FRAME_COMPUTER_STATE_BOARD_SECURED) + var/obj/item/stack/cable_coil/cable = locate() in replacer + if(isnull(cable)) + return FALSE + + if(add_cabling(user, cable, time = 0)) + if(!no_sound) + replacer.play_rped_sound() + if(replacer.works_from_distance) + user.Beam(src, icon_state = "rped_upgrade", time = 0.5 SECONDS) + no_sound = TRUE + return install_parts_from_part_replacer(user, replacer, no_sound = no_sound) // Recursive call to handle the next part + + return FALSE + + if(FRAME_COMPUTER_STATE_WIRED) + var/obj/item/stack/sheet/glass/glass_sheets = locate() in replacer + if(isnull(glass_sheets)) + return FALSE + + if(add_glass(user, glass_sheets, time = 0)) + if(!no_sound) + replacer.play_rped_sound() + if(replacer.works_from_distance) + user.Beam(src, icon_state = "rped_upgrade", time = 0.5 SECONDS) + return TRUE + + return FALSE /obj/structure/frame/computer/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) . = ..() @@ -48,11 +172,9 @@ if(istype(tool, /obj/item/storage/part_replacer)) return install_parts_from_part_replacer(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING - return . - /obj/structure/frame/computer/screwdriver_act(mob/living/user, obj/item/tool) . = ..() - if(. != NONE) + if(. & ITEM_INTERACT_ANY_BLOCKER) return . switch(state) @@ -83,6 +205,9 @@ return ITEM_INTERACT_BLOCKING /obj/structure/frame/computer/crowbar_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return NONE + switch(state) if(FRAME_COMPUTER_STATE_BOARD_INSTALLED) tool.play_tool_sound(src) @@ -92,14 +217,12 @@ return ITEM_INTERACT_SUCCESS if(FRAME_COMPUTER_STATE_BOARD_SECURED) - if(!user.combat_mode) - balloon_alert(user, "unsecure the circuit!") - return ITEM_INTERACT_BLOCKING + balloon_alert(user, "unsecure the circuit!") + return ITEM_INTERACT_BLOCKING if(FRAME_COMPUTER_STATE_WIRED) - if(!user.combat_mode) - balloon_alert(user, "remove the wiring!") - return ITEM_INTERACT_BLOCKING + balloon_alert(user, "remove the wiring!") + return ITEM_INTERACT_BLOCKING if(FRAME_COMPUTER_STATE_GLASSED) tool.play_tool_sound(src) @@ -111,38 +234,34 @@ dropped_glass.add_fingerprint(user) return ITEM_INTERACT_SUCCESS -/obj/structure/frame/computer/install_parts_from_part_replacer(mob/living/user, obj/item/storage/part_replacer/replacer, no_sound = FALSE) - switch(state) - if(FRAME_COMPUTER_STATE_BOARD_SECURED) - var/obj/item/stack/cable_coil/cable = locate() in replacer - if(isnull(cable)) - return FALSE - - if(add_cabling(user, cable, time = 0)) - if(!no_sound) - replacer.play_rped_sound() - if(replacer.works_from_distance) - user.Beam(src, icon_state = "rped_upgrade", time = 0.5 SECONDS) - no_sound = TRUE - return install_parts_from_part_replacer(user, replacer, no_sound = no_sound) // Recursive call to handle the next part - - return FALSE +/obj/structure/frame/computer/wirecutter_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return NONE - if(FRAME_COMPUTER_STATE_WIRED) - var/obj/item/stack/sheet/glass/glass_sheets = locate() in replacer - if(isnull(glass_sheets)) - return FALSE + if(state != FRAME_COMPUTER_STATE_WIRED) + return ITEM_INTERACT_BLOCKING - if(add_glass(user, glass_sheets, time = 0)) - if(!no_sound) - replacer.play_rped_sound() - if(replacer.works_from_distance) - user.Beam(src, icon_state = "rped_upgrade", time = 0.5 SECONDS) - return TRUE + tool.play_tool_sound(src) + balloon_alert(user, "cables removed") + state = FRAME_COMPUTER_STATE_BOARD_SECURED + update_appearance(UPDATE_ICON_STATE) - return FALSE + var/obj/item/stack/cable_coil/dropped_cables = new (drop_location(), 5) + if (!QDELETED(dropped_cables)) + dropped_cables.add_fingerprint(user) + return ITEM_INTERACT_SUCCESS +/** + * Adds cable to the computer to wire it + * Arguments + * + * * mob/living/user - the player who is adding the cable + * * obj/item/stack/cable_coil/cable - the cable we are trying to add + * * time - time taken to complete the operation + */ /obj/structure/frame/computer/proc/add_cabling(mob/living/user, obj/item/stack/cable_coil/cable, time = 2 SECONDS) + PRIVATE_PROC(TRUE) + if(state != FRAME_COMPUTER_STATE_BOARD_SECURED) return FALSE if(!cable.tool_start_check(user, amount = 5)) @@ -156,7 +275,17 @@ update_appearance(UPDATE_ICON_STATE) return TRUE +/** + * Adds glass sheets to the computer to complete the screen + * Arguments + * + * * mob/living/user - the player who is adding the glass + * * obj/item/stack/sheet/glass/glass - the glass we are trying to add + * * time - time taken to complete the operation + */ /obj/structure/frame/computer/proc/add_glass(mob/living/user, obj/item/stack/sheet/glass/glass, time = 2 SECONDS) + PRIVATE_PROC(TRUE) + if(state != FRAME_COMPUTER_STATE_WIRED) return FALSE if(!glass.tool_start_check(user, amount = 2)) @@ -171,22 +300,6 @@ update_appearance(UPDATE_ICON_STATE) return TRUE -/obj/structure/frame/computer/wirecutter_act(mob/living/user, obj/item/tool) - if(state != FRAME_COMPUTER_STATE_WIRED) - return NONE - - tool.play_tool_sound(src) - balloon_alert(user, "cables removed") - state = FRAME_COMPUTER_STATE_BOARD_SECURED - update_appearance(UPDATE_ICON_STATE) - var/obj/item/stack/cable_coil/dropped_cables = new (drop_location(), 5) - if (!QDELETED(dropped_cables)) - dropped_cables.add_fingerprint(user) - return ITEM_INTERACT_SUCCESS - -/obj/structure/frame/computer/AltClick(mob/user) - return ..() // This hotkey is BLACKLISTED since it's used by /datum/component/simple_rotation - /obj/structure/frame/computer/finalize_construction(mob/living/user, obj/item/tool) tool.play_tool_sound(src) var/obj/machinery/new_machine = new circuit.build_path(loc) @@ -221,20 +334,6 @@ qdel(src) return TRUE -/obj/structure/frame/computer/deconstruct(disassembled = TRUE) - if(!(obj_flags & NO_DECONSTRUCTION)) - var/atom/drop_loc = drop_location() - if(state == FRAME_COMPUTER_STATE_GLASSED) - if(disassembled) - new /obj/item/stack/sheet/glass(drop_loc, 2) - else - new /obj/item/shard(drop_loc) - new /obj/item/shard(drop_loc) - if(state >= FRAME_COMPUTER_STATE_WIRED) - new /obj/item/stack/cable_coil(drop_loc, 5) - - return ..() - /// Helpers for rcd /obj/structure/frame/computer/rcd icon = 'icons/hud/radial.dmi' diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index 497babd2e42..e624e3f33d7 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -24,7 +24,7 @@ new /obj/item/stack/sheet/iron(drop_loc, 5) circuit?.forceMove(drop_loc) - qdel(src) + return ..() /// Called when circuit has been set to a new board /obj/structure/frame/proc/circuit_added(obj/item/circuitboard/added) @@ -97,13 +97,13 @@ return FALSE /obj/structure/frame/wrench_act(mob/living/user, obj/item/tool) + . = NONE switch(default_unfasten_wrench(user, tool, 4 SECONDS)) if(SUCCESSFUL_UNFASTEN) return ITEM_INTERACT_SUCCESS if(FAILED_UNFASTEN) return ITEM_INTERACT_BLOCKING - - return NONE + return . /obj/structure/frame/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) . = ..() @@ -201,421 +201,3 @@ */ /obj/structure/frame/proc/install_parts_from_part_replacer(mob/living/user, obj/item/storage/part_replacer/replacer, no_sound = FALSE) return FALSE - -/obj/structure/frame/machine - name = "machine frame" - desc = "The standard frame for most station appliances. Its appearance and function is controlled by the inserted board." - board_type = /obj/item/circuitboard/machine - /// List of all compnents inside the frame contributing to its construction - var/list/components - /// List of all components required to construct the frame - var/list/req_components - /// User-friendly list of names of required components - var/list/req_component_names - -/obj/structure/frame/machine/examine(user) - . = ..() - if(state != FRAME_STATE_BOARD_INSTALLED) - return . - - if(!length(req_components)) - . += span_info("It requires no components.") - return . - - if(!req_component_names) - stack_trace("[src]'s req_components list has items but its req_component_names list is null!") - return . - - var/list/nice_list = list() - for(var/component in req_components) - if(!ispath(component)) - stack_trace("An item in [src]'s req_components list is not a path!") - continue - if(!req_components[component]) - continue - - nice_list += list("[req_components[component]] [req_component_names[component]]\s") - . += span_info("It requires [english_list(nice_list, "no more components")].") - return . - -/** - * Collates the displayed names of the machine's components - * - * Arguments: - * * specific_parts - If true, the component should not use base name, but a specific tier - */ -/obj/structure/frame/machine/proc/update_namelist(specific_parts) - if(!req_components) - return - - req_component_names = list() - for(var/component_path in req_components) - if(!ispath(component_path)) - continue - - if(ispath(component_path, /obj/item/stack)) - var/obj/item/stack/stack_path = component_path - if(initial(stack_path.singular_name)) - req_component_names[component_path] = initial(stack_path.singular_name) - else - req_component_names[component_path] = initial(stack_path.name) - else if(ispath(component_path, /datum/stock_part)) - var/datum/stock_part/stock_part = component_path - var/obj/item/physical_object_type = initial(stock_part.physical_object_type) - - req_component_names[component_path] = initial(physical_object_type.name) - else if(ispath(component_path, /obj/item/stock_parts)) - var/obj/item/stock_parts/stock_part = component_path - - if(!specific_parts && initial(stock_part.base_name)) - req_component_names[component_path] = initial(stock_part.base_name) - else - req_component_names[component_path] = initial(stock_part.name) - else if(ispath(component_path, /obj/item)) - var/obj/item/part = component_path - - req_component_names[component_path] = initial(part.name) - else - stack_trace("Invalid component part [component_path] in [type], couldn't get its name") - req_component_names[component_path] = "[component_path] (this is a bug)" - -/obj/structure/frame/machine/proc/get_req_components_amt() - var/amt = 0 - for(var/path in req_components) - amt += req_components[path] - return amt - -/obj/structure/frame/machine/try_dissassemble(mob/living/user, obj/item/tool, disassemble_time) - if(anchored) - balloon_alert(user, "must be unsecured first!") - return FALSE - return ..() - -/obj/structure/frame/machine/install_board(mob/living/user, obj/item/circuitboard/machine/board, by_hand = TRUE) - if(state == FRAME_STATE_EMPTY) - balloon_alert(user, "needs wiring!") - return FALSE - if(state == FRAME_STATE_BOARD_INSTALLED) - balloon_alert(user, "circuit already installed!") - return FALSE - if(!anchored && istype(board) && board.needs_anchored) - balloon_alert(user, "frame must be anchored!") - return FALSE - - return ..() - -/obj/structure/frame/machine/circuit_added(obj/item/circuitboard/machine/added) - state = FRAME_STATE_BOARD_INSTALLED - update_appearance(UPDATE_ICON_STATE) - //add circuit board as the first component to the list of components - //required for part_replacer to locate it while exchanging parts - //so it does not early return in /obj/machinery/proc/exchange_parts - components = list(circuit) - req_components = added.req_components.Copy() - update_namelist(added.specific_parts) - -/obj/structure/frame/machine/circuit_removed(obj/item/circuitboard/machine/removed) - state = FRAME_STATE_WIRED - update_appearance(UPDATE_ICON_STATE) - -/obj/structure/frame/machine/install_parts_from_part_replacer(mob/living/user, obj/item/storage/part_replacer/replacer, no_sound = FALSE) - if(!length(replacer.contents) || !get_req_components_amt()) - return FALSE - - var/play_sound = FALSE - var/list/part_list = replacer.get_sorted_parts() //parts sorted in order of tier - for(var/path in req_components) - var/target_path - if(ispath(path, /datum/stock_part)) - var/datum/stock_part/datum_part = path - target_path = initial(datum_part.physical_object_base_type) - else - target_path = path - - var/obj/item/part - while(req_components[path] > 0 && (part = look_for(part_list, target_path, ispath(path, /obj/item/stack/ore/bluespace_crystal) ? /obj/item/stack/sheet/bluespace_crystal : null))) - part_list -= part - if(istype(part, /obj/item/stack)) - var/obj/item/stack/S = part - var/used_amt = min(round(S.get_amount()), req_components[path]) - var/stack_name = S.singular_name - if(!used_amt || !S.use(used_amt)) - continue - req_components[path] -= used_amt - // No balloon alert here so they can look back and see what they added - to_chat(user, span_notice("You add [used_amt] [stack_name] to [src].")) - play_sound = TRUE - else if(replacer.atom_storage.attempt_remove(part, src)) - var/stock_part_datum = GLOB.stock_part_datums_per_object[part.type] - if (!isnull(stock_part_datum)) - components += stock_part_datum - qdel(part) - else - components += part - part.forceMove(src) - req_components[path]-- - // No balloon alert here so they can look back and see what they added - to_chat(user, span_notice("You add [part] to [src].")) - play_sound = TRUE - - if(play_sound && !no_sound) - replacer.play_rped_sound() - if(replacer.works_from_distance) - user.Beam(src, icon_state = "rped_upgrade", time = 0.5 SECONDS) - return TRUE - -/** - * Attempts to add the passed part to the frame - * - * Requires no sanity check that the passed part is a stock part - * - * Arguments - * * user - the player - * * tool - the part to add - */ -/obj/structure/frame/machine/proc/add_part(mob/living/user, obj/item/tool) - for(var/stock_part_base in req_components) - if (req_components[stock_part_base] == 0) - continue - - var/stock_part_path - - if(ispath(stock_part_base, /obj/item)) - stock_part_path = stock_part_base - else if(ispath(stock_part_base, /datum/stock_part)) - var/datum/stock_part/stock_part_datum_type = stock_part_base - stock_part_path = initial(stock_part_datum_type.physical_object_type) - else - stack_trace("Bad stock part in req_components: [stock_part_base]") - continue - - //if we require an bluespace crystall and we have an full sheet of them we can allow that - if(ispath(stock_part_path, /obj/item/stack/ore/bluespace_crystal) && istype(tool, /obj/item/stack/sheet/bluespace_crystal)) - //allow it - pass() - else if(!istype(tool, stock_part_path)) - continue - - if(isstack(tool)) - var/obj/item/stack/S = tool - var/used_amt = min(round(S.get_amount()), req_components[stock_part_path]) - if(used_amt && S.use(used_amt)) - req_components[stock_part_path] -= used_amt - // No balloon alert here so they can look back and see what they added - to_chat(user, span_notice("You add [tool] to [src].")) - return - - // We might end up qdel'ing the part if it's a stock part datum. - // In practice, this doesn't have side effects to the name, - // but academically we should not be using an object after it's deleted. - var/part_name = "[tool]" - - if (ispath(stock_part_base, /datum/stock_part)) - // We can't just reuse stock_part_path here or its singleton, - // or else putting in a tier 2 part will deconstruct to a tier 1 part. - var/stock_part_datum = GLOB.stock_part_datums_per_object[tool.type] - if (isnull(stock_part_datum)) - stack_trace("tool.type] does not have an associated stock part datum!") - continue - - components += stock_part_datum - - // We regenerate the stock parts on deconstruct. - // This technically means we lose unique qualities of the stock part, but - // it's worth it for how dramatically this simplifies the code. - // The only place I can see it affecting anything is like...RPG qualities. :P - qdel(tool) - else if(user.transferItemToLoc(tool, src)) - components += tool - else - break - - // No balloon alert here so they can look back and see what they added - to_chat(user, span_notice("You add [part_name] to [src].")) - req_components[stock_part_base]-- - return TRUE - - balloon_alert(user, "can't add that!") - return FALSE - -/** - * Attempt to finalize the construction of the frame into a machine - * as according to our circuit and parts - * - * If successful, results in qdel'ing the frame and newing of a machine - * - * Arguments - * * user - the player - * * tool - the tool used to finalize the construction - */ -/obj/structure/frame/machine/finalize_construction(mob/living/user, obj/item/tool) - for(var/component in req_components) - if(req_components[component] > 0) - user.balloon_alert(user, "missing components!") - return FALSE - - tool.play_tool_sound(src) - var/obj/machinery/new_machine = new circuit.build_path(loc) - if(istype(new_machine)) - new_machine.clear_components() - // Set anchor state - new_machine.set_anchored(anchored) - // Prevent us from dropping stuff thanks to /Exited - var/obj/item/circuitboard/machine/leaving_circuit = circuit - circuit = null - // Assign the circuit & parts & move them all at once into the machine - // no need to seperatly move circuit board as its already part of the components list - new_machine.circuit = leaving_circuit - new_machine.component_parts = components - for (var/obj/new_part in components) - new_part.forceMove(new_machine) - //Inform machine that its finished & cleanup - new_machine.RefreshParts() - new_machine.on_construction(user) - components = null - qdel(src) - return TRUE - -/obj/structure/frame/machine/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) - . = ..() - if(. & ITEM_INTERACT_ANY_BLOCKER) - return . - - switch(state) - if(FRAME_STATE_EMPTY) - if(istype(tool, /obj/item/stack/cable_coil)) - if(!tool.tool_start_check(user, amount = 5)) - return ITEM_INTERACT_BLOCKING - - balloon_alert(user, "adding cables...") - if(!tool.use_tool(src, user, 2 SECONDS, volume = 50, amount = 5) || state != FRAME_STATE_EMPTY) - return ITEM_INTERACT_BLOCKING - - state = FRAME_STATE_WIRED - update_appearance(UPDATE_ICON_STATE) - return ITEM_INTERACT_SUCCESS - - if(FRAME_STATE_WIRED) - if(isnull(circuit) && istype(tool, /obj/item/storage/part_replacer)) - return install_circuit_from_part_replacer(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING - - if(FRAME_STATE_BOARD_INSTALLED) - if(istype(tool, /obj/item/storage/part_replacer)) - return install_parts_from_part_replacer(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING - - if(!user.combat_mode) - return add_part(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING - - return . - -/obj/structure/frame/machine/screwdriver_act(mob/living/user, obj/item/tool) - . = ..() - if(. & ITEM_INTERACT_ANY_BLOCKER) - return . - if(state != FRAME_STATE_BOARD_INSTALLED) - return . - - if(finalize_construction(user, tool)) - return ITEM_INTERACT_SUCCESS - - return ITEM_INTERACT_BLOCKING - -/obj/structure/frame/machine/can_be_unfasten_wrench(mob/user, silent) - . = ..() - if(. != SUCCESSFUL_UNFASTEN) - return . - - if(circuit?.needs_anchored) - balloon_alert(user, "circuit must be anchored!") - return FAILED_UNFASTEN - - return . - -/obj/structure/frame/machine/wirecutter_act(mob/living/user, obj/item/tool) - if(state != FRAME_STATE_WIRED) - return NONE - - balloon_alert(user, "removing cables...") - if(!tool.use_tool(src, user, 2 SECONDS, volume = 50) || state != FRAME_STATE_WIRED) - return ITEM_INTERACT_BLOCKING - - state = FRAME_STATE_EMPTY - update_appearance(UPDATE_ICON_STATE) - new /obj/item/stack/cable_coil(drop_location(), 5) - return ITEM_INTERACT_SUCCESS - -/obj/structure/frame/machine/crowbar_act(mob/living/user, obj/item/tool) - if(state != FRAME_STATE_BOARD_INSTALLED) - return NONE - - tool.play_tool_sound(src) - var/list/leftover_components = components.Copy() - circuit - dump_contents() - balloon_alert(user, "circuit board[length(leftover_components) ? " and components" : ""] removed") - // Circuit exited handles updating state - return ITEM_INTERACT_SUCCESS - -/obj/structure/frame/machine/Exited(atom/movable/gone, direction) - if(gone == circuit) - components -= circuit - return ..() - -/obj/structure/frame/machine/Destroy() - QDEL_LIST(components) - return ..() - -/** - * Returns the instance of path1 in list, else path2 in list - * - * Arguments - * * parts - the list of parts to search - * * path1 - the first path to search for - * * path2 - the second path to search for, if path1 is not found - */ -/obj/structure/frame/machine/proc/look_for(list/parts, path1, path2) - return (locate(path1) in parts) || (path2 ? (locate(path2) in parts) : null) - -/obj/structure/frame/machine/deconstruct(disassembled = TRUE) - if(!(obj_flags & NO_DECONSTRUCTION)) - if(state >= FRAME_STATE_WIRED) - new /obj/item/stack/cable_coil(drop_location(), 5) - dump_contents() - return ..() - -/obj/structure/frame/machine/dump_contents() - var/atom/drop_loc = drop_location() - - // We need a snowflake check for stack items since they don't exist anymore - for(var/component in circuit?.req_components) - if(!ispath(component, /obj/item/stack)) - continue - var/obj/item/stack/stack_path = component - var/stack_amount = circuit.req_components[component] - req_components[component] - if(stack_amount > 0) - new stack_path(drop_loc, stack_amount) - - // Rest of the stuff can just be spat out (this includes the circuitboard0) - for(var/component in components) - if(ismovable(component)) - var/atom/movable/atom_component = component - atom_component.forceMove(drop_loc) - - else if(istype(component, /datum/stock_part)) - var/datum/stock_part/stock_part_datum = component - var/physical_object_type = initial(stock_part_datum.physical_object_type) - new physical_object_type(drop_loc) - - else - stack_trace("Invalid component [component] was found in constructable frame") - - components = null - req_components = null - req_component_names = null - -/obj/structure/frame/machine/secured - state = FRAME_STATE_WIRED - anchored = TRUE - -/obj/structure/frame/machine/secured/Initialize(mapload) - . = ..() - update_appearance(UPDATE_ICON_STATE) diff --git a/code/game/machinery/machine_frame.dm b/code/game/machinery/machine_frame.dm new file mode 100644 index 00000000000..a3c074937f0 --- /dev/null +++ b/code/game/machinery/machine_frame.dm @@ -0,0 +1,474 @@ +/obj/structure/frame/machine + name = "machine frame" + desc = "The standard frame for most station appliances. Its appearance and function is controlled by the inserted board." + board_type = /obj/item/circuitboard/machine + /// List of all compnents inside the frame contributing to its construction + var/list/components + /// List of all components required to construct the frame + var/list/req_components + /// User-friendly list of names of required components + var/list/req_component_names + +/obj/structure/frame/machine/Initialize(mapload) + . = ..() + register_context() + update_appearance(UPDATE_ICON_STATE) + +/obj/structure/frame/machine/Destroy() + QDEL_LIST(components) + return ..() + +/obj/structure/frame/machine/deconstruct(disassembled = TRUE) + if(!(obj_flags & NO_DECONSTRUCTION)) + if(state >= FRAME_STATE_WIRED) + new /obj/item/stack/cable_coil(drop_location(), 5) + dump_contents() + return ..() + +/obj/structure/frame/machine/try_dissassemble(mob/living/user, obj/item/tool, disassemble_time) + if(anchored) + balloon_alert(user, "must be unsecured first!") + return FALSE + return ..() + +/obj/structure/frame/machine/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = NONE + if(isnull(held_item)) + return + + switch(state) + if(FRAME_STATE_EMPTY) + if(istype(held_item, /obj/item/stack/cable_coil)) + context[SCREENTIP_CONTEXT_LMB] = "Wire Frame" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_WELDER) + context[SCREENTIP_CONTEXT_LMB] = "Unweld frame" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "Disassemble frame" + return CONTEXTUAL_SCREENTIP_SET + if(FRAME_STATE_WIRED) + if(held_item.tool_behaviour == TOOL_WIRECUTTER) + context[SCREENTIP_CONTEXT_LMB] = "Cut wires" + return CONTEXTUAL_SCREENTIP_SET + else if(istype(held_item, board_type)) + context[SCREENTIP_CONTEXT_LMB] = "Insert board" + return CONTEXTUAL_SCREENTIP_SET + if(FRAME_STATE_BOARD_INSTALLED) + if(held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Pry out components" + return CONTEXTUAL_SCREENTIP_SET + else if(held_item.tool_behaviour == TOOL_WRENCH) + if(!circuit.needs_anchored) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor" + return CONTEXTUAL_SCREENTIP_SET + return NONE + else if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + var/needs_components = FALSE + for(var/component in req_components) + if(!req_components[component]) + continue + needs_components = TRUE + break + if(!needs_components) + context[SCREENTIP_CONTEXT_LMB] = "Complete frame" + return CONTEXTUAL_SCREENTIP_SET + else if(!istype(held_item, /obj/item/storage/part_replacer)) + for(var/component in req_components) + if(!req_components[component]) + continue + var/stock_part_path + if(ispath(component, /obj/item)) + stock_part_path = component + else if(ispath(component, /datum/stock_part)) + var/datum/stock_part/stock_part_datum_type = component + stock_part_path = initial(stock_part_datum_type.physical_object_type) + if(istype(held_item, stock_part_path)) + context[SCREENTIP_CONTEXT_LMB] = "Insert part" + return CONTEXTUAL_SCREENTIP_SET + +/obj/structure/frame/machine/examine(user) + . = ..() + if(!circuit?.needs_anchored) + . += span_notice("It can be [EXAMINE_HINT("anchored")] [anchored ? "loose" : "in place"]") + if(state == FRAME_STATE_EMPTY) + . += span_warning("It needs [EXAMINE_HINT("5 cable")] pieces to wire it.") + return + if(state == FRAME_STATE_WIRED) + . += span_info("Its wires can be cut with a [EXAMINE_HINT("wirecutter")].") + if(state != FRAME_STATE_BOARD_INSTALLED) + . += span_warning("Its missing a circuit board..") + return + if(!length(req_components)) + . += span_info("It requires no components.") + return + + var/list/nice_list = list() + for(var/component in req_components) + if(!req_components[component]) + continue + nice_list += list("[req_components[component]] [req_component_names[component]]\s") + . += span_info("It requires [english_list(nice_list, "no more components")].") + + . += span_info("All the components can be [EXAMINE_HINT("pried")] out.") + if(!length(nice_list)) + . += span_info("The frame can be [EXAMINE_HINT("screwed")] to complete it.") + +/obj/structure/frame/machine/dump_contents() + var/atom/drop_loc = drop_location() + + // We need a snowflake check for stack items since they don't exist anymore + for(var/component in circuit?.req_components) + if(!ispath(component, /obj/item/stack)) + continue + var/obj/item/stack/stack_path = component + var/stack_amount = circuit.req_components[component] - req_components[component] + if(stack_amount > 0) + new stack_path(drop_loc, stack_amount) + + // Rest of the stuff can just be spat out (this includes the circuitboard0) + for(var/component in components) + if(ismovable(component)) + var/atom/movable/atom_component = component + atom_component.forceMove(drop_loc) + + else if(istype(component, /datum/stock_part)) + var/datum/stock_part/stock_part_datum = component + var/physical_object_type = initial(stock_part_datum.physical_object_type) + new physical_object_type(drop_loc) + + else + stack_trace("Invalid component [component] was found in constructable frame") + + components = null + req_components = null + req_component_names = null + +/obj/structure/frame/machine/install_board(mob/living/user, obj/item/circuitboard/machine/board, by_hand = TRUE) + if(state == FRAME_STATE_EMPTY) + balloon_alert(user, "needs wiring!") + return FALSE + if(state == FRAME_STATE_BOARD_INSTALLED) + balloon_alert(user, "circuit already installed!") + return FALSE + if(!anchored && istype(board) && board.needs_anchored) + balloon_alert(user, "frame must be anchored!") + return FALSE + + return ..() + +/obj/structure/frame/machine/circuit_added(obj/item/circuitboard/machine/added) + state = FRAME_STATE_BOARD_INSTALLED + update_appearance(UPDATE_ICON_STATE) + + //add circuit board as the first component to the list of components + //required for part_replacer to locate it while exchanging parts + //so it does not early return in /obj/machinery/proc/exchange_parts + components = list(circuit) + req_components = added.req_components.Copy() + if(!req_components) + return + + //creates a list of names from all the required parts + req_component_names = list() + for(var/component_path in req_components) + if(!ispath(component_path)) + continue + + if(ispath(component_path, /obj/item/stack)) + var/obj/item/stack/stack_path = component_path + if(initial(stack_path.singular_name)) + req_component_names[component_path] = initial(stack_path.singular_name) + else + req_component_names[component_path] = initial(stack_path.name) + else if(ispath(component_path, /datum/stock_part)) + var/datum/stock_part/stock_part = component_path + var/obj/item/physical_object_type = initial(stock_part.physical_object_type) + + req_component_names[component_path] = initial(physical_object_type.name) + else if(ispath(component_path, /obj/item/stock_parts)) + var/obj/item/stock_parts/stock_part = component_path + + if(!added.specific_parts && initial(stock_part.base_name)) + req_component_names[component_path] = initial(stock_part.base_name) + else + req_component_names[component_path] = initial(stock_part.name) + else if(ispath(component_path, /obj/item)) + var/obj/item/part = component_path + + req_component_names[component_path] = initial(part.name) + else + stack_trace("Invalid component part [component_path] in [type], couldn't get its name") + req_component_names[component_path] = "[component_path] (this is a bug)" + +/obj/structure/frame/machine/circuit_removed(obj/item/circuitboard/machine/removed) + components -= removed + state = FRAME_STATE_WIRED + update_appearance(UPDATE_ICON_STATE) + +/** + * Returns the instance of path1 in list, else path2 in list + * + * Arguments + * * parts - the list of parts to search + * * path1 - the first path to search for + * * path2 - the second path to search for, if path1 is not found + */ +/obj/structure/frame/machine/proc/look_for(list/parts, path1, path2) + PRIVATE_PROC(TRUE) + + return (locate(path1) in parts) || (path2 ? (locate(path2) in parts) : null) + +/obj/structure/frame/machine/install_parts_from_part_replacer(mob/living/user, obj/item/storage/part_replacer/replacer, no_sound = FALSE) + if(!length(replacer.contents)) + return FALSE + var/amt = 0 + for(var/path in req_components) + amt += req_components[path] + if(!amt) + return FALSE + + var/play_sound = FALSE + var/list/part_list = replacer.get_sorted_parts() //parts sorted in order of tier + for(var/path in req_components) + var/target_path + if(ispath(path, /datum/stock_part)) + var/datum/stock_part/datum_part = path + target_path = initial(datum_part.physical_object_base_type) + else + target_path = path + + var/obj/item/part + while(req_components[path] > 0 && (part = look_for(part_list, target_path, ispath(path, /obj/item/stack/ore/bluespace_crystal) ? /obj/item/stack/sheet/bluespace_crystal : null))) + part_list -= part + if(istype(part, /obj/item/stack)) + var/obj/item/stack/S = part + var/used_amt = min(round(S.get_amount()), req_components[path]) + var/stack_name = S.singular_name + if(!used_amt || !S.use(used_amt)) + continue + req_components[path] -= used_amt + // No balloon alert here so they can look back and see what they added + to_chat(user, span_notice("You add [used_amt] [stack_name] to [src].")) + play_sound = TRUE + else if(replacer.atom_storage.attempt_remove(part, src)) + var/stock_part_datum = GLOB.stock_part_datums_per_object[part.type] + if (!isnull(stock_part_datum)) + components += stock_part_datum + qdel(part) + else + components += part + part.forceMove(src) + req_components[path]-- + // No balloon alert here so they can look back and see what they added + to_chat(user, span_notice("You add [part] to [src].")) + play_sound = TRUE + + if(play_sound && !no_sound) + replacer.play_rped_sound() + if(replacer.works_from_distance) + user.Beam(src, icon_state = "rped_upgrade", time = 0.5 SECONDS) + return TRUE + +/obj/structure/frame/machine/can_be_unfasten_wrench(mob/user, silent) + . = ..() + if(. != SUCCESSFUL_UNFASTEN) + return . + + if(circuit?.needs_anchored) + balloon_alert(user, "frame must be anchored!") + return FAILED_UNFASTEN + + return . + +/obj/structure/frame/machine/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + if(. & ITEM_INTERACT_ANY_BLOCKER) + return . + if(state != FRAME_STATE_BOARD_INSTALLED) + return . + + if(finalize_construction(user, tool)) + return ITEM_INTERACT_SUCCESS + + return ITEM_INTERACT_BLOCKING + +/obj/structure/frame/machine/wirecutter_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return NONE + if(state != FRAME_STATE_WIRED) + return ITEM_INTERACT_BLOCKING + + balloon_alert(user, "removing cables...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 50) || state != FRAME_STATE_WIRED) + return ITEM_INTERACT_BLOCKING + + state = FRAME_STATE_EMPTY + update_appearance(UPDATE_ICON_STATE) + new /obj/item/stack/cable_coil(drop_location(), 5) + return ITEM_INTERACT_SUCCESS + +/obj/structure/frame/machine/crowbar_act(mob/living/user, obj/item/tool) + if(user.combat_mode) + return NONE + if(state != FRAME_STATE_BOARD_INSTALLED) + return ITEM_INTERACT_BLOCKING + + tool.play_tool_sound(src) + var/list/leftover_components = components.Copy() - circuit + dump_contents() + balloon_alert(user, "circuit board[length(leftover_components) ? " and components" : ""] removed") + // Circuit exited handles updating state + return ITEM_INTERACT_SUCCESS + +/** + * Attempts to add the passed part to the frame + * + * Requires no sanity check that the passed part is a stock part + * + * Arguments + * * user - the player + * * tool - the part to add + */ +/obj/structure/frame/machine/proc/add_part(mob/living/user, obj/item/tool) + PRIVATE_PROC(TRUE) + + for(var/stock_part_base in req_components) + if (req_components[stock_part_base] == 0) + continue + + var/stock_part_path + + if(ispath(stock_part_base, /obj/item)) + stock_part_path = stock_part_base + else if(ispath(stock_part_base, /datum/stock_part)) + var/datum/stock_part/stock_part_datum_type = stock_part_base + stock_part_path = initial(stock_part_datum_type.physical_object_type) + else + stack_trace("Bad stock part in req_components: [stock_part_base]") + continue + + //if we require an bluespace crystall and we have an full sheet of them we can allow that + if(ispath(stock_part_path, /obj/item/stack/ore/bluespace_crystal) && istype(tool, /obj/item/stack/sheet/bluespace_crystal)) + pass() //allow it + else if(!istype(tool, stock_part_path)) + continue + + if(isstack(tool)) + var/obj/item/stack/S = tool + var/used_amt = min(round(S.get_amount()), req_components[stock_part_path]) + if(used_amt && S.use(used_amt)) + req_components[stock_part_path] -= used_amt + // No balloon alert here so they can look back and see what they added + to_chat(user, span_notice("You add [tool] to [src].")) + return + + // We might end up qdel'ing the part if it's a stock part datum. + // In practice, this doesn't have side effects to the name, + // but academically we should not be using an object after it's deleted. + var/part_name = "[tool]" + + if (ispath(stock_part_base, /datum/stock_part)) + // We can't just reuse stock_part_path here or its singleton, + // or else putting in a tier 2 part will deconstruct to a tier 1 part. + var/stock_part_datum = GLOB.stock_part_datums_per_object[tool.type] + if (isnull(stock_part_datum)) + stack_trace("tool.type] does not have an associated stock part datum!") + continue + + components += stock_part_datum + + // We regenerate the stock parts on deconstruct. + // This technically means we lose unique qualities of the stock part, but + // it's worth it for how dramatically this simplifies the code. + // The only place I can see it affecting anything is like...RPG qualities. :P + qdel(tool) + else if(user.transferItemToLoc(tool, src)) + components += tool + else + break + + // No balloon alert here so they can look back and see what they added + to_chat(user, span_notice("You add [part_name] to [src].")) + req_components[stock_part_base]-- + return TRUE + + balloon_alert(user, "can't add that!") + return FALSE + +/obj/structure/frame/machine/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking) + . = ..() + if(. & ITEM_INTERACT_ANY_BLOCKER) + return . + + switch(state) + if(FRAME_STATE_EMPTY) + if(istype(tool, /obj/item/stack/cable_coil)) + if(!tool.tool_start_check(user, amount = 5)) + return ITEM_INTERACT_BLOCKING + + balloon_alert(user, "adding cables...") + if(!tool.use_tool(src, user, 2 SECONDS, volume = 50, amount = 5) || state != FRAME_STATE_EMPTY) + return ITEM_INTERACT_BLOCKING + + state = FRAME_STATE_WIRED + update_appearance(UPDATE_ICON_STATE) + return ITEM_INTERACT_SUCCESS + + if(FRAME_STATE_WIRED) + if(isnull(circuit) && istype(tool, /obj/item/storage/part_replacer)) + return install_circuit_from_part_replacer(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + + if(FRAME_STATE_BOARD_INSTALLED) + if(istype(tool, /obj/item/storage/part_replacer)) + return install_parts_from_part_replacer(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + + if(!user.combat_mode) + return add_part(user, tool) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING + + return . + +/** + * Attempt to finalize the construction of the frame into a machine + * as according to our circuit and parts + * + * If successful, results in qdel'ing the frame and newing of a machine + * + * Arguments + * * user - the player + * * tool - the tool used to finalize the construction + */ +/obj/structure/frame/machine/finalize_construction(mob/living/user, obj/item/tool) + for(var/component in req_components) + if(req_components[component] > 0) + user.balloon_alert(user, "missing components!") + return FALSE + + tool.play_tool_sound(src) + var/obj/machinery/new_machine = new circuit.build_path(loc) + if(istype(new_machine)) + new_machine.clear_components() + // Set anchor state + new_machine.set_anchored(anchored) + // Prevent us from dropping stuff thanks to /Exited + var/obj/item/circuitboard/machine/leaving_circuit = circuit + circuit = null + // Assign the circuit & parts & move them all at once into the machine + // no need to seperatly move circuit board as its already part of the components list + new_machine.circuit = leaving_circuit + new_machine.component_parts = components + for (var/obj/new_part in components) + new_part.forceMove(new_machine) + //Inform machine that its finished & cleanup + new_machine.RefreshParts() + new_machine.on_construction(user) + components = null + qdel(src) + return TRUE + +/obj/structure/frame/machine/secured + state = FRAME_STATE_WIRED + anchored = TRUE diff --git a/tgstation.dme b/tgstation.dme index c27a78b7319..2bce85421f1 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2056,6 +2056,7 @@ #include "code\game\machinery\launch_pad.dm" #include "code\game\machinery\lightswitch.dm" #include "code\game\machinery\limbgrower.dm" +#include "code\game\machinery\machine_frame.dm" #include "code\game\machinery\mass_driver.dm" #include "code\game\machinery\mechlaunchpad.dm" #include "code\game\machinery\medical_kiosk.dm"