Skip to content

Commit

Permalink
[MIRROR] Improves upon setting custom materials for printed items (#615)
Browse files Browse the repository at this point in the history
* 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
:cl:
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
/:cl:

* Improves upon setting custom materials for printed items

---------

Co-authored-by: SyncIt21 <[email protected]>
  • Loading branch information
2 people authored and FFMirrorBot committed Jan 25, 2024
1 parent c18c4c3 commit ee7e15d
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 42 deletions.
60 changes: 60 additions & 0 deletions code/__HELPERS/construction.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions code/controllers/subsystem/materials.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions code/game/atom/atom_materials.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand Down
41 changes: 20 additions & 21 deletions code/game/machinery/autolathe.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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
Expand All @@ -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))
Expand All @@ -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)
Expand All @@ -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
Expand Down
28 changes: 11 additions & 17 deletions code/modules/research/machinery/_production.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit ee7e15d

Please sign in to comment.