/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