From 13d2dceb319c56dce8d0ddab31341b1b68c27145 Mon Sep 17 00:00:00 2001 From: Zephyr <12817816+ZephyrTFA@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:35:02 -0500 Subject: [PATCH] Minor Autolathe Refactor | Material Dupe-B-Gone (#80839) ## About The Pull Request Refactors underlying autolathe code, mostly about how it prints items Items are now printed sequentially Items now have their custom materials set to the the materials actually used to create them Items are now printed based on their design construction time instead of a default 3.2 seconds per item always Closes https://github.com/tgstation/tgstation/issues/80755 ## Changelog :cl: fix: Autolathes no longer allow you to duplicate materials at higher levels of stock parts qol: Autolathes now show name instead of typepath when selecting a custom material qol: Autolathe now print out items one by one instead of waiting for all of them to print at once /:cl: --- .../components/material/material_container.dm | 4 +- code/game/machinery/autolathe.dm | 246 ++++++++++-------- code/modules/research/designs.dm | 2 +- .../modules/research/machinery/_production.dm | 118 ++++++--- 4 files changed, 231 insertions(+), 139 deletions(-) diff --git a/code/datums/components/material/material_container.dm b/code/datums/components/material/material_container.dm index e84e3a46c82..f2b4ce9e923 100644 --- a/code/datums/components/material/material_container.dm +++ b/code/datums/components/material/material_container.dm @@ -581,7 +581,9 @@ return FALSE for(var/x in mats) //Loop through all required materials - if(!has_enough_of_material(x, OPTIMAL_COST(mats[x] * coefficient) * multiplier))//Not a category, so just check the normal way + var/wanted = OPTIMAL_COST(mats[x] * coefficient) * multiplier + if(!has_enough_of_material(x, wanted))//Not a category, so just check the normal way + testing("didnt have: [x] wanted: [wanted]") return FALSE return TRUE diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index 0a01b325149..61e60258a05 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -145,88 +145,160 @@ get_asset_datum(/datum/asset/spritesheet/research_designs), ) -/obj/machinery/autolathe/ui_act(action, list/params) +/obj/machinery/autolathe/ui_act(action, list/params, datum/tgui/ui) . = ..() if(.) return - if(action == "make") - if(disabled) - say("The autolathe wires are disabled.") - return - if(busy) - say("The autolathe is busy. Please wait for completion of previous operation.") + if(action != "make") + stack_trace("unknown autolathe ui_act: [action]") + return + + if(disabled) + say("Unable to print, voltage mismatch in internal wiring.") + return + + if(busy) + balloon_alert(ui.user, "busy!") + return + + var/turf/target_location = get_step(src, drop_direction) + if(isclosedturf(target_location)) + say("Output path is obstructed by a large object.") + return + + var/design_id = params["id"] + + var/valid_design = stored_research.researched_designs[design_id] + valid_design ||= stored_research.hacked_designs[design_id] + valid_design ||= imported_designs[design_id] + if(!valid_design) + return + + var/datum/design/design = SSresearch.techweb_design_by_id(design_id) + if(isnull(design)) + stack_trace("got passed an invalid design id: [design_id] and somehow made it past all checks") + return + + if(!(design.build_type & AUTOLATHE)) + return + + var/build_count = text2num(params["multiplier"]) + if(!build_count) + return + build_count = clamp(build_count, 1, 50) + + var/is_stack_recipe = ispath(design.build_path, /obj/item/stack) + var/list/materials_per_item = list() + var/material_cost_coefficient = is_stack_recipe ? 1 : creation_efficiency + for(var/datum/material/material as anything in design.materials) + var/amount_needed = design.materials[material] * material_cost_coefficient + if(istext(material)) // category + var/list/choices = list() + for(var/datum/material/valid_candidate as anything in SSmaterials.materials_by_category[material]) + if(materials.get_material_amount(valid_candidate) < amount_needed) + continue + choices[valid_candidate.name] = valid_candidate + if(!length(choices)) + say("No valid materials with applicable amounts detected for design.") + return + var/chosen = tgui_input_list( + ui.user, + "Select the material to use", + "Material Selection", + sort_list(choices), + ) + if(isnull(chosen)) + return // user cancelled + material = choices[chosen] + + if(isnull(material)) + stack_trace("got passed an invalid material id: [material]") return + materials_per_item[material] = amount_needed - if(isclosedturf(get_step(src, drop_direction))) - say("Output is obstructed.") - return + // don't pass the coefficient of creation here, we already modify it for use with setting the custom_materials + if(!materials.has_materials(materials_per_item, multiplier = build_count)) + say("Not enough materials to begin production.") + return - var/design_id = params["id"] - if(!istext(design_id)) - return - if(!stored_research.researched_designs.Find(design_id) && !stored_research.hacked_designs.Find(design_id) && !imported_designs.Find(design_id)) - return - var/datum/design/design = SSresearch.techweb_design_by_id(design_id) - if(!(design.build_type & AUTOLATHE) || design.id != design_id) - return + var/power_use_amount = 0 + for(var/material_used in materials_per_item) + power_use_amount += materials_per_item[material_used] * 0.2 * build_count + if(!directly_use_power(power_use_amount)) + say("Not enough power in local network to begin production.") + return - being_built = design - var/is_stack = ispath(being_built.build_path, /obj/item/stack) - var/coeff = (is_stack ? 1 : creation_efficiency) // Stacks are unaffected by production coefficient + var/total_time = (design.construction_time * design.lathe_time_factor * build_count) ** 0.8 + var/time_per_item = total_time / build_count + start_making(design, build_count, ui.user, time_per_item, materials_per_item) + return TRUE - var/multiplier = round(text2num(params["multiplier"])) - if(!multiplier || !IS_FINITE(multiplier)) - return - multiplier = clamp(multiplier, 1, 50) +/// Begins the act of making the given design the given number of items +/// Does not check or use materials/power/etc +/obj/machinery/autolathe/proc/start_making(datum/design/design, build_count, mob/user, build_time_per_item, list/materials_per_item) + PRIVATE_PROC(TRUE) - //check for materials - var/list/materials_used = list() - var/list/custom_materials = list() // These will apply their material effect, should usually only be one. - for(var/mat in being_built.materials) - var/datum/material/used_material = mat + busy = TRUE + icon_state = "autolathe_n" + update_static_data_for_all_viewers() - var/amount_needed = being_built.materials[mat] - if(istext(used_material)) // This means its a category - var/list/list_to_show = list() - //list all materials in said category - for(var/i in SSmaterials.materials_by_category[used_material]) - if(materials.materials[i] > 0) - list_to_show += i - //ask user to pick specific material from list - used_material = tgui_input_list( - usr, - "Choose [used_material]", - "Custom Material", - sort_list(list_to_show, GLOBAL_PROC_REF(cmp_typepaths_asc)) - ) - if(isnull(used_material)) - return - //the item composition will be made of these materials - custom_materials[used_material] += amount_needed - materials_used[used_material] = amount_needed + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, materials_per_item, build_time_per_item, build_count), build_time_per_item) - if(!materials.has_materials(materials_used, coeff, multiplier)) - say("Not enough materials for this operation!.") - return +/// Callback for start_making, actually makes the item +/// Called using timers started by start_making +/obj/machinery/autolathe/proc/do_make_item(datum/design/design, list/materials_per_item, time_per_item, items_remaining) + PRIVATE_PROC(TRUE) - //use power - var/total_amount = 0 - for(var/material in being_built.materials) - total_amount += being_built.materials[material] - use_power(max(active_power_usage, (total_amount) * multiplier / 5)) + if(!items_remaining) // how + finalize_build() + return - //use materials - materials.use_materials(materials_used, coeff, multiplier) - to_chat(usr, span_notice("You print [multiplier] item(s) from the [src]")) - update_static_data_for_all_viewers() + if(!is_operational) + say("Unable to continue production, power failure.") + finalize_build() + return - //print item - icon_state = "autolathe_n" - var/time_per_item = is_stack ? 32 : ((32 * coeff * multiplier) ** 0.8) / multiplier - make_items(custom_materials, multiplier, is_stack, usr, time_per_item) + var/is_stack = ispath(design.build_path, /obj/item/stack) + if(!materials.has_materials(materials_per_item, multiplier = is_stack ? items_remaining : 1)) + say("Unable to continue production, missing materials.") + return + materials.use_materials(materials_per_item, multiplier = is_stack ? items_remaining : 1) - return TRUE + var/turf/target = get_step(src, drop_direction) + if(isclosedturf(target)) + target = get_turf(src) + + var/atom/movable/created + if(is_stack) + created = new design.build_path(target, items_remaining) + else + created = new design.build_path(target) + created.set_custom_materials(materials_per_item.Copy()) + + created.pixel_x = created.base_pixel_x + rand(-6, 6) + created.pixel_y = created.base_pixel_y + rand(-6, 6) + for(var/atom/movable/content in created) + content.set_custom_materials(list()) // no + created.forceMove(target) + + if(is_stack) + items_remaining = 0 + else + items_remaining -= 1 + + if(!items_remaining) + finalize_build() + return + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, materials_per_item, time_per_item, items_remaining), time_per_item) + +/// Resets the icon state and busy flag +/// Called at the end of do_make_item's timer loop +/obj/machinery/autolathe/proc/finalize_build() + PRIVATE_PROC(TRUE) + icon_state = initial(icon_state) + busy = FALSE + update_static_data_for_all_viewers() /obj/machinery/autolathe/crowbar_act(mob/living/user, obj/item/tool) if(default_deconstruction_crowbar(tool)) @@ -263,7 +335,7 @@ if(!blueprint) continue if(blueprint.build_type & AUTOLATHE) - imported_designs += blueprint.id + imported_designs[blueprint.id] = TRUE else LAZYADD(not_imported, blueprint.name) if(not_imported) @@ -297,41 +369,6 @@ drop_direction = direction balloon_alert(usr, "dropping [dir2text(drop_direction)]") -/obj/machinery/autolathe/proc/make_items(list/picked_materials, multiplier, is_stack, mob/user, time_per_item) - var/atom/our_loc = drop_location() - var/atom/drop_loc = get_step(src, drop_direction) - - busy = TRUE - SStgui.update_uis(src) //so ui immediatly knows its busy - while(multiplier > 0) - if(!busy) - break - stoplag(time_per_item) - var/obj/item/new_item - if(is_stack) - new_item = new being_built.build_path(our_loc, multiplier) - else - new_item = new being_built.build_path(our_loc) - - //custom materials for toolboxes - if(length(picked_materials)) - new_item.set_custom_materials(picked_materials) //Ensure we get the non multiplied amount - for(var/datum/material/mat in picked_materials) - if(!istype(mat, /datum/material/glass) && !istype(mat, /datum/material/iron)) - user.client.give_award(/datum/award/achievement/misc/getting_an_upgrade, user) - - //no need to call if ontop of us - if(drop_direction) - new_item.Move(drop_loc) - //multiplier already applied in stack initialization. work done - if(is_stack) - break - - multiplier-- - - icon_state = "autolathe" - busy = FALSE - /obj/machinery/autolathe/RefreshParts() . = ..() var/mat_capacity = 0 @@ -380,10 +417,7 @@ var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread s.set_up(5, 1, src) s.start() - if (electrocute_mob(user, get_area(src), src, 0.7, TRUE)) - return TRUE - else - return FALSE + return electrocute_mob(user, get_area(src), src, 0.7, TRUE) /obj/machinery/autolathe/proc/adjust_hacked(state) hacked = state diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm index 4d21aef7962..b196a06d301 100644 --- a/code/modules/research/designs.dm +++ b/code/modules/research/designs.dm @@ -32,7 +32,7 @@ other types of metals and chemistry for reagents). /// List of materials required to create one unit of the product. Format is (typepath or caregory) -> amount var/list/materials = list() /// The amount of time required to create one unit of the product. - var/construction_time + var/construction_time = 3.2 SECONDS /// The typepath of the object produced by this design var/build_path = null /// Reagent produced by this design. Currently only supported by the biogenerator. diff --git a/code/modules/research/machinery/_production.dm b/code/modules/research/machinery/_production.dm index 0b3924827fd..66064669494 100644 --- a/code/modules/research/machinery/_production.dm +++ b/code/modules/research/machinery/_production.dm @@ -156,7 +156,7 @@ return data -/obj/machinery/rnd/production/ui_act(action, list/params) +/obj/machinery/rnd/production/ui_act(action, list/params, datum/tgui/ui) . = ..() if(.) @@ -172,7 +172,7 @@ materials.eject_sheets(material, amount) if("build") - user_try_print_id(params["ref"], params["amount"]) + user_try_print_id(ui.user, params["ref"], params["amount"]) /// Updates the fabricator's efficiency coefficient based on the installed parts. /obj/machinery/rnd/production/proc/calculate_efficiency() @@ -193,19 +193,13 @@ efficiency_coeff = max(total_rating, 0) -/obj/machinery/rnd/production/proc/do_print(path, amount) - for(var/i in 1 to amount) - new path(get_turf(src)) - - SSblackbox.record_feedback("nested tally", "item_printed", amount, list("[type]", "[path]")) - /obj/machinery/rnd/production/proc/build_efficiency(path) if(ispath(path, /obj/item/stack/sheet) || ispath(path, /obj/item/stack/ore/bluespace_crystal)) return 1 else return efficiency_coeff -/obj/machinery/rnd/production/proc/user_try_print_id(design_id, print_quantity) +/obj/machinery/rnd/production/proc/user_try_print_id(mob/user, design_id, print_quantity) if(!design_id) return FALSE @@ -243,9 +237,14 @@ print_quantity = clamp(print_quantity, 1, 50) var/coefficient = build_efficiency(design.build_path) - //check if sufficient materials are available - if(!materials.mat_container.has_materials(design.materials, coefficient, print_quantity)) - say("Not enough materials to complete prototype[print_quantity > 1? "s" : ""].") + var/list/materials_per_item = list() + for(var/material in design.materials) + materials_per_item[material] = design.materials[material] * coefficient + + // check if sufficient materials are available. + // don't pass coefficient here, as we already multiplied the design's materials by it for use with custom materials. + if(!materials.mat_container.has_materials(materials_per_item, multiplier = print_quantity)) + say("Not enough materials to complete prototype[print_quantity > 1 ? "s" : ""].") return FALSE //use power @@ -259,9 +258,9 @@ 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(isliving(user)) + var/mob/living/living_user = user + var/obj/item/card/id/card = living_user.get_idcard(TRUE) if(!card && istype(user.pulling, /obj/item/card/id)) card = user.pulling @@ -270,28 +269,85 @@ 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. - 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 FALSE - borg.cell.use(SILICON_LATHE_TAX) - //consume materials - materials.use_materials(design.materials, coefficient, print_quantity, "built", "[design.name]") - //produce item - busy = TRUE + if(total_cost) + if(iscyborg(user)) + var/mob/living/silicon/robot/borg = user + if(!borg.cell) + return FALSE + borg.cell.use(SILICON_LATHE_TAX) + + else if(attempt_charge(src, user, total_cost) & COMPONENT_OBJ_CANCEL_CHARGE) + say("Insufficient funds to complete prototype. Please present a holochip or valid ID card.") + return FALSE + if(production_animation) flick(production_animation, src) - var/time_coefficient = design.lathe_time_factor * efficiency_coeff - addtimer(CALLBACK(src, PROC_REF(reset_busy)), (30 * time_coefficient * print_quantity) ** 0.5) - addtimer(CALLBACK(src, PROC_REF(do_print), design.build_path, print_quantity), (32 * time_coefficient * print_quantity) ** 0.8) - update_static_data_for_all_viewers() + + var/total_time = (design.construction_time * design.lathe_time_factor * print_quantity) ** 0.8 + var/time_per_item = total_time / print_quantity + start_making(design, print_quantity, user, time_per_item, materials_per_item) return TRUE +/// Begins the act of making the given design the given number of items +/// Does not check or use materials/power/etc +/obj/machinery/rnd/production/proc/start_making(datum/design/design, build_count, mob/user, build_time_per_item, list/materials_per_item) + PRIVATE_PROC(TRUE) + + busy = TRUE + update_static_data_for_all_viewers() + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, materials_per_item, build_time_per_item, build_count), build_time_per_item) + +/// Callback for start_making, actually makes the item +/// Called using timers started by start_making +/obj/machinery/rnd/production/proc/do_make_item(datum/design/design, list/materials_per_item, time_per_item, items_remaining) + PRIVATE_PROC(TRUE) + + if(!items_remaining) // how + finalize_build() + return + + if(!is_operational) + say("Unable to continue production, power failure.") + finalize_build() + return + + var/is_stack = ispath(design.build_path, /obj/item/stack) + if(!materials.mat_container.has_materials(materials_per_item, multiplier = is_stack ? items_remaining : 1)) + say("Unable to continue production, missing materials.") + return + materials.use_materials(materials_per_item, multiplier = is_stack ? items_remaining : 1, action = "built", name = "[design.name]") + + var/atom/movable/created + if(is_stack) + created = new design.build_path(get_turf(src), items_remaining) + else + created = new design.build_path(get_turf(src)) + created.set_custom_materials(materials_per_item.Copy()) + + created.pixel_x = created.base_pixel_x + rand(-6, 6) + created.pixel_y = created.base_pixel_y + rand(-6, 6) + for(var/atom/movable/content in created) + content.set_custom_materials(list()) // no + + if(is_stack) + items_remaining = 0 + else + items_remaining -= 1 + + if(!items_remaining) + finalize_build() + return + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, materials_per_item, time_per_item, items_remaining), time_per_item) + +/// Resets the busy flag +/// Called at the end of do_make_item's timer loop +/obj/machinery/rnd/production/proc/finalize_build() + PRIVATE_PROC(TRUE) + busy = FALSE + update_static_data_for_all_viewers() + // 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) . = ..()