SUBSYSTEM_DEF(materials) name = "Materials" subsystem_flags = NONE init_order = INIT_ORDER_MATERIALS wait = 2 SECONDS /// material recipes var/list/datum/stack_recipe/material/material_stack_recipes /// ticked atoms var/list/ticking = list() /// currentrun var/list/currentrun /// layered armor cache var/list/layered_armor_cache = list() /// combined armor cache var/list/combined_armor_cache = list() /// wall armor cache var/list/wall_armor_cache = list() /datum/controller/subsystem/materials/Initialize() initialize_material_recipes() return SS_INIT_SUCCESS /datum/controller/subsystem/materials/Recover() initialize_material_recipes() if(islist(SSmaterials.ticking)) // todo: better sanitization src.ticking = SSmaterials.ticking else src.ticking = list() return ..() /datum/controller/subsystem/materials/proc/add_ticked_object(atom/A) if(A.atom_flags & ATOM_MATERIALS_TICKING) return A.atom_flags |= ATOM_MATERIALS_TICKING ticking += A /datum/controller/subsystem/materials/proc/remove_ticked_object(atom/A) if(!(A.atom_flags & ATOM_MATERIALS_TICKING)) return A.atom_flags &= ~ATOM_MATERIALS_TICKING ticking -= A /datum/controller/subsystem/materials/fire(resumed) if(!resumed) src.currentrun = ticking.Copy() var/list/currentrun = src.currentrun var/atom/A var/dt = nominal_dt_s var/i var/datum/prototype/material_trait/trait for(i in length(currentrun) to 1 step -1) A = currentrun[A] if(length(A.material_traits)) for(trait as anything in A.material_traits) if(!(trait.material_trait_flags & MATERIAL_TRAIT_TICKING)) continue trait.tick(A, A.material_traits[trait], dt) else trait = A.material_traits trait.tick(A, A.material_traits_data, dt) if(MC_TICK_CHECK) break currentrun.len -= (length(currentrun) - i + 1) /datum/controller/subsystem/materials/proc/initialize_material_recipes() material_stack_recipes = list() for(var/path in subtypesof(/datum/stack_recipe/material)) var/datum/stack_recipe/material/this = path if(initial(this.abstract_type) == path) continue material_stack_recipes += new path /** * ensures a list is full of material ids for keys * * if key is not a valid material or is null, it will be nulled. */ /datum/controller/subsystem/materials/proc/preprocess_flat_keys_to_ids(list/L) // todo: optimize . = list() for(var/i in 1 to length(L)) var/datum/prototype/material/resolved = RSmaterials.fetch(L[i]) . += resolved?.id /** * ensures a list is full of material references for keys * * if key is not a valid material or is null, it will be nulled. */ /datum/controller/subsystem/materials/proc/preprocess_flat_keys_to_instances(list/L) // todo: optimize . = list() for(var/i in 1 to length(L)) var/datum/prototype/material/resolved = RSmaterials.fetch(L[i]) . += resolved /** * ensures a list is full of material ids for keys * * if key is not a valid material or is null, it will be dropped. * undefined behavior if there's duped ids / materials - this should never happen. */ /datum/controller/subsystem/materials/proc/preprocess_kv_keys_to_ids(list/L) // todo: optimize . = list() for(var/i in 1 to length(L)) var/datum/prototype/material/key = L[i] var/value = L[key] if(istype(key)) key = key.id else if(ispath(key)) key = initial(key.id) else if(istext(key)) else CRASH("what? '[key]'") .[key] = value /** * ensures a list is full of material references for keys * * if key is not a valid material or is null, it will be dropped. * undefined behavior if there's duped ids / materials - this should never happen. */ /datum/controller/subsystem/materials/proc/preprocess_kv_keys_to_instances(list/L) // todo: optimize . = list() for(var/i in 1 to length(L)) var/key = L[i] var/value = L[key] var/datum/prototype/material/resolved = RSmaterials.fetch_or_defer(key) switch(resolved) if(null) continue if(REPOSITORY_FETCH_DEFER) // todo: handle this continue .[resolved] = value /** * ensures a list is full of material ids for values * * if value is not a valid material or is null, it will be nulled. */ /datum/controller/subsystem/materials/proc/preprocess_kv_values_to_ids(list/L) // todo: optimize . = list() for(var/i in 1 to length(L)) var/key = L[i] var/datum/prototype/material/value = L[key] if(istype(value)) value = value.id else if(ispath(value)) value = initial(value.id) else if(istext(value)) else CRASH("what? '[value]'") .[key] = value /** * ensures a list is full of material references for values * * if value is not a valid material or is null, it will be nulled. */ /datum/controller/subsystem/materials/proc/preprocess_kv_values_to_instances(list/L) // todo: optimize . = list() for(var/i in 1 to length(L)) var/key = L[i] var/value = L[key] var/datum/prototype/material/resolved = RSmaterials.fetch_or_defer(value) switch(resolved) if(REPOSITORY_FETCH_DEFER) // todo: handle this else value = resolved .[key] = resolved /** * returns all material datums * * expensive (duh) use sparingly */ /datum/controller/subsystem/materials/proc/all_materials() RETURN_TYPE(/list) return RSmaterials.fetch_subtypes_immutable(/datum/prototype/material):Copy() /** * drop a material sheet */ /datum/controller/subsystem/materials/proc/drop_sheets(datum/prototype/material/id_or_path, amount, atom/where) var/datum/prototype/material/mat = RSmaterials.fetch(id_or_path) mat.place_sheet(where, amount) /** * ! DEPRECATED * todo: REMOVE * * DO NOT USE THIS PROC * Use RSmaterials.fetch()! */ /proc/get_material_by_name(name) if(istype(name, /datum/prototype/material)) return name return RSmaterials.legacy_material_lookup[name] /** * tgui materials context * * generates data for tgui/interfaces/common/Materials.tsx: * * MaterialsContext * * FullMaterialsContext * * @params * * ids - material ids. defaults to all. * * full - for FullMaterialsContext? usually not needed. */ /datum/controller/subsystem/materials/proc/tgui_materials_context(list/ids, full = FALSE) var/list/data = list() // a hack to make this default to all if not specified. for(var/id in ids || RSmaterials.id_lookup) var/datum/prototype/material/mat = RSmaterials.fetch(id) var/list/built = list( "name" = mat.display_name || mat.name, "id" = mat.id, "iconKey" = mat.tgui_icon_key, "sheetAmount" = SHEET_MATERIAL_AMOUNT, ) if(full) built["relative_integrity"] = mat.relative_integrity built["hardness"] = mat.hardness built["toughness"] = mat.toughness built["refraction"] = mat.refraction built["absorption"] = mat.absorption built["nullification"] = mat.nullification built["density"] = mat.density built["weight_multiplier"] = mat.weight_multiplier built["relative_conductivity"] = mat.relative_conductivity built["relative_reactivity"] = mat.relative_reactivity built["relative_permeability"] = mat.relative_permeability built["melting_point"] = mat.melting_point built["opacity"] = mat.opacity built["tags"] = mat.material_tags var/list/constraint_list = list() var/datum/bitfield_legacy/single/constraint_bf = new /datum/bitfield_legacy/single/material_constraints constraint_list += mat.material_constraints //Add the unified constraint for(var/key in constraint_bf.flags) if(constraint_bf.flags[key] & mat.material_constraints) constraint_list += constraint_bf.flags[key] //And the individual constraints. This is because we'll be using js' .Includes() to check if a constraint bitflag is present. //We do this instead of passing the direct flags to JS for the following reasons and using bitwise and because: //E.g if your material is MATERIAL_CONSTRAINT_RIGID | MATERIAL_CONSTRAINT_TRANSPARENT (e.g clear plastic) //you want it to pass checks for both MATERIAL_CONSTRAINT_RIGID, MATERIAL_CONSTRAINT_TRANSPARENT and (MATERIAL_CONSTRAINT_RIGID | MATERIAL_CONSTRAINT_TRANSPARENT) //TODO: Reconstruction of combined bitfields //Because if your material is RIGID | CONDUCTIVE | CRYSTALLINE //you should be able to use it in RIGID | CONDUCTIVE applications, but not RIGID | CONDUCTIVE | CRYSTALLINE | RADIOACTIVE //But right now, you can't. built["constraints"] = constraint_list data[mat.id] = built // todo: per-material sheetAmount return list( "materials" = data, "sheetAmount" = SHEET_MATERIAL_AMOUNT, )