From 2f5d7e84228d8c40eacd73f325026308ea2e4834 Mon Sep 17 00:00:00 2001 From: Casper3667 <8396443+Casper3667@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:34:34 +0200 Subject: [PATCH] Upgrades the research console to TGUI and adds the material silo (#22987) This updates the research console to be a TGUI and fixes several bugs with it. It now uses a material silo for materials, while the acid for the circuit imprinter is still given directly to the machine. Mech fabricators now also need to be linked to a research console. It is also capable of connecting to multiple fabricators and distribute tasks between them. The sprite originates from TG station. Unit tests were also made to ensure there is no fabricators missing consoles and no console missing material silos. image image image image --- aurorastation.dme | 3 +- .../circuitboards/machinery/research.dm | 6 +- .../structures/crates_lockers/crates.dm | 5 + .../structures/machinery/mecha_fabricator.dm | 506 ------ .../spacesuits/rig/modules/computer.dm | 6 +- code/modules/item_worth/worths_list.dm | 2 +- code/modules/research/circuitprinter.dm | 316 +--- .../research/designs/circuit/circuits.dm | 2 +- .../designs/circuit/machine_circuits.dm | 2 +- .../designs/mechfab/prosthetics/external.dm | 4 +- .../research/designs/mechfab/robot/robot.dm | 4 +- code/modules/research/material_silo.dm | 176 ++ code/modules/research/mecha_fabricator.dm | 156 ++ code/modules/research/protolathe.dm | 308 +--- code/modules/research/rdconsole.dm | 1495 +++++++++-------- code/modules/research/rdmachines.dm | 350 +++- code/unit_tests/map_tests.dm | 40 + html/changelogs/TimeForMoreTGUIInScience.yml | 7 + icons/obj/machinery/research.dmi | Bin 21883 -> 23029 bytes .../abandoned_industrial_station.dmm | 19 +- .../port_volturno/port_volturno.dmm | 23 +- .../away_site/big_derelict/bigderelict.dmm | 22 +- maps/away/away_site/blueriver/blueriver.dmm | 6 +- .../away_site/first_aurora/first_aurora.dmm | 19 +- .../konyang/point_verdant/point_verdant.dmm | 57 +- .../overgrown_mining_station.dmm | 9 +- maps/away/away_site/racers/racers.dmm | 42 +- .../enviro_testing_facility_submaps_main.dmm | 43 +- .../scenarios/nuclear_silo/nuclear_silo.dmm | 6 +- .../biesel/tcaf_corvette/tcaf_corvette.dmm | 21 +- maps/away/ships/golden_deep/golden_deep.dmm | 16 +- .../ships/himeo/himeo_patrol_ship_submaps.dmm | 12 +- maps/away/ships/iac/iac_rescue_ship.dmm | 12 +- .../ships/konyang/kasf_ship/kasf_ship.dmm | 36 +- .../ships/lone_spacer/lone_spacer_submaps.dmm | 41 +- .../ships/pra/headmaster/headmaster_ship.dmm | 60 +- .../ships/sol/sol_frigate/sol_frigate.dmm | 16 +- maps/away/ships/sol/sol_ssrm/ssrm_ship.dmm | 23 +- .../konyang/abandoned/factory_robotics.dmm | 16 +- .../exoplanets/ouerea/ouerea_nt_ruin.dmm | 3 +- maps/runtime/runtime.dmm | 7 +- maps/sccv_horizon/sccv_horizon.dmm | 65 +- .../tgui/interfaces/ResearchConsole.tsx | 578 +++++++ .../tgui/interfaces/ResearchFabricator.tsx | 553 ++++++ .../styles/interfaces/ResearchFabricator.scss | 26 + tgui/packages/tgui/styles/main.scss | 1 + 46 files changed, 3094 insertions(+), 2026 deletions(-) delete mode 100644 code/game/objects/structures/machinery/mecha_fabricator.dm create mode 100644 code/modules/research/material_silo.dm create mode 100644 code/modules/research/mecha_fabricator.dm create mode 100644 html/changelogs/TimeForMoreTGUIInScience.yml create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole.tsx create mode 100644 tgui/packages/tgui/interfaces/ResearchFabricator.tsx create mode 100644 tgui/packages/tgui/styles/interfaces/ResearchFabricator.scss diff --git a/aurorastation.dme b/aurorastation.dme index d697e460811..6e5bb3de469 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -1495,7 +1495,6 @@ #include "code\game\objects\structures\machinery\lightswitch.dm" #include "code\game\objects\structures\machinery\mass_driver.dm" #include "code\game\objects\structures\machinery\mech_recharger.dm" -#include "code\game\objects\structures\machinery\mecha_fabricator.dm" #include "code\game\objects\structures\machinery\navbeacon.dm" #include "code\game\objects\structures\machinery\newscaster.dm" #include "code\game\objects\structures\machinery\nuclear_bomb.dm" @@ -3735,6 +3734,8 @@ #include "code\modules\rendering\render_plate.dm" #include "code\modules\research\circuitprinter.dm" #include "code\modules\research\destructive_analyzer.dm" +#include "code\modules\research\material_silo.dm" +#include "code\modules\research\mecha_fabricator.dm" #include "code\modules\research\protolathe.dm" #include "code\modules\research\rd-readme.dm" #include "code\modules\research\rdconsole.dm" diff --git a/code/game/objects/items/weapons/circuitboards/machinery/research.dm b/code/game/objects/items/weapons/circuitboards/machinery/research.dm index 2cd056192d3..f0910824d23 100644 --- a/code/game/objects/items/weapons/circuitboards/machinery/research.dm +++ b/code/game/objects/items/weapons/circuitboards/machinery/research.dm @@ -51,7 +51,7 @@ /obj/item/circuitboard/protolathe name = T_BOARD("protolathe") - build_path = /obj/structure/machinery/r_n_d/protolathe + build_path = /obj/structure/machinery/r_n_d/fabricator/protolathe board_type = BOARD_MACHINE origin_tech = list(TECH_ENGINEERING = 2, TECH_DATA = 2) req_components = list( @@ -62,7 +62,7 @@ /obj/item/circuitboard/circuit_imprinter name = T_BOARD("circuit imprinter") - build_path = /obj/structure/machinery/r_n_d/circuit_imprinter + build_path = /obj/structure/machinery/r_n_d/fabricator/circuit_imprinter board_type = BOARD_MACHINE origin_tech = list(TECH_ENGINEERING = 2, TECH_DATA = 2) req_components = list( @@ -72,7 +72,7 @@ /obj/item/circuitboard/mechfab name = T_BOARD("mechatronic fabricator") - build_path = /obj/structure/machinery/mecha_part_fabricator + build_path = /obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator board_type = BOARD_MACHINE origin_tech = list(TECH_DATA = 3, TECH_ENGINEERING = 3) req_components = list( diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index 36527a0b07b..098a0d52a35 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -797,3 +797,8 @@ new /obj/item/storage/box/unique/tea/messa(src) new /obj/item/reagent_containers/toothpaste(src) new /obj/item/reagent_containers/food/drinks/flask/vacuumflask/mouthwash(src) + +/obj/structure/closet/crate/tool/machinist/fill() + new /obj/item/reagent_containers/weldpack(src) + new /obj/item/portable_whiteboard(src) + new /obj/item/paint_sprayer(src) diff --git a/code/game/objects/structures/machinery/mecha_fabricator.dm b/code/game/objects/structures/machinery/mecha_fabricator.dm deleted file mode 100644 index 86de54055a4..00000000000 --- a/code/game/objects/structures/machinery/mecha_fabricator.dm +++ /dev/null @@ -1,506 +0,0 @@ -/obj/structure/machinery/mecha_part_fabricator - name = "synthetic fabricator" - desc = "A general purpose fabricator that can be used to fabricate equipment for synthetics or exosuits." - icon = 'icons/obj/machinery/robotics_fabricator.dmi' - icon_state = "fab" - density = TRUE - anchored = TRUE - idle_power_usage = 20 - active_power_usage = 5000 - req_access = list(ACCESS_ROBOTICS) - component_types = list( - /obj/item/circuitboard/mechfab, - /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/manipulator, - /obj/item/stock_parts/micro_laser, - /obj/item/stock_parts/console_screen - ) - manufacturer = "hephaestus" - - /** - * A `/list` of enqueued `/datum/design` to be printed, processed in the queue - */ - var/list/datum/design/queue = list() - - /** - * How much efficient (or inefficient) the protolathe is at manufacturing things - */ - var/mat_efficiency = 1 - - /** - * The production speed of this specific protolathe, a factor - */ - var/production_speed = 1 - - var/list/materials = list(MATERIAL_STEEL = 0, MATERIAL_GLASS = 0, MATERIAL_GOLD = 0, MATERIAL_SILVER = 0, MATERIAL_DIAMOND = 0, MATERIAL_PHORON = 0, MATERIAL_URANIUM = 0, MATERIAL_PLASTEEL = 0, MATERIAL_ALUMINIUM = 0, MATERIAL_LEAD = 0) - var/res_max_amount = 200000 - - var/datum/research/files - - /// The looping sound for production. - var/datum/looping_sound/synth_fab/fab_loop - - var/list/categories = list() - var/category = null - var/sync_message = "" - var/limb_manufacturer - - ///The timer id for the build callback, if we're building something - var/build_callback_timer - -/obj/structure/machinery/mecha_part_fabricator/upgrade_hints(mob/user, distance, is_adjacent) - . += ..() - . += "- Upgraded matter bins will increase material storage capacity." - . += SPAN_NOTICE(" - The current storage capacity is [res_max_amount / 2000] sheets") - . += "- Upgraded micro-lasers will increase fabrication speed." - . += SPAN_NOTICE(" - The current speed increase is [round((1 - (1 / production_speed)) * 100)]%") - . += "- Upgraded manipulators will improve material use efficiency." - . += SPAN_NOTICE(" - The current cost reduction is [round((1 - mat_efficiency) * 100)]%") - -/obj/structure/machinery/mecha_part_fabricator/antagonist_hints(mob/user, distance, is_adjacent) - . += ..() - . += "Click-drag someone with long hair onto this machine to start feeding it their hair! This will hurt and piss them off!" - -/obj/structure/machinery/mecha_part_fabricator/Initialize() - . = ..() - - files = new /datum/research(src) //Setup the research data holder. - fab_loop = new(src) - limb_manufacturer = GLOB.basic_robolimb.company - update_categories() - update_icon() - -/// Make sure the machine starts the round properly synced. -/obj/structure/machinery/mecha_part_fabricator/LateInitialize() - . = ..() - sync() - -/obj/structure/machinery/mecha_part_fabricator/update_icon() - ClearOverlays() - update_fab_audio() - if(panel_open) - AddOverlays("[icon_state]_panel") - if(!(stat & (NOPOWER|BROKEN))) - AddOverlays(emissive_appearance(icon, "[icon_state]_lights")) - AddOverlays("[icon_state]_lights") - if(build_callback_timer) - AddOverlays("[icon_state]_working") - AddOverlays(emissive_appearance(icon, "[icon_state]_lights_working")) - AddOverlays("[icon_state]_lights_working") - -/// Starts and stops the necessary audio. -/obj/structure/machinery/mecha_part_fabricator/proc/update_fab_audio() - if(!fab_loop) - return - - if(build_callback_timer) - fab_loop.start() - else - fab_loop.stop() - -/obj/structure/machinery/mecha_part_fabricator/dismantle() - SSmaterials.normalize_material_amounts(materials) - for(var/f in materials.Copy()) - eject_materials(f, materials[f]) - - //Stop the queue building if you're dismantling - deltimer(build_callback_timer) - - ..() - -/obj/structure/machinery/mecha_part_fabricator/RefreshParts() - ..() - res_max_amount = 0 - - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - res_max_amount += M.rating * 100000 // 200k -> 600k - var/T = 0 - - for(var/obj/item/stock_parts/manipulator/M in component_parts) - T += M.rating - mat_efficiency = 1 - (T - 1) / 4 // 1 -> 0.5 - - for(var/obj/item/stock_parts/micro_laser/M in component_parts) // Not resetting T is intended; speed is affected by both - T += M.rating - production_speed = T / 2 // 1 -> 3 - -/obj/structure/machinery/mecha_part_fabricator/attack_hand(var/mob/user) - if(..()) - return - if(!allowed(user)) - to_chat(user, SPAN_WARNING("Access denied.")) - return - do_hair_pull(user) - - // Yes I'm doing this after do_hair_pull() intentionally, you can totally fail the skill check and still get your hair stuck in a blender. - var/cancelled = FALSE - SEND_SIGNAL(user, COMSIG_USE_MECH_FAB, &cancelled) - if (cancelled) - return - - ui_interact(user) - -/obj/structure/machinery/mecha_part_fabricator/ui_interact(mob/user, datum/tgui/ui) - . = ..() - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "SyntheticFabricator", "Synthetic Fabricator", 1200, 800) - ui.open() - -/obj/structure/machinery/mecha_part_fabricator/ui_data(mob/user) - var/list/data = list() - data["manufacturer"] = manufacturer - var/datum/design/current = queue.len ? queue[1] : null - if(current) - data["current"] = current.name - data["queue"] = get_queue_names() - data["buildable"] = get_build_options() - data["category"] = category - data["available_categories"] = list() - for(var/possible_category in categories) - data["available_categories"] += list(list("name" = possible_category)) - - if(GLOB.fabricator_robolimbs) - var/list/T = list() - for(var/A in GLOB.fabricator_robolimbs) - var/datum/robolimb/R = GLOB.fabricator_robolimbs[A] - T += list(list("name" = R.company, "type" = A)) - data["manufacturers"] = T - data["selected_manufacturer"] = limb_manufacturer - - data["materials"] = get_materials() - data["maximum_resource_amount"] = res_max_amount - data["sync"] = sync_message - - //If we have something in the queue that we're trying to build, show the time left if the build is undergoing, otherwise null - //the logic of displaying it is handled client-side - if(current) - if(build_callback_timer) - data["timeleft"] = round(timeleft(build_callback_timer)) - else - data["timeleft"] = null - return data - - -/obj/structure/machinery/mecha_part_fabricator/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) - . = ..() - if(.) - return - - switch(action) - if("build") - var/path = text2path(params["build"]) - add_to_queue(path) - . = TRUE - - if("remove") - remove_from_queue(text2num(params["remove"])) - . = TRUE - - if("category") - if(params["category"] in categories) - category = params["category"] - . = TRUE - - if("manufacturer") - if(params["manufacturer"] in GLOB.fabricator_robolimbs) - limb_manufacturer = params["manufacturer"] - . = TRUE - - if("eject") - eject_materials(params["eject"], text2num(params["amount"])) - . = TRUE - - if("sync") - sync() - . = TRUE - else - sync_message = "" - - if(.) - playsound(src, 'sound/machines/synthfab/synthfab_button.ogg', 50) - -/obj/structure/machinery/mecha_part_fabricator/attackby(obj/item/attacking_item, mob/user) - if(build_callback_timer) - to_chat(user, SPAN_NOTICE("\The [src] is busy. Please wait for completion of previous operation.")) - return TRUE - if(default_deconstruction_screwdriver(user, attacking_item)) - return TRUE - if(default_deconstruction_crowbar(user, attacking_item)) - return TRUE - if(default_part_replacement(user, attacking_item)) - return TRUE - - if(!istype(attacking_item, /obj/item/stack/material)) - return ..() - - var/obj/item/stack/material/M = attacking_item - if(!M.material) - return ..() - var/material = SSmaterials.material_to_path(M.material, FALSE) - if(!(material in list(MATERIAL_STEEL, MATERIAL_GLASS, MATERIAL_GOLD, MATERIAL_SILVER, MATERIAL_DIAMOND, MATERIAL_PHORON, MATERIAL_URANIUM, MATERIAL_PLASTEEL, MATERIAL_ALUMINIUM, MATERIAL_LEAD))) - to_chat(user, SPAN_WARNING("\The [src] cannot hold [M.material.name].")) - return TRUE - - var/sname = "[M.name]" - SSmaterials.normalize_material_amounts(materials) - if((materials[material] || 0) + SHEET_MATERIAL_AMOUNT <= res_max_amount) - if(M.amount >= 1) - var/count = 0 - var/mutable_appearance/MA = mutable_appearance(icon, "material_insertion") - MA.color = M.material.icon_colour - //first play the insertion animation - flick_overlay_view(MA, 1 SECONDS) - - //now play the progress bar animation - flick_overlay_view(mutable_appearance(icon, "fab_progress"), 1 SECONDS) - - while((materials[material] || 0) + SHEET_MATERIAL_AMOUNT <= res_max_amount && M.amount >= 1) - SSmaterials.add_material_amount(materials, material, M.perunit) - M.use(1) - count++ - to_chat(user, SPAN_NOTICE("You insert [count] [sname] into \the [src].")) - - //Wake up, we have things to do (maybe) - handle_queue() - - else - to_chat(user, SPAN_NOTICE("\The [src] cannot hold more [sname].")) - return TRUE - -/obj/structure/machinery/mecha_part_fabricator/mouse_drop_receive(atom/dropped, mob/user, params) - var/mob/living/carbon/human/target = dropped - if (!istype(target) || target.buckled_to || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || istype(user, /mob/living/silicon/ai)) - return - if(target == user) - return - src.add_fingerprint(user) - var/target_loc = target.loc - - if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis) - if(isskrell(target) || isunathi(target)) - return - - for(var/obj/item/protection in list(target.head)) - if(protection && (protection.flags_inv & BLOCKHAIR)) - return - - var/datum/sprite_accessory/hair/hair_style = GLOB.hair_styles_list[target.h_style] - if(hair_style.length < 4) - return - - user.visible_message(SPAN_WARNING("[user] starts feeding [target]'s hair into \the [src]!"), SPAN_WARNING("You start feeding [target]'s hair into \the [src]!")) - if(!do_after(user, 5 SECONDS, target, DO_UNIQUE)) - return - if(target_loc != target.loc) - return - if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis) - user.visible_message(SPAN_WARNING("[user] feeds the [target]'s hair into the [src] and flicks it on!"), SPAN_ALERT("You turn the [src] on!")) - do_hair_pull(target) - user.attack_log += "\[[time_stamp()]\] Has fed [target.name]'s ([target.ckey]) hair into a [src]." - target.attack_log += "\[[time_stamp()]\] Has had their hair fed into [src] by [user.name] ([user.ckey])" - msg_admin_attack("[key_name_admin(user)] fed [key_name_admin(target)] in a [src]. (JMP)",ckey=key_name(user),ckey_target=key_name(target)) - else - return - if(!do_after(user, 3.5 SECONDS, target, DO_UNIQUE)) - return - if(target_loc != target.loc) - return - if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis) - user.visible_message(SPAN_ALERT("[user] starts tugging on [target]'s head as the [src] keeps running!"), SPAN_ALERT("You start tugging on [target]'s head!")) - do_hair_pull(target) - spawn(10) - user.visible_message(SPAN_ALERT("[user] stops the [src] and leaves [target] resting as they are."), SPAN_ALERT("You turn the [src] off and let go of [target].")) - -/obj/structure/machinery/mecha_part_fabricator/emag_act(var/remaining_charges, var/mob/user) - switch(emagged) - if(0) - emagged = 0.5 - visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"DB error \[Code 0x00F1\]\"") - sleep(10) - visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"Attempting auto-repair\"") - sleep(15) - visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"User DB corrupted \[Code 0x00FA\]. Truncating data structure...\"") - sleep(30) - visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"User DB truncated. Please contact your [SSatlas.current_map.company_name] system operator for future assistance.\"") - req_access = null - emagged = 1 - return 1 - if(0.5) - visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"DB not responding \[Code 0x0003\]...\"") - if(1) - visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"No records in User DB\"") - -/** - * Adds a design to the queue - * - * * path: The path of the design to add - */ -/obj/structure/machinery/mecha_part_fabricator/proc/add_to_queue(path) - var/datum/design/D = files.known_designs[path] - if(!D) - return - queue += D - - //Wake up, we have things to do - handle_queue() - -/** - * Removes a design from the queue - * - * * index: The index of the design to remove - */ -/obj/structure/machinery/mecha_part_fabricator/proc/remove_from_queue(index) - queue.Cut(index, index + 1) - - //If we're removing the first thing in the queue, stop the build timer - if(index == 1) - deltimer(build_callback_timer) - build_callback_timer = null - - //Wake up, we have things to do - handle_queue() - -/** - * Handle the construction queue - */ -/obj/structure/machinery/mecha_part_fabricator/proc/handle_queue() - - //No work to do or already busy, stop - if(!length(queue) || build_callback_timer) - //If we don't have a build in progress, revert to idle power usage - if(!build_callback_timer) - update_use_power(POWER_USE_IDLE) - - update_icon() - return - - //If there's no power, there's no building - if(stat & NOPOWER) - queue = list() - return - - //Get the first design in the queue - var/datum/design/D = queue[1] - - //If we can build it, process the request - if(can_build(D)) - update_use_power(POWER_USE_ACTIVE) - - //Add the callback timer for printing, and remove the design from the queue - build_callback_timer = addtimer(CALLBACK(src, PROC_REF(build), D), (D.time / production_speed), TIMER_UNIQUE|TIMER_STOPPABLE) - //No queue removal here because we show how long it takes to build in the UI - - else - visible_message(SPAN_NOTICE("[icon2html(src, viewers(get_turf(src)))] \The [src] flashes: Insufficient materials to complete build: [D.name].")) - remove_from_queue(1) - - update_icon() - -/obj/structure/machinery/mecha_part_fabricator/proc/can_build(var/datum/design/D) - SSmaterials.normalize_material_amounts(materials) - for(var/M in D.materials) - var/material = SSmaterials.material_to_path(M, FALSE) - if(!material) - material = M - if((materials[material] || 0) < D.materials[M] * mat_efficiency) - return 0 - return 1 - -/** - * Builds the given design, assuming all the necessary conditions are met - * - * * design_to_build: The design to build - */ -/obj/structure/machinery/mecha_part_fabricator/proc/build(datum/design/design_to_build) - //Consume the materials - SSmaterials.normalize_material_amounts(materials) - for(var/M in design_to_build.materials) - SSmaterials.remove_material_amount(materials, M, design_to_build.materials[M] * mat_efficiency) - - intent_message(MACHINE_SOUND) - - if(design_to_build.build_path) - var/loc_offset = get_step(src, dir) - var/obj/new_item = design_to_build.Fabricate(loc_offset, src) - visible_message("\The [src] pings, indicating that \the [new_item] is complete.", "You hear a ping.", intent_message = PING_SOUND) - if(mat_efficiency != 1) - if(new_item.matter && new_item.matter.len > 0) - for(var/i in new_item.matter) - new_item.matter[i] = new_item.matter[i] * mat_efficiency - - //We finished building, clear the timer and remove the thing from the queue - build_callback_timer = null - remove_from_queue(1) - - //Do the queue handling for the next item, or to stop - handle_queue() - -/obj/structure/machinery/mecha_part_fabricator/proc/get_queue_names() - . = list() - for(var/i = 1 to queue.len) - var/datum/design/D = queue[i] - . += list(list("name" = D.name, "time" = get_design_time(D), "index" = i)) - -/obj/structure/machinery/mecha_part_fabricator/proc/get_build_options() - . = list() - for(var/path in files.known_designs) - var/datum/design/D = files.known_designs[path] - if(!D.build_path || !(D.build_type & MECHFAB) || !D.category || (D.category != category)) - continue - . += list(list("name" = D.name, "desc" = D.desc, "type" = D.type, "category" = D.category, "resources" = get_design_resourses(D), "time" = get_design_time(D))) - -/obj/structure/machinery/mecha_part_fabricator/proc/get_design_resourses(var/datum/design/D) - var/list/F = list() - for(var/T in D.materials) - F += "[SSmaterials.material_display_name(T)]: [D.materials[T] * mat_efficiency]" - return english_list(F, and_text = ", ") - -/obj/structure/machinery/mecha_part_fabricator/proc/get_design_time(var/datum/design/D) - return time2text(round(10 * D.time / production_speed), "mm:ss") - -/obj/structure/machinery/mecha_part_fabricator/proc/update_categories() - categories = list() - for(var/path in files.known_designs) - var/datum/design/D = files.known_designs[path] - if(!D.build_path || !(D.build_type & MECHFAB) || !D.category) - continue - categories |= D.category - if(!category || !(category in categories)) - category = categories[1] - -/obj/structure/machinery/mecha_part_fabricator/proc/get_materials() - . = list() - SSmaterials.normalize_material_amounts(materials) - for(var/T in materials) - . += list(list("name" = SSmaterials.material_display_name(T), "amount" = materials[T])) - -/obj/structure/machinery/mecha_part_fabricator/proc/eject_materials(var/material, var/amount) - if(!amount) - return - SSmaterials.normalize_material_amounts(materials) - material = SSmaterials.material_to_path(material, FALSE) - if(!material) - return - var/stack_type = SSmaterials.material_stack_type(material) - if(!stack_type) - return - - var/real_amount = min(round(amount / SHEET_MATERIAL_AMOUNT), round((materials[material] || 0) / SHEET_MATERIAL_AMOUNT)) - if(real_amount < 1) - return - var/obj/item/stack/material/S = new stack_type(loc, real_amount) - S.update_icon() - SSmaterials.remove_material_amount(materials, material, real_amount * SHEET_MATERIAL_AMOUNT) - -/obj/structure/machinery/mecha_part_fabricator/proc/sync() - sync_message = "Error: no console found." - for(var/obj/structure/machinery/computer/rdconsole/RDC in get_area(src)) - if(!RDC.sync) - continue - for(var/id in RDC.files.known_tech) - var/datum/tech/T = RDC.files.known_tech[id] - files.AddTech2Known(T) - files.RefreshResearch() - sync_message = "Sync complete." - update_categories() diff --git a/code/modules/clothing/spacesuits/rig/modules/computer.dm b/code/modules/clothing/spacesuits/rig/modules/computer.dm index 0289b7136ef..1eabc0fed0b 100644 --- a/code/modules/clothing/spacesuits/rig/modules/computer.dm +++ b/code/modules/clothing/spacesuits/rig/modules/computer.dm @@ -315,9 +315,9 @@ else if(istype(input_device,/obj/structure/machinery/r_n_d/server)) var/obj/structure/machinery/r_n_d/server/input_machine = input_device incoming_files = input_machine.files - else if(istype(input_device,/obj/structure/machinery/mecha_part_fabricator)) - var/obj/structure/machinery/mecha_part_fabricator/input_machine = input_device - incoming_files = input_machine.files + else if(istype(input_device, /obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator)) + var/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/input_mechfab = input_device + incoming_files = input_mechfab.linked_console?.files if(!incoming_files || !incoming_files.known_tech || !incoming_files.known_tech.len) to_chat(user, SPAN_WARNING("Memory failure. There is nothing accessible stored on this terminal.")) diff --git a/code/modules/item_worth/worths_list.dm b/code/modules/item_worth/worths_list.dm index 610b34744fe..c5d4b22bb0c 100644 --- a/code/modules/item_worth/worths_list.dm +++ b/code/modules/item_worth/worths_list.dm @@ -752,7 +752,7 @@ var/list/worths = list( /obj/structure/machinery/conveyor_switch = 10, /obj/structure/machinery/disposal = 50, /obj/structure/machinery/blackbox_recorder = -950, - /obj/structure/machinery/r_n_d/protolathe = -1500, + /obj/structure/machinery/r_n_d/fabricator/protolathe = -1500, /obj/structure/machinery/r_n_d/server = -2000, /obj/structure/machinery/r_n_d = -100, /obj/structure/machinery/auto_cloner = -1300, diff --git a/code/modules/research/circuitprinter.dm b/code/modules/research/circuitprinter.dm index 40b1ee17a78..698969471a1 100644 --- a/code/modules/research/circuitprinter.dm +++ b/code/modules/research/circuitprinter.dm @@ -1,82 +1,47 @@ /*///////////////Circuit Imprinter (By Darem)//////////////////////// - Used to print new circuit boards (for computers and similar systems) and AI modules. Each circuit board pattern are stored in -a /datum/desgin on the linked R&D console. You can then print them out in a fasion similar to a regular lathe. However, instead of -using metal and glass, it uses glass and reagents (usually sulphuric acid). + Used to print new circuit boards (for computers and similar systems) and AI modules. */ -/obj/structure/machinery/r_n_d/circuit_imprinter +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter name = "circuit imprinter" desc = "An advanced device that can only be operated via a nearby RnD console, it can print any circuitboard the user requests, provided it has the correct materials to do so." icon_state = "circuit_imprinter" atom_flags = ATOM_FLAG_OPEN_CONTAINER - idle_power_usage = 30 active_power_usage = 2500 - + build_type = IMPRINTER + uses_reagents = TRUE + fabrication_loop_type = /datum/looping_sound/synth_fab component_types = list( /obj/item/circuitboard/circuit_imprinter, - /obj/item/stock_parts/matter_bin, /obj/item/stock_parts/manipulator, /obj/item/reagent_containers/glass/beaker = 2 ) - var/max_material_storage = 75000 - var/list/materials = list(MATERIAL_STEEL = 0, MATERIAL_GLASS = 0, MATERIAL_GOLD = 0, MATERIAL_SILVER = 0, MATERIAL_PHORON = 0, MATERIAL_URANIUM = 0, MATERIAL_DIAMOND = 0) - - /** - * A `/list` of enqueued `/datum/design` to be printed, processed in the queue - */ - var/list/datum/design/queue = list() - - /** - * How much efficient (or inefficient) the circuit imprinter is at manufacturing circuit boards - */ - var/mat_efficiency = 1 - - /** - * The production speed of this specific circuit imprinter, a factor - */ - var/production_speed = 1 - - ///Boolean, if to make the printer spawn its product in a neighboring turf dictated by dir - var/product_offset = FALSE - - ///The timer id for the build callback, if we're building something - var/build_callback_timer - - -/obj/structure/machinery/r_n_d/circuit_imprinter/upgrade_hints(mob/user, distance, is_adjacent) +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter/upgrade_hints(mob/user, distance, is_adjacent) . += ..() - . += "- Upgraded matter bins will increase material storage capacity." - . += SPAN_NOTICE(" - The current storage capacity is [max_material_storage / 2000] sheets") + . += "- Materials are drawn from the research material silo linked to the same R&D console." . += "- Upgraded manipulators will improve material use efficiency and increase fabrication speed." . += SPAN_NOTICE(" - The current speed increase is [round((1 - (1 / production_speed)) * 100)]%") . += SPAN_NOTICE(" - The current cost reduction is [round((1 - mat_efficiency) * 100)]%") -/obj/structure/machinery/r_n_d/circuit_imprinter/RefreshParts() +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter/RefreshParts() ..() - // Adjust reagent container volume to match combined volume of the inserted beakers - var/T = 0 - for(var/obj/item/reagent_containers/glass/G in component_parts) - T += G.reagents.maximum_volume - create_reagents(T) - // Transfer all reagents from the beakers to internal reagent container - for(var/obj/item/reagent_containers/glass/G in component_parts) - G.reagents.trans_to_obj(src, G.reagents.total_volume) + var/total_volume = 0 + for(var/obj/item/reagent_containers/glass/beaker in component_parts) + total_volume += beaker.reagents.maximum_volume + create_reagents(total_volume) + for(var/obj/item/reagent_containers/glass/beaker in component_parts) + beaker.reagents.trans_to_obj(src, beaker.reagents.total_volume) - // Adjust material storage capacity to scale with matter bin rating - max_material_storage = 0 - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - max_material_storage += M.rating * 75000 + var/manipulator_rating = 0 + for(var/obj/item/stock_parts/manipulator/manipulator in component_parts) + manipulator_rating += manipulator.rating + mat_efficiency = 1 - (manipulator_rating - 1) / 8 + production_speed = manipulator_rating + update_icon() - // Adjust production speed to increase with manipulator rating - T = 0 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - T += M.rating - mat_efficiency = 1 - (T - 1) / 4 - production_speed = T - -/obj/structure/machinery/r_n_d/circuit_imprinter/update_icon() +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter/update_icon() if(panel_open) icon_state = "circuit_imprinter_t" else if(build_callback_timer) @@ -84,240 +49,11 @@ using metal and glass, it uses glass and reagents (usually sulphuric acid). else icon_state = "circuit_imprinter" -/obj/structure/machinery/r_n_d/circuit_imprinter/proc/TotalMaterials() - SSmaterials.normalize_material_amounts(materials) - var/t = 0 - for(var/f in materials) - t += materials[f] - return t - -/obj/structure/machinery/r_n_d/circuit_imprinter/dismantle() - for(var/obj/I in component_parts) - // This will distribute all reagents amongst the contained beakers - if(istype(I, /obj/item/reagent_containers/glass/beaker)) - reagents.trans_to_obj(I, reagents.total_volume) - SSmaterials.normalize_material_amounts(materials) - for(var/f in materials) - if(materials[f] >= SHEET_MATERIAL_AMOUNT) - var/path = getMaterialType(f) - if(path) - var/obj/item/stack/S = new path(loc) - S.amount = round(materials[f] / SHEET_MATERIAL_AMOUNT) +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter/dismantle() + for(var/obj/item/reagent_containers/glass/beaker in component_parts) + reagents.trans_to_obj(beaker, reagents.total_volume) ..() -/obj/structure/machinery/r_n_d/circuit_imprinter/attackby(obj/item/attacking_item, mob/user) - //No touch the machine while it's working! - if(build_callback_timer) - to_chat(user, SPAN_NOTICE("\The [src] is busy. Please wait for completion of previous operation.")) - return 1 - - if(default_deconstruction_screwdriver(user, attacking_item)) - if(linked_console) - linked_console.linked_imprinter = null - linked_console = null - return - - if(default_deconstruction_crowbar(user, attacking_item)) - return - - if(default_part_replacement(user, attacking_item)) - return - - if(panel_open) - to_chat(user, SPAN_NOTICE("You can't load \the [src] while it's opened.")) - return 1 - - if(!linked_console) - to_chat(user, "\The [src] must be linked to an R&D console first.") - return 1 - - if(attacking_item.is_open_container()) - return 0 - - if(!istype(attacking_item, /obj/item/stack/material)) - to_chat(user, SPAN_NOTICE("You cannot insert this item into \the [src]!")) - return 1 - - if(stat) - return 1 - - - if(TotalMaterials() + SHEET_MATERIAL_AMOUNT > max_material_storage) - to_chat(user, SPAN_NOTICE("\The [src]'s material bin is full. Please remove material before adding more.")) - return 1 - - var/obj/item/stack/material/stack = attacking_item - if(!stack.default_type) - to_chat(user, SPAN_WARNING("This stack cannot be used!")) - return - - var/max_value = min(stack.get_amount(), round((max_material_storage - TotalMaterials()) / SHEET_MATERIAL_AMOUNT)) - var/amount = tgui_input_number(user, "How many sheets do you want to add?", "Add sheets", min(10, max_value), max_value = max_value, min_value = 1, round_value = TRUE) - - if(!attacking_item) - return - if(!Adjacent(user)) - to_chat(user, SPAN_NOTICE("\The [src] is too far away for you to insert this.")) - return - if(amount <= 0)//No negative numbers - return - - use_power_oneoff(max(1000, (SHEET_MATERIAL_AMOUNT * amount / 10))) - if(do_after(user, 16)) - if(stack.use(amount)) - to_chat(user, SPAN_NOTICE("You add [amount] sheets to \the [src].")) - SSmaterials.add_material_amount(materials, stack.default_type, amount * SHEET_MATERIAL_AMOUNT) - - //In case there's things queued up, we run the queue handler - handle_queue() - - updateUsrDialog() - -/** - * Adds a design to the queue - * - * * design_to_add: The design to add - */ -/obj/structure/machinery/r_n_d/circuit_imprinter/proc/addToQueue(datum/design/design_to_add) - queue += design_to_add - - //Wake up, we have things to do - handle_queue() - - -/** - * Removes a design from the queue - * - * * index: The index of the design to remove - */ -/obj/structure/machinery/r_n_d/circuit_imprinter/proc/removeFromQueue(index) - queue.Cut(index, index + 1) - - //Wake up, we have things to do - handle_queue() - - -/** - * Handle the construction queue - */ -/obj/structure/machinery/r_n_d/circuit_imprinter/proc/handle_queue() - //No work to do or already busy, stop - if(!length(queue) || build_callback_timer) - update_icon() - return - - //If there's no power, there's no building - if(stat & NOPOWER) - queue = list() - return - - //Get the first design in the queue - var/datum/design/D = queue[1] - - //If we can build it, process the request - if(canBuild(D)) - build_callback_timer = addtimer(CALLBACK(src, PROC_REF(build), D), (D.time / production_speed), TIMER_UNIQUE) - removeFromQueue(1) - - else - visible_message(SPAN_NOTICE("[icon2html(src, viewers(get_turf(src)))] \The [src] flashes: insufficient materials: [getLackingMaterials(D)].")) - if(linked_console) - linked_console.updateUsrDialog() - - update_icon() - -/** - * Checks if the protolathe can build the given design - * - * * design_to_check: The design to check - * - * Returns `TRUE` if the design can be built, `FALSE` otherwise - */ -/obj/structure/machinery/r_n_d/circuit_imprinter/proc/canBuild(datum/design/design_to_check) - SSmaterials.normalize_material_amounts(materials) - for(var/M in design_to_check.materials) - var/material = SSmaterials.material_to_path(M, FALSE) - if(!material) - material = M - if((materials[material] || 0) < design_to_check.materials[M]) - return FALSE - - for(var/C in design_to_check.chemicals) - if(!reagents.has_reagent(C, design_to_check.chemicals[C])) - return FALSE - - return TRUE - -/** - * Get what materials (chemicals included) are lacking from being able to build the given design - * - * * design_to_check: The design to check - * - * Returns a string of the materials that are missing - */ -/obj/structure/machinery/r_n_d/circuit_imprinter/proc/getLackingMaterials(datum/design/design_to_check) - SSmaterials.normalize_material_amounts(materials) - var/ret = "" - - for(var/M in design_to_check.materials) - var/amount = SSmaterials.get_material_amount(materials, M) - if(amount < design_to_check.materials[M]) - if(ret != "") - ret += ", " - ret += "[design_to_check.materials[M] - amount] [SSmaterials.material_display_name(M)]" - - for(var/C in design_to_check.chemicals) - if(!reagents.has_reagent(C, design_to_check.chemicals[C])) - var/singleton/reagent/R = GET_SINGLETON(C) - if(ret != "") - ret += ", " - ret += "[R.name]" - - return ret - -/** - * Builds the given design, assuming all the necessary conditions are met - * - * * design_to_build: The design to build - */ -/obj/structure/machinery/r_n_d/circuit_imprinter/proc/build(datum/design/design_to_build) - //Consume some power - var/power = active_power_usage - for(var/M in design_to_build.materials) - power += round(design_to_build.materials[M] / 5) - power = max(active_power_usage, power) - use_power_oneoff(power) - - //Consume the materials - SSmaterials.normalize_material_amounts(materials) - for(var/M in design_to_build.materials) - SSmaterials.remove_material_amount(materials, M, design_to_build.materials[M] * mat_efficiency) - - for(var/C in design_to_build.chemicals) - reagents.remove_reagent(C, design_to_build.chemicals[C] * mat_efficiency) - - if(design_to_build.build_path) - var/obj/new_item = design_to_build.Fabricate(src, src) - if(product_offset) - new_item.forceMove(get_step(src, dir)) - else - new_item.forceMove(src.loc) - if(mat_efficiency != 1) // No matter out of nowhere - if(new_item.matter && new_item.matter.len > 0) - for(var/i in new_item.matter) - new_item.matter[i] = new_item.matter[i] * mat_efficiency - - //We finished building, clear the timer - build_callback_timer = null - - //Do the queue handling for the next item, or to stop - handle_queue() - - -/obj/structure/machinery/r_n_d/circuit_imprinter/is_open_container() +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter/is_open_container() . = ..() - - //This is to recheck the queue when acid is added to the reagent container that we have - //yes this should be done with signals, we don't have those signals yet - //cope - addtimer(CALLBACK(src, PROC_REF(handle_queue)), 1 SECOND, TIMER_UNIQUE) + linked_console?.dispatch_fabrication_jobs() diff --git a/code/modules/research/designs/circuit/circuits.dm b/code/modules/research/designs/circuit/circuits.dm index 1881e6b806a..af10934741e 100644 --- a/code/modules/research/designs/circuit/circuits.dm +++ b/code/modules/research/designs/circuit/circuits.dm @@ -9,7 +9,7 @@ if(ispath(build_path, /obj/item/circuitboard)) var/obj/item/circuitboard/CB = build_path var/atom/machine = initial(CB.build_path) - desc = "Used in the construction of a: [capitalize_first_letters(initial(machine.name))], [initial(machine.desc)]" + desc = "Used in the construction of a: [capitalize_first_letters(initial(machine.name))], [initial(machine.desc)]" else var/atom/A = build_path desc = initial(A.desc) diff --git a/code/modules/research/designs/circuit/machine_circuits.dm b/code/modules/research/designs/circuit/machine_circuits.dm index 8204f6a5c35..12d876e115b 100644 --- a/code/modules/research/designs/circuit/machine_circuits.dm +++ b/code/modules/research/designs/circuit/machine_circuits.dm @@ -213,7 +213,7 @@ /datum/design/circuit/machine/aicore name = "AI Core" - desc = "Used in the construction of an: AI core, Secure housing for an AI, it provides power and protection to its inhabitant." + desc = "Used in the construction of an: AI core, Secure housing for an AI, it provides power and protection to its inhabitant." req_tech = list(TECH_DATA = 4, TECH_BIO = 3) build_path = /obj/item/circuitboard/aicore diff --git a/code/modules/research/designs/mechfab/prosthetics/external.dm b/code/modules/research/designs/mechfab/prosthetics/external.dm index 73d81db3976..72eb008dd4a 100644 --- a/code/modules/research/designs/mechfab/prosthetics/external.dm +++ b/code/modules/research/designs/mechfab/prosthetics/external.dm @@ -4,9 +4,9 @@ //if the fabricator is a mech fab we need to construct the proper typepath for the manufacturer /datum/design/item/mechfab/prosthetic/Fabricate(var/newloc, var/fabricator) - if(istype(fabricator, /obj/structure/machinery/mecha_part_fabricator)) + if(istype(fabricator, /obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator)) if(!use_direct_buildpath) - var/obj/structure/machinery/mecha_part_fabricator/mechfab = fabricator + var/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/mechfab = fabricator for(var/model_path in subtypesof(build_path)) var/obj/item/organ/external/E = new model_path if(E.robotize_type == mechfab.limb_manufacturer) diff --git a/code/modules/research/designs/mechfab/robot/robot.dm b/code/modules/research/designs/mechfab/robot/robot.dm index b5f7e49939c..7cbee8d18d4 100644 --- a/code/modules/research/designs/mechfab/robot/robot.dm +++ b/code/modules/research/designs/mechfab/robot/robot.dm @@ -7,8 +7,8 @@ //if the fabricator is a mech fab pass the manufacturer info over to the robot part constructor /datum/design/item/mechfab/robot/Fabricate(var/newloc, var/fabricator) - if(istype(fabricator, /obj/structure/machinery/mecha_part_fabricator)) - var/obj/structure/machinery/mecha_part_fabricator/mechfab = fabricator + if(istype(fabricator, /obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator)) + var/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/mechfab = fabricator return new build_path(newloc, mechfab.manufacturer) return ..() diff --git a/code/modules/research/material_silo.dm b/code/modules/research/material_silo.dm new file mode 100644 index 00000000000..a6e09a40bf5 --- /dev/null +++ b/code/modules/research/material_silo.dm @@ -0,0 +1,176 @@ +/obj/structure/machinery/r_n_d/material_silo + name = "material silo" + desc = "A centralized material store used by linked research fabricators." + icon_state = "silo" + + idle_power_usage = 30 WATTS + + /// Total material capacity, measured in material units. + var/max_material_storage = 800000 + + /// Materials shared by every fabricator connected to the same R&D console. + var/list/materials = list( + MATERIAL_STEEL = 0, + MATERIAL_GLASS = 0, + MATERIAL_GOLD = 0, + MATERIAL_SILVER = 0, + MATERIAL_PHORON = 0, + MATERIAL_URANIUM = 0, + MATERIAL_DIAMOND = 0, + MATERIAL_PLASTEEL = 0, + MATERIAL_ALUMINIUM = 0, + MATERIAL_LEAD = 0 + ) + +/obj/structure/machinery/r_n_d/material_silo/upgrade_hints(mob/user, distance, is_adjacent) + . += ..() + . += SPAN_NOTICE("\t- The shared storage capacity is [max_material_storage / SHEET_MATERIAL_AMOUNT] sheets.") + +/obj/structure/machinery/r_n_d/material_silo/Initialize(mapload, d, populate_components, is_internal) + . = ..() + update_icon() + +/obj/structure/machinery/r_n_d/material_silo/update_icon() + . = ..() + ClearOverlays() + if(!(stat & (NOPOWER | BROKEN))) + AddOverlays(emissive_appearance(icon, "[icon_state]_lights")) + AddOverlays("[icon_state]_lights") + +/obj/structure/machinery/r_n_d/material_silo/proc/TotalMaterials() + SSmaterials.normalize_material_amounts(materials) + var/total = 0 + for(var/material in materials) + total += materials[material] + return total + +/obj/structure/machinery/r_n_d/material_silo/proc/get_material_amount(material_id) + SSmaterials.normalize_material_amounts(materials) + var/material = SSmaterials.material_to_path(material_id, FALSE) + if(!material) + material = material_id + return materials[material] || 0 + +/obj/structure/machinery/r_n_d/material_silo/proc/has_material(material_id, amount) + return get_material_amount(material_id) >= amount + +/obj/structure/machinery/r_n_d/material_silo/proc/add_material(material_id, amount) + if(amount <= 0) + return 0 + var/material = SSmaterials.material_to_path(material_id, FALSE) + if(!material) + material = material_id + if(!(material in materials)) + return 0 + var/accepted = min(amount, max_material_storage - TotalMaterials()) + if(accepted <= 0) + return 0 + SSmaterials.add_material_amount(materials, material, accepted) + update_linked_uis() + return accepted + +/obj/structure/machinery/r_n_d/material_silo/proc/remove_material(material_id, amount) + if(amount <= 0) + return TRUE + var/material = SSmaterials.material_to_path(material_id, FALSE) + if(!material) + material = material_id + if(!has_material(material, amount)) + return FALSE + SSmaterials.remove_material_amount(materials, material, amount) + update_linked_uis() + return TRUE + +/obj/structure/machinery/r_n_d/material_silo/proc/eject_material(material_id, requested_sheets) + if(requested_sheets < 1) + return FALSE + var/material = SSmaterials.material_to_path(material_id, FALSE) + if(!material) + return FALSE + var/available = round(get_material_amount(material) / SHEET_MATERIAL_AMOUNT) + var/amount = min(requested_sheets, available) + if(amount < 1) + return FALSE + var/stack_type = SSmaterials.material_stack_type(material) + if(!stack_type) + return FALSE + var/obj/item/stack/material/stack = new stack_type(loc, amount) + stack.update_icon() + SSmaterials.remove_material_amount(materials, material, amount * SHEET_MATERIAL_AMOUNT) + update_linked_uis() + return TRUE + +/obj/structure/machinery/r_n_d/material_silo/proc/wake_linked_fabricators() + linked_console?.dispatch_fabrication_jobs() + +/obj/structure/machinery/r_n_d/material_silo/proc/update_linked_uis() + if(!linked_console) + return + SStgui.update_uis(linked_console) + for(var/obj/structure/machinery/r_n_d/fabricator/fabricator as anything in linked_console.linked_fabricators) + SStgui.update_uis(fabricator) + +/obj/structure/machinery/r_n_d/material_silo/attackby(obj/item/attacking_item, mob/user) + if(user.a_intent == I_HURT) + return ..() + if(default_deconstruction_screwdriver(user, attacking_item)) + disconnect_console() + return TRUE + if(default_deconstruction_crowbar(user, attacking_item)) + return TRUE + if(default_part_replacement(user, attacking_item)) + return TRUE + if(panel_open) + to_chat(user, SPAN_NOTICE("You can't load \the [src] while it's opened.")) + return TRUE + if(!istype(attacking_item, /obj/item/stack/material)) + return ..() + if(stat) + return TRUE + + var/obj/item/stack/material/stack = attacking_item + if(!stack.default_type || !stack.material) + to_chat(user, SPAN_WARNING("This stack cannot be stored.")) + return TRUE + + var/material = SSmaterials.material_to_path(stack.default_type, FALSE) + if(!material) + material = stack.default_type + if(!(material in materials)) + to_chat(user, SPAN_WARNING("\The [src] cannot hold [stack.material.name].")) + return TRUE + + var/max_value = min(stack.get_amount(), round((max_material_storage - TotalMaterials()) / SHEET_MATERIAL_AMOUNT)) + if(max_value < 1) + to_chat(user, SPAN_NOTICE("\The [src] is full.")) + return TRUE + + var/amount = tgui_input_number(user, "How many sheets do you want to add?", "Add sheets", min(10, max_value), max_value = max_value, min_value = 1, round_value = TRUE) + if(!amount || !Adjacent(user)) + return TRUE + + use_power_oneoff(max(1000, SHEET_MATERIAL_AMOUNT * amount / 10)) + if(do_after(user, 1.6 SECONDS) && stack.use(amount)) + SSmaterials.add_material_amount(materials, material, amount * SHEET_MATERIAL_AMOUNT) + to_chat(user, SPAN_NOTICE("You add [amount] sheet\s of [stack.material.name] to \the [src].")) + update_linked_uis() + wake_linked_fabricators() + return TRUE + +/obj/structure/machinery/r_n_d/material_silo/dismantle() + SSmaterials.normalize_material_amounts(materials) + for(var/material in materials) + var/sheets = round(materials[material] / SHEET_MATERIAL_AMOUNT) + if(sheets < 1) + continue + var/stack_type = SSmaterials.material_stack_type(material) + if(!stack_type) + continue + var/obj/item/stack/material/stack = new stack_type(loc, sheets) + stack.update_icon() + ..() + +/obj/structure/machinery/r_n_d/material_silo/proc/disconnect_console() + if(linked_console) + linked_console.linked_silo = null + linked_console = null diff --git a/code/modules/research/mecha_fabricator.dm b/code/modules/research/mecha_fabricator.dm new file mode 100644 index 00000000000..8405ae75c9c --- /dev/null +++ b/code/modules/research/mecha_fabricator.dm @@ -0,0 +1,156 @@ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator + name = "synthetic fabricator" + desc = "A general purpose fabricator that can be used to fabricate equipment for synthetics or exosuits." + icon = 'icons/obj/machinery/robotics_fabricator.dmi' + icon_state = "fab" + idle_power_usage = 20 + active_power_usage = 5000 + req_access = list(ACCESS_ROBOTICS) + component_types = list( + /obj/item/circuitboard/mechfab, + /obj/item/stock_parts/manipulator, + /obj/item/stock_parts/micro_laser, + /obj/item/stock_parts/console_screen + ) + manufacturer = "hephaestus" + build_type = MECHFAB + product_offset = TRUE + + fabrication_loop_type = /datum/looping_sound/synth_fab + + /// Manufacturer used for the currently assigned prosthetic job. + var/limb_manufacturer = PROSTHETIC_IND + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/LateInitialize() + . = ..() + if(!linked_console) + for(var/obj/structure/machinery/computer/rdconsole/console in range(3, src)) + console.SyncRDevices() + if(linked_console) + break + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/upgrade_hints(mob/user, distance, is_adjacent) + . += ..() + . += "- Materials are drawn from the research material silo linked to the same R&D console." + . += "- Upgraded micro-lasers will increase fabrication speed." + . += SPAN_NOTICE(" - The current speed increase is [round((1 - (1 / production_speed)) * 100)]%") + . += "- Upgraded manipulators will improve material use efficiency." + . += SPAN_NOTICE(" - The current cost reduction is [round((1 - mat_efficiency) * 100)]%") + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/antagonist_hints(mob/user, distance, is_adjacent) + . += ..() + . += "Click-drag someone with long hair onto this machine to start feeding it their hair! This will hurt and piss them off!" + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/Initialize() + . = ..() + + update_icon() + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/update_icon() + ClearOverlays() + if(panel_open) + AddOverlays("[icon_state]_panel") + if(!(stat & (NOPOWER | BROKEN))) + AddOverlays(emissive_appearance(icon, "[icon_state]_lights")) + AddOverlays("[icon_state]_lights") + if(build_callback_timer) + AddOverlays("[icon_state]_working") + AddOverlays(emissive_appearance(icon, "[icon_state]_lights_working")) + AddOverlays("[icon_state]_lights_working") + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/RefreshParts() + ..() + var/manipulator_rating = 0 + for(var/obj/item/stock_parts/manipulator/manipulator in component_parts) + manipulator_rating += manipulator.rating + mat_efficiency = 1 - (manipulator_rating - 1) / 4 + + var/laser_rating = 0 + for(var/obj/item/stock_parts/micro_laser/laser in component_parts) + laser_rating += laser.rating + production_speed = max(1, laser_rating) + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/attack_hand(mob/user) + if(..()) + return + if(!allowed(user)) + to_chat(user, SPAN_WARNING("Access denied.")) + return + do_hair_pull(user) + + var/cancelled = FALSE + SEND_SIGNAL(user, COMSIG_USE_MECH_FAB, &cancelled) + if(cancelled) + return + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/on_product_completed(obj/new_item, datum/design/design_to_build, datum/research_fabrication_job/job) + visible_message("\The [src] pings, indicating that \the [new_item] is complete.", "You hear a ping.", intent_message = PING_SOUND) + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/mouse_drop_receive(atom/dropped, mob/user, params) + var/mob/living/carbon/human/target = dropped + if (!istype(target) || target.buckled_to || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || istype(user, /mob/living/silicon/ai)) + return + if(target == user) + return + src.add_fingerprint(user) + var/target_loc = target.loc + + if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis) + if(isskrell(target) || isunathi(target)) + return + + for(var/obj/item/protection in list(target.head)) + if(protection && (protection.flags_inv & BLOCKHAIR)) + return + + var/datum/sprite_accessory/hair/hair_style = GLOB.hair_styles_list[target.h_style] + if(hair_style.length < 4) + return + + user.visible_message(SPAN_WARNING("[user] starts feeding [target]'s hair into \the [src]!"), SPAN_WARNING("You start feeding [target]'s hair into \the [src]!")) + if(!do_after(user, 5 SECONDS, target, DO_UNIQUE)) + return + if(target_loc != target.loc) + return + if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis) + user.visible_message(SPAN_WARNING("[user] feeds the [target]'s hair into the [src] and flicks it on!"), SPAN_ALERT("You turn the [src] on!")) + do_hair_pull(target) + user.attack_log += "\[[time_stamp()]\] Has fed [target.name]'s ([target.ckey]) hair into a [src]." + target.attack_log += "\[[time_stamp()]\] Has had their hair fed into [src] by [user.name] ([user.ckey])" + msg_admin_attack("[key_name_admin(user)] fed [key_name_admin(target)] in a [src]. (JMP)",ckey=key_name(user),ckey_target=key_name(target)) + else + return + if(!do_after(user, 3.5 SECONDS, target, DO_UNIQUE)) + return + if(target_loc != target.loc) + return + if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis) + user.visible_message(SPAN_ALERT("[user] starts tugging on [target]'s head as the [src] keeps running!"), SPAN_ALERT("You start tugging on [target]'s head!")) + do_hair_pull(target) + spawn(10) + user.visible_message(SPAN_ALERT("[user] stops the [src] and leaves [target] resting as they are."), SPAN_ALERT("You turn the [src] off and let go of [target].")) + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/emag_act(var/remaining_charges, var/mob/user) + switch(emagged) + if(0) + emagged = 0.5 + visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"DB error \[Code 0x00F1\]\"") + sleep(10) + visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"Attempting auto-repair\"") + sleep(15) + visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"User DB corrupted \[Code 0x00FA\]. Truncating data structure...\"") + sleep(30) + visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"User DB truncated. Please contact your [SSatlas.current_map.company_name] system operator for future assistance.\"") + req_access = null + emagged = 1 + return 1 + if(0.5) + visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"DB not responding \[Code 0x0003\]...\"") + if(1) + visible_message("[icon2html(src, viewers(get_turf(src)))] [src] beeps: \"No records in User DB\"") + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/on_job_started(datum/research_fabrication_job/job) + limb_manufacturer = job?.manufacturer + +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator/on_job_cleared() + limb_manufacturer = PROSTHETIC_IND diff --git a/code/modules/research/protolathe.dm b/code/modules/research/protolathe.dm index 4ba23700636..9914a463175 100644 --- a/code/modules/research/protolathe.dm +++ b/code/modules/research/protolathe.dm @@ -1,102 +1,56 @@ -/obj/structure/machinery/r_n_d/protolathe +/obj/structure/machinery/r_n_d/fabricator/protolathe name = "protolathe" desc = "An upgraded variant of a common Autolathe, this can only be operated via a nearby RnD console, but can manufacture cutting edge technology, provided it has the design and the correct materials." icon_state = "protolathe" atom_flags = ATOM_FLAG_OPEN_CONTAINER - idle_power_usage = 30 WATTS active_power_usage = 25 KILO WATTS - - var/max_material_storage = 100000 - var/list/materials = list(MATERIAL_STEEL = 0, MATERIAL_GLASS = 0, MATERIAL_GOLD = 0, MATERIAL_SILVER = 0, MATERIAL_PHORON = 0, MATERIAL_URANIUM = 0, MATERIAL_DIAMOND = 0) - - /** - * A `/list` of enqueued `/datum/design` to be printed, processed in the queue - */ - var/list/datum/design/queue = list() - - /** - * How much efficient (or inefficient) the protolathe is at manufacturing things - */ - var/mat_efficiency = 1 - - /** - * The production speed of this specific protolathe, a factor - */ - var/production_speed = 1 - - ///The timer id for the build callback, if we're building something - var/build_callback_timer - + build_type = PROTOLATHE + uses_reagents = TRUE + fabrication_loop_type = /datum/looping_sound/synth_fab component_types = list( /obj/item/circuitboard/protolathe, - /obj/item/stock_parts/matter_bin = 2, /obj/item/stock_parts/manipulator = 2, /obj/item/reagent_containers/glass/beaker = 2 ) -/obj/structure/machinery/r_n_d/protolathe/upgrade_hints(mob/user, distance, is_adjacent) +/obj/structure/machinery/r_n_d/fabricator/protolathe/upgrade_hints(mob/user, distance, is_adjacent) . += ..() - . += "- Upgraded matter bins will increase material storage capacity." - . += SPAN_NOTICE(" - The current storage capacity is [max_material_storage / 2000] sheets") + . += "- Materials are drawn from the research material silo linked to the same R&D console." . += "- Upgraded manipulators will improve material use efficiency and increase fabrication speed." . += SPAN_NOTICE(" - The current speed increase is [round((1 - (1 / production_speed)) * 100)]%") . += SPAN_NOTICE(" - The current cost reduction is [round((1 - mat_efficiency) * 100)]%") -///Returns the total of all the stored materials -/obj/structure/machinery/r_n_d/protolathe/proc/TotalMaterials() - SSmaterials.normalize_material_amounts(materials) - var/t = 0 - for(var/f in materials) - t += materials[f] - return t - -/obj/structure/machinery/r_n_d/protolathe/RefreshParts() +/obj/structure/machinery/r_n_d/fabricator/protolathe/RefreshParts() ..() - // Adjust reagent container volume to match combined volume of the inserted beakers - var/T = 0 - for(var/obj/item/reagent_containers/glass/G in component_parts) - T += G.reagents.maximum_volume - create_reagents(T) - // Transfer all reagents from the beakers to internal reagent container - for(var/obj/item/reagent_containers/glass/G in component_parts) - G.reagents.trans_to_obj(src, G.reagents.total_volume) + var/total_volume = 0 + for(var/obj/item/reagent_containers/glass/beaker in component_parts) + total_volume += beaker.reagents.maximum_volume + create_reagents(total_volume) + for(var/obj/item/reagent_containers/glass/beaker in component_parts) + beaker.reagents.trans_to_obj(src, beaker.reagents.total_volume) - // Adjust material storage capacity to scale with matter bin rating - max_material_storage = 0 - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - max_material_storage += M.rating * 75000 - - // Adjust production speed to increase with manipulator rating - T = 0 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - T += M.rating - mat_efficiency = 1 - (T - 2) / 8 - production_speed = T / 2 + var/manipulator_rating = 0 + for(var/obj/item/stock_parts/manipulator/manipulator in component_parts) + manipulator_rating += manipulator.rating + mat_efficiency = 1 - (manipulator_rating - 2) / 8 + production_speed = manipulator_rating / 2 update_icon() -/obj/structure/machinery/r_n_d/protolathe/dismantle() - for(var/obj/I in component_parts) - if(istype(I, /obj/item/reagent_containers/glass/beaker)) - reagents.trans_to_obj(I, reagents.total_volume) - SSmaterials.normalize_material_amounts(materials) - for(var/f in materials) - if(materials[f] >= SHEET_MATERIAL_AMOUNT) - var/path = getMaterialType(f) - if(path) - var/obj/item/stack/S = new path(loc) - S.amount = round(materials[f] / SHEET_MATERIAL_AMOUNT) +/obj/structure/machinery/r_n_d/fabricator/protolathe/dismantle() + for(var/obj/item/reagent_containers/glass/beaker in component_parts) + reagents.trans_to_obj(beaker, reagents.total_volume) ..() -/obj/structure/machinery/r_n_d/protolathe/power_change() +/obj/structure/machinery/r_n_d/fabricator/protolathe/power_change() . = ..() update_icon() -/obj/structure/machinery/r_n_d/protolathe/update_icon() +/obj/structure/machinery/r_n_d/fabricator/protolathe/update_icon() ClearOverlays() if(panel_open) AddOverlays("[icon_state]_panel") - if(!(stat & (NOPOWER|BROKEN))) + if(!(stat & (NOPOWER | BROKEN))) AddOverlays(emissive_appearance(icon, "[icon_state]_lights")) AddOverlays("[icon_state]_lights") if(build_callback_timer) @@ -104,214 +58,6 @@ AddOverlays(emissive_appearance(icon, "[icon_state]_lights_working")) AddOverlays("[icon_state]_lights_working") -/obj/structure/machinery/r_n_d/protolathe/attackby(obj/item/attacking_item, mob/user) - if(user.a_intent == I_HURT) - return ..() - - if(build_callback_timer) - to_chat(user, SPAN_NOTICE("\The [src] is busy. Please wait for completion of previous operation.")) - return TRUE - if(default_deconstruction_screwdriver(user, attacking_item)) - if(linked_console) - linked_console.linked_lathe = null - linked_console = null - return TRUE - if(default_deconstruction_crowbar(user, attacking_item)) - return TRUE - if(default_part_replacement(user, attacking_item)) - return TRUE - if(attacking_item.is_open_container()) - return TRUE - if(panel_open) - to_chat(user, SPAN_NOTICE("You can't load \the [src] while it's opened.")) - return TRUE - if(!linked_console) - to_chat(user, SPAN_NOTICE("The [src] must be linked to an R&D console first!")) - return TRUE - if(!istype(attacking_item, /obj/item/stack/material)) - to_chat(user, SPAN_NOTICE("You cannot insert this item into \the [src]!")) - return TRUE - if(stat) - return TRUE - - if(TotalMaterials() + SHEET_MATERIAL_AMOUNT > max_material_storage) - to_chat(user, SPAN_NOTICE("The [src]'s material bin is full. Please remove material before adding more.")) - return TRUE - - var/obj/item/stack/material/stack = attacking_item - if(!stack.default_type) - to_chat(user, SPAN_WARNING("This stack cannot be used!")) - return - - var/max_value = min(stack.get_amount(), round((max_material_storage - TotalMaterials()) / SHEET_MATERIAL_AMOUNT)) - var/amount = tgui_input_number(user, "How many sheets do you want to add?", "Add sheets", min(10, max_value), max_value = max_value, min_value = 1, round_value = TRUE) - - if(!attacking_item) - return - if(!Adjacent(user)) - to_chat(user, SPAN_NOTICE("The [src] is too far away for you to insert this.")) - return - if(amount <= 0)//No negative numbers, no nulls - return - - var/mutable_appearance/M = mutable_appearance(icon, "material_insertion") - M.color = stack.material.icon_colour - //first play the insertion animation - flick_overlay_view(M, 1 SECONDS) - - //now play the progress bar animation - flick_overlay_view(mutable_appearance(icon, "protolathe_progress"), 1 SECONDS) - - //Use some power and add the materials - use_power_oneoff(max(1000, (SHEET_MATERIAL_AMOUNT * amount / 10))) - if(do_after(user, 1.6 SECONDS)) - if(stack.use(amount)) - if(amount>1) - to_chat(user, SPAN_NOTICE("You add [amount] [stack.material.sheet_plural_name] of [stack.material.name] to \the [src].")) - else - to_chat(user, SPAN_NOTICE("You add [amount] [stack.material.sheet_singular_name] of [stack.material.name] to \the [src].")) - SSmaterials.add_material_amount(materials, stack.default_type, amount * SHEET_MATERIAL_AMOUNT) - - //In case there's things queued up, we run the queue handler - handle_queue() - - //Give an update on the UIs - updateUsrDialog() - if(linked_console) - linked_console.updateUsrDialog() - -/** - * Adds a design to the queue - * - * * design_to_add: The design to add - */ -/obj/structure/machinery/r_n_d/protolathe/proc/addToQueue(datum/design/design_to_add) - queue += design_to_add - - //Wake up, we have things to do - handle_queue() - -/** - * Removes a design from the queue - * - * * index: The index of the design to remove - */ -/obj/structure/machinery/r_n_d/protolathe/proc/removeFromQueue(index) - queue.Cut(index, index + 1) - - //Wake up, we have things to do - handle_queue() - -/** - * Handle the construction queue - */ -/obj/structure/machinery/r_n_d/protolathe/proc/handle_queue() - - //No work to do or already busy, stop - if(!length(queue) || build_callback_timer) - update_icon() - return - - //If there's no power, there's no building - if(stat & NOPOWER) - queue = list() - update_icon() - return - - //Get the first design in the queue - var/datum/design/D = queue[1] - - //If we can build it, process the request - if(canBuild(D)) - build_callback_timer = addtimer(CALLBACK(src, PROC_REF(build), D), (D.time / production_speed), TIMER_UNIQUE) - removeFromQueue(1) - - else - visible_message(SPAN_NOTICE("[icon2html(src, viewers(get_turf(src)))] \The [src] flashes: Insufficient materials: [getLackingMaterials(D)].")) - if(linked_console) - linked_console.updateUsrDialog() - - update_icon() - - -/** - * Checks if the protolathe can build the given design - * - * * design_to_check: The design to check - * - * Returns `TRUE` if the design can be built, `FALSE` otherwise - */ -/obj/structure/machinery/r_n_d/protolathe/proc/canBuild(datum/design/design_to_check) - SSmaterials.normalize_material_amounts(materials) - for(var/M in design_to_check.materials) - var/material = SSmaterials.material_to_path(M, FALSE) - if(!material) - material = M - if((materials[material] || 0) < design_to_check.materials[M]) - return FALSE - - for(var/C in design_to_check.chemicals) - if(!reagents.has_reagent(C, design_to_check.chemicals[C])) - return FALSE - - return TRUE - -/** - * Get what materials (chemicals included) are lacking from being able to build the given design - * - * * design_to_check: The design to check - * - * Returns a string of the materials that are missing - */ -/obj/structure/machinery/r_n_d/protolathe/proc/getLackingMaterials(var/datum/design/design_to_check) - SSmaterials.normalize_material_amounts(materials) - var/ret = "" - for(var/M in design_to_check.materials) - var/amount = SSmaterials.get_material_amount(materials, M) - if(amount < design_to_check.materials[M]) - if(ret != "") - ret += ", " - ret += "[design_to_check.materials[M] - amount] [SSmaterials.material_display_name(M)]" - for(var/C in design_to_check.chemicals) - if(!reagents.has_reagent(C, design_to_check.chemicals[C])) - var/singleton/reagent/R = GET_SINGLETON(C) - if(ret != "") - ret += ", " - ret += "[R.name]" - return ret - -/** - * Builds the given design, assuming all the necessary conditions are met - * - * * design_to_build: The design to build - */ -/obj/structure/machinery/r_n_d/protolathe/proc/build(datum/design/design_to_build) - //Consume some power - var/power = active_power_usage - for(var/M in design_to_build.materials) - power += round(design_to_build.materials[M] / 2) - power = max(active_power_usage, power) - use_power_oneoff(power) - - //Consume the materials - SSmaterials.normalize_material_amounts(materials) - for(var/M in design_to_build.materials) - SSmaterials.remove_material_amount(materials, M, design_to_build.materials[M] * mat_efficiency) - for(var/C in design_to_build.chemicals) - reagents.remove_reagent(C, design_to_build.chemicals[C] * mat_efficiency) - - intent_message(MACHINE_SOUND) - - if(design_to_build.build_path) - var/obj/new_item = design_to_build.Fabricate(src, src) - new_item.forceMove(loc) - if(mat_efficiency != 1) // No matter out of nowhere - if(new_item.matter && new_item.matter.len > 0) - for(var/i in new_item.matter) - new_item.matter[i] = new_item.matter[i] * mat_efficiency - - //We finished building, clear the timer - build_callback_timer = null - - //Do the queue handling for the next item, or to stop - handle_queue() +/obj/structure/machinery/r_n_d/fabricator/protolathe/is_open_container() + . = ..() + linked_console?.dispatch_fabrication_jobs() diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index b123651af9f..a77809fd944 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -35,29 +35,82 @@ won't update every console in existence) but it's more of a hassle to do. Also, icon_keyboard_emis = "purple_key_mask" light_color = LIGHT_COLOR_PURPLE - circuit = /obj/item/circuitboard/rdconsole - var/datum/research/files //Stores all the collected research data. - var/obj/item/disk/tech_disk/t_disk = null //Stores the technology disk. - var/obj/item/disk/design_disk/d_disk = null //Stores the design disk. + manufacturer = "nanotrasen" - var/obj/structure/machinery/r_n_d/destructive_analyzer/linked_destroy = null //Linked Destructive Analyzer - var/obj/structure/machinery/r_n_d/protolathe/linked_lathe = null //Linked Protolathe - var/obj/structure/machinery/r_n_d/circuit_imprinter/linked_imprinter = null //Linked Circuit Imprinter + circuit = /obj/item/circuitboard/rdconsole + //Stores all the collected research data. + var/datum/research/files + //Stores the technology disk. + var/obj/item/disk/tech_disk/t_disk = null + //Stores the design disk. + var/obj/item/disk/design_disk/d_disk = null + + /// All fabrication machinery connected to this console. + var/list/obj/structure/machinery/r_n_d/fabricator/linked_fabricators = list() + + /// Console-owned production queues. + var/list/datum/research_fabrication_job/protolathe_queue = list() + var/list/datum/research_fabrication_job/imprinter_queue = list() + var/list/datum/research_fabrication_job/mechfab_queue = list() + + //Linked Destructive Analyzer + var/obj/structure/machinery/r_n_d/destructive_analyzer/linked_destroy = null + //Shared material storage + var/obj/structure/machinery/r_n_d/material_silo/linked_silo = null + + var/selected_mech_manufacturer + + var/device_sync_range = 7 var/allow_analyzer = TRUE var/allow_lathe = TRUE var/allow_imprinter = TRUE + var/allow_mechfab = TRUE + var/allow_silo = TRUE - var/screen = 1.0 //Which screen is currently showing. - var/id = 0 //ID of the computer (for server restrictions). - var/sync = 1 //If sync = 0, it doesn't show up on Server Control Console + //Which screen is currently showing. + var/screen = 1.0 + //ID of the computer (for server restrictions). + var/id = 0 + //If sync = 0, it doesn't show up on Server Control Console + var/sync = 1 + /// Number of copies to queue when adding a design. + var/queue_amount = 1 + /// Message displayed while a delayed console action is processing. + var/busy_message = "Processing request. Please wait." var/protolathe_category = "All" var/imprinter_category = "All" var/ref_for_ui - req_access = list(ACCESS_TOX) //Data and setting manipulation requires scientist access. + //Data and setting manipulation requires scientist access. + req_access = list(ACCESS_TOX) + +/datum/research_fabrication_job + /// Design being produced. + var/datum/design/design + + /// Machine currently producing this job. + var/obj/structure/machinery/assigned_machine + + /// Manufacturer selected for mech-fabricator products. + var/manufacturer + + /// Materials removed from the silo and reserved for this job. + var/list/reserved_materials = list() + +/datum/research_fabrication_job/New(datum/design/new_design, new_manufacturer) + . = ..() + + design = new_design + manufacturer = new_manufacturer + +/datum/research_fabrication_job/Destroy() + design = null + assigned_machine = null + reserved_materials = null + return ..() /obj/structure/machinery/computer/rdconsole/proc/CallMaterialName(var/ID) return SSmaterials.material_display_name(ID) @@ -66,24 +119,77 @@ won't update every console in existence) but it's more of a hassle to do. Also, var/singleton/reagent/R = GET_SINGLETON(ID) return R ? R.name : "(none)" -/obj/structure/machinery/computer/rdconsole/proc/SyncRDevices() //Makes sure it is properly sync'ed up with the devices attached to it (if any). - for(var/obj/structure/machinery/r_n_d/D in range(3, src)) - if(D.linked_console != null || D.panel_open) +/obj/structure/machinery/computer/rdconsole/proc/SyncRDevices() + linked_fabricators ||= list() + + for(var/obj/structure/machinery/r_n_d/device in range(device_sync_range, src)) + if(device.panel_open) continue - if(istype(D, /obj/structure/machinery/r_n_d/destructive_analyzer) && allow_analyzer) - if(linked_destroy == null) - linked_destroy = D - D.linked_console = src - else if(istype(D, /obj/structure/machinery/r_n_d/protolathe) && allow_lathe) - if(linked_lathe == null) - linked_lathe = D - D.linked_console = src - else if(istype(D, /obj/structure/machinery/r_n_d/circuit_imprinter) && allow_imprinter) - if(linked_imprinter == null) - linked_imprinter = D - D.linked_console = src + + if(istype(device, /obj/structure/machinery/r_n_d/destructive_analyzer) && allow_analyzer) + if(!linked_destroy && !device.linked_console) + linked_destroy = device + device.linked_console = src + else if(istype(device, /obj/structure/machinery/r_n_d/material_silo) && allow_silo) + if(!linked_silo && !device.linked_console) + linked_silo = device + device.linked_console = src + else if(istype(device, /obj/structure/machinery/r_n_d/fabricator)) + var/obj/structure/machinery/r_n_d/fabricator/fabricator = device + if(!fabricator_type_allowed(fabricator)) + continue + if(fabricator.linked_console && fabricator.linked_console != src) + continue + fabricator.linked_console = src + linked_fabricators |= fabricator + + cleanup_fabricators() + dispatch_fabrication_jobs() return +/obj/structure/machinery/computer/rdconsole/proc/fabricator_type_allowed(obj/structure/machinery/r_n_d/fabricator/fabricator) + if(fabricator.build_type & PROTOLATHE) + return allow_lathe + if(fabricator.build_type & IMPRINTER) + return allow_imprinter + if(fabricator.build_type & MECHFAB) + return allow_mechfab + return FALSE + +/obj/structure/machinery/computer/rdconsole/proc/cleanup_fabricators() + for(var/obj/structure/machinery/r_n_d/fabricator/fabricator as anything in linked_fabricators.Copy()) + if(QDELETED(fabricator) || fabricator.linked_console != src) + linked_fabricators -= fabricator + +/obj/structure/machinery/computer/rdconsole/proc/remove_fabricator(obj/structure/machinery/r_n_d/fabricator/fabricator) + if(!fabricator) + return + linked_fabricators -= fabricator + if(fabricator.assigned_job) + fabricator.assigned_job.assigned_machine = null + fabricator.assigned_job = null + fabricator.current_design = null + SStgui.update_uis(src) + dispatch_fabrication_jobs() + +/obj/structure/machinery/computer/rdconsole/proc/get_fabricators(build_flag) + var/list/result = list() + for(var/obj/structure/machinery/r_n_d/fabricator/fabricator as anything in linked_fabricators) + if(fabricator.build_type & build_flag) + result += fabricator + return result + +/obj/structure/machinery/computer/rdconsole/proc/get_primary_fabricator(build_flag) + for(var/obj/structure/machinery/r_n_d/fabricator/fabricator as anything in linked_fabricators) + if(fabricator.build_type & build_flag) + return fabricator + return null + +/obj/structure/machinery/computer/rdconsole/proc/disconnect_fabricators(build_flag) + for(var/obj/structure/machinery/r_n_d/fabricator/fabricator as anything in linked_fabricators.Copy()) + if(fabricator.build_type & build_flag) + fabricator.disconnect_console() + /obj/structure/machinery/computer/rdconsole/proc/SyncTechs() var/turf/turf = get_turf(src) for(var/obj/structure/machinery/r_n_d/server/S in SSmachinery.machinery) @@ -103,16 +209,91 @@ won't update every console in existence) but it's more of a hassle to do. Also, screen = 1.6 updateUsrDialog() -/obj/structure/machinery/computer/rdconsole/proc/griefProtection() //Have it automatically push research to the centcomm server so wild griffins can't fuck up R&D's work +//Have it automatically push research to the centcomm server so wild griffins can't fuck up R&D's work +/obj/structure/machinery/computer/rdconsole/proc/griefProtection() for(var/obj/structure/machinery/r_n_d/server/centcom/C in SSmachinery.machinery) for(var/tech_id in files.known_tech) var/datum/tech/T = files.known_tech[tech_id] C.files.AddTech2Known(files.known_tech[T]) C.files.RefreshResearch() +/obj/structure/machinery/computer/rdconsole/proc/dispatch_fabrication_jobs() + cleanup_fabricators() + dispatch_queue_to_machines(protolathe_queue, PROTOLATHE) + dispatch_queue_to_machines(imprinter_queue, IMPRINTER) + dispatch_queue_to_machines(mechfab_queue, MECHFAB) + SStgui.update_uis(src) + +/obj/structure/machinery/computer/rdconsole/proc/dispatch_queue_to_machines(list/job_queue, build_flag) + if(!length(job_queue)) + return + + for(var/datum/research_fabrication_job/job as anything in job_queue) + if(job.assigned_machine) + continue + for(var/obj/structure/machinery/r_n_d/fabricator/fabricator as anything in linked_fabricators) + if(!(fabricator.build_type & build_flag) || !fabricator.is_available()) + continue + if(!fabricator.can_produce_job(job)) + continue + if(fabricator.begin_console_job(job)) + break + +/obj/structure/machinery/computer/rdconsole/proc/get_job_queue(build_flag) + if(build_flag & PROTOLATHE) + return protolathe_queue + if(build_flag & IMPRINTER) + return imprinter_queue + if(build_flag & MECHFAB) + return mechfab_queue + return null + +/obj/structure/machinery/computer/rdconsole/proc/refund_job_materials(datum/research_fabrication_job/job) + if(!job || !length(job.reserved_materials) || !linked_silo) + return + + for(var/material_id in job.reserved_materials) + SSmaterials.add_material_amount(linked_silo.materials, material_id, job.reserved_materials[material_id]) + + job.reserved_materials.Cut() + linked_silo.update_linked_uis() + +/obj/structure/machinery/computer/rdconsole/proc/refund_all_queued_materials() + for(var/datum/research_fabrication_job/job as anything in protolathe_queue) + refund_job_materials(job) + for(var/datum/research_fabrication_job/job as anything in imprinter_queue) + refund_job_materials(job) + for(var/datum/research_fabrication_job/job as anything in mechfab_queue) + refund_job_materials(job) + +/obj/structure/machinery/computer/rdconsole/proc/remove_fabrication_job(list/job_queue, index) + if(index < 1 || index > length(job_queue)) + return FALSE + var/datum/research_fabrication_job/job = job_queue[index] + if(job.assigned_machine) + return FALSE + refund_job_materials(job) + job_queue.Cut(index, index + 1) + qdel(job) + dispatch_fabrication_jobs() + return TRUE + +/obj/structure/machinery/computer/rdconsole/proc/finish_fabrication_job(datum/research_fabrication_job/job) + if(!job) + return + if(job in protolathe_queue) + protolathe_queue -= job + else if(job in imprinter_queue) + imprinter_queue -= job + else if(job in mechfab_queue) + mechfab_queue -= job + qdel(job) + SStgui.update_uis(src) + /obj/structure/machinery/computer/rdconsole/Initialize() ..() files = new /datum/research(src) //Setup the research data holder. + selected_mech_manufacturer = GLOB.basic_robolimb.company if(!id) for(var/obj/structure/machinery/r_n_d/server/centcom/S in SSmachinery.machinery) S.setup() @@ -127,12 +308,14 @@ won't update every console in existence) but it's more of a hassle to do. Also, screen = 1.0 /obj/structure/machinery/computer/rdconsole/Destroy() - if(linked_destroy != null) + refund_all_queued_materials() + if(linked_destroy) linked_destroy.linked_console = null - if(linked_lathe != null) - linked_lathe.linked_console = null - if(linked_imprinter != null) - linked_imprinter.linked_console = null + if(linked_silo) + linked_silo.linked_console = null + for(var/obj/structure/machinery/r_n_d/fabricator/fabricator as anything in linked_fabricators.Copy()) + fabricator.linked_console = null + linked_fabricators.Cut() return ..() /obj/structure/machinery/computer/rdconsole/attackby(obj/item/attacking_item, mob/user) @@ -155,7 +338,7 @@ won't update every console in existence) but it's more of a hassle to do. Also, //The construction/deconstruction of the console code. ..() - src.updateUsrDialog() + SStgui.update_uis(src) return /obj/structure/machinery/computer/rdconsole/emag_act(remaining_charges, mob/user, emag_source) @@ -167,285 +350,6 @@ won't update every console in existence) but it's more of a hassle to do. Also, to_chat(usr, SPAN_NOTICE("You disable the security protocols.")) return 1 -/obj/structure/machinery/computer/rdconsole/Topic(href, href_list) - if(..()) - return 1 - - add_fingerprint(usr) - - usr.set_machine(src) - if(href_list["menu"]) //Switches menu screens. Converts a sent text string into a number. Saves a LOT of code. - var/temp_screen = text2num(href_list["menu"]) - if(temp_screen <= 1.1 || (3 <= temp_screen && 4.9 >= temp_screen) || allowed(usr) || emagged) //Unless you are making something, you need access. - screen = temp_screen - else - to_chat(usr, "Unauthorized Access.") - - else if(href_list["updt_tech"]) //Update the research holder with information from the technology disk. - screen = 0.0 - spawn(50) - screen = 1.2 - files.AddTech2Known(t_disk.stored) - updateUsrDialog() - griefProtection() //Update centcomm too - - else if(href_list["clear_tech"]) //Erase data on the technology disk. - t_disk.stored = null - - else if(href_list["eject_tech"]) //Eject the technology disk. - t_disk.forceMove(loc) - usr.put_in_hands(t_disk) - t_disk = null - screen = 1.0 - - else if(href_list["copy_tech"]) //Copys some technology data from the research holder to the disk. - var/datum/tech/T = files.known_tech[href_list["copy_tech_sent"]] - t_disk.stored = T - screen = 1.2 - - else if(href_list["updt_design"]) //Updates the research holder with design data from the design disk. - screen = 0.0 - spawn(50) - screen = 1.4 - files.AddDesign2Known(d_disk.blueprint) - updateUsrDialog() - griefProtection() //Update centcomm too - - else if(href_list["clear_design"]) //Erases data on the design disk. - d_disk.blueprint = null - - else if(href_list["eject_design"]) //Eject the design disk. - d_disk.forceMove(loc) - usr.put_in_hands(d_disk) - d_disk = null - screen = 1.0 - - else if(href_list["copy_design"]) //Copy design data from the research holder to the design disk. - var/path = text2path(href_list["copy_design_sent"]) - var/datum/design/D = files.known_designs[path] - d_disk.blueprint = D - screen = 1.4 - - else if(href_list["eject_item"]) //Eject the item inside the destructive analyzer. - if(linked_destroy) - if(linked_destroy.busy) - to_chat(usr, SPAN_NOTICE("The destructive analyzer is busy at the moment.")) - - else if(linked_destroy.loaded_item) - linked_destroy.loaded_item.forceMove(linked_destroy.loc) - if(linked_destroy.Adjacent(usr)) - usr.put_in_hands(linked_destroy.loaded_item) - linked_destroy.loaded_item = null - linked_destroy.icon_state = "d_analyzer" - screen = 2.1 - - else if(href_list["deconstruct"]) //Deconstruct the item in the destructive analyzer and update the research holder. - if(linked_destroy) - if(linked_destroy.busy) - to_chat(usr, SPAN_NOTICE("The destructive analyzer is busy at the moment.")) - else - if(alert("Proceeding will destroy loaded item. Continue?", "Destructive analyzer confirmation", "Yes", "No") == "No" || !linked_destroy) - return - linked_destroy.busy = 1 - screen = 0.1 - updateUsrDialog() - flick("d_analyzer_process", linked_destroy) - spawn(24) - if(linked_destroy) - linked_destroy.busy = 0 - if(!linked_destroy.loaded_item) - to_chat(usr, SPAN_NOTICE("The destructive analyzer appears to be empty.")) - screen = 1.0 - return - - for(var/T in linked_destroy.loaded_item.origin_tech) - files.UpdateTech(T, linked_destroy.loaded_item.origin_tech[T]) - if(linked_lathe && linked_destroy.loaded_item.matter) // Also sends salvaged materials to a linked protolathe, if any. - SSmaterials.normalize_material_amounts(linked_lathe.materials) - for(var/t in linked_destroy.loaded_item.matter) - var/material = SSmaterials.material_to_path(t, FALSE) - if(material) - var/salvage_amount = min(linked_lathe.max_material_storage - linked_lathe.TotalMaterials(), linked_destroy.loaded_item.matter[t] * linked_destroy.decon_mod) - if(salvage_amount > 0) - SSmaterials.add_material_amount(linked_lathe.materials, material, salvage_amount) - - linked_destroy.loaded_item = null - for(var/obj/I in linked_destroy.contents) - for(var/mob/M in I.contents) - M.death() - qdel(M) - if(istype(I,/obj/item/stack/material))//Only deconsturcts one sheet at a time instead of the entire stack - var/obj/item/stack/material/S = I - if(S.get_amount() > 1) - S.use(1) - linked_destroy.loaded_item = S - else - qdel(S) - linked_destroy.icon_state = "d_analyzer" - else - if(!(I in linked_destroy.component_parts)) - qdel(I) - linked_destroy.icon_state = "d_analyzer" - - use_power_oneoff(linked_destroy.active_power_usage) - screen = 1.0 - updateUsrDialog() - - else if(href_list["lock"]) //Lock the console from use by anyone without tox access. - if(allowed(usr)) - screen = text2num(href_list["lock"]) - else - to_chat(usr, "Unauthorized Access.") - - else if(href_list["sync"]) //Sync the research holder with all the R&D consoles in the game that aren't sync protected. - screen = 0.0 - if(!sync) - to_chat(usr, SPAN_NOTICE("You must connect to the network first.")) - else - griefProtection() //Putting this here because I dont trust the sync process - addtimer(CALLBACK(src, PROC_REF(SyncTechs)), 30) - - else if(href_list["togglesync"]) //Prevents the console from being synced by other consoles. Can still send data. - sync = !sync - - else if(href_list["protolathe_category"]) - var/choice = tgui_input_list(usr, "Which category do you wish to display?", "Protolathe Categories", GLOB.designs_protolathe_categories+"All") - if(!choice) - return - protolathe_category = choice - updateUsrDialog() - - else if(href_list["imprinter_category"]) - var/choice = tgui_input_list(usr, "Which category do you wish to display?", "Printer Categories", GLOB.designs_imprinter_categories+"All") - if(!choice) - return - imprinter_category = choice - updateUsrDialog() - - else if(href_list["build"]) //Causes the Protolathe to build something. - if(linked_lathe) - var/path = text2path(href_list["build"]) - var/datum/design/D = files.known_designs[path] - linked_lathe.addToQueue(D) - screen = 3.1 - updateUsrDialog() - - else if(href_list["imprint"]) //Causes the Circuit Imprinter to build something. - if(linked_imprinter) - var/path = text2path(href_list["imprint"]) - var/datum/design/D = files.known_designs[path] - linked_imprinter.addToQueue(D) - screen = 4.1 - updateUsrDialog() - - else if(href_list["disposeI"]) //Causes the circuit imprinter to dispose of a single reagent (all of it) - linked_imprinter?.reagents.del_reagent(href_list["disposeI"]) - - else if(href_list["disposeallI"]) //Causes the circuit imprinter to dispose of all it's reagents. - linked_imprinter?.reagents.clear_reagents() - - else if(href_list["removeI"]) - linked_imprinter?.removeFromQueue(text2num(href_list["removeI"])) - - else if(href_list["disposeP"]) //Causes the protolathe to dispose of a single reagent (all of it) - linked_lathe?.reagents.del_reagent(href_list["dispose"]) - - else if(href_list["disposeallP"]) //Causes the protolathe to dispose of all it's reagents. - linked_lathe?.reagents.clear_reagents() - - else if(href_list["removeP"]) - linked_lathe?.removeFromQueue(text2num(href_list["removeP"])) - - else if(href_list["lathe_ejectsheet"] && linked_lathe) //Causes the protolathe to eject a sheet of material - SSmaterials.normalize_material_amounts(linked_lathe.materials) - var/material = SSmaterials.material_to_path(href_list["lathe_ejectsheet"], FALSE) - if(!material) - return - - var/num_sheets = min(text2num(href_list["amount"]), round((linked_lathe.materials[material] || 0) / SHEET_MATERIAL_AMOUNT)) - - if(num_sheets < 1) - return - - var/mattype = linked_lathe.getMaterialType(material) - if(!mattype) - return - - var/obj/item/stack/material/M = new mattype(linked_lathe.loc) - M.amount = num_sheets - SSmaterials.remove_material_amount(linked_lathe.materials, material, num_sheets * SHEET_MATERIAL_AMOUNT) - - else if(href_list["imprinter_ejectsheet"] && linked_imprinter) //Causes the protolathe to eject a sheet of material - SSmaterials.normalize_material_amounts(linked_imprinter.materials) - var/material = SSmaterials.material_to_path(href_list["imprinter_ejectsheet"], FALSE) - if(!material) - return - - var/num_sheets = min(text2num(href_list["amount"]), round((linked_imprinter.materials[material] || 0) / SHEET_MATERIAL_AMOUNT)) - - if(num_sheets < 1) - return - - var/mattype = linked_imprinter.getMaterialType(material) - if(!mattype) - return - - var/obj/item/stack/material/M = new mattype(linked_imprinter.loc) - M.amount = num_sheets - SSmaterials.remove_material_amount(linked_imprinter.materials, material, num_sheets * SHEET_MATERIAL_AMOUNT) - - else if(href_list["find_device"]) //The R&D console looks for devices nearby to link up with. - screen = 0.0 - spawn(10) - SyncRDevices() - screen = 1.7 - updateUsrDialog() - - else if(href_list["disconnect"]) //The R&D console disconnects with a specific device. - switch(href_list["disconnect"]) - if("destroy") - linked_destroy.linked_console = null - linked_destroy = null - if("lathe") - linked_lathe.linked_console = null - linked_lathe = null - if("imprinter") - linked_imprinter.linked_console = null - linked_imprinter = null - - else if(href_list["reset"]) //Reset the R&D console's database. - griefProtection() - var/choice = alert("R&D Console Database Reset", "Are you sure you want to reset the R&D console's database? Data lost cannot be recovered.", "Continue", "Cancel") - if(choice == "Continue") - screen = 0.0 - qdel(files) - files = new /datum/research(src) - spawn(20) - screen = 1.6 - updateUsrDialog() - - else if (href_list["print"]) //Print research information - screen = 0.5 - spawn(20) - var/obj/item/paper/PR = new/obj/item/paper - var/pname = "list of researched technologies" - var/info = "
[station_name()] Science Laboratories" - info += "

[ (text2num(href_list["print"]) == 2) ? "Detailed" : null] Research Progress Report

" - info += "report prepared at [worldtime2text()] station time

" - if(text2num(href_list["print"]) == 2) - info += GetResearchListInfo() - else - info += GetResearchLevelsInfo() - - PR.set_content_unsafe(pname, info) - print(PR, user = usr) - spawn(10) - screen = ((text2num(href_list["print"]) == 2) ? 5.0 : 1.1) - updateUsrDialog() - - updateUsrDialog() - return - /obj/structure/machinery/computer/rdconsole/proc/GetResearchLevelsInfo() var/dat dat += "" return dat -/obj/structure/machinery/computer/rdconsole/attack_hand(mob/user as mob) - if(stat & (BROKEN|NOPOWER)) +/obj/structure/machinery/computer/rdconsole/attack_hand(mob/user) + if(..()) return + ui_interact(user) - user.set_machine(src) - var/dat = "" - files.RefreshResearch() - switch(screen) //A quick check to make sure you get the right screen when a device is disconnected. - if(2 to 2.9) - if(linked_destroy == null) - screen = 2.0 - else if(linked_destroy.loaded_item == null) - screen = 2.1 - else - screen = 2.2 - if(3 to 3.9) - if(linked_lathe == null) - screen = 3.0 - if(4 to 4.9) - if(linked_imprinter == null) - screen = 4.0 +/obj/structure/machinery/computer/rdconsole/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ResearchConsole", "Research and Development Console", 1200, 800) + ui.open() +/obj/structure/machinery/computer/rdconsole/proc/tgui_screen_name() switch(screen) - - //////////////////////R&D CONSOLE SCREENS////////////////// - if(0.0) - dat += "Updating Database..." - - if(0.1) - dat += "Processing and Updating Database..." - - if(0.2) - dat += "SYSTEM LOCKED

