mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-13 00:55:20 +01:00
21b4095dfd
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>
658 lines
28 KiB
Plaintext
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
|