Upgrades the research console to TGUI and adds the material silo (#22987)

This updates the research console to be a TGUI and fixes several bugs
with it.
It now uses a material silo for materials, while the acid for the
circuit imprinter is still given directly to the machine.
Mech fabricators now also need to be linked to a research console.
It is also capable of connecting to multiple fabricators and distribute
tasks between them.

The sprite originates from TG station.

Unit tests were also made to ensure there is no fabricators missing
consoles and no console missing material silos.

<img width="1200" height="800" alt="image"
src="https://github.com/user-attachments/assets/b2076036-18e5-4518-9af1-3ac3698b3924"
/>
<img width="1200" height="800" alt="image"
src="https://github.com/user-attachments/assets/e954e1e4-cf2d-4472-8386-10d8993ac375"
/>
<img width="1200" height="800" alt="image"
src="https://github.com/user-attachments/assets/df690b93-c7ff-49a3-a1b3-002f6807981b"
/>
<img width="1200" height="800" alt="image"
src="https://github.com/user-attachments/assets/ff10ef88-e1d9-4b6f-9643-309d919a601f"
/>
This commit is contained in:
Casper3667
2026-08-06 09:34:34 +00:00
committed by GitHub
parent 42bba7fa3c
commit 2f5d7e8422
46 changed files with 3094 additions and 2026 deletions
@@ -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."))
+1 -1
View File
@@ -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,
+26 -290
View File
@@ -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 <b>matter bins</b> will increase material storage capacity."
. += SPAN_NOTICE(" - The current storage capacity is <b>[max_material_storage / 2000]</b> sheets")
. += "- Materials are drawn from the research material silo linked to the same R&D console."
. += "- Upgraded <b>manipulators</b> will improve material use efficiency and increase fabrication speed."
. += SPAN_NOTICE(" - The current speed increase is <b>[round((1 - (1 / production_speed)) * 100)]%</b>")
. += SPAN_NOTICE(" - The current cost reduction is <b>[round((1 - mat_efficiency) * 100)]%</b>")
/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()
@@ -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: <b>[capitalize_first_letters(initial(machine.name))]</b>, [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)
@@ -213,7 +213,7 @@
/datum/design/circuit/machine/aicore
name = "AI Core"
desc = "Used in the construction of an: <b>AI core</b>, 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
@@ -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)
@@ -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 ..()
+176
View File
@@ -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 <b>[max_material_storage / SHEET_MATERIAL_AMOUNT]</b> 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
+156
View File
@@ -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 <b>micro-lasers</b> will increase fabrication speed."
. += SPAN_NOTICE(" - The current speed increase is <b>[round((1 - (1 / production_speed)) * 100)]%</b>")
. += "- Upgraded <b>manipulators</b> will improve material use efficiency."
. += SPAN_NOTICE(" - The current cost reduction is <b>[round((1 - mat_efficiency) * 100)]%</b>")
/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 <b>[src]</b> 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()]\] <span class='warning'>Has fed [target.name]'s ([target.ckey]) hair into a [src].</span>"
target.attack_log += "\[[time_stamp()]\] <font color='orange'>Has had their hair fed into [src] by [user.name] ([user.ckey])</font>"
msg_admin_attack("[key_name_admin(user)] fed [key_name_admin(target)] in a [src]. (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[user.x];Y=[user.y];Z=[user.z]'>JMP</a>)",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)))] <b>[src]</b> beeps: \"DB error \[Code 0x00F1\]\"")
sleep(10)
visible_message("[icon2html(src, viewers(get_turf(src)))] <b>[src]</b> beeps: \"Attempting auto-repair\"")
sleep(15)
visible_message("[icon2html(src, viewers(get_turf(src)))] <b>[src]</b> beeps: \"User DB corrupted \[Code 0x00FA\]. Truncating data structure...\"")
sleep(30)
visible_message("[icon2html(src, viewers(get_turf(src)))] <b>[src]</b> 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)))] <b>[src]</b> beeps: \"DB not responding \[Code 0x0003\]...\"")
if(1)
visible_message("[icon2html(src, viewers(get_turf(src)))] <b>[src]</b> 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
+27 -281
View File
@@ -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 <b>matter bins</b> will increase material storage capacity."
. += SPAN_NOTICE(" - The current storage capacity is <b>[max_material_storage / 2000]</b> sheets")
. += "- Materials are drawn from the research material silo linked to the same R&D console."
. += "- Upgraded <b>manipulators</b> will improve material use efficiency and increase fabrication speed."
. += SPAN_NOTICE(" - The current speed increase is <b>[round((1 - (1 / production_speed)) * 100)]%</b>")
. += SPAN_NOTICE(" - The current cost reduction is <b>[round((1 - mat_efficiency) * 100)]%</b>")
///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()
File diff suppressed because it is too large Load Diff
+349 -1
View File
@@ -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