" - dat += "Unlock" - - if(0.3) - dat += "Constructing Prototype. Please Wait..." - - if(0.4) - dat += "Imprinting Circuit. Please Wait..." - - if(0.5) - dat += "Printing Research Information. Please Wait..." - - if(1.0) //Main Menu - dat += "Main Menu:
" - - if(1.1) //Research viewer - dat += "Main Menu || " - dat += "Print This Page
" - dat += "Current Research Levels:
" - dat += GetResearchLevelsInfo() - dat += "" - - if(1.2) //Technology Disk Menu - - dat += "Main Menu
" - dat += "Disk Contents: (Technology Data Disk)

" - dat += "" - - if(1.3) //Technology Disk submenu - dat += "
Main Menu || " - dat += "Return to Disk Operations
" - dat += "" - - if(1.4) //Design Disk menu. - dat += "Main Menu
" - dat += "" - - if(1.5) //Technology disk submenu - dat += "Main Menu || " - dat += "Return to Disk Operations
" - dat += "" - - if(1.6) //R&D console settings - dat += "Main Menu
" - dat += "R&D Console Setting:
" - dat += "" - - if(1.7) //R&D device linkage - dat += "Main Menu || " - dat += "Settings Menu
" - dat += "R&D Console Device Linkage Menu:
" - dat += "" - - ////////////////////DESTRUCTIVE ANALYZER SCREENS//////////////////////////// - if(2.0) - dat += "Main Menu
" - dat += "NO DESTRUCTIVE ANALYZER LINKED TO CONSOLE

