Files
Bubberstation/code/game/atom/atom_materials.dm
Alexis 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

658 lines
28 KiB
Plaintext

/atom
/// The custom materials this atom is made of, used by a lot of things like furniture, walls, and floors (if I finish the functionality, that is.)
/// The list referenced by this var can be shared by multiple objects and should not be directly modified. Instead, use [set_custom_materials][/atom/proc/set_custom_materials].
var/list/datum/material/custom_materials
/// Bitfield for how the atom handles materials.
var/material_flags = NONE
/// Modifier that raises/lowers the effect of the amount of a material, prevents small and easy to get items from being death machines.
var/material_modifier = 1
/// List of material slots to be used to control material behaviors instead of default ones
var/list/datum/material_slot/material_slots = null
/// Sets the custom materials for an atom. This is what you want to call, since most of the ones below are mainly internal.
/atom/proc/set_custom_materials(list/materials, multiplier = 1)
SHOULD_NOT_OVERRIDE(TRUE)
// Easy way to know no changes are being made.
if((custom_materials == materials) && multiplier == 1)
return
var/replace_mats = length(materials)
if(length(custom_materials))
remove_material_effects(replace_mats)
if(!replace_mats)
return
initialize_materials(materials, multiplier)
/**
* The second part of set_custom_materials(), which handles applying the new materials
* It is a separate proc because Initialize calls may make use of this since they should've no prior materials to remove.
*/
/atom/proc/initialize_materials(list/materials, multiplier = 1)
SHOULD_NOT_OVERRIDE(TRUE)
if(multiplier != 1)
materials = materials.Copy() //avoid editing the original list since it may be cached somewhere (likely in the materials subsystem).
for(var/current_material in materials)
materials[current_material] *= multiplier
//Let's be sure that there are absolutely no materials in the list that aren't positive.
var/list/nonpos_mats
for(var/mat in materials)
if(materials[mat] <= 0)
LAZYADD(nonpos_mats, "[mat] = [materials[mat]]")
materials -= mat
if(length(nonpos_mats))
stack_trace("materials with non-positive values found in [type]: [english_list(nonpos_mats, and_text = ", ")]")
if(!length(materials))
return
sortTim(materials, GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE)
apply_material_effects(materials)
///proc responsible for applying material effects when setting materials.
/atom/proc/apply_material_effects(list/materials)
SHOULD_CALL_PARENT(TRUE)
if(material_flags & MATERIAL_EFFECTS)
var/list/material_effects = get_material_effects_list(materials)
finalize_material_effects(material_effects)
custom_materials = SSmaterials.get_material_set_cache(materials)
/// Proc responsible for removing material effects when setting materials.
/atom/proc/remove_material_effects(replace_mats = TRUE)
SHOULD_CALL_PARENT(TRUE)
//Only runs if custom materials existed at first and affected src.
if(material_flags & MATERIAL_EFFECTS)
var/list/material_effects = get_material_effects_list(custom_materials)
finalize_remove_material_effects(material_effects)
if(!replace_mats)
custom_materials = null
/atom/proc/get_material_effects_list(list/materials)
SHOULD_NOT_OVERRIDE(TRUE)
PRIVATE_PROC(TRUE)
var/list/material_effects = list()
var/index = 1
for(var/current_material in materials)
var/datum/material/material = SSmaterials.get_material(current_material)
material_effects[material] = list(
MATERIAL_LIST_OPTIMAL_AMOUNT = OPTIMAL_COST(materials[current_material] * material_modifier),
MATERIAL_LIST_MULTIPLIER = get_material_multiplier(material, materials, index),
)
index++
if(material_slots)
configure_material_slots(material_effects)
return material_effects
/// Add MATERIAL_LIST_SLOTS entries to material effects
/atom/proc/configure_material_slots(list/datum/material/material_effects)
for (var/slot_index in 1 to length(material_slots))
var/slot_type = material_slots[slot_index]
var/datum/material/material = null
// The slot has a specific material assigned to it
if (material_slots[slot_type])
material = SSmaterials.get_material(material_slots[slot_type])
// Slots were unset, abort
if (!material_effects[material])
continue
else if (slot_index <= length(material_effects)) // Otherwise, go by index
material = material_effects[slot_index]
if (!material)
continue
var/list/effects_list = material_effects[material]
if (!effects_list[MATERIAL_LIST_SLOTS])
effects_list[MATERIAL_LIST_SLOTS] = list()
var/list/effects_slots = effects_list[MATERIAL_LIST_SLOTS]
effects_slots[slot_type] = TRUE
/atom/proc/set_material_slot(slot_type, new_material)
if (material_slots[slot_type])
var/datum/material_slot/slot = SSmaterials.material_slots[slot_type]
var/datum/material/material = SSmaterials.get_material(material_slots[slot_type])
// Not present/initialized
if (material && custom_materials[material])
var/list/materials_slots = get_slots_of_material(material)
var/slot_sum = 0
if (length(materials_slots) > 1)
for (var/other_slot_type in materials_slots)
var/datum/material_slot/other_slot = SSmaterials.material_slots[other_slot_type]
slot_sum += other_slot.material_amount
slot.on_removed(src, material, custom_materials[material] * (slot_sum > 0 ? slot.material_amount / slot_sum : 1), get_material_multiplier(material, custom_materials, custom_materials.Find(material)))
// Don't store materials directly, only their IDs
if (istype(new_material, /datum/material))
var/datum/material/as_material = new_material
material_slots[slot_type] = as_material.id
else
material_slots[slot_type] = new_material
var/datum/material_slot/slot = SSmaterials.material_slots[slot_type]
var/datum/material/material = istype(new_material, /datum/material) ? new_material : SSmaterials.get_material(new_material)
if (!material || !custom_materials[material])
return
var/list/materials_slots = get_slots_of_material(material)
var/slot_sum = 0
if (length(materials_slots) > 1)
for (var/other_slot_type in materials_slots)
var/datum/material_slot/other_slot = SSmaterials.material_slots[other_slot_type]
slot_sum += other_slot.material_amount
slot.on_applied(src, material, custom_materials[material] * (slot_sum > 0 ? slot.material_amount / slot_sum : 1), get_material_multiplier(material, custom_materials, custom_materials.Find(material)))
/atom/proc/set_material_slots(list/new_slots)
if (length(material_slots))
for (var/slot_type, material_id in material_slots)
var/datum/material_slot/slot = SSmaterials.material_slots[slot_type]
var/datum/material/material = SSmaterials.get_material(material_id)
// Not present/initialized
if (!material || !custom_materials[material])
continue
var/list/materials_slots = get_slots_of_material(material)
var/slot_sum = 0
if (length(materials_slots) > 1)
for (var/other_slot_type in materials_slots)
var/datum/material_slot/other_slot = SSmaterials.material_slots[other_slot_type]
slot_sum += other_slot.material_amount
slot.on_removed(src, material, custom_materials[material] * (slot_sum > 0 ? slot.material_amount / slot_sum : 1), get_material_multiplier(material, custom_materials, custom_materials.Find(material)))
if (!length(new_slots))
material_slots = null
return
material_slots = new_slots.Copy()
for (var/slot_type, material_id in material_slots)
var/datum/material_slot/slot = SSmaterials.material_slots[slot_type]
var/datum/material/material = SSmaterials.get_material(material_id)
if (!material || !custom_materials[material])
continue
var/list/materials_slots = get_slots_of_material(material)
var/slot_sum = 0
if (length(materials_slots) > 1)
for (var/other_slot_type in materials_slots)
var/datum/material_slot/other_slot = SSmaterials.material_slots[other_slot_type]
slot_sum += other_slot.material_amount
slot.on_applied(src, material, custom_materials[material] * (slot_sum > 0 ? slot.material_amount / slot_sum : 1), get_material_multiplier(material, custom_materials, custom_materials.Find(material)))
/**
* A proc that can be used to selectively control the stat changes and effects from a material without affecting the others.
*
* For example, we can have items made of two different materials, with the primary contributing a good 1.2 multiplier
* and the second a meager 0.3.
*
* The GET_MATERIAL_MODIFIER macro will handles some modifications where the minimum should be 1 if above 1 and the maximum
* be 1 if below 1. Just don't return negative values.
*/
/atom/proc/get_material_multiplier(datum/material/custom_material, list/materials, index)
if (!length(material_slots))
return 1 / length(materials)
// Slots usually account for multipliers in their own behaviors, so unless overriden it should just be 1
for (var/slot_type in material_slots)
if (material_slots[slot_type] == custom_material.id)
return 1
return 1 / length(materials)
///Called by apply_material_effects(). It ACTUALLY handles applying effects common to all atoms (depending on material flags)
/atom/proc/finalize_material_effects(list/materials)
SHOULD_CALL_PARENT(TRUE)
var/total_alpha = 0
var/list/colors = list()
var/mat_length = length(materials)
var/datum/material/main_material = materials[1]//the material with the highest amount (after calculations)
var/main_mat_amount = materials[main_material][MATERIAL_LIST_OPTIMAL_AMOUNT]
var/main_mat_mult = materials[main_material][MATERIAL_LIST_MULTIPLIER]
var/do_main_material = TRUE
for(var/datum/material/custom_material as anything in materials)
var/list/deets = materials[custom_material]
var/mat_amount = deets[MATERIAL_LIST_OPTIMAL_AMOUNT]
var/multiplier = deets[MATERIAL_LIST_MULTIPLIER]
var/do_effects = TRUE
var/from_slot = FALSE
if(!isnull(material_slots) && length(deets[MATERIAL_LIST_SLOTS]))
from_slot = TRUE
var/slot_sum = 0
// A material is in multiple slots, we need to cut it up between them
if(length(deets[MATERIAL_LIST_SLOTS]) > 1)
for(var/slot_type in deets[MATERIAL_LIST_SLOTS])
var/datum/material_slot/slot = SSmaterials.material_slots[slot_type]
slot_sum += slot.material_amount
for(var/slot_type in deets[MATERIAL_LIST_SLOTS])
var/datum/material_slot/slot = SSmaterials.material_slots[slot_type]
var/slot_amt = mat_amount
if (slot_sum > 0)
slot_amt *= slot.material_amount / slot_sum
do_effects &= slot.on_applied(src, custom_material, slot_amt, multiplier)
if(!do_effects && custom_material == main_material)
do_main_material = FALSE
if(do_effects)
apply_single_mat_effect(custom_material, mat_amount, multiplier)
custom_material.on_applied(src, mat_amount, multiplier, from_slot = from_slot)
//Prevent changing things with pre-set colors, to keep colored toolboxes their looks for example
if(material_flags & (MATERIAL_COLOR|MATERIAL_GREYSCALE))
gather_material_color(custom_material, colors, mat_amount, multicolor = mat_length > 1)
var/added_alpha = custom_material.alpha * (custom_material.alpha / 255)
total_alpha += GET_MATERIAL_MODIFIER(added_alpha, multiplier)
if(do_main_material)
apply_main_material_effects(main_material, main_mat_amount, main_mat_mult)
if(material_flags & (MATERIAL_COLOR|MATERIAL_GREYSCALE))
var/previous_alpha = alpha
alpha *= (total_alpha / length(materials))/255
if(alpha < previous_alpha * 0.9)
opacity = FALSE
if(material_flags & MATERIAL_GREYSCALE)
var/config_path = get_material_greyscale_config(main_material.type, greyscale_config)
//Make sure that we've no less than the expected amount
//expected_colors is zero for paths, the value is assigned when reading the json files.
var/datum/greyscale_config/config = SSgreyscale.configurations["[config_path || greyscale_config]"]
var/colors_len = length(colors)
if(config.expected_colors > colors_len)
var/list/filled_colors = colors.Copy()
for(var/index in colors_len to config.expected_colors - 1)
filled_colors += pick(colors)
colors = filled_colors
set_greyscale(colors, config_path)
else if(length(colors))
mix_material_colors(colors)
if(material_flags & MATERIAL_ADD_PREFIX)
var/prefixes = get_material_prefixes(materials)
name = "[prefixes] [name]"
SEND_SIGNAL(src, COMSIG_ATOM_FINALIZE_MATERIAL_EFFECTS, materials, main_material)
/**
* A proc used by both finalize_material_effects() and finalize_remove_material_effects() to get the colors
* that will later be applied to or removed from the atom
*/
/atom/proc/gather_material_color(datum/material/material, list/colors, amount, multicolor = FALSE)
SHOULD_CALL_PARENT(TRUE)
if(!material.color) //the material has no color. Nevermind
return
var/color_to_add = material.color
var/istext = istext(color_to_add)
if(istext)
if(material.alpha != 255)
color_to_add += num2hex(material.alpha, 2)
else
if(multicolor || material_flags & MATERIAL_GREYSCALE)
color_to_add = material.greyscale_color || color_matrix2color_hex(material.color)
if(material.greyscale_color)
color_to_add += num2hex(material.alpha, 2)
else
color_to_add = color_to_full_rgba_matrix(color_to_add)
color_to_add[20] *= (material.alpha / 255) // multiply the constant alpha of the color matrix
colors[color_to_add] += amount
/// Manages mixing, adding or removing the material colors from the atom in absence of the MATERIAL_GREYSCALE flag.
/atom/proc/mix_material_colors(list/colors, remove = FALSE)
SHOULD_NOT_OVERRIDE(TRUE)
var/color_len = length(colors)
if(!color_len)
return
var/mixcolor = colors[1]
var/amount_divisor = colors[mixcolor]
for(var/i in 2 to length(colors))
var/color_to_add = colors[i]
if(islist(color_to_add))
color_to_add = color_matrix2color_hex(color_to_add)
var/mix_amount = colors[color_to_add]
amount_divisor += mix_amount
mixcolor = BlendRGB(mixcolor, color_to_add, mix_amount/amount_divisor)
if(remove)
remove_atom_colour(FIXED_COLOUR_PRIORITY, mixcolor)
else
add_atom_colour(mixcolor, FIXED_COLOUR_PRIORITY)
///Returns the prefixes to attach to the atom when setting materials, from a list argument.
/atom/proc/get_material_prefixes(list/materials)
var/list/mat_names = list()
for(var/datum/material/material as anything in materials)
mat_names |= material.name
return mat_names.Join("-")
///Returns a string like "plasma, paper and glass" from a list of materials
/atom/proc/get_material_english_list(list/materials)
var/list/mat_names = list()
for(var/datum/material/material as anything in materials)
mat_names += material.name
return english_list(mat_names)
///Searches for a subtype of config_type that is to be used in its place for specific materials (like shimmering gold for cleric maces)
/atom/proc/get_material_greyscale_config(mat_type, config_type)
SHOULD_NOT_OVERRIDE(TRUE)
if(!config_type)
return
for(var/datum/greyscale_config/path as anything in subtypesof(config_type))
if(mat_type == initial(path.material_skin))
return path
///Apply material effects of a single material.
/atom/proc/apply_single_mat_effect(datum/material/material, amount, multiplier)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ATOM_SINGLE_MATERIAL_EFFECT_APPLY, material, amount, multiplier)
// Derived and not optional, so this needs to be on base and not in the property code itself
var/beauty_modifier = material.get_property(MATERIAL_BEAUTY)
if(beauty_modifier)
AddElement(/datum/element/beauty, beauty_modifier * amount)
if(beauty_modifier >= 0.15 && HAS_TRAIT(src, TRAIT_FISHING_BAIT))
AddElement(/datum/element/shiny_bait)
if((material_flags & MATERIAL_AFFECT_STATISTICS) && uses_integrity)
change_material_integrity(material, amount, multiplier)
///A proc for material effects that only the main material (which the atom's primarly composed of) should apply.
/atom/proc/apply_main_material_effects(datum/material/main_material, amount, multiplier)
SHOULD_CALL_PARENT(TRUE)
if(main_material.texture_layer_icon_state && (material_flags & MATERIAL_COLOR))
ADD_KEEP_TOGETHER(src, MATERIAL_SOURCE(main_material))
add_filter("material_texture_[main_material.name]", 1, layering_filter(icon = main_material.cached_texture_filter_icon, blend_mode = BLEND_INSET_OVERLAY))
main_material.on_main_applied(src, amount, multiplier)
///Called by remove_material_effects(). It ACTUALLY handles removing effects common to all atoms (depending on material flags)
/atom/proc/finalize_remove_material_effects(list/materials)
var/list/colors = list()
var/datum/material/main_material = get_master_material()
var/mat_length = length(materials)
var/main_mat_amount = materials[main_material][MATERIAL_LIST_OPTIMAL_AMOUNT]
var/main_mat_mult = materials[main_material][MATERIAL_LIST_MULTIPLIER]
var/do_main_material = TRUE
for(var/datum/material/custom_material as anything in materials)
var/list/deets = materials[custom_material]
var/mat_amount = deets[MATERIAL_LIST_OPTIMAL_AMOUNT]
var/multiplier = deets[MATERIAL_LIST_MULTIPLIER]
var/do_effects = TRUE
var/from_slot = FALSE
if(!isnull(material_slots) && length(deets[MATERIAL_LIST_SLOTS]))
from_slot = TRUE
var/slot_sum = 0
// A material is in multiple slots, we need to cut it up between them
if(length(deets[MATERIAL_LIST_SLOTS]) > 1)
for(var/slot_type in deets[MATERIAL_LIST_SLOTS])
var/datum/material_slot/slot = SSmaterials.material_slots[slot_type]
slot_sum += slot.material_amount
for(var/slot_type in deets[MATERIAL_LIST_SLOTS])
var/datum/material_slot/slot = SSmaterials.material_slots[slot_type]
var/slot_amt = mat_amount
if (slot_sum > 0)
slot_amt *= slot.material_amount / slot_sum
do_effects &= slot.on_removed(src, custom_material, mat_amount, multiplier)
if(!do_effects && custom_material == main_material)
do_main_material = FALSE
if(do_effects)
remove_single_mat_effect(custom_material, mat_amount, multiplier)
custom_material.on_removed(src, mat_amount, multiplier, from_slot = from_slot)
if(material_flags & MATERIAL_COLOR)
gather_material_color(custom_material, colors, mat_amount, multicolor = mat_length > 1)
if(do_main_material)
remove_main_material_effects(main_material, main_mat_amount, main_mat_mult)
if(material_flags & (MATERIAL_GREYSCALE|MATERIAL_COLOR))
if(material_flags & MATERIAL_COLOR)
mix_material_colors(colors, remove = TRUE)
else
set_greyscale(initial(greyscale_colors), initial(greyscale_config))
alpha = initial(alpha)
opacity = initial(opacity)
if(material_flags & MATERIAL_ADD_PREFIX)
name = initial(name)
// Ensure that we restore armor zero'd out by zero multipliers, as we don't have anything to go off other than our initial values
if((material_flags & MATERIAL_AFFECT_STATISTICS) && uses_integrity && initial(armor_type))
var/datum/armor/inital_armor = get_armor_by_type(initial(armor_type))
for (var/armor_id in ARMOR_LIST_ALL)
var/initial_rating = inital_armor.get_rating(armor_id)
if (get_armor_rating(armor_id) == 0 && initial_rating != 0)
set_armor_rating(armor_id, initial_rating)
SEND_SIGNAL(src, COMSIG_ATOM_FINALIZE_REMOVE_MATERIAL_EFFECTS, materials, main_material)
///Remove material effects of a single material.
/atom/proc/remove_single_mat_effect(datum/material/material, amount, multiplier)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ATOM_SINGLE_MATERIAL_EFFECT_REMOVE, material, amount, multiplier)
var/beauty_modifier = material.get_property(MATERIAL_BEAUTY)
if(beauty_modifier)
RemoveElement(/datum/element/beauty, beauty_modifier * amount)
if(beauty_modifier >= 0.15 && HAS_TRAIT(src, TRAIT_FISHING_BAIT))
RemoveElement(/datum/element/shiny_bait)
if((material_flags & MATERIAL_AFFECT_STATISTICS) && uses_integrity)
change_material_integrity(material, amount, multiplier, removing = TRUE)
///A proc to remove the material effects previously applied by the (ex-)main material
/atom/proc/remove_main_material_effects(datum/material/main_material, amount, multipier)
SHOULD_CALL_PARENT(TRUE)
if(main_material.texture_layer_icon_state)
remove_filter("material_texture_[main_material.name]")
REMOVE_KEEP_TOGETHER(src, MATERIAL_SOURCE(main_material))
main_material.on_main_removed(src, amount, multipier)
///Remove the old effects, change the material_modifier variable, and then reapply all the effects.
/atom/proc/change_material_modifier(new_value)
SHOULD_NOT_OVERRIDE(TRUE)
remove_material_effects()
material_modifier = new_value
apply_material_effects(custom_materials)
///For enabling and disabling material effects from an item (mainly VV)
/atom/proc/toggle_material_flags(new_flags)
SHOULD_NOT_OVERRIDE(TRUE)
if(material_flags & MATERIAL_EFFECTS && !(new_flags & MATERIAL_EFFECTS))
remove_material_effects()
else if(!(material_flags & MATERIAL_EFFECTS) && new_flags & MATERIAL_EFFECTS)
apply_material_effects()
material_flags = new_flags
/// Applies changes to integrity and armor from a material
/atom/proc/change_material_integrity(datum/material/material, amount, multiplier, removing = FALSE)
var/base_modifier = material.get_property(MATERIAL_INTEGRITY)
var/integrity_mod = GET_MATERIAL_MODIFIER(base_modifier, multiplier)
var/integrity_change = removing ? floor(max_integrity / integrity_mod) : ceil(max_integrity * integrity_mod)
modify_max_integrity(integrity_change)
var/list/armor_mods = material.get_armor_modifiers(multiplier)
// Invert if we're removing our material
if (removing)
for (var/armor_type, value in armor_mods)
if (value != 0) // Needs to be restored to initial values in finalize effects, sorry
armor_mods[armor_type] = 1 / value
set_armor(get_armor().generate_new_with_multipliers(armor_mods))
/// Tries to fetch a material matching a specific slot
/atom/proc/get_material_from_slot(slot_type)
var/mat_type = material_slots?[slot_type]
if (mat_type)
return SSmaterials.get_material(mat_type)
/// Fetches a copy of all material slots.
/atom/proc/get_material_slots()
return material_slots?.Copy()
/// Returns TRUE if this atom utilizes material slots
/atom/proc/has_material_slots()
return !!length(material_slots)
/// Lists all slots in which a material is present
/atom/proc/get_slots_of_material(datum/material/material)
. = list()
for (var/slot_type, material_id in material_slots)
if (material_id == istype(material) ? material.id : material)
. += slot_type
return .
/**
* Returns the material composition of the atom.
*
* Used when recycling items, specifically to turn alloys back into their component mats.
*
* Exists because I'd need to add a way to un-alloy alloys or otherwise deal
* with people converting the entire stations material supply into alloys.
*
* Arguments:
* - flags: A set of flags determining how exactly the materials are broken down.
*/
/atom/proc/get_material_composition(flags)
. = list()
var/list/cached_materials = custom_materials
for(var/mat in cached_materials)
var/datum/material/material = SSmaterials.get_material(mat)
var/list/material_comp = material.return_composition(cached_materials[mat], flags)
for(var/comp_mat in material_comp)
.[comp_mat] += material_comp[comp_mat]
/**
* Fetches a list of all of the materials this object has of the desired type. Returns null if there is no valid materials of the type
*
* Arguments:
* - [required_material][/datum/material]: The type of material we are checking for
* - mat_amount: The minimum required amount of material
*/
/atom/proc/has_material_type(datum/material/required_material, mat_amount = 0)
var/list/cached_materials = custom_materials
if(!length(cached_materials))
return null
var/materials_of_type
for(var/current_material in cached_materials)
if(cached_materials[current_material] < mat_amount)
continue
var/datum/material/material = SSmaterials.get_material(current_material)
if(!istype(material, required_material))
continue
LAZYSET(materials_of_type, material, cached_materials[current_material])
return materials_of_type
/// Gets the most common material in the object.
/atom/proc/get_master_material()
return length(custom_materials) ? SSmaterials.get_material(custom_materials[1]) : null //materials are sorted by amount, the first is always the main one
/// Gets the total amount of materials in this atom.
/atom/proc/get_custom_material_amount()
return isnull(custom_materials) ? 0 : counterlist_sum(custom_materials)
/// A simple proc that iterates through each material that the object is made of and spawns some stacks based on their amount and associated sheet/ore type.
/atom/proc/drop_custom_materials(multiplier = 1)
for(var/datum/material/material as anything in custom_materials)
var/stack_type = material.sheet_type || material.ore_type
if(!stack_type)
continue
var/amount_to_spawn = FLOOR(custom_materials[material] / SHEET_MATERIAL_AMOUNT * multiplier, 1)
if(amount_to_spawn > 0)
new stack_type(loc, amount_to_spawn)
/**
* A bit of leeway when comparing the amount of material of two items.
* This was made to test the material composition of items spawned via crafting/processable component and an items of the same type spawned
* via other means, since small portion of materials can be lost when rounding down values to the nearest integers and we can't do much about it.
* (eg. a slab of meat worth 100 mat points is cut in three cutlets, each 33, with the remaining 1 percent lost to rounding)
*
* right now it's 3 points per 100 units of a material.
*
*/
#define COMPARISION_ACCEPTABLE_MATERIAL_DEVIATION 0.03
/// Compares the materials of two items to see if they're roughly the same. Primarily used in crafting and processing unit tests.
/atom/proc/compare_materials(atom/target)
if(custom_materials == target.custom_materials) // SSmaterials caches the combinations so we don't have to run more complex checks
return TRUE
if(length(custom_materials) != length(target.custom_materials))
return FALSE
for(var/mat in custom_materials)
var/enemy_amount = target.custom_materials[mat]
if(!enemy_amount) //we couldn't find said material, early return so we won't perform a division by zero
return FALSE
var/ratio_difference = abs((custom_materials[mat] / enemy_amount) - 1)
if(ratio_difference > COMPARISION_ACCEPTABLE_MATERIAL_DEVIATION)
return FALSE
return TRUE
/**
* Returns a string with the materials and their respective amounts written in a way that reflects how it's displayed in the code
* (eg. [list(/datum/material/meat = 100, /datum/material/plastic = 10)]). Also used in several unit tests.
* Not to be confused with get_material_english_list()
* Arguments:
* * as_sheets: returns the text in terms of sheets, e.g "[list(/datum/material/titanium = SHEET_MATERIAL_AMOUNT * 2)]"
*/
/atom/proc/transcribe_materials_list(list/mats_list, as_sheets = TRUE)
if(!mats_list)
if(!custom_materials)
return "null"
mats_list = custom_materials
var/text = "list("
var/index = 1
var/mats_len = length(mats_list)
for(var/datum/material/mat as anything in mats_list)
var/amount_string = ""
if(as_sheets)
var/amount = sheets_from_value(mats_list[mat])
switch(amount)
if(0 to 0.49)
amount_string = "SMALL_MATERIAL_AMOUNT * " + num2text(amount * 10)
if(0.5)
amount_string = "HALF_SHEET_MATERIAL_AMOUNT"
if(1)
amount_string = "SHEET_MATERIAL_AMOUNT"
else
amount_string = "SHEET_MATERIAL_AMOUNT * " + num2text(amount)
else
amount_string = "[mats_list[mat]]"
text += "[mat.type] = " + amount_string
if(index < mats_len)
text += ", "
index++
text += ")"
return text
/// Convert a raw material amount into
/// "SHEET_MATERIAL_AMOUNT", or "* N", with rounding rules.
/proc/sheets_from_value(value, sheet_amount = SHEET_MATERIAL_AMOUNT)
if(!value)
return 0
// If value is small, do NOT try rounding to nearest 0 or 5. percentage error becomes huge.
var/final_value
if(value < sheet_amount)
// Use exact amount for small-value materials (0.1, 0.25, 0.55, etc)
final_value = value
else
// Large values: round to nearest 0 or 5
var/nearest5_value = round(value / 5) * 5
var/max_error = value * COMPARISION_ACCEPTABLE_MATERIAL_DEVIATION // 3%
if(abs(nearest5_value - value) <= max_error)
final_value = nearest5_value
else
final_value = value
var/final_sheet_multiplier = final_value / sheet_amount
return final_sheet_multiplier
#undef COMPARISION_ACCEPTABLE_MATERIAL_DEVIATION