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
🆑
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
/🆑
This commit is contained in:
Zephyr
2024-01-15 13:35:02 -05:00
committed by GitHub
parent 72d13059ab
commit 13d2dceb31
4 changed files with 231 additions and 139 deletions
@@ -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
+140 -106
View File
@@ -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
+1 -1
View File
@@ -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.
+87 -31
View File
@@ -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)
. = ..()