" - - if(2.1) - dat += "Main Menu
" - dat += "No Item Loaded. Standing-by...

" - - if(2.2) - dat += "Main Menu
" - dat += "Deconstruction Menu
" - dat += "Name: [linked_destroy.loaded_item.name]
" - dat += "Origin Tech:" - dat += "" - if(!istype(linked_destroy.loaded_item, /obj/item/stack)) - dat += "
Deconstruct Item || " - else - dat += "
Deconstruct One In Stack || " - dat += "Eject Item" - - /////////////////////PROTOLATHE SCREENS///////////////////////// - if(3.0) - dat += "Main Menu
" - dat += "NO PROTOLATHE LINKED TO CONSOLE

" - - if(3.1) - dat += "Main Menu || " - dat += "View Queue || " - dat += "Material Storage || " - dat += "Chemical Storage
" - dat += "Protolathe Menu:
" - dat += "Material Amount: [linked_lathe.TotalMaterials()] cm3 (MAX: [linked_lathe.max_material_storage])
" - dat += "Chemical Volume: [linked_lathe.reagents.total_volume] (MAX: [linked_lathe.reagents.maximum_volume])
" - dat += "Category: [protolathe_category]
" - dat += "
" - dat += "" - dat += "
" - - if(3.2) //Protolathe Material Storage Sub-menu - dat += "Main Menu || " - dat += "Protolathe Menu
" - dat += "Material Storage

