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) . = ..()