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 += ""
@@ -475,415 +379,588 @@ won't update every console in existence) but it's more of a hassle to do. Also,
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 += ""
- for(var/tech_id in linked_destroy.loaded_item.origin_tech)
- var/datum/tech/T = files.known_tech[tech_id]
- dat += "[T.name]: \[Level: [linked_destroy.loaded_item.origin_tech[tech_id]] || Progress Contribution: [files.get_level_value(linked_destroy.loaded_item.origin_tech[tech_id])]\]"
- dat += " (Current Level: [T.level] || Current Progress: [T.next_level_progress]/[T.next_level_threshold])"
- 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 += "
"
- var/last_category = ""
- for(var/path in files.known_designs)
- var/datum/design/D = files.known_designs[path]
- if(!D.build_path || !(D.build_type & PROTOLATHE))
- continue
- if(protolathe_category != "All" && D.p_category != protolathe_category)
- continue
- if(protolathe_category == "All" && D.p_category != last_category)
- last_category = D.p_category
- dat += "[last_category] "
- var/temp_dat
- for(var/M in D.materials)
- temp_dat += ", [D.materials[M]*linked_lathe.mat_efficiency] [CallMaterialName(M)]"
- for(var/T in D.chemicals)
- temp_dat += ", [D.chemicals[T]*linked_lathe.mat_efficiency] [CallReagentName(T)]"
- if(temp_dat)
- temp_dat = " \[[copytext(temp_dat, 3)]\]"
- if(linked_lathe.canBuild(D))
- dat += "[D.name] "
- else
- dat += "[D.name]
"
- dat += "[temp_dat][D.desc] "
- dat += " "
- dat += "
"
-
- if(3.2) //Protolathe Material Storage Sub-menu
- dat += "Main Menu || "
- dat += "Protolathe Menu "
- dat += "Material Storage "
- dat += ""
- SSmaterials.normalize_material_amounts(linked_lathe.materials)
- for(var/M in linked_lathe.materials)
- var/amount = linked_lathe.materials[M]
- dat += "[CallMaterialName(M)] : [amount] cm3 "
- if(amount >= SHEET_MATERIAL_AMOUNT)
- dat += " || Eject "
- for (var/C in list(1, 3, 5, 10, 15, 20, 25, 30, 40))
- if(amount < C * SHEET_MATERIAL_AMOUNT)
- break
- dat += "[C > 1 ? ", " : ""][C] "
-
- dat += " or max sheets"
- dat += ""
- 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 += "
"
- var/last_category = ""
- for(var/path in files.known_designs)
- var/datum/design/D = files.known_designs[path]
- if(!D.build_path || !(D.build_type & IMPRINTER))
- continue
- if(imprinter_category != "All" && D.p_category != imprinter_category)
- continue
- if(imprinter_category == "All" && D.p_category != last_category)
- last_category = D.p_category
- dat += "[last_category] "
- var/temp_dat
- for(var/M in D.materials)
- temp_dat += ", [D.materials[M]*linked_imprinter.mat_efficiency] [CallMaterialName(M)]"
- for(var/T in D.chemicals)
- temp_dat += ", [D.chemicals[T]*linked_imprinter.mat_efficiency] [CallReagentName(T)]"
- if(temp_dat)
- temp_dat = " \[[copytext(temp_dat,3)]\]"
- if(linked_imprinter.canBuild(D))
- dat += "[D.name] "
- else
- dat += "[D.name]
"
- dat += "[temp_dat][D.desc] "
- 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 += ""
- SSmaterials.normalize_material_amounts(linked_imprinter.materials)
- for(var/M in linked_imprinter.materials)
- var/amount = linked_imprinter.materials[M]
- dat += "[CallMaterialName(M)] : [amount] cm3 "
- if(amount >= SHEET_MATERIAL_AMOUNT)
- dat += " || Eject: "
- for (var/C in list(1, 3, 5, 10, 15, 20, 25, 30, 40))
- if(amount < C * SHEET_MATERIAL_AMOUNT)
- break
- dat += "[C > 1 ? ", " : ""][C] "
-
- dat += " or max sheets"
- dat += ""
- 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 05f9dc1141c..a5c1345fe0a 100644
Binary files a/icons/obj/machinery/research.dmi and b/icons/obj/machinery/research.dmi differ
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 (
+ act('back')} />
+ );
+};
+
+const MainMenu = () => {
+ const { act, data } = useBackend();
+
+ return (
+
+
+
+ act('set_screen', { screen: 'levels' })}
+ />
+ act('set_screen', { screen: 'designs' })}
+ />
+ act('print_research', { detailed: false })}
+ />
+ act('print_research', { detailed: true })}
+ />
+
+
+
+
+
+ {data.loaded_disk ? (
+
+
+ {data.loaded_disk.name}
+ {data.loaded_disk.stored_name ?? 'Empty'}
+
+
+
+ act('set_screen', {
+ screen:
+ data.loaded_disk?.type === 'technology'
+ ? 'tech_disk'
+ : 'design_disk',
+ })
+ }
+ />
+
+
+ ) : (
+ No disk loaded.
+ )}
+
+
+
+
+
+ {data.devices.silo ? (
+ Shared material silo connected.
+ ) : (
+
+ No shared material silo connected. Fabrication is unavailable.
+
+ )}
+ act('set_screen', { screen: 'analyzer' })}
+ />
+ act('set_screen', { screen: 'protolathe' })}
+ />
+ act('set_screen', { screen: 'imprinter' })}
+ />
+ act('set_screen', { screen: 'mechfab' })}
+ />
+
+
+
+
+
+ act('set_screen', { screen: 'settings' })}
+ />
+
+
+
+ );
+};
+
+const TechnologyList = ({ technologies }: { technologies: Technology[] }) => {
+ const { act } = useBackend();
+ const [searchTerm, setSearchTerm] = useLocalState('research-tech-search', '');
+ const search = searchTerm.trim().toLowerCase();
+ const visible = technologies.filter(
+ (technology) => !search || technology.name.toLowerCase().includes(search),
+ );
+
+ return (
+
+
+
+
+
+
+
+
+ }
+ >
+
+ {visible.map((technology) => (
+
+ {technology.description}
+ {technology.complete ? (
+ Level {technology.level}: Complete
+ ) : (
+
+ Level {technology.level}: {technology.progress} /{' '}
+ {technology.threshold}
+
+ )}
+
+ ))}
+
+
+ );
+};
+
+const DesignList = ({ designs }: { designs: Design[] }) => {
+ const [searchTerm, setSearchTerm] = useLocalState(
+ 'research-design-search',
+ '',
+ );
+ const search = searchTerm.trim().toLowerCase();
+ const visible = designs.filter(
+ (design) => !search || design.name.toLowerCase().includes(search),
+ );
+
+ return (
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+ Name
+ Category
+ Description
+
+ {visible.map((design) => (
+
+ {design.name}
+ {design.category}
+ {design.description}
+
+ ))}
+
+
+ );
+};
+
+const DiskMenu = ({
+ technologies,
+ designs,
+}: {
+ technologies?: Technology[];
+ designs?: Design[];
+}) => {
+ const { act, data } = useBackend();
+ const disk = data.loaded_disk;
+
+ if (!disk) {
+ return (
+ }>
+ No disk loaded.
+
+ );
+ }
+
+ return (
+
+
+ }>
+
+ {disk.name}
+
+ {disk.stored_name ?? 'Empty'}
+
+ {!!disk.stored_description && (
+
+ {disk.stored_description}
+
+ )}
+
+ act('upload_disk')}
+ />
+ act('clear_disk')}
+ />
+ act('eject_disk')}
+ />
+
+
+
+
+ {disk.type === 'technology' ? (
+
+
+ {technologies?.map((technology) => (
+ act('copy_tech', { id: technology.id })}
+ />
+ }
+ >
+ Level {technology.level}
+
+ ))}
+
+
+ ) : (
+
+
+ {designs?.map((design) => (
+
+ act('copy_design', { design: design.path })
+ }
+ />
+ }
+ >
+ {design.category}
+
+ ))}
+
+
+ )}
+
+
+ );
+};
+
+const Analyzer = ({ analyzer }: { analyzer?: AnalyzerData }) => {
+ const { act } = useBackend();
+
+ return (
+ }>
+ {!analyzer?.linked ? (
+ No destructive analyzer linked.
+ ) : analyzer.busy ? (
+ The analyzer is currently processing.
+ ) : !analyzer.item ? (
+ No item loaded.
+ ) : (
+ <>
+
+ {analyzer.item.name}
+
+
+
+ Technology
+ Item Level
+ Current Level
+ Progress
+
+ {analyzer.item.technologies.map((technology) => (
+
+ {technology.name}
+ {technology.item_level}
+ {technology.current_level}
+
+ {technology.progress} / {technology.threshold}
+
+
+ ))}
+
+ act('analyzer_deconstruct')}
+ />
+ act('analyzer_eject')}
+ />
+ >
+ )}
+
+ );
+};
+
+const Settings = () => {
+ const { act, data } = useBackend();
+
+ return (
+
+
+ }>
+ act('toggle_sync')}
+ />
+ act('sync_network')}
+ />
+ act('find_devices')}
+ />
+ act('reset_database')}
+ />
+
+
+
+
+
+ {Object.entries(data.devices).map(([device, count]) => (
+ act('disconnect', { device })}
+ />
+ ))}
+
+
+
+ );
+};
+
+const formatDeviceName = (device: string) => {
+ switch (device) {
+ case 'analyzer':
+ return 'Destructive Analyzer';
+ case 'protolathe':
+ return 'Protolathe';
+ case 'imprinter':
+ return 'Circuit Imprinter';
+ case 'mechfab':
+ return 'Synthetic Fabricator';
+ case 'silo':
+ return 'Material Silo';
+ default:
+ return device;
+ }
+};
diff --git a/tgui/packages/tgui/interfaces/ResearchFabricator.tsx b/tgui/packages/tgui/interfaces/ResearchFabricator.tsx
new file mode 100644
index 00000000000..b91b70c0249
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ResearchFabricator.tsx
@@ -0,0 +1,553 @@
+import {
+ Box,
+ Button,
+ Flex,
+ LabeledControls,
+ LabeledList,
+ NoticeBox,
+ ProgressBar,
+ Section,
+ Stack,
+ Table,
+ Tabs,
+} from 'tgui-core/components';
+import type { BooleanLike } from 'tgui-core/react';
+import { useBackend, useLocalState } from '../backend';
+import { SearchBar } from './common/SearchBar';
+
+export type ResearchFabricatorData = {
+ linked: BooleanLike;
+ materials: ResearchMaterial[];
+ reagents: ResearchReagent[];
+ recipes: ResearchRecipe[];
+ categories: string[];
+ queue: ResearchQueueItem[];
+ sheet_material_amount: number;
+ maximum_material_storage: number;
+ maximum_reagent_volume: number;
+ reagent_volume: number;
+ supports_reagents: BooleanLike;
+
+ supports_manufacturers: BooleanLike;
+ manufacturers?: ResearchManufacturer[];
+ selected_manufacturer?: string;
+};
+
+type ResearchMaterial = {
+ id: string;
+ name: string;
+ amount: number;
+ maximum: number;
+};
+
+type ResearchReagent = {
+ id: string;
+ name: string;
+ amount: number;
+};
+
+type ResearchRequirement = {
+ name: string;
+ required: number;
+ stored: number;
+ missing: BooleanLike;
+ type: 'material' | 'reagent';
+};
+
+type ResearchRecipe = {
+ name: string;
+ description: string;
+ design: string;
+ category: string;
+ resources: string;
+ requirements: ResearchRequirement[];
+ can_build: BooleanLike;
+ build_time: number;
+};
+
+type ResearchQueueItem = {
+ index: number;
+ name: string;
+ build_time: number;
+ active: BooleanLike;
+ machine: string | null;
+ remaining_time: number;
+};
+
+type ResearchManufacturer = {
+ id: string;
+ name: string;
+};
+
+type Props = {
+ fabricator: ResearchFabricatorData;
+ queueAmount: number;
+ title: string;
+};
+
+export const ResearchFabricator = ({
+ fabricator,
+ queueAmount,
+ title,
+}: Props) => {
+ const { act } = useBackend();
+ const [category, setCategory] = useLocalState(`${title}-category`, 'All');
+ const [searchTerm, setSearchTerm] = useLocalState(`${title}-search`, '');
+ const search = searchTerm.trim().toLowerCase();
+ const recipes = fabricator.recipes.filter((recipe) => {
+ if (category !== 'All' && recipe.category !== category) {
+ return false;
+ }
+ return !search || recipe.name.toLowerCase().includes(search);
+ });
+
+ return (
+
+
+ act('back')}
+ />
+ }
+ >
+
+
+
+
+
+ {!!fabricator.supports_reagents && (
+
+
+
+ )}
+
+
+
+
+ {!!fabricator.supports_manufacturers && (
+
+
+
+ )}
+
+
+
+
+
+ {fabricator.categories.map((entry) => (
+ setCategory(entry)}
+ >
+ {entry}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Queue amount:
+
+ {[1, 5, 10].map((amount) => (
+
+ act('set_queue_amount', { amount })
+ }
+ />
+ ))}
+ act('set_custom_queue_amount')}
+ />
+
+
+
+
+
+
+
+ {category === 'All' ? (
+ fabricator.categories
+ .filter((entry) => entry !== 'All')
+ .map((entry) => {
+ const categoryRecipes = recipes.filter(
+ (recipe) => recipe.category === entry,
+ );
+
+ if (!categoryRecipes.length) {
+ return null;
+ }
+
+ return (
+
+ );
+ })
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+
+const RecipeTable = ({
+ recipes,
+ queueAmount,
+}: {
+ recipes: ResearchRecipe[];
+ queueAmount: number;
+}) => {
+ const { act } = useBackend();
+
+ return (
+
+
+ Design
+ Resources
+ Time
+
+ {recipes.map((recipe, index) => (
+
+
+
+ act('fabricator_build', {
+ design: recipe.design,
+ })
+ }
+ />
+
+
+
+
+
+ {formatDuration(recipe.build_time)}
+
+
+ ))}
+
+ );
+};
+
+const FabricatorMaterials = ({
+ fabricator,
+}: {
+ fabricator: ResearchFabricatorData;
+}) => {
+ const { act } = useBackend();
+ const sheetSize = fabricator.sheet_material_amount;
+
+ return (
+
+
+ {fabricator.materials.map((material) => {
+ const maximum =
+ material.maximum ?? fabricator.maximum_material_storage;
+
+ const sheets = Math.floor(material.amount / sheetSize);
+
+ return (
+
+
+ act('fabricator_eject', {
+ material: material.id,
+ amount: 1,
+ })
+ }
+ />
+
+
+ act('fabricator_eject', {
+ material: material.id,
+ amount: 5,
+ })
+ }
+ />
+
+
+ act('fabricator_eject_custom', {
+ material: material.id,
+ })
+ }
+ />
+
+
+ act('fabricator_eject', {
+ material: material.id,
+ amount: sheets,
+ })
+ }
+ />
+
+
+ {material.amount} / {maximum}
+
+
+ );
+ })}
+
+
+ );
+};
+
+const Chemicals = ({ fabricator }: { fabricator: ResearchFabricatorData }) => {
+ const { act } = useBackend();
+
+ return (
+ act('fabricator_purge_all')}
+ />
+ }
+ >
+
+ {fabricator.reagent_volume} / {fabricator.maximum_reagent_volume}
+
+ {fabricator.reagents.length ? (
+
+
+ {fabricator.reagents.map((reagent) => (
+
+ act('fabricator_purge', { reagent: reagent.id })
+ }
+ />
+ }
+ >
+ {reagent.amount} units
+
+ ))}
+
+
+ ) : (
+ No chemicals stored.
+ )}
+
+ );
+};
+
+const RecipeRequirements = ({ recipe }: { recipe: ResearchRecipe }) => {
+ if (!recipe.requirements?.length) {
+ return {recipe.resources} ;
+ }
+
+ return (
+
+ {recipe.requirements.map((requirement) => (
+
+
+
+ {requirement.name}: {requirement.required}
+
+
+
+ ))}
+
+ );
+};
+
+const Manufacturers = ({
+ fabricator,
+}: {
+ fabricator: ResearchFabricatorData;
+}) => {
+ const { act } = useBackend();
+
+ return (
+
+ {fabricator.manufacturers?.map((manufacturer) => (
+
+ act('fabricator_manufacturer', {
+ manufacturer: manufacturer.id,
+ })
+ }
+ />
+ ))}
+
+ );
+};
+
+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');