" - dat += "" - - if(3.3) //Protolathe Chemical Storage Submenu - dat += "Main Menu || " - dat += "Protolathe Menu
" - dat += "Chemical Storage

" - for(var/_R in linked_lathe.reagents.reagent_volumes) - var/singleton/reagent/R = GET_SINGLETON(_R) - dat += "Name: [R.name] | Units: [linked_lathe.reagents.reagent_volumes[_R]] " - dat += "(Purge)
" - dat += "Disposal All Chemicals in Storage
" - - if(3.4) // Protolathe queue - dat += "Main Menu || " - dat += "Protolathe Menu
" - dat += "Queue

" - if(!linked_lathe.queue.len) - dat += "Empty" - else - var/tmp = 1 - for(var/datum/design/D in linked_lathe.queue) - if(tmp == 1) - if(linked_lathe.busy) - dat += "1: [D.name]
" - else - dat += "1: [D.name] (Awaiting materials) Remove
" - else - dat += "[tmp]: [D.name] Remove
" - ++tmp - - ///////////////////CIRCUIT IMPRINTER SCREENS//////////////////// - if(4.0) - dat += "Main Menu
" - dat += "NO CIRCUIT IMPRINTER LINKED TO CONSOLE

" - - if(4.1) - dat += "Main Menu || " - dat += "View Queue || " - dat += "Material Storage || " - dat += "Chemical Storage
" - dat += "Circuit Imprinter Menu:

" - dat += "Material Amount: [linked_imprinter.TotalMaterials()] cm3
" - dat += "Chemical Volume: [linked_imprinter.reagents.total_volume]
" - dat += "Category: [imprinter_category]
" - dat += "
" - dat += "" - dat += "
" - - if(4.2) - dat += "Main Menu || " - dat += "Imprinter Menu
" - dat += "Chemical Storage

" - for(var/_R in linked_imprinter.reagents.reagent_volumes) - var/singleton/reagent/R = GET_SINGLETON(_R) - dat += "Name: [R.name] | Units: [linked_imprinter.reagents.reagent_volumes[_R]] " - dat += "(Purge)
" - dat += "Disposal All Chemicals in Storage
" - - if(4.3) - dat += "Main Menu || " - dat += "Circuit Imprinter Menu
" - dat += "Material Storage

" - dat += "" - - if(4.4) - dat += "Main Menu || " - dat += "Circuit Imprinter Menu
" - dat += "Queue

" - if(linked_imprinter.queue.len == 0) - dat += "Empty" - else - var/tmp = 1 - for(var/datum/design/D in linked_imprinter.queue) - if(tmp == 1) - dat += "1: [D.name]
" - else - dat += "[tmp]: [D.name] Remove
" - ++tmp - - ///////////////////Research Information Browser//////////////////// + if(0.0 to 0.9) + return "busy" + if(1.1) + return "levels" + if(1.2) + return "tech_disk" + if(1.4) + return "design_disk" + if(1.6, 1.7) + return "settings" + if(2.0 to 2.9) + return "analyzer" + if(3.0 to 3.9) + return "protolathe" + if(4.0 to 4.9) + return "imprinter" if(5.0) - dat += "Main Menu || " - dat += "Print This Page
" - dat += "List of Researched Technologies and Designs:
" - dat += GetResearchListInfo() + return "designs" + if(6.0 to 6.9) + return "mechfab" + return "main" - var/datum/browser/rdconsole = new(user, "rdconsole", "Research and Development Console", 850, 600) - rdconsole.add_stylesheet("rdconsole", 'html/browser/rdconsole.css') - rdconsole.set_content(dat) - rdconsole.open() +/obj/structure/machinery/computer/rdconsole/proc/tgui_screen_number(screen_name) + switch(screen_name) + if("main") + return 1.0 + if("levels") + return 1.1 + if("tech_disk") + return 1.2 + if("design_disk") + return 1.4 + if("settings") + return 1.6 + if("analyzer") + return linked_destroy?.loaded_item ? 2.2 : 2.1 + if("protolathe") + return length(get_fabricators(PROTOLATHE)) ? 3.1 : 3.0 + if("imprinter") + return length(get_fabricators(IMPRINTER)) ? 4.1 : 4.0 + if("mechfab") + return length(get_fabricators(MECHFAB)) ? 6.1 : 6.0 + if("designs") + return 5.0 + return 1.0 + +/obj/structure/machinery/computer/rdconsole/proc/can_open_tgui_screen(screen_name, mob/user) + if(screen_name in list("main", "levels", "designs", "protolathe", "imprinter", "mechfab")) + return TRUE + return allowed(user) || emagged + +/obj/structure/machinery/computer/rdconsole/proc/get_technology_tgui_data() + var/list/result = list() + for(var/tech_id in files.known_tech) + var/datum/tech/T = files.known_tech[tech_id] + if(T.level < 1) + continue + result += list(list( + "id" = tech_id, + "name" = T.name, + "description" = T.desc, + "level" = T.level, + "progress" = T.next_level_progress, + "threshold" = T.next_level_threshold, + "complete" = T.level >= 15 + )) + return result + +/obj/structure/machinery/computer/rdconsole/proc/get_design_category(datum/design/D, build_flag = 0) + if((build_flag & MECHFAB) && D.category) + return D.category + if(D.p_category) + return D.p_category + if(D.category) + return D.category + return "Misc" + +/obj/structure/machinery/computer/rdconsole/proc/get_design_tgui_data() + var/list/result = list() + for(var/path in files.known_designs) + var/datum/design/D = files.known_designs[path] + if(!D.build_path) + continue + result += list(list( + "path" = "[D.type]", + "name" = D.name, + "description" = D.desc, + "category" = get_design_category(D) + )) + return result + +/obj/structure/machinery/computer/rdconsole/proc/get_analyzer_tgui_data() + var/list/result = list("linked" = !!linked_destroy) + if(!linked_destroy) + return result + result["busy"] = linked_destroy.busy + if(!linked_destroy.loaded_item) + return result + result["item"] = list( + "name" = linked_destroy.loaded_item.name, + "stack" = istype(linked_destroy.loaded_item, /obj/item/stack), + "technologies" = list() + ) + var/list/techs = result["item"]["technologies"] + for(var/tech_id in linked_destroy.loaded_item.origin_tech) + var/datum/tech/T = files.known_tech[tech_id] + if(!T) + continue + techs += list(list( + "name" = T.name, + "item_level" = linked_destroy.loaded_item.origin_tech[tech_id], + "current_level" = T.level, + "progress" = T.next_level_progress, + "threshold" = T.next_level_threshold + )) + return result + +/obj/structure/machinery/computer/rdconsole/proc/get_stored_reagent_amount(datum/reagents/reagent_holder, reagent_id) + if(!reagent_holder || !reagent_id) + return 0 + + var/list/reagent_volumes = reagent_holder.reagent_volumes + + if(!islist(reagent_volumes)) + return 0 + + /* + * Use keys obtained directly from reagent_volumes. This avoids attempting + * to index the list with a design chemical identifier that may not be in + * precisely the same representation as the holder's key. + */ + for(var/stored_reagent_id in reagent_volumes) + if(stored_reagent_id == reagent_id || "[stored_reagent_id]" == "[reagent_id]") + return reagent_volumes[stored_reagent_id] || 0 + + return 0 + +/obj/structure/machinery/computer/rdconsole/proc/get_research_fabricator_data(build_flag) + var/list/fabricators = get_fabricators(build_flag) + if(!length(fabricators)) + return null + + var/obj/structure/machinery/r_n_d/fabricator/primary = fabricators[1] + var/list/job_queue = get_job_queue(build_flag) + var/list/stored_materials = linked_silo?.materials || list() + var/datum/reagents/reagent_holder = primary.uses_reagents ? primary.reagents : null + var/list/data = list( + "linked" = TRUE, + "materials" = list(), + "reagents" = list(), + "recipes" = list(), + "categories" = list("All"), + "queue" = list(), + "sheet_material_amount" = SHEET_MATERIAL_AMOUNT, + "maximum_material_storage" = linked_silo?.max_material_storage || 0, + "maximum_reagent_volume" = reagent_holder?.maximum_volume || 0, + "reagent_volume" = reagent_holder?.total_volume || 0, + "supports_manufacturers" = build_flag & MECHFAB, + "manufacturers" = list(), + "selected_manufacturer" = selected_mech_manufacturer, + "supports_reagents" = FALSE + ) + + SSmaterials.normalize_material_amounts(stored_materials) + for(var/material in stored_materials) + data["materials"] += list(list( + "id" = "[material]", + "name" = CallMaterialName(material), + "amount" = stored_materials[material], + "maximum" = linked_silo?.max_material_storage || 0 + )) + + if(reagent_holder) + for(var/reagent_type in reagent_holder.reagent_volumes) + data["reagents"] += list(list( + "id" = "[reagent_type]", + "name" = CallReagentName(reagent_type), + "amount" = reagent_holder.reagent_volumes[reagent_type] + )) + + if(build_flag & MECHFAB) + for(var/manufacturer_id in GLOB.fabricator_robolimbs) + var/datum/robolimb/robolimb = GLOB.fabricator_robolimbs[manufacturer_id] + data["manufacturers"] += list(list("id" = manufacturer_id, "name" = robolimb.company)) + + for(var/path in files.known_designs) + var/datum/design/design = files.known_designs[path] + if(!design.build_path || !(design.build_type & build_flag)) + continue + + var/category = get_design_category(design, build_flag) + data["categories"] |= category + var/list/resources = list() + var/list/requirements = list() + + for(var/material_id in design.materials) + var/material_path = SSmaterials.material_to_path(material_id, FALSE) + if(!material_path) + material_path = material_id + var/required_material = primary.get_required_material_amount(design, material_id) + var/stored_material = stored_materials[material_path] || 0 + var/material_name = CallMaterialName(material_id) + resources += "[required_material] [material_name]" + requirements += list(list("name" = material_name, "required" = required_material, "stored" = stored_material, "missing" = stored_material < required_material, "type" = "material")) + + if(primary.uses_reagents && length(design.chemicals)) + data["supports_reagents"] = TRUE + for(var/reagent_id in design.chemicals) + var/required_reagent = primary.get_required_reagent_amount(design, reagent_id) + var/stored_reagent = get_stored_reagent_amount(reagent_holder, reagent_id) + var/reagent_name = CallReagentName(reagent_id) + resources += "[required_reagent] [reagent_name]" + requirements += list(list("name" = reagent_name, "required" = required_reagent, "stored" = stored_reagent, "missing" = stored_reagent < required_reagent, "type" = "reagent")) + + var/can_build_anywhere = FALSE + var/fastest_time = null + for(var/obj/structure/machinery/r_n_d/fabricator/fabricator as anything in fabricators) + can_build_anywhere ||= fabricator.can_build(design) + var/machine_time = round(design.time / fabricator.production_speed) + if(isnull(fastest_time) || machine_time < fastest_time) + fastest_time = machine_time + + data["recipes"] += list(list( + "name" = design.name, + "description" = design.desc, + "design" = "[path]", + "category" = category, + "resources" = english_list(resources), + "requirements" = requirements, + "can_build" = can_build_anywhere, + "build_time" = fastest_time || design.time + )) + + var/index = 1 + for(var/datum/research_fabrication_job/job as anything in job_queue) + var/obj/structure/machinery/r_n_d/fabricator/machine = job.assigned_machine + var/build_time = machine ? round(job.design.time / machine.production_speed) : round(job.design.time / primary.production_speed) + var/remaining_time = machine?.build_callback_timer ? max(0, timeleft(machine.build_callback_timer)) : 0 + data["queue"] += list(list( + "index" = index, + "name" = job.design.name, + "build_time" = build_time, + "active" = !!machine, + "machine" = machine?.name, + "remaining_time" = remaining_time + )) + index++ + + return data + +/obj/structure/machinery/computer/rdconsole/proc/validate_fabricator_screen() + cleanup_fabricators() + switch(tgui_screen_name()) + if("protolathe") + if(!length(get_fabricators(PROTOLATHE))) + screen = 1.0 + if("imprinter") + if(!length(get_fabricators(IMPRINTER))) + screen = 1.0 + if("mechfab") + if(!length(get_fabricators(MECHFAB))) + screen = 1.0 + +/obj/structure/machinery/computer/rdconsole/ui_data(mob/user) + validate_fabricator_screen() + files.RefreshResearch() + var/list/data = list( + "manufacturer" = manufacturer, + "screen" = tgui_screen_name(), + "authorized" = allowed(user) || emagged, + "emagged" = emagged, + "network_sync" = sync, + "busy_message" = busy_message, + "loaded_disk" = null, + "queue_amount" = queue_amount, + "devices" = list( + "analyzer" = linked_destroy ? 1 : 0, + "protolathe" = length(get_fabricators(PROTOLATHE)), + "imprinter" = length(get_fabricators(IMPRINTER)), + "mechfab" = length(get_fabricators(MECHFAB)), + "silo" = linked_silo ? 1 : 0 + ) + ) + if(t_disk) + data["loaded_disk"] = list("type" = "technology", "name" = t_disk.name, "stored_name" = t_disk.stored?.name, "stored_description" = t_disk.stored?.desc, "stored_level" = t_disk.stored?.level) + else if(d_disk) + data["loaded_disk"] = list("type" = "design", "name" = d_disk.name, "stored_name" = d_disk.blueprint?.name, "stored_description" = d_disk.blueprint?.desc) + switch(data["screen"]) + if("levels") + data["technologies"] = get_technology_tgui_data() + if("designs") + data["designs"] = get_design_tgui_data() + if("tech_disk") + data["technologies"] = get_technology_tgui_data() + if("design_disk") + data["designs"] = get_design_tgui_data() + if("analyzer") + data["analyzer"] = get_analyzer_tgui_data() + if("protolathe") + data["fabricator"] = get_research_fabricator_data(PROTOLATHE) + if("imprinter") + data["fabricator"] = get_research_fabricator_data(IMPRINTER) + if("mechfab") + data["fabricator"] = get_research_fabricator_data(MECHFAB) + return data + +/obj/structure/machinery/computer/rdconsole/proc/eject_fabricator_material(obj/structure/machinery/r_n_d/machine, material_id, requested_sheets) + return linked_silo?.eject_material(material_id, requested_sheets) + +/obj/structure/machinery/computer/rdconsole/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + add_fingerprint(usr) + usr.set_machine(src) + switch(action) + if("set_screen") + var/new_screen = params["screen"] + if(!can_open_tgui_screen(new_screen, usr)) + to_chat(usr, SPAN_WARNING("Unauthorized access.")) + return + screen = tgui_screen_number(new_screen) + . = TRUE + if("back") + screen = 1.0 + . = TRUE + if("upload_disk") + if(t_disk?.stored) + files.AddTech2Known(t_disk.stored) + griefProtection() + . = TRUE + else if(d_disk?.blueprint) + files.AddDesign2Known(d_disk.blueprint) + griefProtection() + . = TRUE + if("clear_disk") + if(t_disk) + t_disk.stored = null + . = TRUE + else if(d_disk) + d_disk.blueprint = null + . = TRUE + if("eject_disk") + var/obj/item/disk/disk = t_disk || d_disk + if(disk) + disk.forceMove(loc) + usr.put_in_hands(disk) + t_disk = null + d_disk = null + screen = 1.0 + . = TRUE + if("copy_tech") + if(t_disk) + t_disk.stored = files.known_tech[params["id"]] + . = TRUE + if("copy_design") + if(d_disk) + var/path = text2path(params["design"]) + d_disk.blueprint = files.known_designs[path] + . = TRUE + if("analyzer_eject") + if(linked_destroy && !linked_destroy.busy && linked_destroy.loaded_item) + linked_destroy.loaded_item.forceMove(linked_destroy.loc) + if(linked_destroy.Adjacent(usr)) + usr.put_in_hands(linked_destroy.loaded_item) + linked_destroy.loaded_item = null + linked_destroy.icon_state = "d_analyzer" + . = TRUE + if("analyzer_deconstruct") + if(linked_destroy && !linked_destroy.busy && linked_destroy.loaded_item) + linked_destroy.busy = TRUE + screen = 0.1 + flick("d_analyzer_process", linked_destroy) + addtimer(CALLBACK(src, PROC_REF(finish_analyzer_deconstruction)), 24) + . = TRUE + if("fabricator_build") + var/design_text = params["design"] + if(!design_text) + return FALSE + + var/path = text2path(design_text) + var/datum/design/design = files.known_designs[path] + + if(!design) + to_chat(usr, SPAN_WARNING("The selected design could not be found in the research database.")) + return FALSE + + switch(tgui_screen_name()) + if("protolathe") + if(!(design.build_type & PROTOLATHE)) + return FALSE + + for(var/i in 1 to queue_amount) + protolathe_queue += new /datum/research_fabrication_job(design) + + if("imprinter") + if(!(design.build_type & IMPRINTER)) + return FALSE + + for(var/i in 1 to queue_amount) + imprinter_queue += new /datum/research_fabrication_job(design) + + if("mechfab") + if(!(design.build_type & MECHFAB)) + return FALSE + + for(var/i in 1 to queue_amount) + mechfab_queue += new /datum/research_fabrication_job(design, selected_mech_manufacturer) + + else + return FALSE + + dispatch_fabrication_jobs() + . = TRUE + if("fabricator_remove") + var/index = text2num(params["index"]) + switch(tgui_screen_name()) + if("protolathe") + . = remove_fabrication_job(protolathe_queue, index) + if("imprinter") + . = remove_fabrication_job(imprinter_queue, index) + if("mechfab") + . = remove_fabrication_job(mechfab_queue, index) + if("fabricator_eject") + var/amount = text2num(params["amount"]) + . = linked_silo?.eject_material(params["material"], amount) + if("fabricator_eject_custom") + if(!linked_silo) + return FALSE + var/material_id = params["material"] + var/available_sheets = floor(linked_silo.get_material_amount(material_id) / SHEET_MATERIAL_AMOUNT) + if(available_sheets < 1) + return FALSE + var/amount = input(usr, "How many sheets should be ejected?", "Material Ejection", 1) as null|num + if(isnull(amount)) + return TRUE + amount = clamp(round(amount), 1, available_sheets) + . = linked_silo.eject_material(material_id, amount) + if("fabricator_purge") + var/build_flag = tgui_screen_name() == "protolathe" ? PROTOLATHE : IMPRINTER + var/obj/structure/machinery/r_n_d/fabricator/fabricator = get_primary_fabricator(build_flag) + if(fabricator?.reagents) + fabricator.reagents.del_reagent(params["reagent"]) + . = TRUE + if("fabricator_purge_all") + var/build_flag = tgui_screen_name() == "protolathe" ? PROTOLATHE : IMPRINTER + var/obj/structure/machinery/r_n_d/fabricator/fabricator = get_primary_fabricator(build_flag) + if(fabricator?.reagents) + fabricator.reagents.clear_reagents() + . = TRUE + if("fabricator_manufacturer") + var/manufacturer_id = params["manufacturer"] + if(manufacturer_id in GLOB.fabricator_robolimbs) + var/datum/robolimb/robolimb = GLOB.fabricator_robolimbs[manufacturer_id] + selected_mech_manufacturer = robolimb.company + . = TRUE + if("set_queue_amount") + var/new_amount = clamp(text2num(params["amount"]), 1, 100) + if(new_amount in list(1, 5, 10)) + queue_amount = new_amount + return TRUE + if("set_custom_queue_amount") + var/new_amount = input(usr, "How many copies should be queued at once?", "Queue Amount", queue_amount) as null|num + if(isnull(new_amount)) + return TRUE + queue_amount = clamp(round(new_amount), 1, 100) + return TRUE + if("find_devices") + if(allowed(usr) || emagged) + busy_message = "Scanning for nearby research devices..." + screen = 0.2 + addtimer(CALLBACK(src, PROC_REF(finish_find_devices)), 5) + . = TRUE + if("disconnect") + switch(params["device"]) + if("analyzer") + if(linked_destroy) + linked_destroy.linked_console = null + linked_destroy = null + if("protolathe") + disconnect_fabricators(PROTOLATHE) + if("imprinter") + disconnect_fabricators(IMPRINTER) + if("mechfab") + disconnect_fabricators(MECHFAB) + if("silo") + linked_silo?.disconnect_console() + . = TRUE + if("toggle_sync") + if(allowed(usr) || emagged) + sync = !sync + . = TRUE + if("sync_network") + if((allowed(usr) || emagged) && sync) + griefProtection() + busy_message = "Synchronizing research database with the network..." + screen = 0.3 + addtimer(CALLBACK(src, PROC_REF(finish_network_sync)), 30) + . = TRUE + if("reset_database") + if(allowed(usr) || emagged) + griefProtection() + qdel(files) + files = new /datum/research(src) + . = TRUE + if("print_research") + var/detailed = !!params["detailed"] + var/obj/item/paper/PR = new /obj/item/paper + var/info = "
[station_name()] Science Laboratories

[detailed ? "Detailed" : ""] Research Progress Report

report prepared at [worldtime2text()] station time

