diff --git a/code/__DEFINES/research.dm b/code/__DEFINES/research.dm index e1d863947e3..a0cad475ea5 100644 --- a/code/__DEFINES/research.dm +++ b/code/__DEFINES/research.dm @@ -1,12 +1,5 @@ #define RDSCREEN_NOBREAK "" -//! Defines for the Protolathe screens, see: [/obj/machinery/rnd/production/protolathe] -#define RESEARCH_FABRICATOR_SCREEN_MAIN 1 -#define RESEARCH_FABRICATOR_SCREEN_CHEMICALS 2 -#define RESEARCH_FABRICATOR_SCREEN_MATERIALS 3 -#define RESEARCH_FABRICATOR_SCREEN_SEARCH 4 -#define RESEARCH_FABRICATOR_SCREEN_CATEGORYVIEW 5 - /// For instances where we don't want a design showing up due to it being for debug/sanity purposes #define DESIGN_ID_IGNORE "IGNORE_THIS_DESIGN" diff --git a/code/datums/components/remote_materials.dm b/code/datums/components/remote_materials.dm index 530563537ad..77ac058c88d 100644 --- a/code/datums/components/remote_materials.dm +++ b/code/datums/components/remote_materials.dm @@ -163,3 +163,10 @@ handles linking back and forth. matlist[material_ref] = eject_amount silo_log(parent, "ejected", -count, "sheets", matlist) return count + +/// Returns `TRUE` if and only if the given material ref can be inserted/removed from this component +/datum/component/remote_materials/proc/can_hold_material(datum/material/material_ref) + if(!mat_container) + return FALSE + + return mat_container.can_hold_material(material_ref) diff --git a/code/modules/research/machinery/_production.dm b/code/modules/research/machinery/_production.dm index 7a2dfdb6a4c..953afb5b2e0 100644 --- a/code/modules/research/machinery/_production.dm +++ b/code/modules/research/machinery/_production.dm @@ -1,449 +1,343 @@ - /obj/machinery/rnd/production name = "technology fabricator" desc = "Makes researched and prototype items with materials and energy." layer = BELOW_OBJ_LAYER - /// Materials needed / coeff = actual. + + /// The efficiency coefficient. Material costs and print times are multiplied by this number; + /// better parts result in a higher efficiency (and lower value). var/efficiency_coeff = 1 - var/list/categories = list() + + /// The material storage used by this fabricator. var/datum/component/remote_materials/materials + + /// Which departments forego the lathe tax when using this lathe. var/allowed_department_flags = ALL + /// What's flick()'d on print. var/production_animation - var/allowed_buildtypes = NONE - var/list/datum/design/cached_designs - var/list/datum/design/matching_designs - /// Used for material distribution among other things. - var/department_tag = "Unidentified" - var/screen = RESEARCH_FABRICATOR_SCREEN_MAIN - var/selected_category + /// The types of designs this fabricator can print. + var/allowed_buildtypes = NONE + + /// All designs in the techweb that can be fabricated by this machine, since the last update. + var/list/datum/design/cached_designs + + /// The department this fabricator is assigned to. + var/department_tag = "Unassigned" /// What color is this machine's stripe? Leave null to not have a stripe. var/stripe_color = null + /// Does this charge the user's ID on fabrication? var/charges_tax = TRUE /obj/machinery/rnd/production/Initialize(mapload) . = ..() - create_reagents(0, OPENCONTAINER) - matching_designs = list() + cached_designs = list() - update_designs() materials = AddComponent(/datum/component/remote_materials, "lathe", mapload, mat_container_flags=BREAKDOWN_FLAGS_LATHE) AddComponent(/datum/component/payment, 0, SSeconomy.get_dep_account(payment_department), PAYMENT_CLINICAL, TRUE) + + create_reagents(0, OPENCONTAINER) + update_designs() RefreshParts() update_icon(UPDATE_OVERLAYS) /obj/machinery/rnd/production/Destroy() materials = null cached_designs = null - matching_designs = null return ..() +/// Updates the list of designs this fabricator can print. /obj/machinery/rnd/production/proc/update_designs() cached_designs.Cut() - for(var/i in stored_research.researched_designs) - var/datum/design/d = SSresearch.techweb_design_by_id(i) - if((isnull(allowed_department_flags) || (d.departmental_flags & allowed_department_flags)) && (d.build_type & allowed_buildtypes)) - cached_designs |= d + + for(var/design_id in stored_research.researched_designs) + var/datum/design/design = SSresearch.techweb_design_by_id(design_id) + + if((isnull(allowed_department_flags) || (design.departmental_flags & allowed_department_flags)) && (design.build_type & allowed_buildtypes)) + cached_designs |= design + + update_static_data(usr) + + say("Successfully synchronized with R&D server.") /obj/machinery/rnd/production/RefreshParts() . = ..() + calculate_efficiency() + update_static_data(usr) -/obj/machinery/rnd/production/ui_interact(mob/user) +/obj/machinery/rnd/production/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/spritesheet/sheetmaterials), + ) + +/obj/machinery/rnd/production/ui_interact(mob/user, datum/tgui/ui) user.set_machine(src) - var/datum/browser/popup = new(user, "rndconsole", name, 460, 550) - popup.set_content(generate_ui()) - popup.open() + ui = SStgui.try_update_ui(user, src, ui) + + if(!ui) + ui = new(user, src, "Fabricator") + ui.open() + +/obj/machinery/rnd/production/ui_static_data(mob/user) + var/list/data = list() + var/list/designs = list() + + for(var/datum/design/design in cached_designs) + var/cost = list() + + for(var/datum/material/material in design.materials) + var/coefficient = efficient_with(design.build_path) ? efficiency_coeff : 1 + cost[material.name] = design.materials[material] * coefficient + + designs[design.id] = list( + "name" = design.name, + "desc" = design.get_description(), + "cost" = cost, + "id" = design.id, + "categories" = design.category + ) + + data["designs"] = designs + data["fab_name"] = name + + return data + +/obj/machinery/rnd/production/ui_data(mob/user) + var/list/data = list() + + data["materials"] = materials.mat_container?.ui_data() + data["on_hold"] = materials.on_hold() + data["busy"] = busy + + return data + +/obj/machinery/rnd/production/ui_act(action, list/params) + . = ..() + + if(.) + return + + . = TRUE + + switch (action) + if("remove_mat") + var/datum/material/material = locate(params["ref"]) + + if(!materials.can_hold_material(material)) + // I don't know who you are or what you want, but whatever it is, + // we don't have it. + return + + eject_sheets(material, params["amount"]) + + if("sync_rnd") + update_designs() + + if("build") + user_try_print_id(params["ref"], params["amount"]) + +/// Updates the fabricator's efficiency coefficient based on the installed parts. /obj/machinery/rnd/production/proc/calculate_efficiency() efficiency_coeff = 1 - if(reagents) //If reagents/materials aren't initialized, don't bother, we'll be doing this again after reagents init anyways. + + if(reagents) reagents.maximum_volume = 0 - for(var/obj/item/reagent_containers/cup/G in component_parts) - reagents.maximum_volume += G.volume - G.reagents.trans_to(src, G.reagents.total_volume) + + for(var/obj/item/reagent_containers/cup/beaker in component_parts) + reagents.maximum_volume += beaker.volume + beaker.reagents.trans_to(src, beaker.reagents.total_volume) + if(materials) var/total_storage = 0 - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - total_storage += M.rating * 75000 - materials.set_local_size(total_storage) - var/total_rating = 1.2 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - total_rating = clamp(total_rating - (M.rating * 0.1), 0, 1) - if(total_rating == 0) - efficiency_coeff = INFINITY - else - efficiency_coeff = 1/total_rating -//we eject the materials upon deconstruction. + for(var/obj/item/stock_parts/matter_bin/bin in component_parts) + total_storage += bin.rating * 75000 + + materials.set_local_size(total_storage) + + var/total_rating = 1.2 + + for(var/obj/item/stock_parts/manipulator/manipulator in component_parts) + total_rating -= manipulator.rating * 0.1 + + efficiency_coeff = max(total_rating, 0) + /obj/machinery/rnd/production/on_deconstruction() for(var/obj/item/reagent_containers/cup/G in component_parts) reagents.trans_to(G, G.reagents.maximum_volume) + return ..() /obj/machinery/rnd/production/proc/do_print(path, amount, list/matlist, notify_admins) if(notify_admins) investigate_log("[key_name(usr)] built [amount] of [path] at [src]([type]).", INVESTIGATE_RESEARCH) message_admins("[ADMIN_LOOKUPFLW(usr)] has built [amount] of [path] at \a [src]([type]).") + for(var/i in 1 to amount) new path(get_turf(src)) + SSblackbox.record_feedback("nested tally", "item_printed", amount, list("[type]", "[path]")) -/** - * Returns how many times over the given material requirement for the given design is satisfied. - * - * Arguments: - * - [being_built][/datum/design]: The design being referenced. - * - material: The material being checked. - */ -/obj/machinery/rnd/production/proc/check_material_req(datum/design/being_built, material) - if(!materials.mat_container) // no connected silo - return 0 - - var/mat_amt = materials.mat_container.get_material_amount(material) - if(!mat_amt) - return 0 - - // these types don't have their .materials set in do_print, so don't allow - // them to be constructed efficiently - var/efficiency = efficient_with(being_built.build_path) ? efficiency_coeff : 1 - return round(mat_amt / max(1, being_built.materials[material] / efficiency)) - -/** - * Returns how many times over the given reagent requirement for the given design is satisfied. - * - * Arguments: - * - [being_built][/datum/design]: The design being referenced. - * - reagent: The reagent being checked. - */ -/obj/machinery/rnd/production/proc/check_reagent_req(datum/design/being_built, reagent) - if(!reagents) // no reagent storage - return 0 - - var/chem_amt = reagents.get_reagent_amount(reagent) - if(!chem_amt) - return 0 - - // these types don't have their .materials set in do_print, so don't allow - // them to be constructed efficiently - var/efficiency = efficient_with(being_built.build_path) ? efficiency_coeff : 1 - return round(chem_amt / max(1, being_built.reagents_list[reagent] / efficiency)) - /obj/machinery/rnd/production/proc/efficient_with(path) return !ispath(path, /obj/item/stack/sheet) && !ispath(path, /obj/item/stack/ore/bluespace_crystal) -/obj/machinery/rnd/production/proc/user_try_print_id(id, print_quantity) - if(!id) +/obj/machinery/rnd/production/proc/user_try_print_id(design_id, print_quantity) + if(!design_id) return FALSE + if(istext(print_quantity)) print_quantity = text2num(print_quantity) + if(isnull(print_quantity)) print_quantity = 1 - var/datum/design/D = stored_research.researched_designs[id] ? SSresearch.techweb_design_by_id(id) : null - if(!istype(D)) + + var/datum/design/design = stored_research.researched_designs[design_id] ? SSresearch.techweb_design_by_id(design_id) : null + + if(!istype(design)) return FALSE - if(!(isnull(allowed_department_flags) || (D.departmental_flags & allowed_department_flags))) - say("Warning: Printing failed: This fabricator does not have the necessary keys to decrypt design schematics. Please update the research data with the on-screen button and contact Nanotrasen Support!") + + if(busy) + say("Warning: fabricator is busy!") return FALSE - if(D.build_type && !(D.build_type & allowed_buildtypes)) - say("This machine does not have the necessary manipulation systems for this design. Please contact Nanotrasen Support!") + + if(!(isnull(allowed_department_flags) || (design.departmental_flags & allowed_department_flags))) + say("This fabricator does not have the necessary keys to decrypt this design.") return FALSE + + if(design.build_type && !(design.build_type & allowed_buildtypes)) + say("This fabricator does not have the necessary manipulation systems for this design.") + return FALSE + if(!materials.mat_container) say("No connection to material storage, please contact the quartermaster.") return FALSE + if(materials.on_hold()) say("Mineral access is on hold, please contact the quartermaster.") return FALSE + var/power = active_power_usage + print_quantity = clamp(print_quantity, 1, 50) - for(var/M in D.materials) - power += round(D.materials[M] * print_quantity / 35) + + for(var/material in design.materials) + power += round(design.materials[material] * print_quantity / 35) + power = min(active_power_usage, power) use_power(power) - var/coeff = efficient_with(D.build_path) ? efficiency_coeff : 1 + + var/coefficient = efficient_with(design.build_path) ? efficiency_coeff : 1 var/list/efficient_mats = list() - for(var/MAT in D.materials) - efficient_mats[MAT] = D.materials[MAT]/coeff + + for(var/material in design.materials) + efficient_mats[material] = design.materials[material] * coefficient + if(!materials.mat_container.has_materials(efficient_mats, print_quantity)) say("Not enough materials to complete prototype[print_quantity > 1? "s" : ""].") return FALSE - for(var/R in D.reagents_list) - if(!reagents.has_reagent(R, D.reagents_list[R]*print_quantity/coeff)) + + for(var/reagent in design.reagents_list) + if(!reagents.has_reagent(reagent, design.reagents_list[reagent] * print_quantity * coefficient)) say("Not enough reagents to complete prototype[print_quantity > 1? "s" : ""].") return FALSE - var/total_cost = LATHE_TAX + + // Charge the lathe tax at least once per ten items. + var/total_cost = LATHE_TAX * max(round(print_quantity / 10), 1) + if(!charges_tax) total_cost = 0 + if(isliving(usr)) var/mob/living/user = usr var/obj/item/card/id/card = user.get_idcard(TRUE) + if(!card && istype(user.pulling, /obj/item/card/id)) card = user.pulling + if(card && card.registered_account) var/datum/bank_account/our_acc = card.registered_account if(our_acc.account_job.departments_bitflags & allowed_department_flags) - total_cost = 0 //We are not charging crew for printing their own supplies and equipment. + total_cost = 0 // We are not charging crew for printing their own supplies and equipment. + if(attempt_charge(src, usr, total_cost) & COMPONENT_OBJ_CANCEL_CHARGE) + say("Insufficient funds to complete prototype. Please present a holochip or valid ID card.") return FALSE if(iscyborg(usr)) var/mob/living/silicon/robot/borg = usr + if(!borg.cell) - return + return FALSE + borg.cell.use(SILICON_LATHE_TAX) materials.mat_container.use_materials(efficient_mats, print_quantity) - materials.silo_log(src, "built", -print_quantity, "[D.name]", efficient_mats) - for(var/R in D.reagents_list) - reagents.remove_reagent(R, D.reagents_list[R]*print_quantity/coeff) + materials.silo_log(src, "built", -print_quantity, "[design.name]", efficient_mats) + + for(var/reagent in design.reagents_list) + reagents.remove_reagent(reagent, design.reagents_list[reagent] * print_quantity * coefficient) + busy = TRUE + if(production_animation) flick(production_animation, src) - var/timecoeff = D.lathe_time_factor / efficiency_coeff - addtimer(CALLBACK(src, .proc/reset_busy), (30 * timecoeff * print_quantity) ** 0.5) - addtimer(CALLBACK(src, .proc/do_print, D.build_path, print_quantity, efficient_mats, D.dangerous_construction), (32 * timecoeff * print_quantity) ** 0.8) + + var/time_coefficient = design.lathe_time_factor * efficiency_coeff + + addtimer(CALLBACK(src, .proc/reset_busy), (30 * time_coefficient * print_quantity) ** 0.5) + addtimer(CALLBACK(src, .proc/do_print, design.build_path, print_quantity, efficient_mats, design.dangerous_construction), (32 * time_coefficient * print_quantity) ** 0.8) + return TRUE -/obj/machinery/rnd/production/proc/search(string) - matching_designs.Cut() - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(D.build_type & allowed_buildtypes) || !(isnull(allowed_department_flags) ||(D.departmental_flags & allowed_department_flags))) - continue - if(findtext(D.name,string)) - matching_designs.Add(D) - -/obj/machinery/rnd/production/proc/generate_ui() - var/list/ui = list() - ui += ui_header() - switch(screen) - if(RESEARCH_FABRICATOR_SCREEN_MATERIALS) - ui += ui_screen_materials() - if(RESEARCH_FABRICATOR_SCREEN_CHEMICALS) - ui += ui_screen_chemicals() - if(RESEARCH_FABRICATOR_SCREEN_SEARCH) - ui += ui_screen_search() - if(RESEARCH_FABRICATOR_SCREEN_CATEGORYVIEW) - ui += ui_screen_category_view() - else - ui += ui_screen_main() - for(var/i in 1 to length(ui)) - if(!findtextEx(ui[i], RDSCREEN_NOBREAK)) - ui[i] += "
" - ui[i] = replacetextEx(ui[i], RDSCREEN_NOBREAK, "") - return ui.Join("") - -/obj/machinery/rnd/production/proc/ui_header() - var/list/l = list() - l += "
[stored_research.organization] [department_tag] Department Lathe" - l += "Security protocols: [(obj_flags & EMAGGED)? "Disabled" : "Enabled"]" - if (materials.mat_container) - l += "Material Amount: [materials.format_amount()]" - else - l += "No material storage connected, please contact the quartermaster." - l += "Chemical volume: [reagents.total_volume] / [reagents.maximum_volume]" - l += "Synchronize Research" - l += "Main Screen
[RDSCREEN_NOBREAK]" - return l - -/obj/machinery/rnd/production/proc/ui_screen_materials() - if (!materials.mat_container) - screen = RESEARCH_FABRICATOR_SCREEN_MAIN - return ui_screen_main() - var/list/l = list() - l += "

