From a56d3ac51b2e0b00fe3464aa21389063658e1a3f Mon Sep 17 00:00:00 2001
From: MrCastmer <125900379+MrCastmer@users.noreply.github.com>
Date: Thu, 22 Feb 2024 17:50:22 +0300
Subject: [PATCH] Spacepod Fabricator (#16)
* Spacepod Fabricator
* Update protolathe.dm
---------
Co-authored-by: Blundir <100090741+Blundir@users.noreply.github.com>
---
code/modules/research/machinery/protolathe.dm | 2 +-
code/modules/research/machinery/techfab.dm | 2 +-
.../code/game/machinery/pod_fabricator.dm | 756 ++++++++++++++++++
.../modules/research/techweb/all_nodes.dm | 5 +
modular_dripstation/includes.dm | 2 +
.../tgui/interfaces/ExosuitFabricator.js | 2 +-
.../research/designs/spacepod_designs.dm | 28 +-
7 files changed, 793 insertions(+), 4 deletions(-)
create mode 100644 modular_dripstation/code/game/machinery/pod_fabricator.dm
create mode 100644 modular_dripstation/code/modules/research/techweb/all_nodes.dm
diff --git a/code/modules/research/machinery/protolathe.dm b/code/modules/research/machinery/protolathe.dm
index 5f5847cc2a5c..ff2327ff62bd 100644
--- a/code/modules/research/machinery/protolathe.dm
+++ b/code/modules/research/machinery/protolathe.dm
@@ -16,7 +16,7 @@
"Ammo",
"Firing Pins",
"Computer Parts",
- "Spacepod Designs", // yoggers
+ //"Spacepod Designs", // dripstation spacepod move to new fab
"Service",
"Assemblies"
)
diff --git a/code/modules/research/machinery/techfab.dm b/code/modules/research/machinery/techfab.dm
index 1289ebcde85e..ed5a8c105eef 100644
--- a/code/modules/research/machinery/techfab.dm
+++ b/code/modules/research/machinery/techfab.dm
@@ -27,7 +27,7 @@
"Research Machinery",
"Misc. Machinery",
"Computer Parts",
- "Spacepod Designs", // yogs
+ //"Spacepod Designs", // dripstation edit
"Service" //yogs
)
console_link = FALSE
diff --git a/modular_dripstation/code/game/machinery/pod_fabricator.dm b/modular_dripstation/code/game/machinery/pod_fabricator.dm
new file mode 100644
index 000000000000..e6c9bd0f4154
--- /dev/null
+++ b/modular_dripstation/code/game/machinery/pod_fabricator.dm
@@ -0,0 +1,756 @@
+/obj/item/circuitboard/machine/pod_fab
+ name = "Space Pod Fabricator (Machine Board)"
+ icon_state = "science"
+ build_path = /obj/machinery/pod_fabricator
+ req_components = list(
+ /obj/item/stock_parts/matter_bin = 2,
+ /obj/item/stock_parts/manipulator = 1,
+ /obj/item/stock_parts/micro_laser = 1,
+ /obj/item/stack/sheet/glass = 1)
+
+/datum/design/board/podfab
+ name = "Machine Design (Space Pod Fabricator Board)"
+ desc = "The circuit board for an Space Pod Fabricator."
+ id = "podfab"
+ build_path = /obj/machinery/pod_fabricator
+ category = list("Research Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/wires/tesla_coil
+ proper_name = "Spacepod Fabricator"
+ randomize = TRUE //Only one wire don't need blueprints
+ holder_type = /obj/machinery/pod_fabricator
+
+/obj/machinery/pod_fabricator
+ icon = 'icons/obj/robotics.dmi'
+ icon_state = "fab-idle"
+ name = "spacepod fabricator"
+ desc = "Nothing is being built."
+ density = TRUE
+ use_power = IDLE_POWER_USE
+ idle_power_usage = 20
+ active_power_usage = 5000
+
+ req_access = list(ACCESS_SECURE_TECH_STORAGE) //place mechanic access here
+ var/hacked = FALSE
+ ///World ticks the machine is electified for
+ var/seconds_electrified = MACHINE_NOT_ELECTRIFIED
+
+
+ circuit = /obj/item/circuitboard/machine/pod_fab
+ subsystem_type = /datum/controller/subsystem/processing/fastprocess
+ /// Controls whether or not the more dangerous designs have been unlocked by a head's id manually, rather than alert level unlocks
+ var/authorization_override = FALSE
+ /// ID card of the person using the machine for the purpose of tracking access
+ var/obj/item/card/id/id_card = new()
+ /// Current items in the build queue.
+ var/list/queue = list()
+ /// Whether or not the machine is building the entire queue automagically.
+ var/process_queue = FALSE
+
+ /// The current design datum that the machine is building.
+ var/datum/design/being_built
+ /// World time when the build will finish.
+ var/build_finish = 0
+ /// World time when the build started.
+ var/build_start = 0
+ /// Reference to all materials used in the creation of the item being_built.
+ var/list/build_materials
+ /// Part currently stored in the Exofab.
+ var/obj/item/stored_part
+
+ /// Coefficient for the speed of item building. Based on the installed parts.
+ var/time_coeff = 1
+ /// Coefficient for the efficiency of material usage in item building. Based on the installed parts.
+ var/component_coeff = 1
+
+ /// Reference to the techweb.
+ var/datum/techweb/stored_research
+
+ /// Whether the Exofab links to the ore silo on init. Special derelict or maintanance variants should set this to FALSE.
+ var/link_on_init = TRUE
+
+ /// Reference to a remote material inventory, such as an ore silo.
+ var/datum/component/remote_materials/rmat
+
+ /// A list of categories that valid pod fab design datums will broadly categorise themselves under.
+ var/list/part_sets = list(
+ "Spacepod Designs",
+ "Shuttle Machinery",
+ "Misc"
+ )
+
+/obj/machinery/pod_fabricator/Initialize(mapload)
+ stored_research = SSresearch.science_tech
+ rmat = AddComponent(/datum/component/remote_materials, "mechfab", mapload && link_on_init)
+ RefreshParts() //Recalculating local material sizes if the fab isn't linked
+ wires = new /datum/wires/mecha_part_fabricator(src)
+ return ..()
+
+/obj/machinery/pod_fabricator/Destroy()
+ QDEL_NULL(wires)
+ return ..()
+
+/obj/machinery/pod_fabricator/RefreshParts()
+ var/T = 0
+
+ //maximum stocking amount (default 300000, 600000 at T4)
+ for(var/obj/item/stock_parts/matter_bin/M in component_parts)
+ T += M.rating
+ rmat.set_local_size((200000 + (T*50000)))
+
+ //resources adjustment coefficient (1 -> 0.85 -> 0.7 -> 0.55)
+ T = 1.15
+ for(var/obj/item/stock_parts/micro_laser/Ma in component_parts)
+ T -= Ma.rating*0.15
+ component_coeff = T
+
+ //building time adjustment coefficient (1 -> 0.8 -> 0.6)
+ T = -1
+ for(var/obj/item/stock_parts/manipulator/Ml in component_parts)
+ T += Ml.rating
+ time_coeff = round(initial(time_coeff) - (initial(time_coeff)*(T))/5,0.01)
+
+ // Adjust the build time of any item currently being built.
+ if(being_built)
+ var/last_const_time = build_finish - build_start
+ var/new_const_time = get_construction_time_w_coeff(initial(being_built.construction_time))
+ var/const_time_left = build_finish - world.time
+ var/new_build_time = (new_const_time / last_const_time) * const_time_left
+ build_finish = world.time + new_build_time
+
+ update_static_data(usr)
+
+/obj/machinery/pod_fabricator/examine(mob/user)
+ . = ..()
+ if(in_range(user, src) || isobserver(user))
+ . += span_notice("The status display reads: Storing up to [rmat.local_size] material units.
Material consumption at [component_coeff*100]%.
Build time reduced by [100-time_coeff*100]%.")
+
+/obj/machinery/pod_fabricator/attackby(obj/item/I, mob/living/user, params)
+ if(panel_open && is_wire_tool(I))
+ wires.interact(user)
+ return TRUE
+ if(I.GetID())
+ var/obj/item/card/id/C = I.GetID()
+ if(obj_flags & EMAGGED)
+ to_chat(user, span_warning("The authentication slot spits sparks at you and the display reads scrambled text!"))
+ do_sparks(1, FALSE, src)
+ authorization_override = TRUE //just in case it wasn't already for some reason. keycard reader is busted.
+ return
+ if(ACCESS_HEADS in C.access)
+ if(!authorization_override)
+ authorization_override = TRUE
+ to_chat(user, span_warning("You override the safety protocols on the [src], removing access restrictions from this terminal."))
+ else
+ authorization_override = FALSE
+ to_chat(user, span_notice("You reengage the safety protocols on the [src], restoring access restrictions to this terminal."))
+ update_static_data(user)
+ return
+ return ..()
+/**
+ * All the negative wire effects
+ * Break wire breaks one limb (Because pain is to be had)
+*/
+/obj/machinery/pod_fabricator/_try_interact(mob/user)
+ if(seconds_electrified && !(stat & NOPOWER))
+ if(shock(user, 100))
+ return
+ return ..()
+
+/obj/machinery/pod_fabricator/proc/wire_break(mob/user)
+ if(stat & (BROKEN|NOPOWER))
+ return FALSE
+ var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
+ s.set_up(5, 1, src)
+ s.start()
+ var/mob/living/carbon/C = user
+ var/datum/wound/blunt/severe/break_it = new
+ ///Picks limb to break. People with less limbs have a chance of it grapping at air
+ var/obj/item/bodypart/bone = C.get_bodypart(pick(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG))
+ if(bone && Adjacent(user))
+ to_chat(C,span_userdanger("The manipulator arms grapple after your [bone.name], attempting to break its bone!"))
+ break_it.apply_wound(bone)
+ bone.receive_damage(brute=50, updating_health=TRUE)
+ else
+ to_chat(C,span_userdanger("The manipulator arms attempt to grab one of your limbs, but grapple air instead!"))
+ qdel(break_it)
+
+/obj/machinery/pod_fabricator/proc/reset(wire)
+ switch(wire)
+ if(WIRE_HACK)
+ if(!wires.is_cut(wire))
+ hacked = FALSE
+/**
+ * Shock the passed in user
+ *
+ * This checks we have power and that the passed in prob is passed, then generates some sparks
+ * and calls electrocute_mob on the user
+ *
+ * Arguments:
+ * * user - the user to shock
+ * * prb - probability the shock happens
+ */
+/obj/machinery/pod_fabricator/proc/shock(mob/user, prb)
+ if(stat & (BROKEN|NOPOWER)) // unpowered, no shock
+ return FALSE
+ if(!prob(prb))
+ return FALSE
+ do_sparks(5, TRUE, src)
+ var/check_range = TRUE
+ if(electrocute_mob(user, get_area(src), src, 0.7, check_range))
+ return TRUE
+ else
+ return FALSE
+/**
+ * Generates an info list for a given part.
+ *
+ * Returns a list of part information.
+ * * D - Design datum to get information on.
+ * * categories - Boolean, whether or not to parse snowflake categories into the part information list.
+ */
+
+/obj/machinery/pod_fabricator/proc/output_part_info(datum/design/D, categories = FALSE)
+ var/cost = list()
+ for(var/c in D.materials)
+ var/datum/material/M = c
+ cost[M.name] = get_resource_cost_w_coeff(D, M)
+
+ var/obj/built_item = D.build_path
+
+ var/list/sub_category = null
+
+ if(categories)
+ var/module_types = D.departmental_flags
+ sub_category = list()
+ if(D.category == "Spacepod Designs" && module_types)
+ if(module_types & DEPARTMENTAL_FLAG_SECURITY)
+ sub_category += "Security"
+ if(module_types & DEPARTMENTAL_FLAG_CARGO)
+ sub_category += "Cargo"
+ if(module_types & DEPARTMENTAL_FLAG_SCIENCE)
+ sub_category += "Science"
+ if(module_types & DEPARTMENTAL_FLAG_ALL)
+ sub_category += "Civilian"
+ else
+ sub_category = "Equipment"
+
+ var/list/part = list(
+ "name" = D.name,
+ "desc" = initial(built_item.desc),
+ "printTime" = get_construction_time_w_coeff(initial(D.construction_time))/10,
+ "cost" = cost,
+ "id" = D.id,
+ "subCategory" = sub_category,
+ "searchMeta" = D.search_metadata
+ )
+
+ return part
+
+/**
+ * Generates a list of resources / materials available to this Exosuit Fab
+ *
+ * Returns null if there is no material container available.
+ * List format is list(material_name = list(amount = ..., ref = ..., etc.))
+ */
+/obj/machinery/pod_fabricator/proc/output_available_resources()
+ var/datum/component/material_container/materials = rmat.mat_container
+
+ var/list/material_data = list()
+
+ if(materials)
+ for(var/mat_id in materials.materials)
+ var/datum/material/M = mat_id
+ var/list/material_info = list()
+ var/amount = materials.materials[mat_id]
+
+ material_info = list(
+ "name" = M.name,
+ "ref" = REF(M),
+ "amount" = amount,
+ "sheets" = round(amount / MINERAL_MATERIAL_AMOUNT),
+ "removable" = amount >= MINERAL_MATERIAL_AMOUNT
+ )
+
+ material_data += list(material_info)
+
+ return material_data
+
+ return null
+
+/**
+ * Intended to be called when an item starts printing.
+ *
+ * Adds the overlay to show the fab working and sets active power usage settings.
+ */
+/obj/machinery/pod_fabricator/proc/on_start_printing()
+ add_overlay("fab-active")
+ use_power = ACTIVE_POWER_USE
+
+/**
+ * Intended to be called when the exofab has stopped working and is no longer printing items.
+ *
+ * Removes the overlay to show the fab working and sets idle power usage settings. Additionally resets the description and turns off queue processing.
+ */
+/obj/machinery/pod_fabricator/proc/on_finish_printing()
+ cut_overlay("fab-active")
+ use_power = IDLE_POWER_USE
+ desc = initial(desc)
+ process_queue = FALSE
+
+/**
+ * Calculates resource/material costs for printing an item based on the machine's resource coefficient.
+ *
+ * Returns a list of k,v resources with their amounts.
+ * * D - Design datum to calculate the modified resource cost of.
+ */
+/obj/machinery/pod_fabricator/proc/get_resources_w_coeff(datum/design/D)
+ var/list/resources = list()
+ for(var/R in D.materials)
+ var/datum/material/M = R
+ resources[M] = get_resource_cost_w_coeff(D, M)
+ return resources
+
+/**
+ * Checks if the Exofab has enough resources to print a given item.
+ *
+ * Returns FALSE if the design has no reagents used in its construction (?) or if there are insufficient resources.
+ * Returns TRUE if there are sufficient resources to print the item.
+ * * D - Design datum to calculate the modified resource cost of.
+ */
+/obj/machinery/pod_fabricator/proc/check_resources(datum/design/D)
+ if(length(D.reagents_list)) // No reagents storage - no reagent designs.
+ return FALSE
+ var/datum/component/material_container/materials = rmat.mat_container
+ if(materials.has_materials(get_resources_w_coeff(D)))
+ return TRUE
+ return FALSE
+
+/**
+ * Attempts to build the next item in the build queue.
+ *
+ * Returns FALSE if either there are no more parts to build or the next part is not buildable.
+ * Returns TRUE if the next part has started building.
+ * * verbose - Whether the machine should use say() procs. Set to FALSE to disable the machine saying reasons for failure to build.
+ */
+/obj/machinery/pod_fabricator/proc/build_next_in_queue(verbose = TRUE)
+ if(!length(queue))
+ return FALSE
+
+ var/datum/design/D = queue[1]
+ if(build_part(D, verbose))
+ remove_from_queue(1)
+ return TRUE
+
+ return FALSE
+
+/**
+ * Starts the build process for a given design datum.
+ *
+ * Returns FALSE if the procedure fails. Returns TRUE when being_built is set.
+ * Uses materials.
+ * * D - Design datum to attempt to print.
+ * * verbose - Whether the machine should use say() procs. Set to FALSE to disable the machine saying reasons for failure to build.
+ */
+/obj/machinery/pod_fabricator/proc/build_part(datum/design/D, verbose = TRUE)
+ if(!D)
+ return FALSE
+
+ var/datum/component/material_container/materials = rmat.mat_container
+ if (!materials)
+ if(verbose)
+ say("No access to material storage, please contact the quartermaster.")
+ return FALSE
+ if (rmat.on_hold())
+ if(verbose)
+ say("Mineral access is on hold, please contact the quartermaster.")
+ return FALSE
+ if(!check_resources(D))
+ if(verbose)
+ say("Not enough resources. Processing stopped.")
+ return FALSE
+
+ build_materials = get_resources_w_coeff(D)
+
+ materials.use_materials(build_materials)
+ being_built = D
+ build_finish = world.time + get_construction_time_w_coeff(initial(D.construction_time))
+ build_start = world.time
+ desc = "It's building \a [D.name]."
+
+ rmat.silo_log(src, "built", -1, "[D.name]", build_materials)
+
+ return TRUE
+
+/obj/machinery/pod_fabricator/process()
+ // Deelectrifies the machine
+ if(seconds_electrified > MACHINE_NOT_ELECTRIFIED)
+ seconds_electrified--
+
+ // If there's a stored part to dispense due to an obstruction, try to dispense it.
+ if(stored_part)
+ var/turf/exit = get_step(src,(dir))
+ if(exit.density)
+ return TRUE
+
+ say("Obstruction cleared. \The [stored_part] is complete.")
+ stored_part.forceMove(exit)
+ stored_part = null
+
+ // If there's nothing being built, try to build something
+ if(!being_built)
+ // If we're not processing the queue anymore or there's nothing to build, end processing.
+ if(!process_queue || !build_next_in_queue())
+ on_finish_printing()
+ end_processing()
+ return TRUE
+ on_start_printing()
+
+ // If there's an item being built, check if it is complete.
+ if(being_built && (build_finish < world.time))
+ // Then attempt to dispense it and if appropriate build the next item.
+ dispense_built_part(being_built)
+ if(process_queue)
+ build_next_in_queue(FALSE)
+ return TRUE
+
+
+/**
+ * Dispenses a part to the tile infront of the Exosuit Fab.
+ *
+ * Returns FALSE is the machine cannot dispense the part on the appropriate turf.
+ * Return TRUE if the part was successfully dispensed.
+ * * D - Design datum to attempt to dispense.
+ */
+/obj/machinery/pod_fabricator/proc/dispense_built_part(datum/design/D)
+ var/obj/item/I = new D.build_path(src)
+ //I.set_custom_materials(build_materials)
+ being_built = null
+
+ var/turf/exit = get_step(src,(dir))
+ if(exit.density)
+ say("Error! Part outlet is obstructed.")
+ desc = "It's trying to dispense \a [D.name], but the part outlet is obstructed."
+ stored_part = I
+ return FALSE
+
+ say("\The [I] is complete.")
+ I.forceMove(exit)
+ return TRUE
+
+/**
+ * Adds a list of datum designs to the build queue.
+ *
+ * Will only add designs that are in this machine's stored techweb.
+ * Does final checks for datum IDs and makes sure this machine can build the designs.
+ * * part_list - List of datum design ids for designs to add to the queue.
+ */
+/obj/machinery/pod_fabricator/proc/add_part_set_to_queue(list/part_list, mob/user)
+ for(var/v in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(v)
+ if((D.id in part_list) && (!D.combat_design || combat_parts_allowed(user)))
+ add_to_queue(D, user)
+
+/**
+ * Adds a datum design to the build queue.
+ *
+ * Returns TRUE if successful and FALSE if the design was not added to the queue.
+ * * D - Datum design to add to the queue.
+ */
+/obj/machinery/pod_fabricator/proc/add_to_queue(datum/design/D, mob/user)
+ if(D.combat_design && !combat_parts_allowed(user))
+ return FALSE
+ if(!istype(queue))
+ queue = list()
+ if(D)
+ queue[++queue.len] = D
+ return TRUE
+ return FALSE
+
+/**
+ * Removes datum design from the build queue based on index.
+ *
+ * Returns TRUE if successful and FALSE if a design was not removed from the queue.
+ * * index - Index in the build queue of the element to remove.
+ */
+/obj/machinery/pod_fabricator/proc/remove_from_queue(index)
+ if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>length(queue)))
+ return FALSE
+ queue.Cut(index,++index)
+ return TRUE
+
+/**
+ * Generates a list of parts formatted for tgui based on the current build queue.
+ *
+ * Returns a formatted list of lists containing formatted part information for every part in the build queue.
+ */
+/obj/machinery/pod_fabricator/proc/list_queue()
+ if(!istype(queue) || !length(queue))
+ return null
+
+ var/list/queued_parts = list()
+ for(var/datum/design/D in queue)
+ var/list/part = output_part_info(D)
+ queued_parts += list(part)
+ return queued_parts
+
+/**
+ * Calculates the coefficient-modified resource cost of a single material component of a design's recipe.
+ *
+ * Returns coefficient-modified resource cost for the given material component.
+ * * D - Design datum to pull the resource cost from.
+ * * resource - Material datum reference to the resource to calculate the cost of.
+ * * roundto - Rounding value for round() proc
+ */
+/obj/machinery/pod_fabricator/proc/get_resource_cost_w_coeff(datum/design/D, datum/material/resource, roundto = 1)
+ return round(D.materials[resource]*component_coeff, roundto)
+
+/**
+ * Calculates the coefficient-modified build time of a design.
+ *
+ * Returns coefficient-modified build time of a given design.
+ * * D - Design datum to calculate the modified build time of.
+ * * roundto - Rounding value for round() proc
+ */
+/obj/machinery/pod_fabricator/proc/get_construction_time_w_coeff(construction_time, roundto = 1) //aran
+ return round(construction_time*time_coeff, roundto)
+
+/obj/machinery/pod_fabricator/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
+ )
+
+/obj/machinery/pod_fabricator/ui_status(mob/user)
+ if(stat & BROKEN || panel_open)
+ return UI_CLOSE
+ return ..()
+
+/obj/machinery/pod_fabricator/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ExosuitFabricator")
+ ui.open()
+
+/obj/machinery/pod_fabricator/ui_static_data(mob/user)
+ var/list/data = list()
+
+ var/list/final_sets = part_sets.Copy()
+ var/list/buildable_parts = list()
+
+ for(var/v in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(v)
+ if(D.combat_design && !combat_parts_allowed(user)) // Yogs -- ID swiping for combat parts
+ continue
+ // This is for us.
+ var/list/part = output_part_info(D, TRUE)
+ for(var/cat in part_sets)
+ // Find all matching categories.
+ if(!(cat in D.category))
+ continue
+ buildable_parts[cat] += list(part)
+ data["partSets"] = final_sets
+ data["buildableParts"] = buildable_parts
+
+ return data
+
+/obj/machinery/pod_fabricator/ui_data(mob/user)
+ var/list/data = list()
+ data["materials"] = output_available_resources()
+
+ if(being_built)
+ var/list/part = list(
+ "name" = being_built.name,
+ "duration" = build_finish - world.time,
+ "printTime" = get_construction_time_w_coeff(initial(being_built.construction_time))
+ )
+ data["buildingPart"] = part
+ else
+ data["buildingPart"] = null
+
+ data["queue"] = list_queue()
+
+ if(stored_part)
+ data["storedPart"] = stored_part.name
+ else
+ data["storedPart"] = null
+
+ data["isProcessingQueue"] = process_queue
+ data["authorization"] = authorization_override
+ data["user_clearance"] = head_or_silicon(user)
+ data["alert_level"] = GLOB.security_level
+ data["combat_parts_allowed"] = combat_parts_allowed(user)
+ data["emagged"] = (obj_flags & EMAGGED)
+ data["silicon_user"] = issilicon(user)
+
+ return data
+
+/// Updates the various authorization checks used to determine if combat parts are available to the current user
+/obj/machinery/pod_fabricator/proc/combat_parts_allowed(mob/user)
+ return authorization_override || GLOB.security_level >= SEC_LEVEL_RED || head_or_silicon(user)
+
+/// made as a lazy check to allow silicons full access always
+/obj/machinery/pod_fabricator/proc/head_or_silicon(mob/user)
+ if(issilicon(user))
+ return TRUE
+ id_card = user.get_idcard(hand_first = TRUE)
+ return ACCESS_HEADS in id_card?.access
+
+/obj/machinery/pod_fabricator/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ . = TRUE
+
+ add_fingerprint(usr)
+ usr.set_machine(src)
+
+ switch(action)
+ if("sync_rnd")
+ // Syncronises designs on interface with R&D techweb.
+ update_static_data(usr)
+ say("Successfully synchronized with R&D server.")
+ return
+ if("add_queue_set")
+ // Add all parts of a set to queue
+ var/part_list = params["part_list"]
+ add_part_set_to_queue(part_list, usr)
+ return
+ if("add_queue_part")
+ // Add a specific part to queue
+ var/T = params["id"]
+ for(var/v in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(v)
+ if((D.id == T))
+ add_to_queue(D, usr)
+ break
+ return
+ if("del_queue_part")
+ // Delete a specific from from the queue
+ var/index = text2num(params["index"])
+ remove_from_queue(index)
+ return
+ if("clear_queue")
+ // Delete everything from queue
+ queue.Cut()
+ return
+ if("build_queue")
+ // Build everything in queue
+ if(process_queue)
+ return
+ process_queue = TRUE
+
+ if(!being_built)
+ begin_processing()
+ return
+ if("stop_queue")
+ // Pause queue building. Also known as stop.
+ process_queue = FALSE
+ return
+ if("build_part")
+ // Build a single part
+ if(being_built || process_queue)
+ return
+
+ var/id = params["id"]
+ var/datum/design/D = SSresearch.techweb_design_by_id(id)
+
+ if(!(D.id == id))
+ return
+
+ if(build_part(D))
+ on_start_printing()
+ begin_processing()
+
+ return
+ if("move_queue_part")
+ // Moves a part up or down in the queue.
+ var/index = text2num(params["index"])
+ var/new_index = index + text2num(params["newindex"])
+ if(isnum(index) && isnum(new_index) && ISINTEGER(index) && ISINTEGER(new_index))
+ if(ISINRANGE(new_index,1,length(queue)))
+ queue.Swap(index,new_index)
+ return
+ if("remove_mat")
+ // Remove a material from the fab
+ var/mat_ref = params["ref"]
+ var/amount = text2num(params["amount"])
+ var/datum/material/mat = locate(mat_ref)
+ eject_sheets(mat, amount)
+ return
+
+ return FALSE
+
+/**
+ * Eject material sheets.
+ *
+ * Returns the number of sheets successfully ejected.
+ * eject_sheet - Byond REF of the material to eject.
+ * eject_amt - Number of sheets to attempt to eject.
+ */
+/obj/machinery/pod_fabricator/proc/eject_sheets(eject_sheet, eject_amt)
+ var/datum/component/material_container/mat_container = rmat.mat_container
+ if (!mat_container)
+ say("No access to material storage, please contact the quartermaster.")
+ return 0
+ if (rmat.on_hold())
+ say("Mineral access is on hold, please contact the quartermaster.")
+ return 0
+ var/count = mat_container.retrieve_sheets(text2num(eject_amt), eject_sheet, drop_location())
+ var/list/matlist = list()
+ matlist[eject_sheet] = text2num(eject_amt)
+ rmat.silo_log(src, "ejected", -count, "sheets", matlist)
+ return count
+
+/obj/machinery/pod_fabricator/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted)
+ var/datum/material/M = id_inserted
+ add_overlay("fab-load-[M.name]")
+ addtimer(CALLBACK(src, /atom/proc/cut_overlay, "fab-load-[M.name]"), 10)
+
+/obj/machinery/pod_fabricator/screwdriver_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+ if(being_built)
+ to_chat(user, span_warning("\The [src] is currently processing! Please wait until completion."))
+ return FALSE
+ return default_deconstruction_screwdriver(user, "fab-o", "fab-idle", I)
+
+/obj/machinery/pod_fabricator/crowbar_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+ if(being_built)
+ to_chat(user, span_warning("\The [src] is currently processing! Please wait until completion."))
+ return FALSE
+ return default_deconstruction_crowbar(I)
+
+/obj/machinery/pod_fabricator/proc/is_insertion_ready(mob/user)
+ if(panel_open)
+ to_chat(user, span_warning("You can't load [src] while it's panel is opened!"))
+ return FALSE
+ if(being_built)
+ to_chat(user, span_warning("\The [src] is currently processing! Please wait until completion."))
+ return FALSE
+ return TRUE
+
+/obj/machinery/pod_fabricator/emag_act(mob/user, obj/item/card/emag/emag_card)
+ if(obj_flags & EMAGGED)
+ to_chat(user, span_warning("[src] has no functional safeties to emag."))
+ return FALSE
+ do_sparks(1, FALSE, src)
+ to_chat(user, span_notice("You short out [src]'s safeties."))
+ authorization_override = TRUE
+ obj_flags |= EMAGGED
+ update_static_data(user)
+ return TRUE
+
+
+/obj/machinery/pod_fabricator/maint
+ link_on_init = FALSE
+
+/obj/machinery/pod_fabricator/ruin
+ link_on_init = FALSE
+ authorization_override = TRUE
+ hacked = TRUE
+
+/obj/machinery/pod_fabricator/ruin/Initialize(mapload)
+ . = ..()
+ stored_research = SSresearch.ruin_tech
diff --git a/modular_dripstation/code/modules/research/techweb/all_nodes.dm b/modular_dripstation/code/modules/research/techweb/all_nodes.dm
new file mode 100644
index 000000000000..af3fe10c67c6
--- /dev/null
+++ b/modular_dripstation/code/modules/research/techweb/all_nodes.dm
@@ -0,0 +1,5 @@
+/datum/techweb_node/base //BASE NODES OVERRIDE!
+ design_ids = list("basic_matter_bin", "basic_cell", "basic_scanning", "basic_capacitor", "basic_micro_laser", "micro_mani", "desttagger", "handlabel", "packagewrap",
+ "destructive_analyzer", "circuit_imprinter", "rack_creator", "experimentor", "rdconsole", "design_disk", "tech_disk", "rdserver", "rdservercontrol", "mechfab", "podfab", "paystand", "ticket_machine", "ticket_remote", "light_tube", "light_bulb",
+ "space_heater", "beaker", "large_beaker", "vial", "bucket", "fork", "tray","plate", "bowl", "mixing_bowl", "drinking_glass", "shot_glass", "shaker", "xlarge_beaker", "sec_rshot", "sec_beanbag_slug", "sec_bshot", "sec_slug", "sec_Islug", "sec_Brslug", "sec_38", "sec_38_lethal", "apc_control", "power control", "airlock_board", "firelock_board", "airalarm_electronics", "firealarm_electronics", "blastdoorcontroller", "aac_electronics", "mousetrap",
+ "rglass","plasteel","plastitanium","plasmaglass","plasmareinforcedglass","titaniumglass","plastitaniumglass","wallframe/flasher", "rsf", "rls", "oven_tray", "bounced_radio", "signaler", "signalbutton", "inspector_booth", "intercom_frame", "infrared_emitter", "health_sensor", "timer", "voice_analyser", "camera_assembly", "newscaster_frame", "prox_sensor", "flashlight", "extinguisher", "pocketfireextinguisher")
diff --git a/modular_dripstation/includes.dm b/modular_dripstation/includes.dm
index 6bcd39c21a97..e4964aa060fb 100644
--- a/modular_dripstation/includes.dm
+++ b/modular_dripstation/includes.dm
@@ -106,6 +106,8 @@
#include "code\game\turfs\simulated\floor.dm"
#include "code\game\turfs\open.dm"
#include "code\game\mecha\mech_bay.dm"
+#include "code\modules\research\techweb\all_nodes.dm"
+#include "code\game\machinery\pod_fabricator.dm"
#include "code\game\objects\effects\contraband.dm"
#include "code\game\objects\structures\artstuff.dm"
#include "code\game\objects\structures\bedsheet_bin.dm"
diff --git a/tgui/packages/tgui/interfaces/ExosuitFabricator.js b/tgui/packages/tgui/interfaces/ExosuitFabricator.js
index a9c55184145c..8d37e53e4822 100644
--- a/tgui/packages/tgui/interfaces/ExosuitFabricator.js
+++ b/tgui/packages/tgui/interfaces/ExosuitFabricator.js
@@ -146,7 +146,7 @@ export const ExosuitFabricator = (props, context) => {
return (