mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-10 07:41:16 +01:00
13814fb55b
## About The Pull Request Closes #96062 ## Changelog 🆑 fix: Fixed materials runtiming whenever thrown by something other than a mob /🆑
375 lines
17 KiB
Plaintext
375 lines
17 KiB
Plaintext
/*! Material datum
|
|
|
|
Simple datum which is instanced once per type and is used for every object of said material. It has a variety of variables that define behavior. Subtyping from this makes it easier to create your own materials.
|
|
|
|
*/
|
|
|
|
|
|
/datum/material
|
|
abstract_type = /datum/material
|
|
/// What the material is referred to as IC.
|
|
var/name = "material"
|
|
/// A short description of the material. Not used anywhere, yet...
|
|
var/desc = "its..stuff."
|
|
/// What the material is indexed by in the SSmaterials.materials list. Defaults to the type of the material.
|
|
var/id = null
|
|
|
|
/// Bitflags that influence how SSmaterials handles this material.
|
|
var/init_flags = MATERIAL_INIT_MAPLOAD
|
|
/// Material behaviors, controls how the material is categorized and in what recipes it can be used
|
|
var/mat_flags = NONE
|
|
/// List of material property IDs to their values, 0 - 10
|
|
var/mat_properties = null
|
|
/// Flags for which comsigs we should track/send
|
|
/// These exist for performance reasons as to avoid unnecessary work on materials without properties that trigger off these, or doing the same checks for each property
|
|
var/track_flags = NONE
|
|
|
|
// Color values
|
|
/// Base color of the material, for items that don't have greyscale configs nor are made of multiple materials. Item isn't changed in color if this is null.
|
|
/// This can be a RGB or color matrix, but it cannot be RGBA as alpha is automatically filled in.
|
|
var/color = null
|
|
/**
|
|
* If the color is a color matrix and either the item uses greyscale configs or is made of multiple colored materials. This will be used instead because
|
|
* neither greyscale configs nor BlendRGB() support color matrices.
|
|
* Also this has to be RRGGBB, six characters, no alpha channel as it's automatically filled in.
|
|
*
|
|
* Basically, set this if the color is a color matrix (list)
|
|
*/
|
|
var/greyscale_color = null
|
|
/// Base alpha of the material
|
|
var/alpha = 255
|
|
/// Starlight color of the material
|
|
/// This is the color of light it'll emit if its turf is transparent and over space. Defaults to GLOB.starlight_color if not set
|
|
var/starlight_color = null
|
|
|
|
// Trading values
|
|
/// This is the amount of value per 1 unit of the material
|
|
var/value_per_unit = 0
|
|
/// This is the minimum value of the material, used in the stock market for any mat that isn't set to null
|
|
var/minimum_value_override = null
|
|
/// Is this material traded on the stock market?
|
|
var/tradable = FALSE
|
|
/// If this material is tradable, what is the base quantity of the material on the stock market?
|
|
var/tradable_base_quantity = 0
|
|
|
|
// Associated item types
|
|
/// The type of sheet this material creates.
|
|
var/sheet_type = null
|
|
/// What type of ore is this material associated with? Used for mining, and not every material has one.
|
|
var/obj/item/ore_type = null
|
|
/// What type of shard the material will shatter to
|
|
var/obj/item/shard_type = null
|
|
/// What type of debris the tile will leave behind when shattered.
|
|
var/obj/effect/decal/debris_type = null
|
|
/// Reagent type(s) of this material. Can be a reagent typepath or a list.
|
|
var/list/material_reagent = null
|
|
|
|
// Misc stats
|
|
/// How resistant the material is to rusting when applied to a turf
|
|
var/mat_rust_resistance = RUST_RESISTANCE_ORGANIC
|
|
/// How likely this mineral is to be found in a boulder during mining.
|
|
var/mineral_rarity = MATERIAL_RARITY_COMMON
|
|
/// How many points per units of ore does this grant? This is used by ore in the ORM, NOT for boulder machinery due to the automated nature.
|
|
var/points_per_unit = 1
|
|
/// How many points per unit does this ore grant when processed by a smelter/refinery?
|
|
var/points_per_boulder_unit = 1
|
|
|
|
// Sound/icon stats, not inherited
|
|
/// Can be used to override the sound items make, lets add some SLOSHing.
|
|
var/item_sound_override = null
|
|
/// Can be used to override the stepsound a turf makes. MORE SLOOOSH
|
|
var/turf_sound_override = null
|
|
/// What texture icon state to overlay
|
|
var/texture_layer_icon_state = null
|
|
/// A cached icon for the texture filter
|
|
var/icon/cached_texture_filter_icon = null
|
|
|
|
/** Handles initializing the material.
|
|
*
|
|
* Arguments:
|
|
* - _id: The ID the material should use. Overrides the existing ID.
|
|
*/
|
|
/datum/material/proc/Initialize(_id, ...)
|
|
if(_id)
|
|
id = _id
|
|
else if(isnull(id))
|
|
id = type
|
|
|
|
if(texture_layer_icon_state)
|
|
cached_texture_filter_icon = icon('icons/turf/composite.dmi', texture_layer_icon_state)
|
|
|
|
for (var/prop_id in mat_properties)
|
|
var/datum/material_property/property = SSmaterials.properties[prop_id]
|
|
property.attach_to(src)
|
|
|
|
return TRUE
|
|
|
|
/// This proc is called when the material is added to an object.
|
|
/// Can be called even if the material is covered by a slot
|
|
/datum/material/proc/on_applied(atom/source, mat_amount, multiplier, from_slot)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
SEND_SIGNAL(src, COMSIG_MATERIAL_APPLIED, source, mat_amount, multiplier, from_slot)
|
|
|
|
if (!(source.material_flags & MATERIAL_EFFECTS) || from_slot)
|
|
return
|
|
|
|
if (track_flags & MATERIAL_TRACK_CONTACT)
|
|
var/static/list/turf_interactions = typecacheof(list(
|
|
/turf/open,
|
|
/obj/structure/platform,
|
|
/obj/structure/table,
|
|
/obj/structure/rack,
|
|
/obj/structure/bed,
|
|
/obj/structure/closet/crate,
|
|
/obj/structure/reagent_dispensers,
|
|
/obj/structure/altar,
|
|
))
|
|
if (is_type_in_typecache(source, turf_interactions))
|
|
source.AddComponent(/datum/component/material_turf_tracking, src)
|
|
|
|
if (isclosedturf(source))
|
|
RegisterSignal(source, COMSIG_LIVING_DISARM_COLLIDE, PROC_REF(on_wall_shove_collide))
|
|
|
|
if (track_flags & MATERIAL_TRACK_IMPACT)
|
|
RegisterSignal(source, COMSIG_MOVABLE_IMPACT, PROC_REF(on_throw_impact))
|
|
RegisterSignal(source, COMSIG_MOVABLE_IMPACT_ZONE, PROC_REF(on_throw_impact_living))
|
|
RegisterSignal(source, COMSIG_ITEM_ATTACK, PROC_REF(on_item_attack))
|
|
RegisterSignal(source, COMSIG_ITEM_ATTACK_ATOM, PROC_REF(on_item_attack))
|
|
// Allow recipe crafting for stack items
|
|
if(!isstack(source))
|
|
RegisterSignal(source, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_item_attack_self))
|
|
RegisterSignal(source, COMSIG_ITEM_ATTACK_ZONE, PROC_REF(on_item_attack_living))
|
|
|
|
/// This proc is called when the material becomes the one the object is composed of the most
|
|
/// Only called when the material isn't assigned to a slot
|
|
/datum/material/proc/on_main_applied(atom/source, mat_amount, multiplier)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
SEND_SIGNAL(src, COMSIG_MATERIAL_MAIN_APPLIED, source, mat_amount, multiplier)
|
|
|
|
/// This proc is called when the material is removed from an object.
|
|
/datum/material/proc/on_removed(atom/source, mat_amount, material_flags, from_slot)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
SEND_SIGNAL(src, COMSIG_MATERIAL_REMOVED, source, mat_amount, material_flags, from_slot)
|
|
|
|
if (!(source.material_flags & MATERIAL_EFFECTS))
|
|
return
|
|
|
|
if (track_flags & MATERIAL_TRACK_CONTACT)
|
|
var/static/list/material_signals = list(
|
|
COMSIG_LIVING_DISARM_COLLIDE,
|
|
)
|
|
UnregisterSignal(source, material_signals)
|
|
var/list/tracker_components = source.GetComponents(/datum/component/material_turf_tracking)
|
|
if (istype(tracker_components, /datum/component/material_turf_tracking))
|
|
qdel(tracker_components)
|
|
else
|
|
// Delete all trackers linked to ourselves (might be multiple if we've got multiple slots going on)
|
|
for (var/datum/component/material_turf_tracking/tracker as anything in tracker_components)
|
|
if (tracker.owner_material == src)
|
|
qdel(tracker)
|
|
|
|
if (track_flags & MATERIAL_TRACK_IMPACT)
|
|
var/static/list/material_signals = list(
|
|
COMSIG_MOVABLE_IMPACT,
|
|
COMSIG_MOVABLE_IMPACT_ZONE,
|
|
COMSIG_ITEM_ATTACK,
|
|
COMSIG_ITEM_ATTACK_ATOM,
|
|
COMSIG_ITEM_ATTACK_SELF,
|
|
COMSIG_ITEM_ATTACK_ZONE,
|
|
)
|
|
UnregisterSignal(source, material_signals)
|
|
|
|
/// This proc is called when the material is no longer the one the object is composed by the most
|
|
/datum/material/proc/on_main_removed(atom/source, mat_amount, multiplier)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
SEND_SIGNAL(src, COMSIG_MATERIAL_MAIN_REMOVED, source, mat_amount, multiplier)
|
|
|
|
/datum/material/proc/on_wall_shove_collide(turf/closed/source, mob/living/shover, mob/living/target, shove_flags, obj/item/weapon)
|
|
SIGNAL_HANDLER
|
|
|
|
// If any part is exposed it makes sense it'd impact the wall
|
|
var/skin_contact = HEAD|CHEST|GROIN|LEGS|FEET|ARMS|HANDS
|
|
for (var/obj/item/worn_item in target.get_equipped_items(INCLUDE_ABSTRACT))
|
|
skin_contact &= ~worn_item.body_parts_covered
|
|
if (!skin_contact)
|
|
break
|
|
|
|
SEND_SIGNAL(src, COMSIG_MATERIAL_EFFECT_TOUCH, source, target, shover, null, !!skin_contact)
|
|
|
|
/datum/material/proc/on_item_attack(obj/item/source, mob/living/target, mob/living/user)
|
|
SIGNAL_HANDLER
|
|
impact_affect_touch(source, user, user)
|
|
// Living mobs use a different signal
|
|
if (!isliving(target))
|
|
impact_affect_target(source, target, user)
|
|
|
|
/datum/material/proc/on_item_attack_living(obj/item/source, mob/living/target, mob/living/user, def_zone)
|
|
SIGNAL_HANDLER
|
|
var/skin_contact = body_zone2cover_flags(def_zone)
|
|
for (var/obj/item/worn_item in target.get_equipped_items(INCLUDE_ABSTRACT))
|
|
skin_contact &= ~worn_item.body_parts_covered
|
|
if (!skin_contact)
|
|
break
|
|
|
|
impact_affect_target(source, target, user, def_zone, !!skin_contact)
|
|
|
|
/datum/material/proc/on_item_attack_self(obj/item/source, mob/living/user)
|
|
SIGNAL_HANDLER
|
|
impact_affect_touch(source, user, user)
|
|
|
|
/datum/material/proc/on_throw_impact(obj/item/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught)
|
|
SIGNAL_HANDLER
|
|
if (caught)
|
|
impact_affect_touch(source, hit_atom, astype(throwing_datum.thrower?.resolve(), /mob/living))
|
|
else if (!isliving(hit_atom)) // Hit mobs have armor checking
|
|
impact_affect_throw_impact(source, hit_atom, astype(throwing_datum.thrower?.resolve(), /mob/living))
|
|
|
|
/datum/material/proc/on_throw_impact_living(obj/item/source, mob/living/target, def_zone, blocked, datum/thrownthing/throwing_datum)
|
|
SIGNAL_HANDLER
|
|
|
|
var/skin_contact = body_zone2cover_flags(def_zone)
|
|
for (var/obj/item/worn_item in target.get_equipped_items(INCLUDE_ABSTRACT))
|
|
skin_contact &= ~worn_item.body_parts_covered
|
|
if (!skin_contact)
|
|
break
|
|
|
|
impact_affect_throw_impact(source, target, astype(throwing_datum.thrower?.resolve(), /mob/living), def_zone, !!skin_contact)
|
|
|
|
/datum/material/proc/impact_affect_touch(obj/item/source, mob/living/user, mob/living/initiator)
|
|
var/arm_dir = IS_LEFT_INDEX(user.active_hand_index) ? BODY_ZONE_L_ARM : BODY_ZONE_R_ARM
|
|
if (!ishuman(user))
|
|
SEND_SIGNAL(src, COMSIG_MATERIAL_EFFECT_TOUCH, source, user, initiator, arm_dir, TRUE)
|
|
return
|
|
|
|
var/mob/living/carbon/human/as_human = user
|
|
var/obj/item/bodypart/hand = as_human.has_hand_for_held_index(as_human.get_held_index_of_item(source))
|
|
if (!hand)
|
|
SEND_SIGNAL(src, COMSIG_MATERIAL_EFFECT_TOUCH, source, user, initiator, arm_dir, FALSE)
|
|
return
|
|
|
|
var/list/obj/item/hand_covers = as_human.get_clothing_on_part(hand)
|
|
var/hand_covered = FALSE
|
|
for (var/obj/item/worn_item in hand_covers)
|
|
if (worn_item.body_parts_covered & HANDS)
|
|
hand_covered = TRUE
|
|
break
|
|
|
|
SEND_SIGNAL(src, COMSIG_MATERIAL_EFFECT_TOUCH, source, user, initiator, hand.body_zone, !hand_covered)
|
|
|
|
/datum/material/proc/impact_affect_target(obj/item/source, atom/target, mob/living/user, def_zone, skin_contact = TRUE)
|
|
SEND_SIGNAL(src, COMSIG_MATERIAL_EFFECT_HIT, source, target, user, def_zone, skin_contact)
|
|
|
|
/datum/material/proc/impact_affect_throw_impact(obj/item/source, atom/target, mob/living/user, def_zone, skin_contact = TRUE)
|
|
SEND_SIGNAL(src, COMSIG_MATERIAL_EFFECT_THROW_IMPACT, source, target, user, def_zone, skin_contact)
|
|
|
|
/datum/material/proc/setup_glow(turf/on)
|
|
if(GET_TURF_PLANE_OFFSET(on) != GET_LOWEST_STACK_OFFSET(on.z)) // We ain't the bottom brother
|
|
return
|
|
// We assume no parallax means no space means no light
|
|
if(SSmapping.level_trait(on.z, ZTRAIT_NOPARALLAX))
|
|
return
|
|
if(!starlight_color)
|
|
on.RegisterSignal(SSdcs, COMSIG_STARLIGHT_COLOR_CHANGED, TYPE_PROC_REF(/turf, material_starlight_changed))
|
|
RegisterSignal(on, COMSIG_QDELETING, PROC_REF(lit_turf_deleted))
|
|
on.set_light(2, 1, starlight_color || GLOB.starlight_color, l_height = LIGHTING_HEIGHT_SPACE)
|
|
|
|
/turf/proc/material_starlight_changed(datum/source, old_star, new_star)
|
|
if(light_color == old_star)
|
|
set_light_color(new_star)
|
|
|
|
/datum/material/proc/lit_turf_deleted(turf/source)
|
|
source.set_light(0, 0, null)
|
|
|
|
|
|
////Called in `/datum/component/edible/proc/on_material_effects`
|
|
/datum/material/proc/on_edible_applied(atom/source, datum/component/edible/edible)
|
|
return
|
|
|
|
////Called in `/datum/component/edible/proc/on_remove_material_effects`
|
|
/datum/material/proc/on_edible_removed(atom/source, datum/component/edible/edible)
|
|
return
|
|
|
|
/**
|
|
* This proc is called when the mat is found in an item that's consumed by accident. see /obj/item/proc/on_accidental_consumption.
|
|
* Arguments
|
|
* * M - person consuming the mat
|
|
* * S - (optional) item the mat is contained in (NOT the item with the mat itself)
|
|
*/
|
|
/datum/material/proc/on_accidental_mat_consumption(mob/living/carbon/victim, obj/item/source_item)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
if (!material_reagent)
|
|
return FALSE
|
|
|
|
var/effect_multiplier = source_item.custom_materials[type] / SHEET_MATERIAL_AMOUNT
|
|
if (!islist(material_reagent))
|
|
victim.reagents?.add_reagent(material_reagent, rand(6, 8) * effect_multiplier)
|
|
source_item?.reagents?.add_reagent(material_reagent, source_item.reagents.total_volume * MATERIAL_REAGENT_CONSUMPTION_MULT * effect_multiplier)
|
|
return TRUE
|
|
|
|
for (var/datum/reagent/reagent_type as anything in material_reagent)
|
|
var/amount_mult = material_reagent[reagent_type] / length(material_reagent)
|
|
victim.reagents?.add_reagent(material_reagent, rand(6, 8) * effect_multiplier * amount_mult)
|
|
source_item?.reagents?.add_reagent(material_reagent, source_item.reagents.total_volume * MATERIAL_REAGENT_CONSUMPTION_MULT * effect_multiplier * amount_mult)
|
|
return TRUE
|
|
|
|
/** Returns the composition of this material.
|
|
*
|
|
* Mostly used for alloys when breaking down materials.
|
|
*
|
|
* Arguments:
|
|
* - amount: The amount of the material to break down.
|
|
*/
|
|
/datum/material/proc/return_composition(amount = 1, flags)
|
|
// Yes we need the parenthesis, without them BYOND stringifies src into "src" and things break.
|
|
return list((src) = amount)
|
|
|
|
///Returns the list of armor modifiers, with each element having its assoc value multiplied by the multiplier arg
|
|
/datum/material/proc/get_armor_modifiers(multiplier)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
var/density = get_property(MATERIAL_DENSITY)
|
|
var/hardness = get_property(MATERIAL_HARDNESS)
|
|
var/flexibility = get_property(MATERIAL_FLEXIBILITY)
|
|
var/reflectivity = get_property(MATERIAL_REFLECTIVITY)
|
|
var/electric = get_property(MATERIAL_ELECTRICAL)
|
|
var/thermal = get_property(MATERIAL_THERMAL)
|
|
var/chemical = get_property(MATERIAL_CHEMICAL)
|
|
var/flammability = get_property(MATERIAL_FLAMMABILITY) // Optional, might be not present
|
|
// Welcome to hell
|
|
var/list/armor_modifiers = list(
|
|
// Based on density, with a bonus/malus for matching flexibility
|
|
// We cap divergence at 4 (reduced by hardness above 6 for REALLY dense stuff) to make it not extremely punishing on light fabrics or heavy materials
|
|
// Iron at a baseline of density of 6 and flexibility of 4 has divergence of 4, so (1 + 0.2) / (0.8 + 0.4) = 1
|
|
MELEE = (1 + (density - 4) * 0.1) / (0.8 + min(4 - max(0, hardness - 6), abs(flexibility - density)) * 0.1),
|
|
// Hardness and density, with flexibility actually being detrimental
|
|
BULLET = (1 + (density - 4) * 0.025 + (hardness - 4) * 0.075) / (1 - max(0, flexibility - 2) * 0.1),
|
|
// 0.6 ~ 1 for reflectivity below 4, 1 ~ 1.4 for reflectivity above 6, reduced for transparent materials
|
|
LASER = 1 + MATERIAL_PROPERTY_DIVERGENCE(reflectivity, 4, 6) * 0.1 - (255 - alpha) / 50 * 0.2,
|
|
// Essentially laser but with contribution split between reflectivity and inverse electric conductivity
|
|
// Here reflectivity applies if its below 4 or above 8, and conductivity if its below 4 or above 6
|
|
ENERGY = 1 + MATERIAL_PROPERTY_DIVERGENCE(reflectivity, 4, 8) * 0.05 - MATERIAL_PROPERTY_DIVERGENCE(electric, 4, 6) * 0.1,
|
|
// Linearly scales from 0.2 to 1.8 with density
|
|
BOMB = 1 + (density - 4) * 0.2,
|
|
// Each level of flammability reduces FIRE armor by 20%, with thermal conductivity reducing it by further 20% for each level above 6 and increasing for each level below 2
|
|
FIRE = max(0, 1 - max(0, (thermal - 6) * 0.2) + max(0, (2 - thermal) * 0.2) - flammability * 0.2),
|
|
// Linearly scales from 0.2 to 1.8 with chemical resistance
|
|
ACID = 1 + (chemical - 4) * 0.2,
|
|
)
|
|
|
|
for (var/armor_key in armor_modifiers)
|
|
// Safety check to ensure that we don't have inverted armor values
|
|
if (armor_modifiers[armor_key] < 0)
|
|
armor_modifiers[armor_key] = 0
|
|
armor_modifiers[armor_key] = GET_MATERIAL_MODIFIER(armor_modifiers[armor_key], multiplier)
|
|
|
|
return armor_modifiers
|
|
|
|
/datum/material/proc/get_property(prop_id)
|
|
if (!isnull(mat_properties?[prop_id]))
|
|
return mat_properties[prop_id]
|
|
|
|
var/datum/material_property/derived/derived_prop = SSmaterials.properties[prop_id]
|
|
if (!istype(derived_prop))
|
|
return null // Property was not specified on the material and wasn't a derived one
|
|
return derived_prop.get_value(src)
|