Files
Alexis 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

552 lines
19 KiB
Plaintext

/// Component printer, creates components for integrated circuits.
/obj/machinery/component_printer
name = "component printer"
desc = "Produces components for the creation of integrated circuits."
icon = 'icons/obj/machines/wiremod_fab.dmi'
icon_state = "fab-idle"
base_icon_state = "fab"
circuit = /obj/item/circuitboard/machine/component_printer
/// The internal material bus
var/datum/remote_materials/materials
density = TRUE
/// The techweb the printer will get researched designs from
var/datum/techweb/techweb
/// The current unlocked circuit component designs. Used by integrated circuits to print off circuit components remotely.
var/list/current_unlocked_designs = list()
/// The efficiency of this machine
var/efficiency_coeff = 1
/obj/machinery/component_printer/Initialize(mapload)
. = ..()
materials = new (src, mapload)
/obj/machinery/component_printer/post_machine_initialize()
. = ..()
if(!CONFIG_GET(flag/no_default_techweb_link) && !techweb)
CONNECT_TO_RND_SERVER_ROUNDSTART(techweb, src)
if(techweb)
on_connected_techweb()
/obj/machinery/component_printer/Destroy(force)
QDEL_NULL(materials)
return ..()
/obj/machinery/component_printer/proc/connect_techweb(datum/techweb/new_techweb)
if(techweb)
UnregisterSignal(techweb, list(COMSIG_TECHWEB_ADD_DESIGN, COMSIG_TECHWEB_REMOVE_DESIGN))
techweb = new_techweb
if(!isnull(techweb))
on_connected_techweb()
/obj/machinery/component_printer/proc/on_connected_techweb()
for (var/researched_design_id in techweb.researched_designs)
var/datum/design/design = SSresearch.techweb_design_by_id(researched_design_id)
if (!(design.build_type & COMPONENT_PRINTER) || !ispath(design.build_path, /obj/item/circuit_component))
continue
current_unlocked_designs[design.build_path] = design.id
RegisterSignal(techweb, COMSIG_TECHWEB_ADD_DESIGN, PROC_REF(on_research))
RegisterSignal(techweb, COMSIG_TECHWEB_REMOVE_DESIGN, PROC_REF(on_removed))
/obj/machinery/component_printer/multitool_act(mob/living/user, obj/item/multitool/tool)
if(!QDELETED(tool.buffer) && istype(tool.buffer, /datum/techweb))
connect_techweb(tool.buffer)
return TRUE
/obj/machinery/component_printer/proc/on_research(datum/source, datum/design/added_design, custom)
SIGNAL_HANDLER
if (!(added_design.build_type & COMPONENT_PRINTER) || !ispath(added_design.build_path, /obj/item/circuit_component))
return
current_unlocked_designs[added_design.build_path] = added_design.id
/obj/machinery/component_printer/proc/on_removed(datum/source, datum/design/added_design, custom)
SIGNAL_HANDLER
if (!(added_design.build_type & COMPONENT_PRINTER) || !ispath(added_design.build_path, /obj/item/circuit_component))
return
current_unlocked_designs -= added_design.build_path
/obj/machinery/component_printer/base_item_interaction(mob/living/user, obj/item/tool, list/modifiers)
//to allow quick recycling of circuits
if(istype(tool, /obj/item/circuit_component))
var/amount_inserted = materials.insert_item(tool, user_data = ID_DATA(user))
if(amount_inserted)
to_chat(user, span_notice("[tool] worth [amount_inserted / SHEET_MATERIAL_AMOUNT] sheets of material was consumed by [src]"))
else
to_chat(user, span_warning("[tool] was rejected by [src]"))
return amount_inserted > 0 ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_FAILURE
return ..()
/obj/machinery/component_printer/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ComponentPrinter", name)
ui.open()
/obj/machinery/component_printer/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials),
get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
)
/obj/machinery/component_printer/RefreshParts()
. = ..()
if(materials)
var/total_storage = 0
for(var/datum/stock_part/matter_bin/bin in component_parts)
total_storage += bin.tier * (37.5*SHEET_MATERIAL_AMOUNT)
materials.set_local_size(total_storage)
efficiency_coeff = 0
for(var/datum/stock_part/servo/servo in component_parts)
///we do -1 because normal manipulators rating of 1 gives us 1-1=0 i.e no decrement in cost
efficiency_coeff += servo.tier-1
///linear interpolation between full cost i.e 1 & 1/8th the cost i.e 0.125
///we do it in 6 steps because maximum rating of 2 manipulators is 8 but -1 gives us 6
efficiency_coeff = 1.0+((0.125-1.0)*(efficiency_coeff / 6))
update_static_data_for_all_viewers()
/**
* typepath - the type path of the component to be printed
* user_data - data in the form rendered by ID_DATA(user), for print logging, see the proc on SSid_access
*/
/obj/machinery/component_printer/proc/print_component(typepath, alist/user_data)
var/design_id = current_unlocked_designs[typepath]
var/datum/design/design = SSresearch.techweb_design_by_id(design_id)
if (!(design.build_type & COMPONENT_PRINTER))
return
if (!materials.can_use_resource(user_data = user_data))
return
if (!materials.mat_container.has_materials(design.materials, efficiency_coeff))
return
materials.use_materials(design.materials, efficiency_coeff, 1, "processed", "[design.name]", user_data)
return design.create_result(drop_location())
/obj/machinery/component_printer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if (.)
return
var/alist/user_data = ID_DATA(usr)
switch (action)
if ("print")
var/design_id = params["designId"]
if (!techweb.researched_designs[design_id])
return TRUE
var/datum/design/design = SSresearch.techweb_design_by_id(design_id)
if (!(design.build_type & COMPONENT_PRINTER))
return TRUE
if (!materials.can_use_resource(user_data = user_data))
return TRUE
if (!materials.mat_container.has_materials(design.materials, efficiency_coeff))
say("Not enough materials.")
return TRUE
balloon_alert_to_viewers("printed [design.name]")
materials.use_materials(design.materials, efficiency_coeff, 1, "processed", "[design.name]", user_data)
var/atom/printed_design = design.create_result(drop_location())
printed_design.pixel_x = printed_design.base_pixel_x + rand(-5, 5)
printed_design.pixel_y = printed_design.base_pixel_y + rand(-5, 5)
if ("remove_mat")
var/datum/material/material = locate(params["ref"])
var/amount = text2num(params["amount"])
// SAFETY: eject_sheets checks for valid mats
materials.eject_sheets(material_ref = material, eject_amount = amount, user_data = user_data)
return TRUE
/obj/machinery/component_printer/ui_data(mob/user)
var/list/data = list()
data["materials"] = materials.mat_container.ui_data()
return data
/obj/machinery/component_printer/ui_static_data(mob/user)
var/list/data = materials.mat_container.ui_static_data()
var/list/designs = list()
var/datum/asset/spritesheet_batched/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
var/size32x32 = "[spritesheet.name]32x32"
// for (var/datum/design/component/component_design_type as anything in subtypesof(/datum/design/component))
for (var/researched_design_id in techweb.researched_designs)
var/datum/design/design = SSresearch.techweb_design_by_id(researched_design_id)
if (!(design.build_type & COMPONENT_PRINTER))
continue
var/list/cost = list()
for(var/datum/material/mat, amount in design.materials)
cost[mat.name] = OPTIMAL_COST(amount * efficiency_coeff)
var/icon_size = spritesheet.icon_size_id(design.id)
designs[researched_design_id] = list(
"name" = design.name,
"desc" = design.desc,
"cost" = cost,
"id" = researched_design_id,
"categories" = design.category,
"icon" = "[icon_size == size32x32 ? "" : "[icon_size] "][design.id]"
)
data["designs"] = designs
return data
/obj/machinery/component_printer/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if (user.combat_mode)
return NONE
var/obj/item/integrated_circuit/circuit
if(istype(tool, /obj/item/integrated_circuit))
circuit = tool
else if (istype(tool, /obj/item/circuit_component/module))
var/obj/item/circuit_component/module/module = tool
circuit = module.internal_circuit
if (isnull(circuit))
return NONE
circuit.linked_component_printer = WEAKREF(src)
circuit.update_static_data_for_all_viewers()
balloon_alert(user, "successfully linked to the integrated circuit")
return ITEM_INTERACT_SUCCESS
/obj/machinery/component_printer/crowbar_act(mob/living/user, obj/item/tool)
return default_deconstruction_crowbar(user, tool)
/obj/machinery/component_printer/screwdriver_act(mob/living/user, obj/item/tool)
return default_deconstruction_screwdriver(user, tool)
/obj/machinery/component_printer/update_icon_state()
. = ..()
icon_state = panel_open ? "[base_icon_state]-o" : "[base_icon_state]-idle"
/obj/machinery/component_printer/proc/get_material_cost_data(list/materials, efficiency_coeff)
var/list/data = list()
for (var/datum/material/material_type as anything in materials)
data[initial(material_type.name)] = materials[material_type] * efficiency_coeff
return data
/obj/machinery/debug_component_printer
name = "debug component printer"
desc = "Produces components for the creation of integrated circuits."
icon = 'icons/obj/machines/wiremod_fab.dmi'
icon_state = "fab-idle"
/// All of the possible circuit designs stored by this debug printer
var/list/all_circuit_designs
density = TRUE
/obj/machinery/debug_component_printer/Initialize(mapload)
. = ..()
all_circuit_designs = list()
for(var/id in SSresearch.techweb_designs)
var/datum/design/design = SSresearch.techweb_design_by_id(id)
if((design.build_type & COMPONENT_PRINTER) && design.build_path)
all_circuit_designs[design.build_path] = list(
"id" = design.build_path,
"categories" = design.category,
"cost" = design.materials,
"desc" = design.desc,
"name" = design.name,
)
for(var/obj/item/circuit_component/component as anything in subtypesof(/obj/item/circuit_component))
var/categories = list("Inaccessible")
if(initial(component.circuit_flags) & CIRCUIT_FLAG_ADMIN)
categories = list("Admin")
if(!(component in all_circuit_designs))
all_circuit_designs[component] = list(
"id" = component.type,
"categories" = categories,
"cost" = list(),
"desc" = initial(component.desc),
"name" = initial(component.display_name),
)
/obj/machinery/debug_component_printer/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ComponentPrinter", name)
ui.open()
/obj/machinery/debug_component_printer/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials),
get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
)
/obj/machinery/debug_component_printer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if (.)
return
switch (action)
if ("print")
var/build_path = text2path(params["designId"])
if (!build_path)
return TRUE
var/list/design = all_circuit_designs[build_path]
if (!design)
return TRUE
balloon_alert_to_viewers("printed [design["name"]]")
var/atom/printed_design = new build_path(drop_location())
printed_design.pixel_x = printed_design.base_pixel_x + rand(-5, 5)
printed_design.pixel_y = printed_design.base_pixel_y + rand(-5, 5)
return TRUE
/obj/machinery/debug_component_printer/ui_static_data(mob/user)
var/list/data = list()
data["debug"] = TRUE
data["designs"] = all_circuit_designs
data["materials"] = list()
data["SHEET_MATERIAL_AMOUNT"] = SHEET_MATERIAL_AMOUNT
return data
/// Module duplicator, allows you to save and recreate module components.
/obj/machinery/module_duplicator
name = "module duplicator"
desc = "Allows you to duplicate module components so that you don't have to recreate them. Scan a module component over this machine to add it as an entry."
icon = 'icons/obj/machines/wiremod_fab.dmi'
icon_state = "module-fab-idle"
base_icon_state = "module-fab"
circuit = /obj/item/circuitboard/machine/module_duplicator
density = TRUE
///The internal material bus
var/datum/remote_materials/materials
///List of designs scanned and saved
var/list/scanned_designs = list()
///Constant material cost per component
var/cost_per_component = SHEET_MATERIAL_AMOUNT / 10
///Cost efficiency of this machine
var/efficiency_coeff = 1
/obj/machinery/module_duplicator/Initialize(mapload)
. = ..()
materials = new (src, mapload)
/obj/machinery/module_duplicator/Destroy(force)
QDEL_NULL(materials)
return ..()
/obj/machinery/module_duplicator/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ComponentPrinter", name)
ui.open()
/obj/machinery/module_duplicator/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials),
get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
)
/obj/machinery/module_duplicator/RefreshParts()
. = ..()
if(materials)
var/total_storage = 0
for(var/datum/stock_part/matter_bin/bin in component_parts)
total_storage += bin.tier * (37.5*SHEET_MATERIAL_AMOUNT)
materials.set_local_size(total_storage)
efficiency_coeff = 0
for(var/datum/stock_part/servo/servo in component_parts)
///we do -1 because normal manipulators rating of 1 gives us 1-1=0 i.e no decrement in cost
efficiency_coeff += servo.tier - 1
///linear interpolation between full cost i.e 1 & 1/8th the cost i.e 0.125
///we do it in 6 steps because maximum rating of 2 manipulators is 8 but -1 gives us 6
efficiency_coeff = 1.0 + ((0.125 - 1.0) * (efficiency_coeff / 6))
update_static_data_for_all_viewers()
/obj/machinery/module_duplicator/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if (.)
return
var/alist/user_data = ID_DATA(usr)
switch (action)
if ("print")
var/design_id = text2num(params["designId"])
if (design_id < 1 || design_id > length(scanned_designs))
return TRUE
var/list/design = scanned_designs[design_id]
if (!materials.can_use_resource(user_data = user_data))
return TRUE
if (!materials.mat_container.has_materials(design["materials"], efficiency_coeff))
say("Not enough materials.")
return TRUE
materials.use_materials(design["materials"], efficiency_coeff, 1, design["name"], design["materials"], user_data = user_data)
print_module(design)
balloon_alert_to_viewers("printed [design["name"]]")
if ("remove_mat")
var/datum/material/material = locate(params["ref"])
var/amount = text2num(params["amount"])
// SAFETY: eject_sheets checks for valid mats
materials.eject_sheets(material, amount, user_data = ID_DATA(usr))
return TRUE
/obj/machinery/module_duplicator/proc/print_module(list/design)
flick("module-fab-print", src)
addtimer(CALLBACK(src, PROC_REF(finish_module_print), design), 1.6 SECONDS)
/obj/machinery/module_duplicator/proc/finish_module_print(list/design)
var/atom/movable/created_atom
if(design["integrated_circuit"])
var/obj/item/integrated_circuit/circuit = new(drop_location())
var/list/errors = list()
circuit.load_circuit_data(design["dupe_data"], errors)
if(length(errors))
stack_trace("Error loading user saved circuit [errors.Join(", ")].")
created_atom = circuit
else
var/obj/item/circuit_component/module/module = new(drop_location())
module.load_data_from_list(design["dupe_data"])
created_atom = module
created_atom.pixel_x = created_atom.base_pixel_x + rand(-5, 5)
created_atom.pixel_y = created_atom.base_pixel_y + rand(-5, 5)
/obj/machinery/module_duplicator/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
var/list/data = list()
if(istype(tool, /obj/item/circuit_component/module))
var/obj/item/circuit_component/module/module = tool
if(HAS_TRAIT(module, TRAIT_CIRCUIT_UNDUPABLE))
balloon_alert(user, "integrated circuit cannot be saved!")
return ITEM_INTERACT_BLOCKING
data["dupe_data"] = list()
module.save_data_to_list(data["dupe_data"])
data["name"] = module.display_name
data["desc"] = "A module that has been loaded in by [user]."
data["materials"] = list(SSmaterials.get_material(/datum/material/glass) = module.circuit_size * cost_per_component)
else if(istype(tool, /obj/item/integrated_circuit))
var/obj/item/integrated_circuit/integrated_circuit = tool
if(HAS_TRAIT(integrated_circuit, TRAIT_CIRCUIT_UNDUPABLE))
balloon_alert(user, "integrated circuit cannot be saved!")
return ITEM_INTERACT_BLOCKING
data["dupe_data"] = integrated_circuit.convert_to_json()
data["name"] = integrated_circuit.display_name
data["desc"] = "An integrated circuit that has been loaded in by [user]."
var/datum/design/integrated_circuit/circuit_design = SSresearch.techweb_design_by_id("integrated_circuit")
var/materials = list(SSmaterials.get_material(/datum/material/glass) = integrated_circuit.current_size * cost_per_component)
for(var/material_type, amount in circuit_design.materials)
materials[material_type] += amount
data["materials"] = materials
data["integrated_circuit"] = TRUE
if(!length(data))
return NONE
if(!data["name"])
balloon_alert(user, "it needs a name!")
return ITEM_INTERACT_BLOCKING
for(var/list/component_data as anything in scanned_designs)
if(component_data["name"] == data["name"])
balloon_alert(user, "name already exists!")
return ITEM_INTERACT_BLOCKING
flick("module-fab-scan", src)
addtimer(CALLBACK(src, PROC_REF(finish_module_scan), user, data), 1.4 SECONDS)
return ITEM_INTERACT_SUCCESS
/obj/machinery/module_duplicator/proc/finish_module_scan(mob/user, data)
scanned_designs += list(data)
balloon_alert(user, "module has been saved.")
playsound(src, 'sound/machines/ping.ogg', 50)
update_static_data_for_all_viewers()
/obj/machinery/module_duplicator/ui_data(mob/user)
var/list/data = list()
data["materials"] = materials.mat_container.ui_data()
return data
/obj/machinery/module_duplicator/ui_static_data(mob/user)
var/list/data = materials.mat_container.ui_static_data()
var/list/designs = list()
var/index = 1
for (var/list/design as anything in scanned_designs)
var/list/cost = list()
var/list/materials = design["materials"]
for(var/datum/material/mat in materials)
cost[mat.name] = OPTIMAL_COST(materials[mat] * efficiency_coeff)
designs["[index]"] = list(
"name" = design["name"],
"desc" = design["desc"],
"cost" = cost,
"id" = "[index]",
"icon" = "integrated_circuit",
"categories" = list("/Saved Circuits"),
)
index++
data["designs"] = designs
return data
/obj/machinery/module_duplicator/crowbar_act(mob/living/user, obj/item/tool)
return default_deconstruction_crowbar(user, tool)
/obj/machinery/module_duplicator/screwdriver_act(mob/living/user, obj/item/tool)
return default_deconstruction_screwdriver(user, tool)
/obj/machinery/module_duplicator/update_icon_state()
. = ..()
icon_state = panel_open ? "[base_icon_state]-o" : "[base_icon_state]-idle"