[MIRROR] Improves upon setting custom materials for printed items (#26242)

* Improves upon setting custom materials for printed items (#81015)

## About The Pull Request
This is an improvement on #80839 regarding how custom materials are set
on items, it's based on the following findings.

1. `set_custom_materials()` proc already comes with an prebuilt
`multiplier` var

https://github.com/tgstation/tgstation/blob/1e8d511946d194f92f744f5f957a7c41683d84a6/code/game/atom/atom_materials.dm#L11
This means we can pass the machine's cost coefficiency directly to this
proc rather than creating our own list of materials with their values
scaled by the factor like so

https://github.com/tgstation/tgstation/blob/1e8d511946d194f92f744f5f957a7c41683d84a6/code/game/machinery/autolathe.dm#L192-L193
We can instead just do `set_custom_materials(design.materials,
material_cost_coefficient)` without ever needing this list, thus making
code cleaner, with that the changes propogate to how `has_materials()` &
`use_materials()` procs are also used as we can now use their
`coefficiency` param rather than seeing it go to waste

2. All items custom materials will now always be integer values. With
this `SSmaterials.FindOrCreateMaterialCombo` now has better performance
because when computing the key for caching values like `1.5` or `1.7` it
will become just `1` and will point to the same cache thus reducing
memory usage

3. Materials are now uniformly split among all the contents of a printed
item from techfab or autolathe. What this means is items like the foam
ammo box printed from autolathe will have both the ammo case and their
40 bullets each set with custom materials such that their final sum
becomes equal to the design cost. For info on how that's done see the
documentation of `split_materials_uniformly` proc.

One downside of this proc is when items have a very small amount of
`custom_materials`(less than 2). In that case values like `0.8` or `1.5`
or `1.7` ends up getting rounded to 1 which means you end up getting
less materials when recycling than what you used for printing that item.
In this case You get 0.48 iron from both the box & it's ammo instead of
0.79 iron used for printing in tier1 autolathe(Or 0.50 for tier4
autolathe).

This shouldn't be an issue as you still can't make any profits from this
but at least everything in the box is now recyclable.

## Changelog
🆑
fix: items that contain recursive contents inside them (like foam dart
boxes from autolathes) now have their custom materials set to match with
its design cost rather than being nullified, meaning they are now
recyclable.
code: all custom materials are now integer values. Improved code for how
materials are used in techfab & auto lathe for printing
/🆑

* Improves upon setting custom materials for printed items

---------

Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
This commit is contained in:
SkyratBot
2024-01-25 22:47:49 -05:00
committed by GitHub
co-authored by SyncIt21
parent 97cb6e9137
commit ddfd3e36bd
5 changed files with 95 additions and 42 deletions
+60
View File
@@ -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
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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)
+20 -21
View File
@@ -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
+11 -17
View File
@@ -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