diff --git a/code/__HELPERS/construction.dm b/code/__HELPERS/construction.dm index 9cc7d112532..f7b0ece13f8 100644 --- a/code/__HELPERS/construction.dm +++ b/code/__HELPERS/construction.dm @@ -59,3 +59,63 @@ return null . = new target.type(target.drop_location(), amount, FALSE, target.mats_per_unit) + +/** + * divides a list of materials uniformly among all contents of the target_object reccursively + * Used to set materials of printed items with their design cost by taking into consideration their already existing materials + * e.g. if 12 iron is to be divided uniformly among 2 objects A, B who's current iron contents are 3 & 7 + * Then first we normalize those values i.e. find their weights to decide who gets an higher share of iron + * total_sum = 3 + 7 = 10, A = 3/10 = 0.3, B = 7/10 = 0.7 + * Then we finally multiply those weights with the user value of 12 we get + * A = 0.3 * 12 = 3.6, B = 0.7 * 12 = 8.4 i.e. 3.6 + 8.4 = 12!! + * Off course we round the values so we don't have to deal with floating point materials so the actual value + * ends being less but that's not an issue + * Arguments + * + * * [custom_materials][list] - the list of materials to set for the object + * * multiplier - multiplier passed to set_custom_materials + * * [target_object][atom] - the target object who's custom materials we are trying to modify + */ +/proc/split_materials_uniformly(list/custom_materials, multiplier, atom/target_object) + if(!length(target_object.contents)) //most common case where the object is just 1 thing + target_object.set_custom_materials(custom_materials, multiplier) + return + + //Step 1: Get reccursive contents of all objects, only filter obj cause that what's material container accepts + var/list/reccursive_contents = target_object.get_all_contents_type(/obj/item) + + //Step 2: find the sum of each material type per object and record their amounts into an 2D list + var/list/material_map_sum = list() + var/list/material_map_amounts = list() + for(var/atom/object as anything in reccursive_contents) + var/list/item_materials = object.custom_materials + for(var/mat as anything in custom_materials) + var/mat_amount = 1 //no materials mean we assign this default amount + if(length(item_materials)) + mat_amount = item_materials[mat] || 1 //if this object doesn't have our material type then assign a default value of 1 + + //record the sum of mats for normalizing + material_map_sum[mat] += mat_amount + //record the material amount for each item into an 2D list + var/list/mat_list_per_item = material_map_amounts[mat] + if(isnull(mat_list_per_item)) + material_map_amounts[mat] = list(mat_amount) + else + mat_list_per_item += mat_amount + + //Step 3: normalize & scale material_map_amounts with material_map_sum + for(var/mat as anything in material_map_amounts) + var/mat_sum = material_map_sum[mat] + var/list/mat_per_item = material_map_amounts[mat] + for(var/i in 1 to mat_per_item.len) + mat_per_item[i] = (mat_per_item[i] / mat_sum) * custom_materials[mat] + + //Step 4 flatten the 2D list and assign the final values to each atom + var/index = 1 + for(var/atom/object as anything in reccursive_contents) + var/list/final_material_list = list() + for(var/mat as anything in material_map_amounts) + var/list/mat_per_item = material_map_amounts[mat] + final_material_list[mat] = mat_per_item[index] + object.set_custom_materials(final_material_list, multiplier) + index += 1 diff --git a/code/controllers/subsystem/materials.dm b/code/controllers/subsystem/materials.dm index a9c807cc38a..4ae9272e970 100644 --- a/code/controllers/subsystem/materials.dm +++ b/code/controllers/subsystem/materials.dm @@ -154,13 +154,13 @@ SUBSYSTEM_DEF(materials) var/list/combo_params = list() for(var/x in materials_declaration) var/datum/material/mat = x - combo_params += "[istype(mat) ? mat.id : mat]=[materials_declaration[mat] * multiplier]" + combo_params += "[istype(mat) ? mat.id : mat]=[OPTIMAL_COST(materials_declaration[mat] * multiplier)]" sortTim(combo_params, GLOBAL_PROC_REF(cmp_text_asc)) // We have to sort now in case the declaration was not in order var/combo_index = combo_params.Join("-") var/list/combo = material_combos[combo_index] if(!combo) combo = list() for(var/mat in materials_declaration) - combo[GET_MATERIAL_REF(mat)] = materials_declaration[mat] * multiplier + combo[GET_MATERIAL_REF(mat)] = OPTIMAL_COST(materials_declaration[mat] * multiplier) material_combos[combo_index] = combo return combo diff --git a/code/game/atom/atom_materials.dm b/code/game/atom/atom_materials.dm index 803a79110a1..345a8486dd6 100644 --- a/code/game/atom/atom_materials.dm +++ b/code/game/atom/atom_materials.dm @@ -12,7 +12,7 @@ if(custom_materials && material_flags & MATERIAL_EFFECTS) //Only runs if custom materials existed at first and affected src. for(var/current_material in custom_materials) var/datum/material/custom_material = GET_MATERIAL_REF(current_material) - custom_material.on_removed(src, custom_materials[current_material] * material_modifier, material_flags) //Remove the current materials + custom_material.on_removed(src, OPTIMAL_COST(custom_materials[current_material] * material_modifier), material_flags) //Remove the current materials if(!length(materials)) custom_materials = null @@ -21,7 +21,7 @@ if(material_flags & MATERIAL_EFFECTS) for(var/current_material in materials) var/datum/material/custom_material = GET_MATERIAL_REF(current_material) - custom_material.on_applied(src, materials[current_material] * multiplier * material_modifier, material_flags) + custom_material.on_applied(src, OPTIMAL_COST(materials[current_material] * multiplier * material_modifier), material_flags) custom_materials = SSmaterials.FindOrCreateMaterialCombo(materials, multiplier) diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index 63bb36c2b4a..3047995e7b3 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -188,11 +188,9 @@ 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 + var/list/materials_needed = list() for(var/datum/material/material as anything in design.materials) - var/amount_needed = design.materials[material] * material_cost_coefficient + var/amount_needed = design.materials[material] if(istext(material)) // category var/list/choices = list() for(var/datum/material/valid_candidate as anything in SSmaterials.materials_by_category[material]) @@ -215,39 +213,41 @@ if(isnull(material)) stack_trace("got passed an invalid material id: [material]") return - materials_per_item[material] = amount_needed + materials_needed[material] = amount_needed - // 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)) + var/material_cost_coefficient = ispath(design.build_path, /obj/item/stack) ? 1 : creation_efficiency + if(!materials.has_materials(materials_needed, material_cost_coefficient, build_count)) say("Not enough materials to begin production.") 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)) + //use power + var/power = active_power_usage + for(var/material in design.materials) + power += round(design.materials[material] * material_cost_coefficient * build_count) + power = min(active_power_usage, power) + if(!directly_use_power(power)) say("Not enough power in local network to begin production.") return 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) + start_making(design, build_count, time_per_item, material_cost_coefficient) 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/autolathe/proc/start_making(datum/design/design, build_count, mob/user, build_time_per_item, list/materials_per_item) +/obj/machinery/autolathe/proc/start_making(datum/design/design, build_count, build_time_per_item, material_cost_coefficient) PROTECTED_PROC(TRUE) busy = TRUE icon_state = "autolathe_n" 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) + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, material_cost_coefficient, 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/autolathe/proc/do_make_item(datum/design/design, list/materials_per_item, time_per_item, items_remaining) +/obj/machinery/autolathe/proc/do_make_item(datum/design/design, material_cost_coefficient, time_per_item, items_remaining) PROTECTED_PROC(TRUE) if(!items_remaining) // how @@ -259,11 +259,12 @@ finalize_build() return + var/list/design_materials = design.materials var/is_stack = ispath(design.build_path, /obj/item/stack) - if(!materials.has_materials(materials_per_item, multiplier = is_stack ? items_remaining : 1)) + if(!materials.has_materials(design_materials, material_cost_coefficient, 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) + materials.use_materials(design_materials, material_cost_coefficient, is_stack ? items_remaining : 1) var/turf/target = get_step(src, drop_direction) if(isclosedturf(target)) @@ -274,12 +275,10 @@ created = new design.build_path(target, items_remaining) else created = new design.build_path(target) - created.set_custom_materials(materials_per_item.Copy()) + split_materials_uniformly(design_materials, material_cost_coefficient, created) 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) @@ -290,7 +289,7 @@ 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) + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, material_cost_coefficient, 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 diff --git a/code/modules/research/machinery/_production.dm b/code/modules/research/machinery/_production.dm index 32ff4480cbf..0067b05fd14 100644 --- a/code/modules/research/machinery/_production.dm +++ b/code/modules/research/machinery/_production.dm @@ -237,20 +237,15 @@ print_quantity = clamp(print_quantity, 1, 50) var/coefficient = build_efficiency(design.build_path) - 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)) + if(!materials.mat_container.has_materials(design.materials, coefficient, print_quantity)) say("Not enough materials to complete prototype[print_quantity > 1 ? "s" : ""].") return FALSE //use power var/power = active_power_usage for(var/material in design.materials) - power += round(design.materials[material] * print_quantity / 35) + power += round(design.materials[material] * coefficient * print_quantity / 35) power = min(active_power_usage, power) use_power(power) @@ -286,22 +281,22 @@ 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) + start_making(design, print_quantity, time_per_item, coefficient) 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) +/obj/machinery/rnd/production/proc/start_making(datum/design/design, build_count, build_time_per_item, build_efficiency) PROTECTED_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) + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, build_efficiency, 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) +/obj/machinery/rnd/production/proc/do_make_item(datum/design/design, build_efficiency, time_per_item, items_remaining) PROTECTED_PROC(TRUE) if(!items_remaining) // how @@ -314,22 +309,21 @@ 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)) + var/list/design_materials = design.materials + if(!materials.mat_container.has_materials(design_materials, build_efficiency, 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]") + materials.use_materials(design_materials, build_efficiency, is_stack ? items_remaining : 1, "built", "[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()) + split_materials_uniformly(design_materials, build_efficiency, created) 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 @@ -339,7 +333,7 @@ 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) + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, build_efficiency, time_per_item, items_remaining), time_per_item) /// Resets the busy flag /// Called at the end of do_make_item's timer loop