" + info += detailed ? GetResearchListInfo() : GetResearchLevelsInfo() + PR.set_content_unsafe("list of researched technologies", info) + print(PR, user = usr) + . = TRUE + if(.) + SStgui.update_uis(src) + + +/obj/structure/machinery/computer/rdconsole/proc/finish_find_devices() + SyncRDevices() + busy_message = "Processing request. Please wait." + screen = 1.6 + SStgui.update_uis(src) + +/obj/structure/machinery/computer/rdconsole/proc/finish_network_sync() + SyncTechs() + busy_message = "Processing request. Please wait." + screen = 1.6 + SStgui.update_uis(src) + +/obj/structure/machinery/computer/rdconsole/proc/finish_analyzer_deconstruction() + if(!linked_destroy) + screen = 1.0 + return + linked_destroy.busy = FALSE + if(!linked_destroy.loaded_item) + screen = 1.0 + SStgui.update_uis(src) + return + for(var/T in linked_destroy.loaded_item.origin_tech) + files.UpdateTech(T, linked_destroy.loaded_item.origin_tech[T]) + if(linked_silo && linked_destroy.loaded_item.matter) + for(var/t in linked_destroy.loaded_item.matter) + linked_silo.add_material(t, linked_destroy.loaded_item.matter[t] * linked_destroy.decon_mod) + linked_destroy.loaded_item = null + for(var/obj/I in linked_destroy.contents) + for(var/mob/M in I.contents) + M.death() + qdel(M) + if(istype(I, /obj/item/stack/material)) + var/obj/item/stack/material/S = I + if(S.get_amount() > 1) + S.use(1) + linked_destroy.loaded_item = S + else + qdel(S) + else if(!(I in linked_destroy.component_parts)) + qdel(I) + linked_destroy.icon_state = linked_destroy.loaded_item ? linked_destroy.icon_state : "d_analyzer" + use_power_oneoff(linked_destroy.active_power_usage) + griefProtection() + screen = 1.0 + SStgui.update_uis(src) /obj/structure/machinery/computer/rdconsole/robotics name = "robotics R&D console" id = 1 req_access = list(ACCESS_ROBOTICS) allow_analyzer = FALSE + manufacturer = "hephaestus" circuit = /obj/item/circuitboard/robotics_console /obj/structure/machinery/computer/rdconsole/robotics/terminal diff --git a/code/modules/research/rdmachines.dm b/code/modules/research/rdmachines.dm index fb716cecdd2..fbcfbdcb7bb 100644 --- a/code/modules/research/rdmachines.dm +++ b/code/modules/research/rdmachines.dm @@ -1,6 +1,6 @@ //This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:33 -//All devices that link into the R&D console fall into thise type for easy identification and some shared procs. +//All devices that link into the R&D console fall into this type for easy identification and shared procs. /obj/structure/machinery/r_n_d name = "R&D device" @@ -15,3 +15,351 @@ /obj/structure/machinery/r_n_d/proc/getMaterialType(var/name) return SSmaterials.material_stack_type(name) + +/obj/structure/machinery/r_n_d/fabricator + /// The design build flag accepted by this machine. + var/build_type + + /// Whether this machine checks and consumes design chemicals. + var/uses_reagents = FALSE + + /// Whether completed products are placed one tile in front of the machine. + var/product_offset = FALSE + + /// Material cost multiplier. + var/mat_efficiency = 1 + + /// Fabrication speed multiplier. + var/production_speed = 1 + + /// Job currently assigned by the research console. + var/datum/research_fabrication_job/assigned_job + + /// Design currently being fabricated. + var/datum/design/current_design + + /// Timer ID for the active fabrication. + var/build_callback_timer + + /// Looping-sound datum selected by this machine type. + var/fabrication_loop_type + + /// Active looping-sound controller for this machine. + var/datum/looping_sound/fabrication_loop + + /// Whether this fabricator's construction loop is currently playing. + var/fabrication_audio_playing = FALSE + +/obj/structure/machinery/r_n_d/fabricator/Initialize(mapload, d, populate_components, is_internal) + . = ..() + + if(fabrication_loop_type) + fabrication_loop = new fabrication_loop_type(src) + +/obj/structure/machinery/r_n_d/fabricator/proc/start_fabrication_audio() + if(!fabrication_loop || fabrication_audio_playing) + return + + fabrication_loop.start() + fabrication_audio_playing = TRUE + +/obj/structure/machinery/r_n_d/fabricator/proc/stop_fabrication_audio() + if(!fabrication_loop || !fabrication_audio_playing) + return + + fabrication_loop.stop() + fabrication_audio_playing = FALSE + +/obj/structure/machinery/r_n_d/fabricator/power_change() + . = ..() + if((stat & NOPOWER) && build_callback_timer) + if(build_callback_timer) + deltimer(build_callback_timer) + pause_console_job() + +/obj/structure/machinery/r_n_d/fabricator/proc/is_available() + return !assigned_job && !build_callback_timer && !(stat & (NOPOWER | BROKEN)) && !panel_open + +/obj/structure/machinery/r_n_d/fabricator/proc/get_material_silo() + return linked_console?.linked_silo + +/obj/structure/machinery/r_n_d/fabricator/proc/get_required_material_amount(datum/design/design_to_check, material_id) + return round(design_to_check.materials[material_id] * mat_efficiency) + +/obj/structure/machinery/r_n_d/fabricator/proc/get_required_reagent_amount(datum/design/design_to_check, reagent_id) + return round(design_to_check.chemicals[reagent_id] * mat_efficiency) + +/obj/structure/machinery/r_n_d/fabricator/proc/has_required_materials(datum/design/design_to_check) + if(!design_to_check) + return FALSE + + var/obj/structure/machinery/r_n_d/material_silo/silo = get_material_silo() + if(!silo) + return FALSE + + for(var/material_id in design_to_check.materials) + if(!silo.has_material(material_id, get_required_material_amount(design_to_check, material_id))) + return FALSE + + return TRUE + +/obj/structure/machinery/r_n_d/fabricator/proc/has_required_reagents(datum/design/design_to_check) + if(!design_to_check) + return FALSE + if(!uses_reagents) + return TRUE + + for(var/reagent_id in design_to_check.chemicals) + if(!reagents?.has_reagent(reagent_id, get_required_reagent_amount(design_to_check, reagent_id))) + return FALSE + + return TRUE + +/obj/structure/machinery/r_n_d/fabricator/proc/can_build(datum/design/design_to_check) + return has_required_materials(design_to_check) && has_required_reagents(design_to_check) + +/obj/structure/machinery/r_n_d/fabricator/proc/canBuild(datum/design/design_to_check) + return can_build(design_to_check) + +/obj/structure/machinery/r_n_d/fabricator/proc/getLackingMaterials(datum/design/design_to_check) + var/obj/structure/machinery/r_n_d/material_silo/silo = get_material_silo() + var/list/missing = list() + + for(var/material_id in design_to_check.materials) + var/required_amount = get_required_material_amount(design_to_check, material_id) + var/stored_amount = silo?.get_material_amount(material_id) || 0 + if(stored_amount < required_amount) + missing += "[required_amount - stored_amount] [SSmaterials.material_display_name(material_id)]" + + if(uses_reagents) + for(var/reagent_id in design_to_check.chemicals) + var/required_amount = get_required_reagent_amount(design_to_check, reagent_id) + if(!reagents?.has_reagent(reagent_id, required_amount)) + var/singleton/reagent/reagent = GET_SINGLETON(reagent_id) + missing += reagent?.name || "unknown reagent" + + return english_list(missing) + +/obj/structure/machinery/r_n_d/fabricator/proc/can_produce_job(datum/research_fabrication_job/job) + if(!job?.design) + return FALSE + if(!(job.design.build_type & build_type)) + return FALSE + if(!has_required_reagents(job.design)) + return FALSE + if(length(job.reserved_materials)) + return TRUE + return has_required_materials(job.design) + +/obj/structure/machinery/r_n_d/fabricator/proc/reserve_job_materials(datum/research_fabrication_job/job) + if(!job?.design) + return FALSE + if(length(job.reserved_materials)) + return TRUE + + var/obj/structure/machinery/r_n_d/material_silo/silo = get_material_silo() + if(!silo) + return FALSE + + for(var/material_id in job.design.materials) + var/required_amount = get_required_material_amount(job.design, material_id) + if(!silo.has_material(material_id, required_amount)) + return FALSE + + for(var/material_id in job.design.materials) + var/required_amount = get_required_material_amount(job.design, material_id) + var/material = SSmaterials.material_to_path(material_id, FALSE) + if(!material) + material = material_id + SSmaterials.remove_material_amount(silo.materials, material, required_amount) + job.reserved_materials[material] = required_amount + + silo.update_linked_uis() + return TRUE + +/obj/structure/machinery/r_n_d/fabricator/proc/begin_console_job(datum/research_fabrication_job/job) + if(!job || !is_available() || !can_produce_job(job)) + return FALSE + if(!reserve_job_materials(job)) + return FALSE + + assigned_job = job + current_design = job.design + job.assigned_machine = src + on_job_started(job) + update_use_power(POWER_USE_ACTIVE) + build_callback_timer = addtimer(CALLBACK(src, PROC_REF(complete_console_job)), current_design.time / production_speed, TIMER_UNIQUE | TIMER_STOPPABLE) + start_fabrication_audio() + update_icon() + SStgui.update_uis(linked_console) + return TRUE + +/obj/structure/machinery/r_n_d/fabricator/proc/on_job_started(datum/research_fabrication_job/job) + return + +/obj/structure/machinery/r_n_d/fabricator/proc/complete_console_job() + build_callback_timer = null + + var/datum/research_fabrication_job/job = assigned_job + var/datum/design/design_to_build = job?.design + if(!job || !design_to_build) + clear_console_job() + return + + if(!has_required_reagents(design_to_build)) + visible_message(SPAN_WARNING("\The [src] stops because the required reagents are no longer available.")) + pause_console_job() + return + + consume_reagents(design_to_build) + use_fabrication_power(design_to_build) + fabricate_design(design_to_build, job) + job.reserved_materials.Cut() + + var/obj/structure/machinery/computer/rdconsole/console = linked_console + console?.finish_fabrication_job(job) + + /* + * Keep construction audio running while the console attempts to assign + * another job. This prevents the end and startup sounds from playing + * between consecutive items. + */ + clear_console_job(FALSE, FALSE) + console?.dispatch_fabrication_jobs() + + /* + * If dispatch did not assign another job to this machine, construction + * has actually ended and the loop can now stop. + */ + if(!assigned_job) + stop_fabrication_audio() + +/obj/structure/machinery/r_n_d/fabricator/proc/pause_console_job() + var/datum/research_fabrication_job/job = assigned_job + if(job) + job.assigned_machine = null + + assigned_job = null + current_design = null + build_callback_timer = null + stop_fabrication_audio() + update_use_power(POWER_USE_IDLE) + update_icon() + on_job_cleared() + + if(linked_console) + SStgui.update_uis(linked_console) + linked_console.dispatch_fabrication_jobs() + +/obj/structure/machinery/r_n_d/fabricator/proc/clear_console_job(clear_assignment = TRUE, stop_audio = TRUE) + if(clear_assignment && assigned_job) + assigned_job.assigned_machine = null + + assigned_job = null + current_design = null + build_callback_timer = null + + if(stop_audio) + stop_fabrication_audio() + + update_use_power(POWER_USE_IDLE) + update_icon() + on_job_cleared() + +/obj/structure/machinery/r_n_d/fabricator/proc/consume_reagents(datum/design/design_to_build) + if(!uses_reagents) + return + + for(var/reagent_id in design_to_build.chemicals) + reagents.remove_reagent(reagent_id, get_required_reagent_amount(design_to_build, reagent_id)) + +/obj/structure/machinery/r_n_d/fabricator/proc/use_fabrication_power(datum/design/design_to_build) + var/power_cost = active_power_usage + for(var/material_id in design_to_build.materials) + power_cost += round(design_to_build.materials[material_id] / 2) + use_power_oneoff(max(active_power_usage, power_cost)) + +/obj/structure/machinery/r_n_d/fabricator/proc/fabricate_design(datum/design/design_to_build, datum/research_fabrication_job/job) + intent_message(MACHINE_SOUND) + if(!design_to_build.build_path) + return + + var/atom/product_location = product_offset ? get_step(src, dir) : src + var/obj/new_item = design_to_build.Fabricate(product_location, src) + if(!new_item) + return + + if(!product_offset) + new_item.forceMove(loc) + + if(mat_efficiency != 1 && new_item.matter?.len) + for(var/material_id in new_item.matter) + new_item.matter[material_id] *= mat_efficiency + + apply_product_data(new_item, design_to_build, job) + on_product_completed(new_item, design_to_build, job) + +/obj/structure/machinery/r_n_d/fabricator/proc/apply_product_data(obj/new_item, datum/design/design_to_build, datum/research_fabrication_job/job) + return + +/obj/structure/machinery/r_n_d/fabricator/proc/on_product_completed(obj/new_item, datum/design/design_to_build, datum/research_fabrication_job/job) + return + + +/obj/structure/machinery/r_n_d/fabricator/proc/wake_console_dispatch() + linked_console?.dispatch_fabrication_jobs() + +/obj/structure/machinery/r_n_d/fabricator/proc/disconnect_console() + if(!linked_console) + return + var/obj/structure/machinery/computer/rdconsole/console = linked_console + if(build_callback_timer) + deltimer(build_callback_timer) + if(assigned_job) + assigned_job.assigned_machine = null + assigned_job = null + current_design = null + build_callback_timer = null + stop_fabrication_audio() + update_use_power(POWER_USE_IDLE) + update_icon() + linked_console = null + console.remove_fabricator(src) + +/obj/structure/machinery/r_n_d/fabricator/attackby(obj/item/attacking_item, mob/user) + if(user.a_intent == I_HURT) + return ..() + if(build_callback_timer) + to_chat(user, SPAN_NOTICE("\The [src] is busy. Please wait for the current operation to finish.")) + return TRUE + if(default_deconstruction_screwdriver(user, attacking_item)) + disconnect_console() + return TRUE + if(default_deconstruction_crowbar(user, attacking_item)) + return TRUE + if(default_part_replacement(user, attacking_item)) + return TRUE + if(istype(attacking_item, /obj/item/stack/material)) + to_chat(user, SPAN_NOTICE("Materials must be inserted into the linked research material silo.")) + return TRUE + return ..() + +/obj/structure/machinery/r_n_d/fabricator/Destroy() + if(build_callback_timer) + deltimer(build_callback_timer) + + stop_fabrication_audio() + QDEL_NULL(fabrication_loop) + + if(assigned_job) + assigned_job.assigned_machine = null + + if(linked_console) + linked_console.remove_fabricator(src) + + assigned_job = null + current_design = null + return ..() + +/obj/structure/machinery/r_n_d/fabricator/proc/on_job_cleared() + return diff --git a/code/unit_tests/map_tests.dm b/code/unit_tests/map_tests.dm index e9d5e25747e..d769829db1a 100644 --- a/code/unit_tests/map_tests.dm +++ b/code/unit_tests/map_tests.dm @@ -495,5 +495,45 @@ return test_status +/datum/unit_test/map_test/research_fabricator_connections + name = "MAP: Research fabricator connections" + +/datum/unit_test/map_test/research_fabricator_connections/start_test() + var/fabricator_count = 0 + var/console_count = 0 + var/fabricators_without_console = 0 + var/consoles_without_silo = 0 + var/test_status = UNIT_TEST_PASSED + + for(var/obj/structure/machinery/r_n_d/fabricator/fabricator in world) + fabricator_count++ + + var/obj/structure/machinery/computer/rdconsole/nearby_console = locate() in range(7, fabricator) + if(nearby_console) + continue + + fabricators_without_console++ + TEST_FAIL("[fabricator.name] at ([fabricator.x], [fabricator.y], [fabricator.z]) in [get_area(fabricator)] has no research console within 7 turfs.") + + for(var/obj/structure/machinery/computer/rdconsole/console in world) + if(QDELETED(console) || !is_station_level(console.z)) + continue + + console_count++ + + var/obj/structure/machinery/r_n_d/material_silo/nearby_silo = locate() in range(7, console) + if(nearby_silo) + continue + + consoles_without_silo++ + TEST_FAIL("[console.name] at ([console.x], [console.y], [console.z]) in [get_area(console)] has no material silo within 7 turfs.") + + if(fabricators_without_console || consoles_without_silo) + test_status = TEST_FAIL("\[[fabricators_without_console] / [fabricator_count]\] research fabricators lacked a nearby research console. \[[consoles_without_silo] / [console_count]\] research consoles lacked a nearby material silo.") + else + TEST_PASS("All [fabricator_count] research fabricators had a nearby research console, and all [console_count] research consoles had a nearby material silo.") + + return test_status + #undef SUCCESS #undef FAILURE diff --git a/html/changelogs/TimeForMoreTGUIInScience.yml b/html/changelogs/TimeForMoreTGUIInScience.yml new file mode 100644 index 00000000000..647341b840d --- /dev/null +++ b/html/changelogs/TimeForMoreTGUIInScience.yml @@ -0,0 +1,7 @@ +author: TheGreyWolf + +delete-after: True + +changes: + - rscadd: "Added a material silo and mech fabricators to the research console." + - rscadd: "The research console now uses TGUI." diff --git a/icons/obj/machinery/research.dmi b/icons/obj/machinery/research.dmi index 05f9dc1141cc442ec001e8dd385914e1623c3272..a5c1345fe0ac27f2172e37d0d12fcd7019a781dd 100644 GIT binary patch literal 23029 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 literal 21883 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

