Files
Bubberstation/code/game/atom/atom_materials.dm

380 lines
15 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
/// 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)
if(length(custom_materials))
remove_material_effects()
if(!length(materials))
custom_materials = null
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 list that was originally used as argument if it's ever going to be used again.
for(var/current_material in materials)
materials[current_material] *= multiplier
apply_material_effects(materials)
custom_materials = SSmaterials.FindOrCreateMaterialCombo(materials)
///proc responsible for applying material effects when setting materials.
/atom/proc/apply_material_effects(list/materials)
SHOULD_CALL_PARENT(TRUE)
if(!materials || !(material_flags & MATERIAL_EFFECTS))
return
var/list/material_effects = get_material_effects_list(materials)
finalize_material_effects(material_effects)
/// Proc responsible for removing material effects when setting materials.
/atom/proc/remove_material_effects()
SHOULD_CALL_PARENT(TRUE)
//Only runs if custom materials existed at first and affected src.
if(!custom_materials || !(material_flags & MATERIAL_EFFECTS))
return
var/list/material_effects = get_material_effects_list(custom_materials)
finalize_remove_material_effects(material_effects)
/atom/proc/get_material_effects_list(list/materials)
SHOULD_NOT_OVERRIDE(TRUE)
var/list/material_effects = list()
var/index = 1
for(var/current_material in materials)
var/datum/material/material = GET_MATERIAL_REF(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++
return material_effects
/**
* 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)
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 //the material with the highest amount (after calculations)
var/main_mat_amount
var/main_mat_mult
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]
if(mat_amount > main_mat_amount)
main_material = custom_material
main_mat_amount = mat_amount
main_mat_mult = multiplier
apply_single_mat_effect(custom_material, mat_amount, multiplier)
custom_material.on_applied(src, mat_amount, multiplier)
//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(custom_material.beauty_modifier)
AddElement(/datum/element/beauty, custom_material.beauty_modifier * mat_amount)
apply_main_material_effects(main_material, main_mat_amount, main_mat_mult)
if(material_flags & (MATERIAL_COLOR|MATERIAL_GREYSCALE))
var/init_alpha = initial(alpha)
var/alpha_value = (total_alpha / length(materials)) * init_alpha
if(alpha_value < init_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]"
/**
* 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))
continue
return path
///Apply material effects of a single material.
/atom/proc/apply_single_mat_effect(datum/material/custom_material, amount, multipier)
SHOULD_CALL_PARENT(TRUE)
return
///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, multipier)
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, multipier)
///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
var/main_mat_mult
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]
if(custom_material == main_material)
main_mat_amount = mat_amount
main_mat_mult = multiplier
remove_single_mat_effect(custom_material, mat_amount, multiplier)
custom_material.on_removed(src, mat_amount, multiplier)
if(material_flags & MATERIAL_COLOR)
gather_material_color(custom_material, colors, mat_amount, multicolor = mat_length > 1)
if(custom_material.beauty_modifier)
RemoveElement(/datum/element/beauty, custom_material.beauty_modifier * mat_amount)
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)
///Remove material effects of a single material.
/atom/proc/remove_single_mat_effect(datum/material/custom_material, amount, multipier)
SHOULD_CALL_PARENT(TRUE)
return
///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
/**
* 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()
. = list()
var/list/cached_materials = custom_materials
for(var/mat in cached_materials)
var/datum/material/material = GET_MATERIAL_REF(mat)
var/list/material_comp = material.return_composition(cached_materials[mat])
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 = GET_MATERIAL_REF(current_material)
if(!istype(material, required_material))
continue
LAZYSET(materials_of_type, material, cached_materials[current_material])
return materials_of_type
/**
* Fetches a list of all of the materials this object has with the desired material category.
*
* Arguments:
* - category: The category to check for
* - any_flags: Any bitflags that must be present for the category
* - all_flags: All bitflags that must be present for the category
* - no_flags: Any bitflags that must not be present for the category
* - mat_amount: The minimum amount of materials that must be present
*/
/atom/proc/has_material_category(category, any_flags=0, all_flags=0, no_flags=0, mat_amount=0)
var/list/cached_materials = custom_materials
if(!length(cached_materials))
return null
var/materials_of_category
for(var/current_material in cached_materials)
if(cached_materials[current_material] < mat_amount)
continue
var/datum/material/material = GET_MATERIAL_REF(current_material)
var/category_flags = material?.categories[category]
if(isnull(category_flags))
continue
if(any_flags && !(category_flags & any_flags))
continue
if(all_flags && (all_flags != (category_flags & all_flags)))
continue
if(no_flags && (category_flags & no_flags))
continue
LAZYSET(materials_of_category, material, cached_materials[current_material])
return materials_of_category
/**
* Gets the most common material in the object.
*/
/atom/proc/get_master_material()
var/list/cached_materials = custom_materials
if(!length(cached_materials))
return null
var/most_common_material = null
var/max_amount = 0
for(var/material in cached_materials)
if(cached_materials[material] > max_amount)
most_common_material = material
max_amount = cached_materials[material]
if(most_common_material)
return GET_MATERIAL_REF(most_common_material)
/**
* Gets the total amount of materials in this atom.
*/
/atom/proc/get_custom_material_amount()
return isnull(custom_materials) ? 0 : counterlist_sum(custom_materials)