Material Storage:

" - for(var/mat_id in materials.mat_container.materials) - var/datum/material/M = mat_id - var/amount = materials.mat_container.materials[mat_id] - var/ref = REF(M) - l += "* [amount] of [M.name]: " - if(amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" - if(amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" - if(amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]" - l += "" - l += "
[RDSCREEN_NOBREAK]" - return l - -/obj/machinery/rnd/production/proc/ui_screen_chemicals() - var/list/l = list() - l += "
Disposal All Chemicals in Storage" - l += "

Chemical Storage:

" - for(var/datum/reagent/R in reagents.reagent_list) - l += "[R.name]: [R.volume]" - l += "Purge" - l += "
" - return l - -/obj/machinery/rnd/production/proc/ui_screen_search() - var/list/l = list() - var/coeff = efficiency_coeff - l += "

Search Results:

" - l += "
\ - \ - \ - \ - \ -

" - for(var/datum/design/D in matching_designs) - l += design_menu_entry(D, coeff) - l += "" - return l - -/obj/machinery/rnd/production/proc/design_menu_entry(datum/design/D, coeff) - if(!istype(D)) - return - if(!efficient_with(D.build_path)) - coeff = 1 - else if(!coeff) - coeff = efficiency_coeff - - var/list/entry_text = list() - var/temp_material - var/max_production = 50 - var/list/cached_mats = D.materials - for(var/material in cached_mats) - var/enough_mats = check_material_req(D, material) - max_production = min(max_production, enough_mats) - - temp_material += " | " - if (enough_mats < 1) - temp_material += "[cached_mats[material]/coeff] [CallMaterialName(material)]" - else - temp_material += " [cached_mats[material]/coeff] [CallMaterialName(material)]" - - var/list/cached_reagents = D.reagents_list - for(var/reagent in cached_reagents) - var/enough_chems = check_reagent_req(D, reagent) - max_production = min(max_production, enough_chems) - - temp_material += " | " - if (enough_chems < 1) - temp_material += "[cached_reagents[reagent]/coeff] [CallMaterialName(reagent)]" - else - temp_material += " [cached_reagents[reagent]/coeff] [CallMaterialName(reagent)]" - - if (max_production >= 1) - entry_text += "[D.name][RDSCREEN_NOBREAK]" - if(max_production >= 5) - entry_text += "x5[RDSCREEN_NOBREAK]" - if(max_production >= 10) - entry_text += "x10[RDSCREEN_NOBREAK]" - entry_text += "[temp_material][RDSCREEN_NOBREAK]" - else - entry_text += "[D.name][temp_material][RDSCREEN_NOBREAK]" - entry_text += "" - return entry_text - -/obj/machinery/rnd/production/Topic(raw, ls) - if(..()) - return - usr.set_machine(src) - if(ls["switch_screen"]) - screen = text2num(ls["switch_screen"]) - if(ls["build"]) //Causes the Protolathe to build something. - if(busy) - say("Warning: Fabricators busy!") - else - user_try_print_id(ls["build"], ls["amount"]) - if(ls["search"]) //Search for designs with name matching pattern - search(ls["to_search"]) - screen = RESEARCH_FABRICATOR_SCREEN_SEARCH - if(ls["sync_research"]) - update_designs() - say("Synchronizing research with host technology database.") - if(ls["category"]) - selected_category = ls["category"] - if(ls["dispose"]) //Causes the protolathe to dispose of a single reagent (all of it) - var/reagent_path = text2path(ls["dispose"]) - if(!ispath(reagent_path, /datum/reagent)) - stack_trace("Invalid reagent typepath - [ls["dispose"]] - returned in reagent disposal topic call") - else - reagents.del_reagent(reagent_path) - if(ls["disposeall"]) //Causes the protolathe to dispose of all it's reagents. - reagents.clear_reagents() - if(ls["ejectsheet"]) //Causes the protolathe to eject a sheet of material - var/datum/material/M = locate(ls["ejectsheet"]) - eject_sheets(M, ls["eject_amt"]) - updateUsrDialog() - /obj/machinery/rnd/production/proc/eject_sheets(eject_sheet, eject_amt) var/datum/component/material_container/mat_container = materials.mat_container - if (!mat_container) + + if(!mat_container) say("No access to material storage, please contact the quartermaster.") return 0 - if (materials.on_hold()) + + if(materials.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] = MINERAL_MATERIAL_AMOUNT + materials.silo_log(src, "ejected", -count, "sheets", matlist) + return count -/obj/machinery/rnd/production/proc/ui_screen_main() - var/list/l = list() - l += "
\ - \ - \ - \ - \ - \ -

" - - l += list_categories(categories, RESEARCH_FABRICATOR_SCREEN_CATEGORYVIEW) - - return l - -/obj/machinery/rnd/production/proc/ui_screen_category_view() - if(!selected_category) - return ui_screen_main() - var/list/l = list() - l += "

Browsing [selected_category]:

" - var/coeff = efficiency_coeff - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(selected_category in D.category)|| !(D.build_type & allowed_buildtypes)) - continue - if(!(isnull(allowed_department_flags) || (D.departmental_flags & allowed_department_flags))) - continue - l += design_menu_entry(D, coeff) - l += "
" - return l - -/obj/machinery/rnd/production/proc/list_categories(list/categories, menu_num) - if(!categories) - return - - var/line_length = 1 - var/list/l = "" - - for(var/C in categories) - if(line_length > 2) - l += "" - line_length = 1 - - l += "" - line_length++ - - l += "
[C]
" - return l - // Stuff for the stripe on the department machines /obj/machinery/rnd/production/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/screwdriver) . = ..() + update_icon(UPDATE_OVERLAYS) /obj/machinery/rnd/production/update_overlays() . = ..() + if(!stripe_color) return + var/mutable_appearance/stripe = mutable_appearance('icons/obj/machines/research.dmi', "protolate_stripe") + if(!panel_open) stripe.icon_state = "protolathe_stripe" else stripe.icon_state = "protolathe_stripe_t" + stripe.color = stripe_color + . += stripe + +/obj/machinery/rnd/production/examine(mob/user) + . = ..() + + if(in_range(user, src) || isobserver(user)) + . += span_notice("The status display reads: Storing up to [materials.local_size] material units.
Material consumption at [efficiency_coeff * 100]%.
Build time reduced by [100 - efficiency_coeff * 100]%.") diff --git a/code/modules/research/machinery/circuit_imprinter.dm b/code/modules/research/machinery/circuit_imprinter.dm index 4c4646989f8..0691f035528 100644 --- a/code/modules/research/machinery/circuit_imprinter.dm +++ b/code/modules/research/machinery/circuit_imprinter.dm @@ -3,30 +3,18 @@ desc = "Manufactures circuit boards for the construction of machines." icon_state = "circuit_imprinter" circuit = /obj/item/circuitboard/machine/circuit_imprinter - categories = list( - RND_CATEGORY_AI_MODULES, - RND_CATEGORY_COMPUTER_BOARDS, - RND_CATEGORY_TELEPORTATION_MACHINERY, - RND_CATEGORY_MEDICAL_MACHINERY, - RND_CATEGORY_ENGINEERING_MACHINERY, - RND_CATEGORY_EXOSUIT_MODULES, - RND_CATEGORY_HYDROPONICS_MACHINERY, - RND_CATEGORY_SUBSPACE_TELECOMMS, - RND_CATEGORY_RESEARCH_MACHINERY, - RND_CATEGORY_MISC_MACHINERY, - RND_CATEGORY_COMPUTER_PARTS, - RND_CATEGORY_CIRCUITRY - ) production_animation = "circuit_imprinter_ani" allowed_buildtypes = IMPRINTER /obj/machinery/rnd/production/circuit_imprinter/calculate_efficiency() . = ..() - var/total_rating = 0 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - total_rating += M.rating * 2 //There is only one. - total_rating = max(1, total_rating) - efficiency_coeff = total_rating + + var/rating = 0 + + for(var/obj/item/stock_parts/manipulator/manipulator in component_parts) + rating += manipulator.rating // There is only one. + + efficiency_coeff = 0.5 ** max(rating - 1, 0) // One sheet, half sheet, quarter sheet, eighth sheet. /obj/machinery/rnd/production/circuit_imprinter/offstation name = "ancient circuit imprinter" diff --git a/code/modules/research/machinery/protolathe.dm b/code/modules/research/machinery/protolathe.dm index 5a6cb1fae9b..b48f64ca9d7 100644 --- a/code/modules/research/machinery/protolathe.dm +++ b/code/modules/research/machinery/protolathe.dm @@ -3,21 +3,6 @@ desc = "Converts raw materials into useful objects." icon_state = "protolathe" circuit = /obj/item/circuitboard/machine/protolathe - categories = list( - RND_CATEGORY_POWER_DESIGNS, - RND_CATEGORY_MEDICAL_DESIGNS, - RND_CATEGORY_BLUESPACE_DESIGNS, - RND_CATEGORY_STOCK_PARTS, - RND_CATEGORY_EQUIPMENT, - RND_CATEGORY_TOOL_DESIGNS, - RND_CATEGORY_MINING_DESIGNS, - RND_CATEGORY_ELECTRONICS, - RND_CATEGORY_WEAPONS, - RND_CATEGORY_AMMO, - RND_CATEGORY_FIRING_PINS, - RND_CATEGORY_COMPUTER_PARTS, - RND_CATEGORY_CIRCUITRY - ) production_animation = "protolathe_n" allowed_buildtypes = PROTOLATHE diff --git a/code/modules/research/machinery/techfab.dm b/code/modules/research/machinery/techfab.dm index ffcebef8baa..38df3fc038d 100644 --- a/code/modules/research/machinery/techfab.dm +++ b/code/modules/research/machinery/techfab.dm @@ -3,32 +3,6 @@ desc = "Produces researched prototypes with raw materials and energy." icon_state = "protolathe" circuit = /obj/item/circuitboard/machine/techfab - categories = list( - RND_CATEGORY_POWER_DESIGNS, - RND_CATEGORY_MEDICAL_DESIGNS, - RND_CATEGORY_BLUESPACE_DESIGNS, - RND_CATEGORY_STOCK_PARTS, - RND_CATEGORY_EQUIPMENT, - RND_CATEGORY_TOOL_DESIGNS, - RND_CATEGORY_MINING_DESIGNS, - RND_CATEGORY_ELECTRONICS, - RND_CATEGORY_WEAPONS, - RND_CATEGORY_AMMO, - RND_CATEGORY_FIRING_PINS, - RND_CATEGORY_COMPUTER_PARTS, - RND_CATEGORY_AI_MODULES, - RND_CATEGORY_COMPUTER_BOARDS, - RND_CATEGORY_TELEPORTATION_MACHINERY, - RND_CATEGORY_MEDICAL_MACHINERY, - RND_CATEGORY_ENGINEERING_MACHINERY, - RND_CATEGORY_EXOSUIT_MODULES, - RND_CATEGORY_HYDROPONICS_MACHINERY, - RND_CATEGORY_SUBSPACE_TELECOMMS, - RND_CATEGORY_RESEARCH_MACHINERY, - RND_CATEGORY_MISC_MACHINERY, - RND_CATEGORY_COMPUTER_PARTS, - RND_CATEGORY_CIRCUITRY - ) console_link = FALSE production_animation = "protolathe_n" allowed_buildtypes = PROTOLATHE | IMPRINTER diff --git a/tgui/packages/tgui/interfaces/Fabricator.tsx b/tgui/packages/tgui/interfaces/Fabricator.tsx new file mode 100644 index 00000000000..d3fa5daf77f --- /dev/null +++ b/tgui/packages/tgui/interfaces/Fabricator.tsx @@ -0,0 +1,408 @@ +import { useBackend, useSharedState } from '../backend'; +import { Stack, Section, Button, Input, Icon, Tabs, Dimmer } from '../components'; +import { Window } from '../layouts'; +import { Material, MaterialAmount, MaterialFormatting, Materials, MATERIAL_KEYS } from './common/Materials'; +import { Fragment } from 'inferno'; +import { sortBy } from 'common/collections'; + +type MaterialMap = Partial>; + +/** + * A single design that the fabricator can print. + */ +type Design = { + /** + * The name of the design. + */ + name: string; + + /** + * A human-readable description of the design. + */ + desc: string; + + /** + * The individual material cost to print the design, adjusted for the + * fabricator's part efficiency. + */ + cost: MaterialMap; + + /** + * A reference to the design's design datum. + */ + id: string; + + /** + * The categories the design should be present in. + */ + categories?: string[]; +}; + +type FabricatorData = { + /** + * The materials available to the fabricator, via ore silo or local storage. + */ + materials: Material[]; + + /** + * The name of the fabricator, as displayed on the title bar. + */ + fab_name: string; + + /** + * Whether mineral access is disabled from the ore silo (contact the + * quartermaster). + */ + on_hold: boolean; + + /** + * The set of designs that this fabricator can print, ordered by their ID. + */ + designs: Record; + + /** + * Whether the fabricator is currently printing an item. + */ + busy: boolean; +}; + +/** + * Categories present in this object are not rendered to the final fabricator + * UI. + */ +const BLACKLISTED_CATEGORIES: Record = { + 'initial': true, + 'core': true, + 'hacked': true, +}; + +/** + * A dummy category that, when selected, renders ALL recipes to the UI. + */ +const ALL_CATEGORY = '__ALL'; + +export const Fabricator = (props, context) => { + const { act, data } = useBackend(context); + const { materials, fab_name, on_hold, designs, busy } = data; + + const [selectedCategory, setSelectedCategory] = useSharedState( + context, + 'selected_category', + ALL_CATEGORY + ); + const [searchText, setSearchText] = useSharedState( + context, + 'search_text', + '' + ); + const [displayMatCost, setDisplayMatCost] = useSharedState( + context, + 'display_material_cost', + true + ); + + // Sort the designs by name. + const sortedDesigns = sortBy((design: Design) => design.name)( + Object.values(designs) + ); + + // Find the number of items in each unique category, and the sum total of all + // printable items. + const categoryCounts: Record = {}; + let totalRecipes = 0; + + for (const design of sortedDesigns) { + totalRecipes += 1; + + for (const category of design.categories ?? []) { + categoryCounts[category] = (categoryCounts[category] ?? 0) + 1; + } + } + + // Strip blacklisted categories from the output. + for (const blacklistedCategory in BLACKLISTED_CATEGORIES) { + delete categoryCounts[blacklistedCategory]; + } + + // Reduce the material count array to a map of actually available materials. + const availableMaterials: MaterialMap = {}; + + for (const material of data.materials) { + availableMaterials[material.name] = material.amount; + } + + // Render all categories with items, sorted by name. + const namedCategories = Object.keys(categoryCounts).sort(); + + return ( + + + + + + +
+ + { + setSelectedCategory(ALL_CATEGORY); + setSearchText(''); + }} + color="transparent"> + All Designs ({totalRecipes}) + + + {namedCategories.map((categoryName) => ( + { + setSelectedCategory(categoryName); + setSearchText(''); + }} + fluid + color="transparent"> + {categoryName} ({categoryCounts[categoryName]}) + + ))} + +
+
+ +
+ setDisplayMatCost(!displayMatCost)} + checked={displayMatCost}> + Display Material Costs + +
+ {!!on_hold && ( + + Mineral access is on hold, please contact the quartermaster. + + )} +
+
+
+ +
+ a.name)( + data.materials ?? [] + )} + onEject={(ref, amount) => act('remove_mat', { ref, amount })} + /> +
+
+
+
+
+ ); +}; + +type SearchBarProps = { + searchText: string; + setSearchText: (text: string) => void; +}; + +const SearchBar = (props: SearchBarProps, context) => { + const { searchText, setSearchText } = props; + + return ( + + + + + + setSearchText(v.toLowerCase())} + value={searchText} + /> + + + ); +}; + +type MaterialCostProps = { + design: Design; + amount: number; + available: MaterialMap; +}; + +const MaterialCost = (props: MaterialCostProps, context) => { + const { design, amount, available } = props; + + return ( + + {Object.entries(design.cost).map(([material, cost]) => ( + + available[material] + ? 'bad' + : cost * amount * 2 > available[material] + ? 'average' + : 'normal' + } + /> + + ))} + + ); +}; + +type PrintButtonProps = { + design: Design; + quantity: number; + available: MaterialMap; +}; + +const PrintButton = (props: PrintButtonProps, context) => { + const { act, data } = useBackend(context); + const { design, quantity, available } = props; + + const [displayMatCost] = useSharedState( + context, + 'display_material_cost', + true + ); + const canPrint = !Object.entries(design.cost).some( + ([material, amount]) => + !available[material] || amount * quantity > (available[material] ?? 0) + ); + + return ( + + ); +}; + +const Recipe = (props: { design: Design; available: MaterialMap }, context) => { + const { act, data } = useBackend(context); + const { design, available } = props; + + const [displayMatCost] = useSharedState( + context, + 'display_material_cost', + true + ); + const canPrint = !Object.entries(design.cost).some( + ([material, amount]) => + !available[material] || amount > (available[material] ?? 0) + ); + + return ( +
+ + + + + + + + + + + +
+ ); +}; diff --git a/tgui/packages/tgui/styles/interfaces/Fabricator.scss b/tgui/packages/tgui/styles/interfaces/Fabricator.scss new file mode 100644 index 00000000000..651d406b16e --- /dev/null +++ b/tgui/packages/tgui/styles/interfaces/Fabricator.scss @@ -0,0 +1,22 @@ +.Fabricator__Recipe { + padding: 0.25em 0; + border-bottom: 1px solid #000; +} + +.Fabricator__Button { + white-space: normal; +} + +Fabricator__Icon { + vertical-align: middle; +} + +.Fabricator__Button--disabled, +.Fabricator__PrintAmount--disabled { + opacity: 0.5; + background-color: transparent; +} + +.Fabricator__PrintAmount--disabled { + text-decoration: line-through; +} diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index 80aa22bd224..281cff4f6dd 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -69,6 +69,7 @@ @include meta.load-css('./interfaces/Techweb.scss'); @include meta.load-css('./interfaces/RequestManager.scss'); @include meta.load-css('./interfaces/UtilityModulesPane.scss'); +@include meta.load-css('./interfaces/Fabricator.scss'); // Layouts @include meta.load-css('./layouts/Layout.scss');