YF-in5LwY1wz1Q=B@4R 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 Pq8tt!+Fx#S;?jQr;38-P diff --git a/maps/away/away_site/abandoned_industrial/abandoned_industrial_station.dmm b/maps/away/away_site/abandoned_industrial/abandoned_industrial_station.dmm index 66193d8b2cf..786d613b7ef 100644 --- a/maps/away/away_site/abandoned_industrial/abandoned_industrial_station.dmm +++ b/maps/away/away_site/abandoned_industrial/abandoned_industrial_station.dmm @@ -2283,6 +2283,14 @@ /obj/random/dirt_75, /turf/simulated/floor/airless, /area/abandoned_industrial_station/exterior) +"hr" = ( +/obj/random/tool, +/obj/random/dirt_75, +/obj/structure/machinery/computer/rdconsole/robotics{ + dir = 8 + }, +/turf/simulated/floor/tiled, +/area/abandoned_industrial_station/hangar) "ht" = ( /obj/effect/floor_decal/corner/beige{ dir = 5 @@ -6886,7 +6894,7 @@ /turf/simulated/floor/plating, /area/abandoned_industrial_station/bridge) "vX" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ dir = 4; req_access = null }, @@ -15992,9 +16000,7 @@ /obj/effect/floor_decal/spline/plain{ dir = 4 }, -/obj/random/tech_supply{ - pixel_x = 5 - }, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/carpet/rubber, /area/abandoned_industrial_station/hangar) "YN" = ( @@ -16098,6 +16104,9 @@ pixel_y = -16 }, /obj/random/dirt_75, +/obj/random/tech_supply{ + pixel_x = 5 + }, /turf/simulated/floor/carpet/rubber, /area/abandoned_industrial_station/hangar) "Zb" = ( @@ -52583,7 +52592,7 @@ kI eA gX gM -sw +hr Sq Zv bX diff --git a/maps/away/away_site/assunzione/port_volturno/port_volturno.dmm b/maps/away/away_site/assunzione/port_volturno/port_volturno.dmm index cccf5a98e5c..1d04a930851 100644 --- a/maps/away/away_site/assunzione/port_volturno/port_volturno.dmm +++ b/maps/away/away_site/assunzione/port_volturno/port_volturno.dmm @@ -7614,16 +7614,13 @@ /turf/simulated/floor/grass1/no_edge, /area/port_volturno/exterior) "fjA" = ( -/obj/structure/table/steel, -/obj/item/flashlight/headlights, -/obj/item/cell/hyper{ - pixel_x = -4; - pixel_y = -10 - }, /obj/item/stack/nanopaste{ pixel_x = 8; pixel_y = -14 }, +/obj/structure/machinery/computer/rdconsole/robotics/terminal{ + dir = 4 + }, /turf/simulated/floor/tiled, /area/port_volturno/interior/robotics) "fjK" = ( @@ -8700,6 +8697,9 @@ dir = 8 }, /obj/item/storage/toolbox/mechanical, +/obj/item/cell/hyper{ + pixel_y = 7 + }, /turf/simulated/floor/tiled, /area/port_volturno/interior/robotics) "fYD" = ( @@ -12462,6 +12462,7 @@ pixel_y = 8 }, /obj/item/clothing/gloves/yellow, +/obj/item/flashlight/headlights, /turf/simulated/floor/tiled, /area/port_volturno/interior/robotics) "iNR" = ( @@ -19093,7 +19094,7 @@ /turf/simulated/floor/tiled/cargo/brass/full, /area/port_volturno/interior/chapel) "ntu" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ req_access = list(219) }, /obj/structure/machinery/light{ @@ -21610,6 +21611,10 @@ }, /turf/simulated/floor/tiled/white, /area/port_volturno/interior/minimart) +"oWJ" = ( +/obj/structure/machinery/r_n_d/material_silo, +/turf/simulated/floor/tiled, +/area/port_volturno/interior/robotics) "oWV" = ( /obj/structure/lattice/catwalk/indoor, /turf/simulated/floor, @@ -33804,7 +33809,7 @@ /turf/simulated/floor/tiled/slate, /area/port_volturno/interior/stafylia) "xLh" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ req_access = list(219) }, /obj/random/dirt_75, @@ -138077,7 +138082,7 @@ rGy xWS agK dvD -bhN +oWJ bhN bhN bhN diff --git a/maps/away/away_site/big_derelict/bigderelict.dmm b/maps/away/away_site/big_derelict/bigderelict.dmm index 50449835e66..8f9d4b4b5b2 100644 --- a/maps/away/away_site/big_derelict/bigderelict.dmm +++ b/maps/away/away_site/big_derelict/bigderelict.dmm @@ -243,7 +243,7 @@ /turf/simulated/floor/tiled/white/airless, /area/derelict) "bm" = ( -/obj/structure/machinery/mecha_part_fabricator, +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator, /obj/effect/floor_decal/corner/purple/diagonal, /turf/simulated/floor/tiled/white/airless, /area/derelict) @@ -757,7 +757,7 @@ /turf/simulated/floor/tiled/dark, /area/derelict) "cx" = ( -/obj/structure/machinery/r_n_d/circuit_imprinter, +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter, /obj/effect/floor_decal/corner/purple/diagonal, /turf/simulated/floor/tiled/white/airless, /area/derelict) @@ -771,7 +771,7 @@ /turf/simulated/floor/tiled/white/airless, /area/derelict) "cA" = ( -/obj/structure/machinery/r_n_d/protolathe{ +/obj/structure/machinery/r_n_d/fabricator/protolathe{ icon_state = "protolathe_t"; stat = 1 }, @@ -1785,6 +1785,18 @@ /obj/structure/closet/crate/loot, /turf/simulated/floor/airless, /area/derelict) +"DN" = ( +/obj/effect/floor_decal/corner/purple/diagonal, +/obj/structure/machinery/computer/rdconsole/core{ + dir = 4 + }, +/turf/simulated/floor/tiled/white/airless, +/area/derelict) +"JN" = ( +/obj/effect/floor_decal/corner/purple/diagonal, +/obj/structure/machinery/r_n_d/material_silo, +/turf/simulated/floor/tiled/white/airless, +/area/derelict) (1,1,1) = {" fI @@ -29878,7 +29890,7 @@ bU bU bU bH -bU +DN dO dO fk @@ -30393,7 +30405,7 @@ bU bU bH bU -bU +JN dO fk dO diff --git a/maps/away/away_site/blueriver/blueriver.dmm b/maps/away/away_site/blueriver/blueriver.dmm index e4610b504ea..5d1bd4ca040 100644 --- a/maps/away/away_site/blueriver/blueriver.dmm +++ b/maps/away/away_site/blueriver/blueriver.dmm @@ -368,7 +368,7 @@ /turf/simulated/floor/tiled/white, /area/bluespaceriver/ship) "si" = ( -/obj/structure/machinery/r_n_d/protolathe, +/obj/structure/machinery/r_n_d/fabricator/protolathe, /obj/item/paper/blueriver/expedition_log_4, /turf/simulated/floor/tiled/white, /area/bluespaceriver/ship) @@ -402,11 +402,11 @@ /turf/simulated/floor/tiled/white, /area/bluespaceriver/ship) "tS" = ( -/obj/structure/closet/emcloset, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/tiled/white, /area/bluespaceriver/ship) "uw" = ( -/obj/structure/machinery/r_n_d/circuit_imprinter, +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter, /obj/item/ano_scanner, /turf/simulated/floor/tiled/white, /area/bluespaceriver/ship) diff --git a/maps/away/away_site/first_aurora/first_aurora.dmm b/maps/away/away_site/first_aurora/first_aurora.dmm index f15d92726ee..9319faf56c7 100644 --- a/maps/away/away_site/first_aurora/first_aurora.dmm +++ b/maps/away/away_site/first_aurora/first_aurora.dmm @@ -3102,7 +3102,7 @@ /turf/template_noop, /area/template_noop) "kJ" = ( -/obj/structure/machinery/r_n_d/circuit_imprinter, +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter, /turf/simulated/floor/tiled/white/airless, /area/derelict/hallway/southeast) "kK" = ( @@ -3248,7 +3248,7 @@ /turf/simulated/floor/tiled/white/airless, /area/derelict/hallway/southeast) "ll" = ( -/obj/structure/machinery/mecha_part_fabricator, +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator, /turf/simulated/floor/tiled/white/airless, /area/derelict/hallway/southeast) "lm" = ( @@ -7235,6 +7235,11 @@ /obj/effect/decal/cleanable/dirt, /turf/simulated/floor/tiled/white/airless, /area/derelict/hallway/southeast) +"CV" = ( +/obj/effect/decal/cleanable/dirt, +/obj/structure/machinery/r_n_d/material_silo, +/turf/simulated/floor/tiled/white/airless, +/area/derelict/hallway/southeast) "Da" = ( /obj/structure/grille, /obj/structure/window/reinforced, @@ -7252,6 +7257,12 @@ "FM" = ( /turf/template_noop, /area/space) +"Hn" = ( +/obj/structure/machinery/computer/rdconsole/robotics{ + dir = 8 + }, +/turf/simulated/floor/tiled/white/airless, +/area/derelict/hallway/southeast) "HF" = ( /obj/structure/door_assembly, /turf/simulated/floor/tiled/white/airless, @@ -50076,7 +50087,7 @@ BS BS kJ jd -BS +CV lX ik aa @@ -51362,7 +51373,7 @@ kp ij ll Aa -Aa +Hn ik aa aa diff --git a/maps/away/away_site/konyang/point_verdant/point_verdant.dmm b/maps/away/away_site/konyang/point_verdant/point_verdant.dmm index 186194d531f..8ef358577e0 100644 --- a/maps/away/away_site/konyang/point_verdant/point_verdant.dmm +++ b/maps/away/away_site/konyang/point_verdant/point_verdant.dmm @@ -1445,6 +1445,30 @@ /obj/item/stack/material/plasteel/full{ pixel_x = 7 }, +/obj/item/stack/material/glass/full{ + pixel_x = -7 + }, +/obj/item/stack/material/glass/full{ + pixel_x = -7 + }, +/obj/item/stack/material/glass/full{ + pixel_x = -7 + }, +/obj/item/stack/material/glass/full{ + pixel_x = -7 + }, +/obj/item/stack/material/steel/full{ + pixel_x = 7 + }, +/obj/item/stack/material/steel/full{ + pixel_x = 7 + }, +/obj/item/stack/material/steel/full{ + pixel_x = 7 + }, +/obj/item/stack/material/steel/full{ + pixel_x = 7 + }, /turf/simulated/floor/tiled, /area/point_verdant/interior/robotics) "aQV" = ( @@ -22353,7 +22377,7 @@ /turf/simulated/floor/wood, /area/point_verdant/interior/police) "mGn" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ req_access = list(219) }, /obj/structure/machinery/light{ @@ -24957,31 +24981,7 @@ /turf/simulated/floor/tiled, /area/point_verdant/interior/maint_janitorial) "olp" = ( -/obj/structure/table/rack/retail_shelf, -/obj/item/stack/material/steel/full{ - pixel_x = 7 - }, -/obj/item/stack/material/glass/full{ - pixel_x = -7 - }, -/obj/item/stack/material/glass/full{ - pixel_x = -7 - }, -/obj/item/stack/material/glass/full{ - pixel_x = -7 - }, -/obj/item/stack/material/glass/full{ - pixel_x = -7 - }, -/obj/item/stack/material/steel/full{ - pixel_x = 7 - }, -/obj/item/stack/material/steel/full{ - pixel_x = 7 - }, -/obj/item/stack/material/steel/full{ - pixel_x = 7 - }, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/tiled, /area/point_verdant/interior/robotics) "olq" = ( @@ -32321,7 +32321,7 @@ /turf/simulated/floor/exoplanet/water/shallow/konyang/no_smooth, /area/point_verdant/interior/offices/kaf) "sKK" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ req_access = list(219) }, /obj/random/dirt_75, @@ -41913,6 +41913,9 @@ /obj/structure/machinery/light{ dir = 4 }, +/obj/structure/machinery/computer/rdconsole/robotics{ + dir = 8 + }, /turf/simulated/floor/tiled, /area/point_verdant/interior/robotics) "yhQ" = ( diff --git a/maps/away/away_site/overgrown_mining_station/overgrown_mining_station.dmm b/maps/away/away_site/overgrown_mining_station/overgrown_mining_station.dmm index 8fc0f76dee6..964b1c3aa43 100644 --- a/maps/away/away_site/overgrown_mining_station/overgrown_mining_station.dmm +++ b/maps/away/away_site/overgrown_mining_station/overgrown_mining_station.dmm @@ -1147,7 +1147,7 @@ /area/overgrown_mining_station) "yw" = ( /obj/structure/platform, -/obj/structure/machinery/mecha_part_fabricator, +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator, /turf/simulated/floor/tiled/dark/full/airless, /area/overgrown_mining_station) "yy" = ( @@ -1770,6 +1770,10 @@ }, /turf/simulated/floor/tiled/airless, /area/overgrown_mining_station) +"Mc" = ( +/obj/structure/machinery/r_n_d/material_silo, +/turf/simulated/floor/tiled/dark/full/airless, +/area/overgrown_mining_station) "Mh" = ( /obj/structure/table/reinforced/wood, /obj/item/trash/can, @@ -2089,6 +2093,7 @@ /area/overgrown_mining_station) "Uu" = ( /obj/structure/platform, +/obj/structure/machinery/computer/rdconsole/robotics, /turf/simulated/floor/tiled/dark/full/airless, /area/overgrown_mining_station) "UA" = ( @@ -42777,7 +42782,7 @@ eK Xe Fg oj -oj +Mc qK Uj jm diff --git a/maps/away/away_site/racers/racers.dmm b/maps/away/away_site/racers/racers.dmm index bfe8f96076d..c4b9d990e7a 100644 --- a/maps/away/away_site/racers/racers.dmm +++ b/maps/away/away_site/racers/racers.dmm @@ -676,6 +676,10 @@ /obj/structure/machinery/atm, /turf/simulated/wall/rusty, /area/racers) +"cHq" = ( +/obj/structure/machinery/pipedispenser, +/turf/simulated/floor/plating, +/area/racers) "cJe" = ( /obj/structure/reagent_dispensers/cookingoil, /turf/simulated/floor/tiled/freezer{ @@ -864,7 +868,7 @@ /turf/simulated/floor/plating, /area/racers) "dNX" = ( -/obj/structure/machinery/mecha_part_fabricator, +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator, /obj/structure/machinery/light/small/red{ dir = 4 }, @@ -1369,13 +1373,7 @@ /turf/template_noop, /area/space) "gyV" = ( -/obj/item/stack/material/steel{ - amount = 50 - }, -/obj/item/stack/material/plasteel{ - amount = 10 - }, -/obj/structure/table/rack, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/plating, /area/racers) "gAI" = ( @@ -1395,6 +1393,16 @@ }, /turf/simulated/floor/holofloor/tiled, /area/racers) +"gOv" = ( +/obj/structure/table/rack, +/obj/item/stack/material/plasteel{ + amount = 10 + }, +/obj/item/stack/material/steel{ + amount = 50 + }, +/turf/simulated/floor/plating, +/area/racers) "gPD" = ( /obj/structure/largecrate/hoverpod, /obj/effect/floor_decal/industrial/outline/yellow, @@ -2936,6 +2944,13 @@ }, /turf/simulated/floor/carpet/rubber, /area/racers) +"okb" = ( +/obj/structure/machinery/atmospherics/portables_connector{ + dir = 1 + }, +/obj/item/laser_pointer/red, +/turf/simulated/floor/plating, +/area/racers) "okF" = ( /obj/effect/decal/cleanable/dirt, /obj/effect/decal/remains/robot, @@ -3049,8 +3064,9 @@ /turf/simulated/floor/plating, /area/racers) "oNS" = ( -/obj/structure/machinery/pipedispenser, -/obj/item/laser_pointer/red, +/obj/structure/machinery/computer/rdconsole/robotics{ + dir = 4 + }, /turf/simulated/floor/plating, /area/racers) "oQI" = ( @@ -41187,7 +41203,7 @@ dGi owK wUg vhm -iVB +okb oNS qWG iVB @@ -41955,7 +41971,7 @@ qiM xjB jzb xjB -xjB +gOv xjB xjB gyV @@ -42216,7 +42232,7 @@ qVo jjB xjB dNX -xjB +cHq hRn xjB mDd diff --git a/maps/away/scenarios/enviro_testing_facility/enviro_testing_facility_submaps_main.dmm b/maps/away/scenarios/enviro_testing_facility/enviro_testing_facility_submaps_main.dmm index 99b84128f0c..89200c1029c 100644 --- a/maps/away/scenarios/enviro_testing_facility/enviro_testing_facility_submaps_main.dmm +++ b/maps/away/scenarios/enviro_testing_facility/enviro_testing_facility_submaps_main.dmm @@ -1106,6 +1106,12 @@ /obj/random/tech_supply, /turf/template_noop, /area/template_noop) +"bcW" = ( +/obj/structure/machinery/computer/rdconsole/robotics{ + dir = 8 + }, +/turf/simulated/floor/tiled/dark, +/area/enviro_testing_facility/inside/engineering/mech_lab) "bde" = ( /obj/structure/bed/stool/chair, /obj/random/dirt_75, @@ -2445,6 +2451,12 @@ }, /turf/simulated/floor/tiled, /area/enviro_testing_facility/inside/control/offices) +"cqI" = ( +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ + dir = 8 + }, +/turf/simulated/floor/carpet/rubber, +/area/enviro_testing_facility/inside/engineering/mech_lab) "cqK" = ( /obj/effect/map_effect/marker/mapmanip/submap/extract/enviro_testing_facility/misc/science_storage, /turf/template_noop, @@ -3506,6 +3518,9 @@ /obj/random/dirt_75, /turf/simulated/floor/exoplanet/crystal, /area/template_noop) +"dtW" = ( +/turf/simulated/floor/tiled/dark/full, +/area/enviro_testing_facility/inside/engineering/mech_lab) "dup" = ( /obj/effect/floor_decal/industrial/hatch_door/red{ dir = 1 @@ -4020,7 +4035,7 @@ /turf/simulated/floor/tiled/dark, /area/enviro_testing_facility/inside/engineering/reactors) "dVz" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ dir = 8 }, /turf/simulated/floor/tiled/dark/full, @@ -5822,7 +5837,7 @@ /turf/simulated/floor/carpet/green, /area/enviro_testing_facility/inside/medical/chief_office) "fQy" = ( -/obj/structure/machinery/r_n_d/circuit_imprinter, +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter, /turf/simulated/floor/carpet/rubber, /area/enviro_testing_facility/inside/engineering/mech_lab) "fQJ" = ( @@ -6686,7 +6701,7 @@ /turf/simulated/floor/tiled/white, /area/enviro_testing_facility/inside/research/meeting) "gBG" = ( -/obj/structure/machinery/r_n_d/protolathe, +/obj/structure/machinery/r_n_d/fabricator/protolathe, /obj/effect/floor_decal/spline/plain{ dir = 4 }, @@ -10752,6 +10767,7 @@ pixel_x = 8; pixel_y = 11 }, +/obj/item/stack/material/steel/full, /turf/simulated/floor/carpet/rubber, /area/enviro_testing_facility/inside/engineering/mech_lab) "kIM" = ( @@ -10909,11 +10925,11 @@ /area/enviro_testing_facility/inside/research/rnd) "kSl" = ( /obj/effect/floor_decal/industrial/outline/yellow, -/obj/item/stack/material/steel/full, /obj/random/dirt_75, /obj/structure/machinery/alarm/north{ req_one_access = null }, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/plating, /area/enviro_testing_facility/inside/engineering/mech_lab) "kUe" = ( @@ -11587,7 +11603,7 @@ /turf/simulated/floor/tiled/dark, /area/enviro_testing_facility/inside/engineering/reactors) "lDw" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ dir = 4 }, /turf/simulated/floor/tiled/dark/full, @@ -11600,10 +11616,10 @@ /turf/simulated/floor/reinforced, /area/enviro_testing_facility/inside/security/armory) "lDP" = ( -/obj/structure/table/steel, /obj/effect/floor_decal/corner/brown{ dir = 5 }, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/tiled, /area/enviro_testing_facility/inside/engineering/mech_lab) "lDW" = ( @@ -15696,7 +15712,7 @@ /turf/simulated/floor/tiled/dark, /area/enviro_testing_facility/inside/engineering/reactors) "pMv" = ( -/obj/structure/machinery/r_n_d/circuit_imprinter, +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter, /obj/effect/floor_decal/spline/plain{ dir = 9 }, @@ -16463,7 +16479,7 @@ /turf/simulated/floor/tiled/dark, /area/enviro_testing_facility/inside/security/storage) "qDv" = ( -/obj/structure/machinery/r_n_d/protolathe, +/obj/structure/machinery/r_n_d/fabricator/protolathe, /turf/simulated/floor/carpet/rubber, /area/enviro_testing_facility/inside/engineering/mech_lab) "qDB" = ( @@ -17487,6 +17503,7 @@ dir = 1 }, /obj/random/dirt_75, +/obj/structure/machinery/computer/rdconsole/robotics, /turf/simulated/floor/plating, /area/enviro_testing_facility/inside/engineering/mech_lab) "rOm" = ( @@ -21384,10 +21401,6 @@ dir = 9 }, /obj/structure/table/steel, -/obj/item/clothing/under/medical_gown/white{ - pixel_x = 5; - pixel_y = 8 - }, /turf/simulated/floor/reinforced, /area/enviro_testing_facility/inside/research/electronics) "vKp" = ( @@ -39367,7 +39380,7 @@ duD pos kSl toG -dVz +dtW dVz jdE dVz @@ -39569,7 +39582,7 @@ duD pos rNI toG -ydD +cqI ydD ydD jTa @@ -42601,7 +42614,7 @@ pif orb lDw lDw -eWD +bcW lDw lDw gPy diff --git a/maps/away/scenarios/nuclear_silo/nuclear_silo.dmm b/maps/away/scenarios/nuclear_silo/nuclear_silo.dmm index 354ef16d7fe..5122d1e7fad 100644 --- a/maps/away/scenarios/nuclear_silo/nuclear_silo.dmm +++ b/maps/away/scenarios/nuclear_silo/nuclear_silo.dmm @@ -64,6 +64,7 @@ /area/nuclear_silo/outside/cave/bunker) "ai" = ( /obj/random/dirt_75, +/obj/structure/heavy_vehicle_frame, /turf/simulated/floor/carpet/rubber, /area/nuclear_silo/lower_level/maintenance/east/robotics_bay) "aj" = ( @@ -7357,6 +7358,7 @@ /obj/random/dirt_75, /obj/random/dirt_75, /obj/structure/machinery/atmospherics/pipe/simple/hidden/scrubbers, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/plating, /area/nuclear_silo/lower_level/maintenance/east/robotics_bay) "qs" = ( @@ -12427,7 +12429,7 @@ /turf/simulated/floor/exoplanet/barren, /area/nuclear_silo/outside/buildings/town) "Bu" = ( -/obj/structure/heavy_vehicle_frame, +/obj/structure/machinery/computer/rdconsole/robotics, /turf/simulated/floor/carpet/rubber, /area/nuclear_silo/lower_level/maintenance/east/robotics_bay) "Bv" = ( @@ -18489,7 +18491,7 @@ /turf/simulated/floor/plating, /area/nuclear_silo/lower_level/maintenance/engimed) "Pb" = ( -/obj/structure/machinery/mecha_part_fabricator, +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator, /turf/simulated/floor/plating, /area/nuclear_silo/lower_level/maintenance/east/robotics_bay) "Pc" = ( diff --git a/maps/away/ships/biesel/tcaf_corvette/tcaf_corvette.dmm b/maps/away/ships/biesel/tcaf_corvette/tcaf_corvette.dmm index 93eb4c0db5a..501dd42b5e1 100644 --- a/maps/away/ships/biesel/tcaf_corvette/tcaf_corvette.dmm +++ b/maps/away/ships/biesel/tcaf_corvette/tcaf_corvette.dmm @@ -2463,9 +2463,6 @@ dir = 8 }, /obj/effect/floor_decal/corner/brown/diagonal, -/obj/structure/machinery/atmospherics/pipe/simple/hidden/scrubbers{ - dir = 10 - }, /obj/structure/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 9 }, @@ -2474,6 +2471,9 @@ d2 = 8; icon_state = "1-8" }, +/obj/structure/machinery/atmospherics/unary/vent_scrubber/on{ + dir = 8 + }, /turf/simulated/floor/tiled/gunmetal, /area/tcaf_corvette/workshop) "iM" = ( @@ -3552,7 +3552,7 @@ /turf/simulated/floor/tiled, /area/tcaf_corvette/lounge) "mT" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ dir = 8; req_access = list(111) }, @@ -4320,6 +4320,7 @@ /obj/structure/machinery/mech_recharger, /obj/effect/floor_decal/industrial/outline/yellow, /obj/effect/decal/cleanable/dirt, +/obj/structure/reagent_dispensers/fueltank, /turf/simulated/floor/tiled/gunmetal, /area/tcaf_corvette/workshop) "pJ" = ( @@ -9973,12 +9974,11 @@ /obj/structure/machinery/alarm/south{ req_one_access = list(111) }, -/obj/structure/machinery/atmospherics/unary/vent_scrubber/on{ - dir = 1 - }, /obj/structure/platform{ dir = 8 }, +/obj/effect/floor_decal/industrial/outline/operations, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/tiled/gunmetal, /area/tcaf_corvette/workshop) "MG" = ( @@ -12331,10 +12331,11 @@ /turf/simulated/floor/tiled/dark, /area/tcaf_corvette/autocannon) "Wq" = ( -/obj/structure/reagent_dispensers/fueltank, -/obj/effect/floor_decal/industrial/outline/operations, -/obj/effect/decal/cleanable/dirt, /obj/structure/machinery/light/small, +/obj/effect/decal/cleanable/dirt, +/obj/structure/machinery/computer/rdconsole/robotics{ + dir = 8 + }, /turf/simulated/floor/tiled/gunmetal/full, /area/tcaf_corvette/workshop) "Wt" = ( diff --git a/maps/away/ships/golden_deep/golden_deep.dmm b/maps/away/ships/golden_deep/golden_deep.dmm index 737ba4217f9..f56af383b1f 100644 --- a/maps/away/ships/golden_deep/golden_deep.dmm +++ b/maps/away/ships/golden_deep/golden_deep.dmm @@ -2494,6 +2494,10 @@ /obj/item/assembly/prox_sensor{ pixel_x = 5 }, +/obj/item/paint_sprayer{ + pixel_y = 5; + pixel_x = 2 + }, /turf/simulated/floor/tiled/dark/full, /area/golden_deep/workshop) "kw" = ( @@ -7059,7 +7063,7 @@ /obj/effect/floor_decal/spline/fancy/wood{ dir = 1 }, -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ req_access = list(229, 217) }, /obj/random/dirt_75, @@ -9089,11 +9093,7 @@ /obj/effect/floor_decal/spline/fancy/wood{ dir = 10 }, -/obj/structure/table/reinforced/steel, -/obj/item/paint_sprayer{ - pixel_y = 5; - pixel_x = 2 - }, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/tiled/dark/full, /area/golden_deep/workshop) "LD" = ( @@ -12399,8 +12399,10 @@ /obj/effect/floor_decal/spline/fancy/wood{ dir = 8 }, -/obj/structure/machinery/mech_recharger, /obj/effect/floor_decal/industrial/outline/yellow, +/obj/structure/machinery/computer/rdconsole/robotics{ + dir = 4 + }, /obj/random/dirt_75, /turf/simulated/floor/tiled/dark/full, /area/golden_deep/workshop) diff --git a/maps/away/ships/himeo/himeo_patrol_ship_submaps.dmm b/maps/away/ships/himeo/himeo_patrol_ship_submaps.dmm index 4d301041ea5..2ec938ce8cb 100644 --- a/maps/away/ships/himeo/himeo_patrol_ship_submaps.dmm +++ b/maps/away/ships/himeo/himeo_patrol_ship_submaps.dmm @@ -217,7 +217,7 @@ /turf/simulated/floor/carpet/rubber, /area/template_noop) "kp" = ( -/obj/structure/closet/crate/loot, +/obj/structure/reagent_dispensers/fueltank, /turf/simulated/floor/carpet/rubber, /area/template_noop) "kE" = ( @@ -319,7 +319,7 @@ /turf/simulated/floor/carpet/rubber, /area/template_noop) "py" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ req_access = null; dir = 1 }, @@ -332,7 +332,6 @@ /turf/simulated/floor/carpet/rubber, /area/template_noop) "qm" = ( -/obj/structure/closet/crate/loot, /obj/random/dirt_75, /obj/structure/machinery/alarm/south{ req_access = list(253); @@ -342,6 +341,9 @@ dir = 1; level = 2 }, +/obj/structure/machinery/computer/rdconsole/robotics{ + dir = 1 + }, /turf/simulated/floor/carpet/rubber, /area/template_noop) "qx" = ( @@ -532,8 +534,8 @@ /turf/simulated/floor/carpet/rubber, /area/template_noop) "Ab" = ( -/obj/structure/reagent_dispensers/fueltank, /obj/random/dirt_75, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/carpet/rubber, /area/template_noop) "Ae" = ( @@ -920,12 +922,12 @@ /turf/simulated/floor/tiled/dark/full, /area/template_noop) "Vn" = ( -/obj/structure/closet/crate/drop, /obj/item/paint_sprayer, /obj/item/mmi/digital/posibrain, /obj/item/stack/nanopaste, /obj/item/stack/nanopaste, /obj/item/stack/nanopaste, +/obj/structure/closet/crate/loot, /turf/simulated/floor/carpet/rubber, /area/template_noop) "Wv" = ( diff --git a/maps/away/ships/iac/iac_rescue_ship.dmm b/maps/away/ships/iac/iac_rescue_ship.dmm index c1190809788..398c17de25b 100644 --- a/maps/away/ships/iac/iac_rescue_ship.dmm +++ b/maps/away/ships/iac/iac_rescue_ship.dmm @@ -6900,7 +6900,7 @@ /turf/simulated/floor/tiled/dark, /area/ship/iac_rescue_ship/custodial) "pHr" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ req_access = list(211) }, /obj/effect/floor_decal/spline/plain/cee{ @@ -7176,6 +7176,7 @@ "qpf" = ( /obj/structure/machinery/firealarm/west, /obj/effect/floor_decal/industrial/warning, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor, /area/ship/iac_rescue_ship/machinist) "qsF" = ( @@ -7672,6 +7673,9 @@ pixel_x = 22; pixel_y = 4 }, +/obj/structure/closet/crate/bin{ + name = "trashbin" + }, /turf/simulated/floor/tiled, /area/ship/iac_rescue_ship/machinist) "rBa" = ( @@ -7767,9 +7771,6 @@ /turf/simulated/floor/tiled/dark/full, /area/ship/iac_rescue_ship/bridge) "rMI" = ( -/obj/structure/closet/crate/bin{ - name = "trashbin" - }, /obj/effect/floor_decal/corner/black{ dir = 5 }, @@ -7779,6 +7780,9 @@ /obj/structure/machinery/atmospherics/pipe/simple/hidden/black{ dir = 5 }, +/obj/structure/machinery/computer/rdconsole/robotics/terminal{ + dir = 4 + }, /turf/simulated/floor/tiled, /area/ship/iac_rescue_ship/machinist) "rNe" = ( diff --git a/maps/away/ships/konyang/kasf_ship/kasf_ship.dmm b/maps/away/ships/konyang/kasf_ship/kasf_ship.dmm index c1aef3b8141..1c0080f7a16 100644 --- a/maps/away/ships/konyang/kasf_ship/kasf_ship.dmm +++ b/maps/away/ships/konyang/kasf_ship/kasf_ship.dmm @@ -637,8 +637,6 @@ /area/ship/kasf_corvette/starboardthrust) "dY" = ( /obj/effect/floor_decal/industrial/outline/yellow, -/obj/structure/machinery/mech_recharger, -/obj/effect/decal/cleanable/blood/oil, /obj/structure/machinery/button/remote/blast_door{ dir = 4; pixel_x = 24; @@ -647,9 +645,8 @@ name = "Mechbay Garage Shutters"; req_one_access = list(218) }, -/mob/living/heavy_vehicle/premade/light/recon{ - desc = "A light and nimble recon exosuit bearing the colour scheme of the Konyang Armed Forces, evidently a hand-me-down from the Solarian Armed Forces."; - e_color = "#767e8c" +/obj/structure/machinery/computer/rdconsole/robotics/terminal{ + dir = 1 }, /turf/simulated/floor/carpet/rubber, /area/ship/kasf_corvette/engie) @@ -1096,6 +1093,11 @@ /obj/effect/floor_decal/industrial/outline/yellow, /obj/structure/machinery/mech_recharger, /obj/structure/machinery/light, +/mob/living/heavy_vehicle/premade/light/recon{ + desc = "A light and nimble recon exosuit bearing the colour scheme of the Konyang Armed Forces, evidently a hand-me-down from the Solarian Armed Forces."; + e_color = "#767e8c" + }, +/obj/effect/decal/cleanable/blood/oil, /turf/simulated/floor/carpet/rubber, /area/ship/kasf_corvette/engie) "gu" = ( @@ -5411,7 +5413,6 @@ /turf/simulated/floor/plating, /area/ship/kasf_corvette/armory) "EY" = ( -/obj/structure/table/steel, /obj/effect/floor_decal/corner/orange/full{ dir = 4 }, @@ -5425,13 +5426,8 @@ /obj/structure/cable{ icon_state = "1-2" }, -/obj/item/reagent_containers/weldpack{ - pixel_y = 14; - pixel_x = 6 - }, -/obj/item/storage/box/lights/mixed{ - pixel_y = 12; - pixel_x = -6 +/obj/structure/machinery/vending/robotics{ + req_access = null }, /turf/simulated/floor/tiled/dark/full, /area/ship/kasf_corvette/engie) @@ -6279,10 +6275,8 @@ /turf/simulated/wall/shuttle/space_ship/mercenary, /area/shuttle/kasf_shuttle) "Jn" = ( -/obj/structure/machinery/vending/robotics{ - req_access = null - }, /obj/effect/floor_decal/industrial/outline/operations, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/carpet/rubber, /area/ship/kasf_corvette/engie) "Jq" = ( @@ -6427,7 +6421,7 @@ /turf/simulated/floor/carpet/rubber, /area/ship/kasf_corvette/cic) "Ko" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ req_access = null }, /obj/structure/machinery/light{ @@ -8582,6 +8576,14 @@ icon_state = "0-2" }, /obj/item/blueprints, +/obj/item/storage/box/lights/mixed{ + pixel_y = 12; + pixel_x = -6 + }, +/obj/item/reagent_containers/weldpack{ + pixel_y = 14; + pixel_x = 6 + }, /turf/simulated/floor/tiled/dark/full, /area/ship/kasf_corvette/engie) "Xu" = ( diff --git a/maps/away/ships/lone_spacer/lone_spacer_submaps.dmm b/maps/away/ships/lone_spacer/lone_spacer_submaps.dmm index dae7b2bbb3d..ccee1cd86df 100644 --- a/maps/away/ships/lone_spacer/lone_spacer_submaps.dmm +++ b/maps/away/ships/lone_spacer/lone_spacer_submaps.dmm @@ -89,20 +89,8 @@ pixel_y = -1; req_access = list(243) }, -/obj/structure/table/rack/folding_table, -/obj/item/storage/box/masks{ - pixel_x = -6; - pixel_y = 12 - }, -/obj/item/storage/box/gloves{ - pixel_x = -6; - pixel_y = 1 - }, -/obj/item/stack/nanopaste{ - pixel_y = 4; - pixel_x = 8 - }, /obj/effect/decal/cleanable/dirt, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/tiled/dark/full, /area/shuttle/lone_spacer/storage) "k" = ( @@ -110,8 +98,10 @@ /obj/structure/machinery/light/small{ dir = 4 }, -/obj/structure/closet/crate/loot, /obj/effect/decal/cleanable/dirt, +/obj/structure/machinery/computer/rdconsole/robotics/terminal{ + dir = 1 + }, /turf/simulated/floor/tiled/dark/full, /area/shuttle/lone_spacer/storage) "l" = ( @@ -180,6 +170,18 @@ /obj/item/robot_parts/r_arm, /obj/effect/decal/cleanable/dirt, /obj/item/paint_sprayer, +/obj/item/storage/box/masks{ + pixel_x = -6; + pixel_y = 12 + }, +/obj/item/storage/box/gloves{ + pixel_x = -6; + pixel_y = 1 + }, +/obj/item/stack/nanopaste{ + pixel_y = 4; + pixel_x = 8 + }, /turf/simulated/floor/tiled/dark/full, /area/shuttle/lone_spacer/storage) "t" = ( @@ -384,7 +386,7 @@ /area/shuttle/lone_spacer/storage) "O" = ( /obj/effect/floor_decal/industrial/outline/operations, -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ req_access = null; dir = 1 }, @@ -435,6 +437,13 @@ /obj/effect/decal/cleanable/dirt, /turf/simulated/floor/tiled/dark/full, /area/shuttle/lone_spacer/storage) +"U" = ( +/obj/structure/sign/poster{ + pixel_x = 32 + }, +/obj/structure/closet/crate/loot, +/turf/simulated/floor/tiled/dark/full, +/area/shuttle/lone_spacer/storage) "W" = ( /obj/structure/sink/kitchen{ dir = 8; @@ -727,7 +736,7 @@ a a r W -B +U k a a diff --git a/maps/away/ships/pra/headmaster/headmaster_ship.dmm b/maps/away/ships/pra/headmaster/headmaster_ship.dmm index b82f5462152..8d7142c8287 100644 --- a/maps/away/ships/pra/headmaster/headmaster_ship.dmm +++ b/maps/away/ships/pra/headmaster/headmaster_ship.dmm @@ -1557,6 +1557,31 @@ temperature = 278.15 }, /area/headmaster_ship/hallway_lower) +"eW" = ( +/obj/effect/floor_decal/corner/red/diagonal, +/obj/random/dirt_75, +/obj/item/spaceflare{ + pixel_y = 3; + pixel_x = 6 + }, +/obj/item/spaceflare{ + pixel_y = 1; + pixel_x = -5 + }, +/obj/item/tank/hydrogen/shuttle{ + pixel_x = 3; + pixel_y = 8 + }, +/obj/item/tank/hydrogen/shuttle{ + pixel_x = -3; + pixel_y = 5 + }, +/obj/structure/table/rack, +/obj/effect/floor_decal/industrial/outline/grey, +/turf/simulated/floor/tiled/dark{ + temperature = 278.15 + }, +/area/headmaster_ship/hangar) "eZ" = ( /obj/structure/closet/crate/hydroponics, /obj/item/reagent_containers/glass/bucket, @@ -4984,6 +5009,14 @@ temperature = 278.15 }, /area/headmaster_ship/cic) +"sF" = ( +/obj/effect/floor_decal/corner/red/diagonal, +/obj/random/dirt_75, +/obj/structure/machinery/r_n_d/material_silo, +/turf/simulated/floor/tiled/dark{ + temperature = 278.15 + }, +/area/headmaster_ship/hangar) "sK" = ( /obj/structure/machinery/atmospherics/pipe/simple/hidden/scrubbers{ dir = 5 @@ -7632,25 +7665,10 @@ pixel_y = -32; stand_icon = null }, -/obj/structure/table/rack, -/obj/effect/floor_decal/industrial/outline/grey, -/obj/item/tank/hydrogen/shuttle{ - pixel_x = -3; - pixel_y = 5 - }, -/obj/item/tank/hydrogen/shuttle{ - pixel_x = 3; - pixel_y = 8 - }, -/obj/item/spaceflare{ - pixel_y = 1; - pixel_x = -5 - }, -/obj/item/spaceflare{ - pixel_y = 3; - pixel_x = 6 - }, /obj/random/dirt_75, +/obj/structure/machinery/computer/rdconsole/robotics/terminal{ + dir = 1 + }, /turf/simulated/floor/tiled/dark{ temperature = 278.15 }, @@ -10341,7 +10359,7 @@ }, /area/headmaster_ship/helm) "QS" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ dir = 8; req_access = list(209) }, @@ -46485,7 +46503,7 @@ jG aT us nA -My +eW pf rm QL @@ -46999,7 +47017,7 @@ aT aT BA kZ -My +sF pf nD QL diff --git a/maps/away/ships/sol/sol_frigate/sol_frigate.dmm b/maps/away/ships/sol/sol_frigate/sol_frigate.dmm index bc5865c92b2..41156617f27 100644 --- a/maps/away/ships/sol/sol_frigate/sol_frigate.dmm +++ b/maps/away/ships/sol/sol_frigate/sol_frigate.dmm @@ -1500,6 +1500,7 @@ /obj/effect/floor_decal/corner/orange{ dir = 5 }, +/obj/structure/machinery/computer/rdconsole/robotics, /turf/simulated/floor/tiled/dark, /area/ship/sol_frigate/Exo_Bay) "gD" = ( @@ -1652,6 +1653,9 @@ /obj/structure/cable/blue{ icon_state = "4-8" }, +/obj/structure/machinery/atmospherics/unary/vent_scrubber/on{ + dir = 4 + }, /turf/simulated/floor/tiled/dark, /area/ship/sol_frigate/Exo_Bay) "hk" = ( @@ -4019,7 +4023,7 @@ /turf/simulated/floor/tiled/white, /area/ship/sol_frigate/canteen) "rT" = ( -/obj/structure/machinery/mecha_part_fabricator, +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator, /obj/structure/cable/blue{ icon_state = "4-8" }, @@ -6478,12 +6482,10 @@ /area/ship/sol_frigate/sickbay) "Dn" = ( /obj/structure/extinguisher_cabinet/east, -/obj/structure/machinery/atmospherics/unary/vent_scrubber/on{ - dir = 1 - }, /obj/effect/floor_decal/corner/orange{ dir = 6 }, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/tiled/dark, /area/ship/sol_frigate/Exo_Bay) "Dr" = ( @@ -11012,12 +11014,12 @@ /obj/structure/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 4 }, -/obj/structure/machinery/atmospherics/pipe/simple/hidden/scrubbers{ - dir = 6 - }, /obj/structure/cable/blue{ icon_state = "4-8" }, +/obj/structure/machinery/atmospherics/pipe/simple/hidden/scrubbers{ + dir = 4 + }, /turf/simulated/floor/tiled/dark, /area/ship/sol_frigate/Exo_Bay) "Za" = ( diff --git a/maps/away/ships/sol/sol_ssrm/ssrm_ship.dmm b/maps/away/ships/sol/sol_ssrm/ssrm_ship.dmm index 58ad3d293c8..4cd67bd8657 100644 --- a/maps/away/ships/sol/sol_ssrm/ssrm_ship.dmm +++ b/maps/away/ships/sol/sol_ssrm/ssrm_ship.dmm @@ -2472,12 +2472,10 @@ "eWz" = ( /obj/structure/railing/mapped, /obj/effect/floor_decal/corner/brown/diagonal, -/obj/structure/machinery/atmospherics/unary/vent_scrubber/on{ - dir = 8 - }, /obj/structure/machinery/light{ dir = 1 }, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/tiled/dark, /area/ship/ssrm_corvette/garage) "eYX" = ( @@ -4288,6 +4286,9 @@ }, /obj/effect/floor_decal/industrial/outline/red, /obj/structure/reagent_dispensers/fueltank, +/obj/structure/machinery/atmospherics/unary/vent_scrubber/on{ + dir = 8 + }, /turf/simulated/floor/tiled/dark/full, /area/ship/ssrm_corvette/garage) "iOV" = ( @@ -4918,7 +4919,7 @@ dir = 4 }, /obj/effect/floor_decal/industrial/hatch/operations, -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ dir = 8; req_access = list(203) }, @@ -8895,6 +8896,10 @@ pixel_y = 12 }, /obj/item/robotanalyzer, +/obj/structure/machinery/cell_charger{ + pixel_y = 4; + pixel_x = 1 + }, /turf/simulated/floor/tiled/dark, /area/ship/ssrm_corvette/garage) "uyh" = ( @@ -10242,10 +10247,8 @@ dir = 4 }, /obj/effect/floor_decal/industrial/outline/grey, -/obj/structure/table/reinforced/steel, -/obj/structure/machinery/cell_charger{ - pixel_y = 4; - pixel_x = 1 +/obj/structure/machinery/computer/rdconsole/robotics/terminal{ + dir = 4 }, /turf/simulated/floor/tiled/dark, /area/ship/ssrm_corvette/garage) @@ -10376,12 +10379,14 @@ "xJQ" = ( /obj/effect/floor_decal/corner/brown/diagonal, /obj/structure/machinery/atmospherics/pipe/simple/hidden/supply, -/obj/structure/machinery/atmospherics/pipe/simple/hidden/scrubbers, /obj/structure/cable{ icon_state = "1-2"; d1 = 1; d2 = 2 }, +/obj/structure/machinery/atmospherics/pipe/manifold/hidden/scrubbers{ + dir = 8 + }, /turf/simulated/floor/tiled/dark, /area/ship/ssrm_corvette/garage) "xLV" = ( diff --git a/maps/random_ruins/exoplanets/konyang/abandoned/factory_robotics.dmm b/maps/random_ruins/exoplanets/konyang/abandoned/factory_robotics.dmm index 4280bdb2389..6cdb49922ff 100644 --- a/maps/random_ruins/exoplanets/konyang/abandoned/factory_robotics.dmm +++ b/maps/random_ruins/exoplanets/konyang/abandoned/factory_robotics.dmm @@ -149,6 +149,11 @@ /obj/structure/machinery/smartfridge, /turf/simulated/floor/foundation, /area/konyang/robotics_factory) +"jl" = ( +/obj/random/dirt_75, +/obj/structure/machinery/r_n_d/material_silo, +/turf/simulated/floor/sidewalk/dark, +/area/konyang/robotics_factory) "jm" = ( /obj/structure/machinery/light_switch{ dir = 4; @@ -219,6 +224,7 @@ "lN" = ( /obj/random/dirt_75, /obj/random/tech_supply, +/mob/living/simple_animal/thinbug, /turf/simulated/floor/sidewalk/dark, /area/konyang/robotics_factory) "lT" = ( @@ -343,8 +349,10 @@ /area/konyang/robotics_factory) "rq" = ( /obj/random/dirt_75, -/mob/living/simple_animal/thinbug, /obj/structure/machinery/light/broken, +/obj/structure/machinery/computer/rdconsole/robotics/terminal{ + dir = 1 + }, /turf/simulated/floor/sidewalk/dark, /area/konyang/robotics_factory) "rT" = ( @@ -723,7 +731,7 @@ /turf/simulated/floor/sidewalk/dark, /area/konyang/robotics_factory) "Ku" = ( -/obj/structure/machinery/mecha_part_fabricator, +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator, /turf/simulated/floor/foundation, /area/konyang/robotics_factory) "KC" = ( @@ -939,7 +947,7 @@ /area/konyang/robotics_factory) "XD" = ( /obj/random/dirt_75, -/obj/structure/machinery/mecha_part_fabricator, +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator, /turf/simulated/floor/sidewalk/dark, /area/konyang/robotics_factory) "XI" = ( @@ -1899,8 +1907,8 @@ eM SY tf BH -Us CF +jl SY cP se diff --git a/maps/random_ruins/exoplanets/ouerea/ouerea_nt_ruin.dmm b/maps/random_ruins/exoplanets/ouerea/ouerea_nt_ruin.dmm index 924322b7d10..09262c951dc 100644 --- a/maps/random_ruins/exoplanets/ouerea/ouerea_nt_ruin.dmm +++ b/maps/random_ruins/exoplanets/ouerea/ouerea_nt_ruin.dmm @@ -110,7 +110,7 @@ /obj/effect/floor_decal/corner/mauve/full{ dir = 4 }, -/obj/item/melee/baton/stunrod, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/exoplanet/tiled/white{ temperature = 298.15 }, @@ -123,6 +123,7 @@ dir = 6 }, /obj/random/dirt_75, +/obj/item/melee/baton/stunrod, /turf/simulated/floor/exoplanet/tiled/white{ temperature = 298.15 }, diff --git a/maps/runtime/runtime.dmm b/maps/runtime/runtime.dmm index 699b20d108b..07d0f2e36a0 100644 --- a/maps/runtime/runtime.dmm +++ b/maps/runtime/runtime.dmm @@ -734,7 +734,7 @@ /obj/effect/floor_decal/corner/mauve/full{ dir = 1 }, -/obj/structure/machinery/r_n_d/circuit_imprinter, +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter, /obj/effect/floor_decal/industrial/outline/yellow, /obj/effect/floor_decal/industrial/warning{ dir = 5 @@ -760,7 +760,7 @@ /obj/effect/floor_decal/corner/mauve{ dir = 5 }, -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ dir = 8 }, /obj/effect/floor_decal/industrial/outline/yellow, @@ -2766,7 +2766,7 @@ /obj/effect/floor_decal/industrial/warning{ dir = 1 }, -/obj/structure/machinery/r_n_d/protolathe, +/obj/structure/machinery/r_n_d/fabricator/protolathe, /turf/simulated/floor/tiled/white, /area/runtime/floor_one/main) "mh" = ( @@ -8322,6 +8322,7 @@ /obj/effect/floor_decal/industrial/warning{ dir = 4 }, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/tiled/white, /area/runtime/floor_one/main) "SB" = ( diff --git a/maps/sccv_horizon/sccv_horizon.dmm b/maps/sccv_horizon/sccv_horizon.dmm index 9f2b3defb6c..63420df6f8f 100644 --- a/maps/sccv_horizon/sccv_horizon.dmm +++ b/maps/sccv_horizon/sccv_horizon.dmm @@ -2600,6 +2600,14 @@ pixel_x = 10; pixel_y = -3 }, +/obj/item/clothing/glasses/safety/goggles/science{ + pixel_y = 10 + }, +/obj/item/reagent_containers/glass/beaker/sulphuric{ + pixel_y = 3 + }, +/obj/item/stack/material/glass/full, +/obj/item/stack/material/steel/full, /turf/simulated/floor/tiled/white, /area/horizon/rnd/lab) "apc" = ( @@ -3063,28 +3071,11 @@ /turf/simulated/floor/wood, /area/horizon/service/library) "ato" = ( -/obj/item/stack/material/lead{ - amount = 10 - }, -/obj/item/reagent_containers/weldpack, -/obj/item/portable_whiteboard, -/obj/item/paint_sprayer, -/obj/structure/closet/crate/tool, -/obj/item/storage/bag/circuits/basic, -/obj/item/integrated_electronics/wirer, -/obj/item/integrated_electronics/debugger{ - pixel_x = 2; - pixel_y = 7 - }, -/obj/item/stack/material/phoron{ - amount = 20 - }, -/obj/item/stack/material/uranium{ - amount = 10 - }, /obj/effect/floor_decal/spline/plain/red{ dir = 5 }, +/obj/structure/crate_shelf, +/obj/structure/closet/crate/tool/machinist, /turf/simulated/floor/carpet/rubber, /area/horizon/operations/machinist) "atp" = ( @@ -10460,7 +10451,7 @@ dir = 5 }, /obj/effect/floor_decal/industrial/hatch/yellow, -/obj/structure/machinery/r_n_d/protolathe, +/obj/structure/machinery/r_n_d/fabricator/protolathe, /turf/simulated/floor/carpet/rubber, /area/horizon/rnd/lab) "bot" = ( @@ -21224,7 +21215,7 @@ dir = 9 }, /obj/effect/floor_decal/industrial/hatch/yellow, -/obj/structure/machinery/r_n_d/circuit_imprinter, +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter, /obj/structure/railing/mapped{ dir = 8 }, @@ -76550,7 +76541,7 @@ }, /area/centcom/shared_dream) "jSK" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ dir = 8 }, /obj/effect/floor_decal/industrial/outline/red, @@ -96059,7 +96050,7 @@ /turf/simulated/floor/tiled/white, /area/horizon/rnd/xenobiology) "mtu" = ( -/obj/structure/machinery/mecha_part_fabricator{ +/obj/structure/machinery/r_n_d/fabricator/mecha_part_fabricator{ dir = 8 }, /obj/effect/floor_decal/industrial/outline/red, @@ -103545,16 +103536,7 @@ /turf/simulated/floor, /area/horizon/maintenance/deck_3/aft/port/far) "ntL" = ( -/obj/structure/table/standard, /obj/item/firing_pin/test_range, -/obj/item/clothing/glasses/safety/goggles/science{ - pixel_y = 10 - }, -/obj/item/stack/material/steel/full, -/obj/item/stack/material/glass/full, -/obj/item/reagent_containers/glass/beaker/sulphuric{ - pixel_y = 3 - }, /obj/structure/platform{ dir = 1 }, @@ -103566,6 +103548,7 @@ pixel_x = 28; pixel_y = -5 }, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/tiled/white, /area/horizon/rnd/lab) "ntU" = ( @@ -106467,7 +106450,7 @@ /turf/simulated/floor/wood, /area/horizon/service/mess_hall) "nMY" = ( -/obj/structure/machinery/r_n_d/protolathe, +/obj/structure/machinery/r_n_d/fabricator/protolathe, /obj/structure/machinery/camera{ c_tag = "Operations - Machinist Workshop 3"; network = list("Operations"); @@ -110171,10 +110154,10 @@ network = list("Operations"); dir = 1 }, -/obj/structure/crate_shelf, /obj/effect/floor_decal/spline/plain/red{ dir = 1 }, +/obj/structure/machinery/r_n_d/material_silo, /turf/simulated/floor/carpet/rubber, /area/horizon/operations/machinist) "omQ" = ( @@ -111490,10 +111473,20 @@ pixel_y = 4; randpixel = 0 }, -/obj/structure/closet/crate/drop, /obj/effect/floor_decal/spline/plain/red{ dir = 9 }, +/obj/item/stack/material/phoron{ + amount = 20 + }, +/obj/item/stack/material/uranium{ + amount = 10 + }, +/obj/structure/closet/crate/drop, +/obj/structure/crate_shelf, +/obj/item/stack/material/lead{ + amount = 10 + }, /turf/simulated/floor/carpet/rubber, /area/horizon/operations/machinist) "ovc" = ( @@ -182570,7 +182563,7 @@ /obj/structure/machinery/light/small{ dir = 8 }, -/obj/structure/machinery/r_n_d/circuit_imprinter, +/obj/structure/machinery/r_n_d/fabricator/circuit_imprinter, /obj/effect/floor_decal/industrial/outline/research, /turf/simulated/floor/tiled/dark/full, /area/horizon/operations/machinist) diff --git a/tgui/packages/tgui/interfaces/ResearchConsole.tsx b/tgui/packages/tgui/interfaces/ResearchConsole.tsx new file mode 100644 index 00000000000..a3e2bd1be0c --- /dev/null +++ b/tgui/packages/tgui/interfaces/ResearchConsole.tsx @@ -0,0 +1,578 @@ +import { + Box, + Button, + LabeledList, + NoticeBox, + ProgressBar, + Section, + Stack, + Table, +} from 'tgui-core/components'; +import type { BooleanLike } from 'tgui-core/react'; +import { useBackend, useLocalState } from '../backend'; +import { Window } from '../layouts'; +import { SearchBar } from './common/SearchBar'; +import { + ResearchFabricator, + type ResearchFabricatorData, +} from './ResearchFabricator'; + +type ResearchConsoleData = { + manufacturer: string; + screen: string; + authorized: BooleanLike; + emagged: BooleanLike; + network_sync: BooleanLike; + busy_message: string; + devices: { + analyzer: number; + protolathe: number; + imprinter: number; + mechfab: number; + silo: number; + }; + loaded_disk: LoadedDisk | null; + technologies?: Technology[]; + designs?: Design[]; + analyzer?: AnalyzerData; + fabricator?: ResearchFabricatorData; + queue_amount: number; +}; + +type LoadedDisk = { + type: 'technology' | 'design'; + name: string; + stored_name?: string; + stored_description?: string; + stored_level?: number; +}; + +type Technology = { + id: string; + name: string; + description: string; + level: number; + progress: number; + threshold: number; + complete: BooleanLike; +}; + +type Design = { + path: string; + name: string; + description: string; + category: string; +}; + +type AnalyzerData = { + linked: BooleanLike; + busy?: BooleanLike; + item?: { + name: string; + stack: BooleanLike; + technologies: AnalyzerTechnology[]; + }; +}; + +type AnalyzerTechnology = { + name: string; + item_level: number; + current_level: number; + progress: number; + threshold: number; +}; + +export const ResearchConsole = () => { + const { data } = useBackend(); + + return ( + + + {data.screen === 'main' && } + {data.screen === 'busy' && ( +

+ {data.busy_message} + + Please wait... + +
+ )} + {data.screen === 'levels' && ( + + )} + {data.screen === 'designs' && ( + + )} + {data.screen === 'tech_disk' && ( + + )} + {data.screen === 'design_disk' && ( + + )} + {data.screen === 'analyzer' && } + {data.screen === 'protolathe' && data.fabricator && ( + + )} + {data.screen === 'imprinter' && data.fabricator && ( + + )} + {data.screen === 'mechfab' && data.fabricator && ( + + )} + {data.screen === 'settings' && } + + + ); +}; + +const BackButton = () => { + const { act } = useBackend(); + return ( + + + ))} + + ); +}; + +const Manufacturers = ({ + fabricator, +}: { + fabricator: ResearchFabricatorData; +}) => { + const { act } = useBackend(); + + return ( +
+ {fabricator.manufacturers?.map((manufacturer) => ( +
+ ); +}; + +const Queue = ({ fabricator }: { fabricator: ResearchFabricatorData }) => { + const { act } = useBackend(); + + return ( +
+ {fabricator.queue.length ? ( + + {fabricator.queue.map((entry) => ( + + act('fabricator_remove', { index: entry.index }) + } + /> + } + > + {entry.active ? ( + <> + {entry.machine ?? 'Fabricator'} + + {formatDuration(entry.remaining_time)} remaining + + + ) : ( + {formatDuration(entry.build_time)} + )} + + ))} + + ) : ( + The queue is empty. + )} +
+ ); +}; + +const formatDuration = (deciseconds: number) => { + const totalSeconds = Math.max(0, Math.ceil(deciseconds / 10)); + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + + if (minutes > 0) { + return `${minutes}:${seconds.toString().padStart(2, '0')}`; + } + + return `${seconds}s`; +}; diff --git a/tgui/packages/tgui/styles/interfaces/ResearchFabricator.scss b/tgui/packages/tgui/styles/interfaces/ResearchFabricator.scss new file mode 100644 index 00000000000..b49beadb2e0 --- /dev/null +++ b/tgui/packages/tgui/styles/interfaces/ResearchFabricator.scss @@ -0,0 +1,26 @@ +.ResearchFabricator { + &__recipeRow { + &--odd { + background-color: rgba(255, 255, 255, 0.035); + } + + &--even { + background-color: rgba(0, 0, 0, 0.035); + } + + &--odd:hover, + &--even:hover { + background-color: rgba(255, 255, 255, 0.08); + } + } + + &__requirement { + &--missing { + background-color: rgba(160, 20, 20, 0.18); + } + + &--available { + background-color: rgba(20, 120, 20, 0.12); + } + } +} diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index 8b346bffa7a..47204490112 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -60,6 +60,7 @@ @include meta.load-css('./interfaces/PersonalCrafting.scss'); @include meta.load-css('./interfaces/Radio.scss'); @include meta.load-css('./interfaces/LoreConsole.scss'); +@include meta.load-css('./interfaces/ResearchFabricator.scss'); // Layouts @include meta.load-css('./layouts/Layout.scss');