diff --git a/_maps/RandomZLevels/heretic.dmm b/_maps/RandomZLevels/heretic.dmm index 831b608268a..69266c7c2e3 100644 --- a/_maps/RandomZLevels/heretic.dmm +++ b/_maps/RandomZLevels/heretic.dmm @@ -7777,7 +7777,7 @@ /obj/effect/decal/cleanable/blood/tracks/xeno, /obj/item/organ/tail/xeno, /obj/item/stack/sheet/animalhide/xeno, -/obj/item/stack/sheet/xenochitin, +/obj/item/stack/sheet/animalhide/xeno, /obj/effect/decal/cleanable/blood/gibs/xeno/larva/body, /obj/effect/decal/remains/xeno, /obj/effect/light_emitter, diff --git a/code/__DEFINES/construction/material.dm b/code/__DEFINES/construction/material.dm index 310ebc4d02d..7e5c6d6263a 100644 --- a/code/__DEFINES/construction/material.dm +++ b/code/__DEFINES/construction/material.dm @@ -74,10 +74,21 @@ GLOBAL_LIST_INIT(material_flags_to_string, alist( // Optional material property IDs #define MATERIAL_FLAMMABILITY "flammability" #define MATERIAL_RADIOACTIVITY "radioactivity" +#define MATERIAL_FIRESTACKER "firestacker" +#define MATERIAL_VAMPIRES_BANE "vampires_bane" +#define MATERIAL_TELEPORTING "teleporting" +#define MATERIAL_PENETRATING "penetrating" // Derived material property IDs #define MATERIAL_INTEGRITY "integrity" #define MATERIAL_BEAUTY "beauty" +#define MATERIAL_INSULATION "insulation" + +// Flags for signal tracking on materials +/// Send COMSIG_MATERIAL_EFFECT_X whenever a mob steps onto a material atom/is shoved into this material wall +#define MATERIAL_TRACK_CONTACT (1 << 0) +/// Send COMSIG_MATERIAL_EFFECT_X whenever a mob is hit by/hits/catches the material atom +#define MATERIAL_TRACK_IMPACT (1 << 1) /// Maximum value for a core material property #define MATERIAL_PROPERTY_MAX 10 @@ -170,5 +181,7 @@ GLOBAL_LIST_INIT(material_flags_to_string, alist( #define MATERIAL_LIST_OPTIMAL_AMOUNT "optimal_amount" /// The key to access the multiplier used to selectively control effects and modifiers of a material. #define MATERIAL_LIST_MULTIPLIER "multiplier" +/// Key controlling which material slots a material is assigned to +#define MATERIAL_LIST_SLOTS "slot" /// A macro that ensures some multiplicative modifiers higher than 1 don't become lower than 1 and vice-versa because of the multiplier. #define GET_MATERIAL_MODIFIER(modifier, multiplier) (modifier >= 1 ? 1 + ((modifier) - 1) * (multiplier) : (modifier)**(multiplier)) diff --git a/code/__DEFINES/dcs/signals/signals_materials.dm b/code/__DEFINES/dcs/signals/signals_materials.dm index 2eefb523548..c99b8f1f56c 100644 --- a/code/__DEFINES/dcs/signals/signals_materials.dm +++ b/code/__DEFINES/dcs/signals/signals_materials.dm @@ -8,3 +8,13 @@ #define COMSIG_MATERIAL_REMOVED "material_removed" /// from /datum/material/proc/on_main_removed(source, mat_amount, multiplier): (atom/old_atom, mat_amount, multiplier) #define COMSIG_MATERIAL_MAIN_REMOVED "material_main_removed" + +// Material property effect triggers +/// When a material is touched by an atom: (datum/material/source, atom/object, atom/target, mob/living/initiator, def_zone, skin_contact) +#define COMSIG_MATERIAL_EFFECT_TOUCH "material_effect_touch" +/// When a material is stepped onto: (datum/material/source, atom/object, atom/target, mob/living/initiator, def_zone, skin_contact) +#define COMSIG_MATERIAL_EFFECT_STEP "material_effect_step" +/// When a material hits something: (datum/material/source, atom/object, atom/target, mob/living/user, def_zone, skin_contact) +#define COMSIG_MATERIAL_EFFECT_HIT "material_effect_hit" +/// When a material hits something when thrown: (datum/material/source, atom/object, atom/target, mob/living/thrower, def_zone, skin_contact) +#define COMSIG_MATERIAL_EFFECT_THROW_IMPACT "material_effect_throw_impact" diff --git a/code/controllers/subsystem/materials.dm b/code/controllers/subsystem/materials.dm index 0bbeb16fbe3..b4bf65a501c 100644 --- a/code/controllers/subsystem/materials.dm +++ b/code/controllers/subsystem/materials.dm @@ -35,6 +35,8 @@ SUBSYSTEM_DEF(materials) var/list/datum/material_property/properties /// A typepath -> instance list of material requirements var/list/datum/material_requirement/requirements + /// A typepath -> instance list of material slots + var/list/datum/material_slot/material_slots ///Ran on initialize, populated the materials and material dictionaries with their appropriate vars (See these variables for more info) /datum/controller/subsystem/materials/proc/initialize_materials() @@ -51,6 +53,10 @@ SUBSYSTEM_DEF(materials) for(var/datum/material_requirement/requirement_type as anything in valid_subtypesof(/datum/material_requirement)) requirements[requirement_type] = new requirement_type() + material_slots = list() + for(var/datum/material_slot/slot_type as anything in valid_subtypesof(/datum/material_slot)) + material_slots[slot_type] = new slot_type() + for(var/datum/material/mat_type as anything in valid_subtypesof(/datum/material)) if(initial(mat_type.init_flags) & MATERIAL_INIT_MAPLOAD) initialize_material(mat_type) @@ -188,7 +194,7 @@ SUBSYSTEM_DEF(materials) if(!combo) combo = list() for(var/mat in materials_declaration) - combo[SSmaterials.get_material(mat)] = OPTIMAL_COST(materials_declaration[mat] * multiplier) + combo[get_material(mat)] = OPTIMAL_COST(materials_declaration[mat] * multiplier) material_combos[combo_index] = combo return combo diff --git a/code/datums/components/crafting/melee_weapon.dm b/code/datums/components/crafting/melee_weapon.dm index 9f0fab50964..00ced214f81 100644 --- a/code/datums/components/crafting/melee_weapon.dm +++ b/code/datums/components/crafting/melee_weapon.dm @@ -145,6 +145,16 @@ time = 4 SECONDS category = CAT_WEAPON_MELEE +/datum/crafting_recipe/wireprod + name = "Wireprod assembly" + result = /obj/item/wireprod + reqs = list( + /obj/item/restraints/handcuffs/cable = 1, + /obj/item/stack/rods = 1, + ) + time = 2 SECONDS + category = CAT_WEAPON_MELEE + /datum/crafting_recipe/toysword name = "Toy Sword" reqs = list( diff --git a/code/datums/components/material_turf_tracking.dm b/code/datums/components/material_turf_tracking.dm new file mode 100644 index 00000000000..492b4d0f495 --- /dev/null +++ b/code/datums/components/material_turf_tracking.dm @@ -0,0 +1,203 @@ +/// Sends a signal to the owner material whenever something enters its objects' turf and steps onto said object +/datum/component/material_turf_tracking + dupe_mode = COMPONENT_DUPE_ALLOWED + /// Material we're linked to + var/datum/material/owner_material = null + /// Does our parent require the target to be elevated for us to trigger? + var/requires_elevation = FALSE + + /// Typecache of things we should ignore + var/static/list/interaction_blacklist = typecacheof(list( + /obj/docking_port, + /obj/effect/abstract, + /obj/effect/atmos_shield, + /obj/effect/collapse, + /obj/effect/constructing_effect, + /obj/effect/dummy/phased_mob, + /obj/effect/ebeam, + /obj/effect/fishing_float, + /obj/effect/hotspot, + /obj/effect/landmark, + /obj/effect/light_emitter/tendril, + /obj/effect/mapping_helpers, + /obj/effect/particle_effect/ion_trails, + /obj/effect/particle_effect/sparks, + /obj/effect/portal, + /obj/effect/projectile, + /obj/effect/spectre_of_resurrection, + /obj/effect/temp_visual, + /obj/effect/wisp, + /obj/energy_ball, + /obj/narsie, + /obj/singularity, + )) + + /// Typecache of objects which we only consider "touched" when they elevate the mob, or the mob is buckled to them + /// Easy way to keep track of snowflake behavior like flipped tables + var/static/list/elevation_interactions = typecacheof(list( + /obj/structure/platform, + /obj/structure/table, + /obj/structure/rack, + /obj/structure/bed, + /obj/structure/closet/crate, + /obj/structure/reagent_dispensers, + /obj/structure/altar, + )) + +/datum/component/material_turf_tracking/Initialize(datum/material/owner_material) + if (!isopenturf(parent) && !isobj(parent)) + return COMPONENT_INCOMPATIBLE + src.owner_material = owner_material + if (is_type_in_typecache(parent, elevation_interactions)) + requires_elevation = TRUE + +/datum/component/material_turf_tracking/Destroy(force) + owner_material = null + return ..() + +/datum/component/material_turf_tracking/RegisterWithParent() + var/turf/target_turf = parent + if (ismovable(parent)) + var/atom/movable/as_movable = parent + RegisterSignal(as_movable, COMSIG_ATOM_ENTERING, PROC_REF(on_source_entering)) + RegisterSignal(as_movable, COMSIG_ATOM_EXITING, PROC_REF(on_source_exiting)) + target_turf = as_movable.loc + + if (!isopenturf(target_turf)) + return + + if (!requires_elevation) + RegisterSignal(target_turf, SIGNAL_ADDTRAIT(TRAIT_ELEVATED_TURF), PROC_REF(on_turf_lost)) + RegisterSignal(target_turf, SIGNAL_REMOVETRAIT(TRAIT_ELEVATED_TURF), PROC_REF(on_turf_gained)) + if (HAS_TRAIT(target_turf, TRAIT_ELEVATED_TURF)) + return + + // Not tracking initializations or existing objects as this would allow you to TP someone from plating by placing a tile underneath + RegisterSignal(target_turf, COMSIG_ATOM_ENTERED, PROC_REF(on_entered)) + RegisterSignal(target_turf, COMSIG_TURF_MOVABLE_THROW_LANDED, PROC_REF(on_entered)) // Need this as shoves are 1 tile throws, and COMSIG_ATOM_ENTERED runs before the throw ends + RegisterSignal(target_turf, COMSIG_ATOM_EXITED, PROC_REF(on_exited)) + +/datum/component/material_turf_tracking/UnregisterFromParent() + . = ..() + if (isturf(parent)) + on_source_exiting(parent) + return + + var/atom/movable/as_movable = parent + UnregisterSignal(as_movable, list(COMSIG_ATOM_ENTERING, COMSIG_ATOM_EXITING)) + on_source_exiting(as_movable.loc) + +/datum/component/material_turf_tracking/proc/on_source_entering(atom/movable/source, atom/entering, atom/old_loc) + SIGNAL_HANDLER + + if (!isopenturf(entering)) + return + + if (!requires_elevation) + RegisterSignal(entering, SIGNAL_ADDTRAIT(TRAIT_ELEVATED_TURF), PROC_REF(on_turf_lost)) + RegisterSignal(entering, SIGNAL_REMOVETRAIT(TRAIT_ELEVATED_TURF), PROC_REF(on_turf_gained)) + if (HAS_TRAIT(entering, TRAIT_ELEVATED_TURF)) + return + on_turf_gained(entering) + +/datum/component/material_turf_tracking/proc/on_source_exiting(atom/movable/source, atom/exiting) + SIGNAL_HANDLER + + if (!isturf(exiting)) + return + + UnregisterSignal(exiting, list(SIGNAL_ADDTRAIT(TRAIT_ELEVATED_TURF), SIGNAL_REMOVETRAIT(TRAIT_ELEVATED_TURF))) + on_turf_lost(exiting) + +/datum/component/material_turf_tracking/proc/on_turf_gained(turf/source) + SIGNAL_HANDLER + + RegisterSignal(source, COMSIG_ATOM_ENTERED, PROC_REF(on_entered)) + RegisterSignal(source, COMSIG_TURF_MOVABLE_THROW_LANDED, PROC_REF(on_entered)) + RegisterSignal(source, COMSIG_ATOM_EXITED, PROC_REF(on_exited)) + for (var/atom/movable/thing in source) + on_entered(source, thing) + +/datum/component/material_turf_tracking/proc/on_turf_lost(turf/source) + SIGNAL_HANDLER + + UnregisterSignal(source, list(COMSIG_ATOM_ENTERED, COMSIG_TURF_MOVABLE_THROW_LANDED, COMSIG_ATOM_EXITED)) + for (var/atom/movable/thing in source) + UnregisterSignal(thing, list(SIGNAL_ADDTRAIT(TRAIT_MOB_ELEVATED), COMSIG_MOVETYPE_FLAG_DISABLED)) + +/datum/component/material_turf_tracking/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) + SIGNAL_HANDLER + + if (arrived.throwing || arrived.invisibility >= INVISIBILITY_ABSTRACT || arrived == parent) + return + + if (is_type_in_typecache(arrived, interaction_blacklist)) + return + + if (!isliving(arrived) && requires_elevation) + return + + // Its floating but it may touch down + if (arrived.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) + RegisterSignal(arrived, COMSIG_MOVETYPE_FLAG_DISABLED, PROC_REF(on_move_flag_disabled)) + return + + if (!isliving(arrived)) + trigger_effect(arrived) + return + + if (requires_elevation) + // We want to know when they touch down so we can interact with them + RegisterSignal(arrived, SIGNAL_ADDTRAIT(TRAIT_MOB_ELEVATED), PROC_REF(on_mob_elevated)) + // The trait is kept even if the mob is buckled which is weird but plays into our hand here + if (!HAS_TRAIT(arrived, TRAIT_MOB_ELEVATED)) + return + + trigger_effect(arrived) + +/datum/component/material_turf_tracking/proc/on_exited(datum/source, atom/movable/gone) + SIGNAL_HANDLER + + UnregisterSignal(gone, list(SIGNAL_ADDTRAIT(TRAIT_MOB_ELEVATED), COMSIG_MOVETYPE_FLAG_DISABLED)) + +/datum/component/material_turf_tracking/proc/trigger_effect(atom/movable/arrived) + if (!isliving(arrived)) + SEND_SIGNAL(owner_material, COMSIG_MATERIAL_EFFECT_STEP, parent, arrived, null, null, FALSE) + return + + var/mob/living/victim = arrived + var/skin_contact = FEET + if (victim.body_position == LYING_DOWN) + skin_contact = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + + for (var/obj/item/worn_item in victim.get_equipped_items(INCLUDE_ABSTRACT)) + skin_contact &= ~worn_item.body_parts_covered + if (!skin_contact) + break + + SEND_SIGNAL(owner_material, COMSIG_MATERIAL_EFFECT_STEP, parent, arrived, null, pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG), !!skin_contact) + +/datum/component/material_turf_tracking/proc/on_mob_elevated(mob/living/source, trait) + if (source.throwing || source.invisibility >= INVISIBILITY_ABSTRACT || (source.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)) + return + + if (is_type_in_typecache(source, interaction_blacklist)) + return + + trigger_effect(source) + +/datum/component/material_turf_tracking/proc/on_move_flag_disabled(atom/movable/source, flag, old_state) + if (source.throwing || source.invisibility >= INVISIBILITY_ABSTRACT || (source.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)) + return + + if (is_type_in_typecache(source, interaction_blacklist)) + return + + if (requires_elevation) + if (!isliving(source)) + return + RegisterSignal(source, SIGNAL_ADDTRAIT(TRAIT_MOB_ELEVATED), PROC_REF(on_mob_elevated)) + if (!HAS_TRAIT(source, TRAIT_MOB_ELEVATED)) + return + + trigger_effect(source) diff --git a/code/datums/components/twohanded.dm b/code/datums/components/twohanded.dm index 4414b466170..a65d8c4f3c7 100644 --- a/code/datums/components/twohanded.dm +++ b/code/datums/components/twohanded.dm @@ -147,6 +147,8 @@ RegisterSignal(parent, COMSIG_ITEM_SHARPEN_ACT, PROC_REF(on_sharpen)) RegisterSignal(parent, COMSIG_ITEM_APPLY_FANTASY_BONUSES, PROC_REF(apply_fantasy_bonuses)) RegisterSignal(parent, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, PROC_REF(remove_fantasy_bonuses)) + RegisterSignal(parent, COMSIG_ATOM_FINALIZE_MATERIAL_EFFECTS, PROC_REF(on_materials_updated)) + RegisterSignal(parent, COMSIG_ATOM_FINALIZE_REMOVE_MATERIAL_EFFECTS, PROC_REF(on_materials_updated)) // Remove all siginals registered to the parent item /datum/component/two_handed/UnregisterFromParent() @@ -160,6 +162,8 @@ COMSIG_ITEM_SHARPEN_ACT, COMSIG_ITEM_APPLY_FANTASY_BONUSES, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, + COMSIG_ATOM_FINALIZE_MATERIAL_EFFECTS, + COMSIG_ATOM_FINALIZE_REMOVE_MATERIAL_EFFECTS, )) /// Triggered on equip of the item containing the component @@ -419,6 +423,19 @@ unwield(source.loc) force_multiplier = source.reset_fantasy_variable("force_multiplier", force_multiplier) +/datum/component/two_handed/proc/on_materials_updated(obj/item/source, list/materials, datum/material/main_material) + SIGNAL_HANDLER + // With materials assigned we need to update our forces. + if (wielded) + // Materials modify force multiplicatively! Most of the time, for snowflakes they gotta handle it themselves + if (!isnull(force_wielded)) + force_unwielded *= source.force / force_wielded + force_wielded = source.force + else + if (!isnull(force_unwielded)) + force_wielded *= source.force / force_unwielded + force_unwielded = source.force + /** * The offhand dummy item for two handed items */ diff --git a/code/datums/elements/elevation.dm b/code/datums/elements/elevation.dm index a0c3da5f3ab..fcf6f524ff0 100644 --- a/code/datums/elements/elevation.dm +++ b/code/datums/elements/elevation.dm @@ -130,15 +130,19 @@ COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, COMSIG_TURF_RESET_ELEVATION, )) - REMOVE_TRAIT(source, TRAIT_ELEVATED_TURF, ELEVATION_SOURCE(src)) for(var/mob/living/living in source) deelevate_mob(living) UnregisterSignal(living, list(COMSIG_LIVING_SET_BUCKLED, SIGNAL_ADDTRAIT(TRAIT_IGNORE_ELEVATION), SIGNAL_REMOVETRAIT(TRAIT_IGNORE_ELEVATION))) + REMOVE_TRAIT(source, TRAIT_ELEVATED_TURF, ELEVATION_SOURCE(src)) return ..() /datum/element/elevation_core/proc/on_entered(turf/source, atom/movable/entered, atom/old_loc) SIGNAL_HANDLER - if((isnull(old_loc) || !HAS_TRAIT_FROM(old_loc, TRAIT_ELEVATED_TURF, ELEVATION_SOURCE(src))) && isliving(entered)) + // If the movement has been aborted by something else within the chain we need to abort + if(!isliving(entered) || entered.loc != source) + return + + if(isnull(old_loc) || !HAS_TRAIT_FROM(old_loc, TRAIT_ELEVATED_TURF, ELEVATION_SOURCE(src))) register_new_mob(entered, elevate_time = isturf(old_loc) && source.Adjacent(old_loc) ? ELEVATE_TIME : 0) /datum/element/elevation_core/proc/on_initialized_on(turf/source, atom/movable/spawned) @@ -178,7 +182,6 @@ // we want to avoid accidentally double-elevating anything they're buckled to (namely vehicles) if(target.has_offset(source = ELEVATION_SOURCE(src))) return - ADD_TRAIT(target, TRAIT_MOB_ELEVATED, ELEVATION_SOURCE(src)) // We are buckled to something if(target.buckled) // We are buckled to a vehicle, so it also must be elevated @@ -189,15 +192,18 @@ pass() // We are buckled to some other object - perhaps the object itself - so skip else + ADD_TRAIT(target, TRAIT_MOB_ELEVATED, ELEVATION_SOURCE(src)) return + target.add_offsets(ELEVATION_SOURCE(src), z_add = pixel_shift, animate = elevate_time > 0) + ADD_TRAIT(target, TRAIT_MOB_ELEVATED, ELEVATION_SOURCE(src)) /// Reverts elevation of the mob. /datum/element/elevation_core/proc/deelevate_mob(mob/living/target, elevate_time = ELEVATE_TIME) - REMOVE_TRAIT(target, TRAIT_MOB_ELEVATED, ELEVATION_SOURCE(src)) target.remove_offsets(ELEVATION_SOURCE(src), animate = elevate_time > 0) if(isvehicle(target.buckled)) animate(target.buckled, pixel_z = -pixel_shift, time = elevate_time, flags = ANIMATION_RELATIVE|ANIMATION_PARALLEL) + REMOVE_TRAIT(target, TRAIT_MOB_ELEVATED, ELEVATION_SOURCE(src)) /** * If the mob is buckled or unbuckled to/from a vehicle, shift it up/down diff --git a/code/datums/elements/loomable.dm b/code/datums/elements/loomable.dm index 76ee071a9a2..ca32bde165b 100644 --- a/code/datums/elements/loomable.dm +++ b/code/datums/elements/loomable.dm @@ -22,7 +22,7 @@ loom_type = /obj/structure/loom, process_completion_verb = "spun", target_needs_anchoring = TRUE, - loom_time = 1 SECONDS + loom_time = 1 SECONDS, ) . = ..() //currently this element only works for items as we need to call /obj/item/attack_atom() @@ -80,7 +80,8 @@ break if(!stack_we_use.use(required_amount)) - user.balloon_alert(user, "need [required_amount] of [source]!") + if (!spawning_amount) + user.balloon_alert(user, "need [required_amount] of [source]!") break spawning_amount++ @@ -96,8 +97,13 @@ if(spawning_amount == 0) return - var/new_thing - for(var/repeated in 1 to spawning_amount) - new_thing = new resulting_atom(target.drop_location()) - + var/atom/new_thing = null + if (ispath(resulting_atom, /obj/item/stack)) + var/obj/item/stack/stack_type = resulting_atom + while (spawning_amount > 0) + new_thing = new resulting_atom(target.drop_location(), new_amount = min(spawning_amount, stack_type::max_amount)) + spawning_amount -= stack_type::max_amount + else + for(var/repeated in 1 to spawning_amount) + new_thing = new resulting_atom(target.drop_location()) user.balloon_alert_to_viewers("[process_completion_verb] [new_thing]") diff --git a/code/datums/materials/_material.dm b/code/datums/materials/_material.dm index f830fa5b7b5..5d34b97c75d 100644 --- a/code/datums/materials/_material.dm +++ b/code/datums/materials/_material.dm @@ -20,6 +20,9 @@ Simple datum which is instanced once per type and is used for every object of sa 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. @@ -99,16 +102,164 @@ Simple datum which is instanced once per type and is used for every object of sa return TRUE -///This proc is called when the material is added to an object. -/datum/material/proc/on_applied(atom/source, mat_amount, multiplier) +/// 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) + SEND_SIGNAL(src, COMSIG_MATERIAL_APPLIED, source, mat_amount, multiplier, from_slot) -///This proc is called when the material becomes the one the object is composed of the most + 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 @@ -127,15 +278,6 @@ Simple datum which is instanced once per type and is used for every object of sa /datum/material/proc/lit_turf_deleted(turf/source) source.set_light(0, 0, null) -/// This proc is called when the material is removed from an object. -/datum/material/proc/on_removed(atom/source, amount, material_flags) - SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_MATERIAL_REMOVED, source, amount, material_flags) - -/// 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) ////Called in `/datum/component/edible/proc/on_material_effects` /datum/material/proc/on_edible_applied(atom/source, datum/component/edible/edible) diff --git a/code/datums/materials/alloys.dm b/code/datums/materials/alloys.dm index 6d90eb4ff1a..861be125aef 100644 --- a/code/datums/materials/alloys.dm +++ b/code/datums/materials/alloys.dm @@ -44,15 +44,15 @@ composition = list(/datum/material/iron = 1, /datum/material/plasma = 1) mat_rust_resistance = RUST_RESISTANCE_REINFORCED -/datum/material/alloy/plasteel/on_applied(atom/target, mat_amount, multiplier) +/datum/material/alloy/plasteel/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() - if(istype(target, /obj/item/fishing_rod)) - ADD_TRAIT(target, TRAIT_ROD_LAVA_USABLE, REF(src)) + if(istype(source, /obj/item/fishing_rod)) + ADD_TRAIT(source, TRAIT_ROD_LAVA_USABLE, REF(src)) -/datum/material/alloy/plasteel/on_removed(atom/target, mat_amount, multiplier) +/datum/material/alloy/plasteel/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() - if(istype(target, /obj/item/fishing_rod)) - REMOVE_TRAIT(target, TRAIT_ROD_LAVA_USABLE, REF(src)) + if(istype(source, /obj/item/fishing_rod)) + REMOVE_TRAIT(source, TRAIT_ROD_LAVA_USABLE, REF(src)) /** * Plastitanium @@ -78,15 +78,15 @@ composition = list(/datum/material/titanium = 1, /datum/material/plasma = 1) mat_rust_resistance = RUST_RESISTANCE_TITANIUM -/datum/material/alloy/plastitanium/on_applied(atom/target, mat_amount, multiplier) +/datum/material/alloy/plastitanium/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() - if(istype(target, /obj/item/fishing_rod)) - ADD_TRAIT(target, TRAIT_ROD_LAVA_USABLE, REF(src)) + if(istype(source, /obj/item/fishing_rod)) + ADD_TRAIT(source, TRAIT_ROD_LAVA_USABLE, REF(src)) -/datum/material/alloy/plastitanium/on_removed(atom/target, mat_amount, multiplier) +/datum/material/alloy/plastitanium/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() - if(istype(target, /obj/item/fishing_rod)) - REMOVE_TRAIT(target, TRAIT_ROD_LAVA_USABLE, REF(src)) + if(istype(source, /obj/item/fishing_rod)) + REMOVE_TRAIT(source, TRAIT_ROD_LAVA_USABLE, REF(src)) /** * Plasmaglass @@ -194,16 +194,16 @@ value_per_unit = 0.4 composition = list(/datum/material/iron = 2, /datum/material/plasma = 2) -/datum/material/alloy/alien/on_applied(atom/target, mat_amount, multiplier) +/datum/material/alloy/alien/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() - if(isobj(target)) - target.AddElement(/datum/element/obj_regen, _rate=0.02) // 2% regen per tick. - if(istype(target, /obj/item/fishing_rod)) - ADD_TRAIT(target, TRAIT_ROD_LAVA_USABLE, REF(src)) + if(isobj(source)) + source.AddElement(/datum/element/obj_regen, _rate=0.02) // 2% regen per tick. + if(istype(source, /obj/item/fishing_rod)) + ADD_TRAIT(source, TRAIT_ROD_LAVA_USABLE, REF(src)) -/datum/material/alloy/alien/on_removed(atom/target, mat_amount, multiplier) +/datum/material/alloy/alien/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() - if(isobj(target)) - target.RemoveElement(/datum/element/obj_regen, _rate=0.02) - if(istype(target, /obj/item/fishing_rod)) - REMOVE_TRAIT(target, TRAIT_ROD_LAVA_USABLE, REF(src)) + if(isobj(source)) + source.RemoveElement(/datum/element/obj_regen, _rate=0.02) + if(istype(source, /obj/item/fishing_rod)) + REMOVE_TRAIT(source, TRAIT_ROD_LAVA_USABLE, REF(src)) diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm index 412e94090d9..4284503cede 100644 --- a/code/datums/materials/basemats.dm +++ b/code/datums/materials/basemats.dm @@ -89,6 +89,7 @@ MATERIAL_ELECTRICAL = 9, MATERIAL_THERMAL = 4, MATERIAL_CHEMICAL = 4, + MATERIAL_VAMPIRES_BANE = 5, ) sheet_type = /obj/item/stack/sheet/mineral/silver ore_type = /obj/item/stack/ore/silver @@ -209,7 +210,8 @@ MATERIAL_ELECTRICAL = 10, MATERIAL_THERMAL = 8, MATERIAL_CHEMICAL = 0, - MATERIAL_FLAMMABILITY = 9, // Literally sets itself on fire from any excitement + MATERIAL_FLAMMABILITY = 10, // Literally sets itself on fire from any excitement + MATERIAL_FIRESTACKER = 1, ) sheet_type = /obj/item/stack/sheet/mineral/plasma ore_type = /obj/item/stack/ore/plasma @@ -218,22 +220,19 @@ mineral_rarity = MATERIAL_RARITY_PRECIOUS points_per_unit = 15 / SHEET_MATERIAL_AMOUNT -/datum/material/plasma/on_applied(atom/source, mat_amount, multiplier) +/datum/material/plasma/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() - if(ismovable(source)) - source.AddElement(/datum/element/firestacker, 1 * multiplier) source.AddComponent(/datum/component/combustible_flooder, GAS_PLASMA, mat_amount * 0.05 * multiplier) //Empty temp arg, fully dependent on whatever ignited it. if(istype(source, /obj/item/fishing_rod)) ADD_TRAIT(source, TRAIT_ROD_LAVA_USABLE, REF(src)) -/datum/material/plasma/on_removed(atom/source, mat_amount, multiplier) +/datum/material/plasma/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() - source.RemoveElement(/datum/element/firestacker, mat_amount = 1 * multiplier) qdel(source.GetComponent(/datum/component/combustible_flooder)) if(istype(source, /obj/item/fishing_rod)) ADD_TRAIT(source, TRAIT_ROD_LAVA_USABLE, REF(src)) -///Can cause bluespace effects on use. (Teleportation) (Not yet implemented) +/// Can cause bluespace effects on use. (Teleportation) /datum/material/bluespace name = "bluespace crystal" desc = "Crystals with bluespace properties." @@ -250,6 +249,7 @@ MATERIAL_THERMAL = 4, MATERIAL_CHEMICAL = 4, MATERIAL_BEAUTY = 0.5, // Absolutely mesmerizing + MATERIAL_TELEPORTING = 5, ) sheet_type = /obj/item/stack/sheet/bluespace_crystal ore_type = /obj/item/stack/ore/bluespace_crystal @@ -306,7 +306,7 @@ mineral_rarity = MATERIAL_RARITY_UNDISCOVERED points_per_unit = 60 / SHEET_MATERIAL_AMOUNT -/datum/material/bananium/on_applied(atom/source, mat_amount, multiplier) +/datum/material/bananium/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() source.LoadComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50 * multiplier, falloff_exponent = 20) source.AddComponent(/datum/component/slippery, min(mat_amount / 10 * multiplier, 80 * multiplier)) @@ -332,7 +332,7 @@ ) rewards += pick_weight(funny_fish) -/datum/material/bananium/on_removed(atom/source, mat_amount, multiplier) +/datum/material/bananium/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() qdel(source.GetComponent(/datum/component/slippery)) qdel(source.GetComponent(/datum/component/squeak)) @@ -391,12 +391,12 @@ mineral_rarity = MATERIAL_RARITY_UNDISCOVERED points_per_unit = 100 / SHEET_MATERIAL_AMOUNT -/datum/material/runite/on_applied(atom/source, mat_amount, multiplier) +/datum/material/runite/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() if(istype(source, /obj/item/fishing_rod)) ADD_TRAIT(source, TRAIT_ROD_REMOVE_FISHING_DUD, REF(src)) //light-absorbing, environment-cancelling fishing rod. -/datum/material/runite/on_removed(atom/source, mat_amount, multiplier) +/datum/material/runite/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() if(istype(source, /obj/item/fishing_rod)) REMOVE_TRAIT(source, TRAIT_ROD_REMOVE_FISHING_DUD, REF(src)) //light-absorbing, environment-cancelling fishing rod. @@ -484,12 +484,12 @@ mineral_rarity = MATERIAL_RARITY_UNDISCOVERED // Doesn't naturally spawn on lavaland. points_per_unit = 100 / SHEET_MATERIAL_AMOUNT -/datum/material/adamantine/on_applied(atom/source, mat_amount, multiplier) +/datum/material/adamantine/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() if(istype(source, /obj/item/fishing_rod)) ADD_TRAIT(source, TRAIT_ROD_REMOVE_FISHING_DUD, REF(src)) // light-absorbing, environment-cancelling fishing rod. -/datum/material/adamantine/on_removed(atom/source, mat_amount, multiplier) +/datum/material/adamantine/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() if(istype(source, /obj/item/fishing_rod)) REMOVE_TRAIT(source, TRAIT_ROD_REMOVE_FISHING_DUD, REF(src)) // light-absorbing, environment-cancelling fishing rod. @@ -522,13 +522,13 @@ mineral_rarity = MATERIAL_RARITY_UNDISCOVERED // Doesn't naturally spawn on lavaland. points_per_unit = 100 / SHEET_MATERIAL_AMOUNT -/datum/material/mythril/on_applied(atom/source, mat_amount, multiplier) +/datum/material/mythril/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() if(isitem(source)) source.AddComponent(/datum/component/fantasy) ADD_TRAIT(source, TRAIT_INNATELY_FANTASTICAL_ITEM, REF(src)) // DO THIS LAST OR WE WILL NEVER GET OUR BONUSES!!! -/datum/material/mythril/on_removed(atom/source, mat_amount, multiplier) +/datum/material/mythril/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() if(isitem(source)) REMOVE_TRAIT(source, TRAIT_INNATELY_FANTASTICAL_ITEM, REF(src)) // DO THIS FIRST OR WE WILL NEVER GET OUR BONUSES DELETED!!! @@ -557,16 +557,17 @@ MATERIAL_THERMAL = 8, MATERIAL_CHEMICAL = 4, MATERIAL_FLAMMABILITY = 10, + MATERIAL_FIRESTACKER = 1, ) sheet_type = /obj/item/stack/sheet/hot_ice material_reagent = /datum/reagent/toxin/hot_ice value_per_unit = 400 / SHEET_MATERIAL_AMOUNT -/datum/material/hot_ice/on_applied(atom/source, mat_amount, multiplier) +/datum/material/hot_ice/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() source.AddComponent(/datum/component/combustible_flooder, GAS_PLASMA, mat_amount * 1.5 * multiplier, (mat_amount * 0.2 + 300) * multiplier) -/datum/material/hot_ice/on_removed(atom/source, mat_amount, multiplier) +/datum/material/hot_ice/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() qdel(source.GetComponent(/datum/component/combustible_flooder)) @@ -603,7 +604,7 @@ color = "#EDC9AF" mat_flags = MATERIAL_BASIC_RECIPES | MATERIAL_CLASS_AMORPHOUS mat_properties = list( - MATERIAL_DENSITY = 2, + MATERIAL_DENSITY = 3, MATERIAL_HARDNESS = 0, MATERIAL_FLEXIBILITY = 0, MATERIAL_REFLECTIVITY = 7, @@ -679,13 +680,13 @@ MATERIAL_THERMAL = 1, MATERIAL_CHEMICAL = 8, ) + material_reagent = list(/datum/reagent/iron = 1, /datum/reagent/fuel/unholywater = 2) sheet_type = /obj/item/stack/sheet/runed_metal value_per_unit = 1500 / SHEET_MATERIAL_AMOUNT texture_layer_icon_state = "runed" /datum/material/runedmetal/on_accidental_mat_consumption(mob/living/carbon/victim, obj/item/source_item) . = ..() - victim.reagents.add_reagent(/datum/reagent/fuel/unholywater, rand(8, 12)) if(!HAS_TRAIT(victim, TRAIT_ROCK_EATER)) victim.apply_damage(10, BRUTE, BODY_ZONE_HEAD, wound_bonus = 5) return TRUE @@ -869,12 +870,12 @@ material_reagent = /datum/reagent/toxin/plasma value_per_unit = 900 / SHEET_MATERIAL_AMOUNT -/datum/material/zaukerite/on_applied(atom/source, mat_amount, multiplier) +/datum/material/zaukerite/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() if(istype(source, /obj/item/fishing_rod)) ADD_TRAIT(source, TRAIT_ROD_IGNORE_ENVIRONMENT, REF(src)) //light-absorbing, environment-cancelling fishing rod. -/datum/material/zaukerite/on_removed(atom/source, mat_amount, multiplier) +/datum/material/zaukerite/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() if(istype(source, /obj/item/fishing_rod)) REMOVE_TRAIT(source, TRAIT_ROD_IGNORE_ENVIRONMENT, REF(src)) //light-absorbing, environment-cancelling fishing rod. @@ -884,3 +885,44 @@ if(!HAS_TRAIT(victim, TRAIT_ROCK_EATER)) victim.apply_damage(30, BURN, BODY_ZONE_HEAD, wound_bonus = 5) return TRUE + +/// Evil and very unstable version of bluespace crystals +/datum/material/telecrystal + name = "telecrystal" + desc = "An ominous-looking gemstone capable of transporting objects vast distances through bluespace." + color = "#BD1B28" + alpha = 200 + starlight_color = COLOR_SYNDIE_RED + mat_flags = MATERIAL_BASIC_RECIPES | MATERIAL_CLASS_CRYSTAL | MATERIAL_CLASS_RIGID + mat_properties = list( + MATERIAL_DENSITY = 1, + MATERIAL_HARDNESS = 4, + MATERIAL_FLEXIBILITY = 0, + MATERIAL_REFLECTIVITY = 10, + MATERIAL_ELECTRICAL = 10, + MATERIAL_THERMAL = 2, + MATERIAL_CHEMICAL = 8, + MATERIAL_BEAUTY = -0.5, // very evil bad no good + MATERIAL_TELEPORTING = 8, + MATERIAL_PENETRATING = TRUE, + ) + sheet_type = /obj/item/stack/sheet/telepolycrystal + material_reagent = list(/datum/reagent/bluespace = 1, /datum/reagent/medicine/stimulants = 1) // We don't have liquid telecrystals and I don't wanna risk it + value_per_unit = 1200 / SHEET_MATERIAL_AMOUNT + texture_layer_icon_state = "shine" + +/datum/material/telecrystal/on_main_applied(atom/source, mat_amount, multiplier) + . = ..() + if(istype(source, /obj/item/fishing_rod)) + RegisterSignal(source, COMSIG_ROD_BEGIN_FISHING, PROC_REF(on_begin_fishing)) + +/datum/material/telecrystal/on_main_removed(atom/source, mat_amount, multiplier) + . = ..() + if(istype(source, /obj/item/fishing_rod)) + UnregisterSignal(source, COMSIG_ROD_BEGIN_FISHING) + +/datum/material/telecrystal/proc/on_begin_fishing(obj/item/fishing_rod/rod, datum/fishing_challenge/challenge) + SIGNAL_HANDLER + // Oops, all chainsawfish! + challenge.register_reward_signals(GLOB.preset_fish_sources[/datum/fish_source/portal/syndicate]) + diff --git a/code/datums/materials/material_slots/_slot.dm b/code/datums/materials/material_slots/_slot.dm new file mode 100644 index 00000000000..239458f3981 --- /dev/null +++ b/code/datums/materials/material_slots/_slot.dm @@ -0,0 +1,19 @@ +/// Singleton datum which controls how materials affect atoms they're applied to +/datum/material_slot + abstract_type = /datum/material_slot + /// Name of the slot for autolathe UI + var/name = "error" + /// Material requirement type which controls what materials can be used to fill this slot when printing an item + var/datum/material_requirement/requirement_type = null + /// Relative amount of material in this slot for when multiple slots are filled with a single material + var/material_amount = 1 + +/// Called when the material in this slot is applied to the atom. Return FALSE to prevent base apply_single_mat_effect from running. +/// If the material is main, main material application will also be cancelled. Should be consistent with on_removed. +/datum/material_slot/proc/on_applied(atom/target, datum/material/material, amount, multiplier) + return TRUE + +/// Called when the material in this slot is removed from the atom. Return FALSE to prevent base remove_single_mat_effect from running. +/// If the material is main, main material removal will also be cancelled. Should be consistent with on_applied. +/datum/material_slot/proc/on_removed(atom/target, datum/material/material, amount, multiplier) + return TRUE diff --git a/code/datums/materials/material_slots/generic.dm b/code/datums/materials/material_slots/generic.dm new file mode 100644 index 00000000000..971ae28b612 --- /dev/null +++ b/code/datums/materials/material_slots/generic.dm @@ -0,0 +1,210 @@ +// Generic slots for weapons + +/// Generic main/parent type for all weapon heads +/datum/material_slot/weapon_head + name = "weapon head" + requirement_type = /datum/material_requirement/solid_material + +/datum/material_slot/weapon_head/on_applied(obj/item/target, datum/material/material, amount, multiplier) + // Weapon head controls strength and conductivity + if (!(target.material_flags & MATERIAL_EFFECTS)) + return FALSE + + // Effect signals + RegisterSignal(target, COMSIG_MOVABLE_IMPACT, PROC_REF(on_throw_impact)) + RegisterSignal(target, COMSIG_MOVABLE_IMPACT_ZONE, PROC_REF(on_throw_impact_living)) + RegisterSignal(target, COMSIG_ITEM_ATTACK, PROC_REF(on_item_attack)) + RegisterSignal(target, COMSIG_ITEM_ATTACK_ATOM, PROC_REF(on_item_attack)) + RegisterSignal(target, COMSIG_ITEM_ATTACK_ZONE, PROC_REF(on_item_attack_living)) + + if (!(target.material_flags & MATERIAL_AFFECT_STATISTICS)) + return FALSE + + // Damage + target.change_material_strength(material, amount, multiplier) + + // Conductivity + var/conductivity = material.get_property(MATERIAL_ELECTRICAL) + var/siemens_modifier = round(max(0, conductivity - 1) ** 1.18 * 0.15, 0.01) + var/siemens_mult = 1 + (siemens_modifier - 1) * multiplier + target.siemens_coefficient *= max(0, siemens_mult) + + if (target.siemens_coefficient == 0) + target.obj_flags &= ~CONDUCTS_ELECTRICITY + +/datum/material_slot/weapon_head/on_removed(obj/item/target, datum/material/material, amount, multiplier) + var/static/list/interaction_signals = list( + COMSIG_MOVABLE_IMPACT, + COMSIG_MOVABLE_IMPACT_ZONE, + COMSIG_ITEM_ATTACK, + COMSIG_ITEM_ATTACK_ATOM, + COMSIG_ITEM_ATTACK_ZONE, + ) + UnregisterSignal(target, interaction_signals) + + if (!(target.material_flags & MATERIAL_AFFECT_STATISTICS) || !(target.material_flags & MATERIAL_EFFECTS)) + return FALSE + + // Damage + target.change_material_strength(material, amount, multiplier, remove = TRUE) + + // Conductivity + var/conductivity = material.get_property(MATERIAL_ELECTRICAL) + var/siemens_modifier = round(max(0, conductivity - 1) ** 1.18 * 0.15, 0.01) + var/siemens_mult = 1 + (siemens_modifier - 1) * multiplier + if (siemens_mult > 0) + target.siemens_coefficient /= siemens_mult + + if (target.siemens_coefficient > 0 && (initial(target.obj_flags) & CONDUCTS_ELECTRICITY) && !(target.obj_flags & CONDUCTS_ELECTRICITY)) + target.obj_flags |= CONDUCTS_ELECTRICITY + +/datum/material_slot/weapon_head/proc/on_throw_impact(obj/item/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught) + SIGNAL_HANDLER + if (!caught && !isliving(hit_atom)) + affect_throw_impact(source, hit_atom, astype(throwing_datum.thrower.resolve(), /mob/living)) + +/datum/material_slot/weapon_head/proc/on_item_attack(obj/item/source, atom/movable/target, mob/living/user) + SIGNAL_HANDLER + // Living mobs use a different signal + if (!isliving(target)) + affect_target(source, target, user) + +/datum/material_slot/weapon_head/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 + + affect_target(source, target, user, def_zone, !!skin_contact) + +/datum/material_slot/weapon_head/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 + + affect_throw_impact(source, target, astype(throwing_datum.thrower.resolve(), /mob/living), def_zone, !!skin_contact) + +/datum/material_slot/weapon_head/proc/affect_target(obj/item/source, atom/target, mob/living/user, def_zone, skin_contact = TRUE) + var/datum/material/source_mat = source.get_material_from_slot(type) + SEND_SIGNAL(source_mat, COMSIG_MATERIAL_EFFECT_HIT, source, target, user, def_zone, skin_contact) + +/datum/material_slot/weapon_head/proc/affect_throw_impact(obj/item/source, atom/target, mob/living/user, def_zone, skin_contact = TRUE) + var/datum/material/source_mat = source.get_material_from_slot(type) + SEND_SIGNAL(source_mat, COMSIG_MATERIAL_EFFECT_THROW_IMPACT, source, target, user, def_zone, skin_contact) + +/// Main type for all weapon handles +/datum/material_slot/handle + name = "handle" + requirement_type = /datum/material_requirement/solid_material + +/datum/material_slot/handle/on_applied(obj/item/target, datum/material/material, amount, multiplier) + // Handle controls integrity, armor, conductivity and wieldiness stats-wise + if (!(target.material_flags & MATERIAL_EFFECTS)) + return FALSE + + // Effect signals + RegisterSignal(target, COMSIG_MOVABLE_IMPACT, PROC_REF(on_throw_impact)) + RegisterSignal(target, COMSIG_ITEM_ATTACK, PROC_REF(on_item_attack)) + RegisterSignal(target, COMSIG_ITEM_ATTACK_ATOM, PROC_REF(on_item_attack)) + RegisterSignal(target, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_item_attack_self)) + + if (!(target.material_flags & MATERIAL_AFFECT_STATISTICS)) + return FALSE + + // Armor/integrity + var/integrity_mod = material.get_property(MATERIAL_INTEGRITY) + target.modify_max_integrity(ceil(target.max_integrity * integrity_mod)) + var/list/armor_mods = material.get_armor_modifiers(multiplier) + target.set_armor(target.get_armor().generate_new_with_multipliers(armor_mods)) + + // Conductivity + var/conductivity = material.get_property(MATERIAL_ELECTRICAL) + var/siemens_modifier = round(max(0, conductivity - 1) ** 1.18 * 0.15, 0.01) + var/siemens_mult = 1 + (siemens_modifier - 1) * multiplier + target.siemens_coefficient *= max(0, siemens_mult) + + if (target.siemens_coefficient == 0) + target.obj_flags &= ~CONDUCTS_ELECTRICITY + + // Wielding + var/density = material.get_property(MATERIAL_DENSITY) + var/hardness = material.get_property(MATERIAL_HARDNESS) + // Can be faster/slower by 2 dcs + target.attack_speed += MATERIAL_PROPERTY_DIVERGENCE(density, 4, 6) * 0.5 * multiplier + target.throw_range += ((hardness - 4) - (density - 4) * 2) * multiplier + return FALSE + +/datum/material_slot/handle/on_removed(obj/item/target, datum/material/material, amount, multiplier) + UnregisterSignal(target, list(COMSIG_MOVABLE_IMPACT, COMSIG_ITEM_ATTACK, COMSIG_ITEM_ATTACK_ATOM, COMSIG_ITEM_ATTACK_SELF)) + + if (!(target.material_flags & MATERIAL_AFFECT_STATISTICS) || !(target.material_flags & MATERIAL_EFFECTS)) + return FALSE + + // Armor/integrity + var/integrity_mod = material.get_property(MATERIAL_INTEGRITY) + + target.modify_max_integrity(ceil(target.max_integrity * integrity_mod)) + var/list/armor_mods = material.get_armor_modifiers(multiplier) + 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 + target.set_armor(target.get_armor().generate_new_with_multipliers(armor_mods)) + + // Conductivity + var/conductivity = material.get_property(MATERIAL_ELECTRICAL) + var/siemens_modifier = round(max(0, conductivity - 1) ** 1.18 * 0.15, 0.01) + var/siemens_mult = 1 + (siemens_modifier - 1) * multiplier + if (siemens_mult > 0) + target.siemens_coefficient /= siemens_mult + + if (target.siemens_coefficient > 0 && (initial(target.obj_flags) & CONDUCTS_ELECTRICITY) && !(target.obj_flags & CONDUCTS_ELECTRICITY)) + target.obj_flags |= CONDUCTS_ELECTRICITY + + // Wielding + var/density = material.get_property(MATERIAL_DENSITY) + var/hardness = material.get_property(MATERIAL_HARDNESS) + target.attack_speed -= MATERIAL_PROPERTY_DIVERGENCE(density, 4, 6) * 0.5 * multiplier + target.throw_range -= ((hardness - 4) - (density - 4) * 2) * multiplier + return FALSE + +/datum/material_slot/handle/proc/on_item_attack(obj/item/source, atom/movable/target, mob/living/user) + SIGNAL_HANDLER + affect_user(source, user, user) + +/datum/material_slot/handle/proc/on_item_attack_self(obj/item/source, mob/living/user) + SIGNAL_HANDLER + affect_user(source, user, user) + +/datum/material_slot/handle/proc/on_throw_impact(obj/item/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught) + SIGNAL_HANDLER + if (caught) + affect_user(source, hit_atom, astype(throwing_datum.thrower.resolve(), /mob/living)) + +/datum/material_slot/handle/proc/affect_user(obj/item/source, mob/living/user, mob/living/initiator) + var/datum/material/source_mat = source.get_material_from_slot(type) + var/arm_dir = IS_LEFT_INDEX(user.active_hand_index) ? BODY_ZONE_L_ARM : BODY_ZONE_R_ARM + if (!ishuman(user)) + SEND_SIGNAL(source_mat, 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(source_mat, COMSIG_MATERIAL_EFFECT_TOUCH, source, user, initiator, arm_dir, FALSE) // ...no hand, no skin contact? + 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(source_mat, COMSIG_MATERIAL_EFFECT_TOUCH, source, user, initiator, hand.body_zone, !hand_covered) diff --git a/code/datums/materials/meat.dm b/code/datums/materials/meat.dm index 8e33c31a50f..00c26c98797 100644 --- a/code/datums/materials/meat.dm +++ b/code/datums/materials/meat.dm @@ -8,7 +8,7 @@ mat_properties = list( MATERIAL_DENSITY = 5, MATERIAL_HARDNESS = 0, - MATERIAL_FLEXIBILITY = 6, + MATERIAL_FLEXIBILITY = 5, MATERIAL_REFLECTIVITY = 4, MATERIAL_ELECTRICAL = 8, MATERIAL_THERMAL = 4, @@ -34,7 +34,7 @@ if(!(organ::organ_flags & ORGAN_ORGANIC)) organ.organ_flags |= ORGAN_ORGANIC -/datum/material/meat/on_applied(atom/source, mat_amount, multiplier) +/datum/material/meat/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() if(IS_EDIBLE(source)) make_edible(source, mat_amount, multiplier) @@ -92,7 +92,7 @@ blood_dna_info = blood_dna,\ ) -/datum/material/meat/on_removed(atom/source, mat_amount, multiplier) +/datum/material/meat/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() source.RemoveComponentSource(SOURCE_EDIBLE_MEAT_MAT, /datum/component/edible) qdel(source.GetComponent(/datum/component/blood_walk)) diff --git a/code/datums/materials/pizza.dm b/code/datums/materials/pizza.dm index 3eb8bb533e2..df77ba1d1e4 100644 --- a/code/datums/materials/pizza.dm +++ b/code/datums/materials/pizza.dm @@ -6,7 +6,7 @@ mat_properties = list( MATERIAL_DENSITY = 4, MATERIAL_HARDNESS = 1, - MATERIAL_FLEXIBILITY = 6, + MATERIAL_FLEXIBILITY = 5, MATERIAL_REFLECTIVITY = 2, MATERIAL_ELECTRICAL = 8, MATERIAL_THERMAL = 4, @@ -23,7 +23,7 @@ make_edible(source, mat_amount) ADD_TRAIT(source, TRAIT_ROD_REMOVE_FISHING_DUD, REF(src)) //the fishing rod itself is the bait... sorta. -/datum/material/pizza/on_applied(atom/source, mat_amount, multiplier) +/datum/material/pizza/on_applied(atom/source, mat_amount, multiplier, from_slot) . = ..() if(IS_EDIBLE(source)) make_edible(source, mat_amount, multiplier) @@ -54,7 +54,7 @@ eat_time = 3 SECONDS, \ tastes = /obj/item/food/pizza/margherita::tastes) -/datum/material/pizza/on_removed(atom/source, mat_amount, multiplier) +/datum/material/pizza/on_removed(atom/source, mat_amount, multiplier, from_slot) . = ..() source.RemoveComponentSource(SOURCE_EDIBLE_PIZZA_MAT, /datum/component/edible) diff --git a/code/datums/materials/properties/_properties.dm b/code/datums/materials/properties/_properties.dm index b9fa27e26a0..abb6dd6b8cc 100644 --- a/code/datums/materials/properties/_properties.dm +++ b/code/datums/materials/properties/_properties.dm @@ -11,6 +11,10 @@ /datum/material_property/proc/get_descriptor(value) return null +/// Returns the contents of the tooltip under our descriptor +/datum/material_property/proc/get_tooltip(value) + return "[value < 0 ? "-" : ""]\Roman[round(abs(value), 1)]" + /// Called whenever a material with this property initializes. Mostly used for behavior tracking on optional properties /datum/material_property/proc/attach_to(datum/material/material) return diff --git a/code/datums/materials/properties/derived.dm b/code/datums/materials/properties/derived.dm index 4c18c035de7..162c4954927 100644 --- a/code/datums/materials/properties/derived.dm +++ b/code/datums/materials/properties/derived.dm @@ -45,3 +45,14 @@ // Requires the material to be especially shiny or dull var/reflectivity = material.get_property(MATERIAL_REFLECTIVITY) return MATERIAL_PROPERTY_DIVERGENCE(reflectivity, 3, 6) * 0.05 + +/// Siemens coeff multiplier for our material +/datum/material_property/derived/insulation + id = MATERIAL_INSULATION + +/datum/material_property/derived/insulation/get_value(datum/material/material) + // [0 ~ 1] is fully insulating, (1 ~ 6] maps to (0 ~ 1] and [6 ~ 10] maps to [1 ~ 2] + // 1.18 and 0.15 here are to allow 6 to map to 1 and 10 to map to 2 and are pulled out of my ass (system in the desmos below) + // See https://www.desmos.com/calculator/rdbv1x8oty + var/conductivity = material.get_property(MATERIAL_ELECTRICAL) + return round(max(0, conductivity - 1) ** 1.18 * 0.15, 0.01) diff --git a/code/datums/materials/properties/optional.dm b/code/datums/materials/properties/optional.dm index 8982e0d5e7c..38d5287d3cf 100644 --- a/code/datums/materials/properties/optional.dm +++ b/code/datums/materials/properties/optional.dm @@ -33,16 +33,16 @@ RegisterSignal(material, COMSIG_MATERIAL_APPLIED, PROC_REF(on_applied)) RegisterSignal(material, COMSIG_MATERIAL_REMOVED, PROC_REF(on_removed)) -/datum/material_property/flammability/proc/on_applied(datum/material/source, atom/new_atom, mat_amount, multiplier) +/datum/material_property/flammability/proc/on_applied(datum/material/source, atom/new_atom, mat_amount, multiplier, from_slot) SIGNAL_HANDLER - if (isobj(new_atom) && (new_atom.material_flags & MATERIAL_AFFECT_STATISTICS) && source.get_property(id) > MINIMUM_FLAMMABILITY) + if (isobj(new_atom) && (new_atom.material_flags & MATERIAL_AFFECT_STATISTICS) && source.get_property(id) >= MINIMUM_FLAMMABILITY) new_atom.resistance_flags |= FLAMMABLE -/datum/material_property/flammability/proc/on_removed(datum/material/source, atom/old_atom, mat_amount, multiplier) +/datum/material_property/flammability/proc/on_removed(datum/material/source, atom/old_atom, mat_amount, multiplier, from_slot) SIGNAL_HANDLER - if (isobj(old_atom) && (old_atom.material_flags & MATERIAL_AFFECT_STATISTICS) && source.get_property(id) > MINIMUM_FLAMMABILITY && !(initial(old_atom.resistance_flags) & FLAMMABLE)) + if (isobj(old_atom) && (old_atom.material_flags & MATERIAL_AFFECT_STATISTICS) && source.get_property(id) >= MINIMUM_FLAMMABILITY && !(initial(old_atom.resistance_flags) & FLAMMABLE)) old_atom.resistance_flags &= ~FLAMMABLE #undef MINIMUM_FLAMMABILITY @@ -74,16 +74,135 @@ RegisterSignal(material, COMSIG_MATERIAL_APPLIED, PROC_REF(on_applied)) RegisterSignal(material, COMSIG_MATERIAL_REMOVED, PROC_REF(on_removed)) -/datum/material_property/radioactivity/proc/on_applied(datum/material/source, atom/new_atom, mat_amount, multiplier) +/datum/material_property/radioactivity/proc/on_applied(datum/material/source, atom/new_atom, mat_amount, multiplier, from_slot) SIGNAL_HANDLER // Uranium structures should irradiate, but not items, because item irradiation is a lot more annoying. if (!isitem(new_atom)) new_atom.AddElement(/datum/element/radioactive, chance = source.get_property(id) / URANIUM_RADIOACTIVITY * URANIUM_IRRADIATION_CHANCE * multiplier) -/datum/material_property/radioactivity/proc/on_removed(datum/material/source, atom/old_atom, mat_amount, multiplier) +/datum/material_property/radioactivity/proc/on_removed(datum/material/source, atom/old_atom, mat_amount, multiplier, from_slot) SIGNAL_HANDLER if (!isitem(old_atom)) old_atom.RemoveElement(/datum/element/radioactive, chance = source.get_property(id) / URANIUM_RADIOACTIVITY * URANIUM_IRRADIATION_CHANCE * multiplier) #undef URANIUM_RADIOACTIVITY + +/// Applies firestacks to affected mobs +/datum/material_property/firestacker + name = "Igniting" + id = MATERIAL_FIRESTACKER + +/datum/material_property/firestacker/get_descriptor(value) + return "igniting" + +/datum/material_property/firestacker/get_tooltip(value) + return "Applies [value] firestacks to affected mobs" + +/datum/material_property/firestacker/attach_to(datum/material/material) + . = ..() + material.track_flags |= MATERIAL_TRACK_CONTACT | MATERIAL_TRACK_IMPACT + var/static/list/interaction_signals = list( + COMSIG_MATERIAL_EFFECT_TOUCH, + COMSIG_MATERIAL_EFFECT_STEP, + COMSIG_MATERIAL_EFFECT_HIT, + COMSIG_MATERIAL_EFFECT_THROW_IMPACT, + ) + RegisterSignals(material, interaction_signals, PROC_REF(on_contact)) + +/datum/material_property/firestacker/proc/on_contact(datum/material/source, atom/object, mob/living/target, mob/living/user, def_zone, skin_contact) + SIGNAL_HANDLER + + // Floors don't trigger if you're wearing shoes because it'd be too cancer + if (isfloorturf(object) && !skin_contact && !source.get_property(MATERIAL_PENETRATING)) + return + + if (isliving(target)) + target.adjust_fire_stacks(source.get_property(id)) + +/// Deals additional burn damage to vampires, property value determines damage +/datum/material_property/vampires_bane + name = "Vampires' Bane" + id = MATERIAL_VAMPIRES_BANE + +/datum/material_property/vampires_bane/get_descriptor(value) + return "vampires' bane" + +/datum/material_property/vampires_bane/get_tooltip(value) + return "Deals [value] additional burn damage to vampires on contact" + +/datum/material_property/vampires_bane/attach_to(datum/material/material) + . = ..() + material.track_flags |= MATERIAL_TRACK_CONTACT | MATERIAL_TRACK_IMPACT + var/static/list/interaction_signals = list( + COMSIG_MATERIAL_EFFECT_TOUCH, + COMSIG_MATERIAL_EFFECT_STEP, + COMSIG_MATERIAL_EFFECT_HIT, + COMSIG_MATERIAL_EFFECT_THROW_IMPACT, + ) + RegisterSignals(material, interaction_signals, PROC_REF(on_contact)) + +/datum/material_property/vampires_bane/proc/on_contact(datum/material/source, atom/object, mob/living/target, mob/living/user, def_zone, skin_contact) + SIGNAL_HANDLER + + if (!isvampire(target) || (!skin_contact && !source.get_property(MATERIAL_PENETRATING))) + return + + to_chat(target, span_userdanger("Contact with [object] sears your undead flesh!")) + target.apply_damage(source.get_property(id), BURN, def_zone, wound_bonus = 10, wound_clothing = FALSE) + +/// Teleports targets who come into active contact with the material around, property value determines teleport radius and damage taken per teleport +/datum/material_property/teleporting + name = "Teleporting" + id = MATERIAL_TELEPORTING + +/datum/material_property/teleporting/get_descriptor(value) + return "dimensionally unstable" + +/datum/material_property/teleporting/get_tooltip(value) + return "Randomly teleports whoever comes into contact with it in a [value] tile radius" + +/datum/material_property/teleporting/attach_to(datum/material/material) + . = ..() + material.track_flags |= MATERIAL_TRACK_CONTACT | MATERIAL_TRACK_IMPACT + var/static/list/interaction_signals = list( + COMSIG_MATERIAL_EFFECT_TOUCH, + COMSIG_MATERIAL_EFFECT_STEP, + COMSIG_MATERIAL_EFFECT_HIT, + COMSIG_MATERIAL_EFFECT_THROW_IMPACT, + ) + RegisterSignals(material, interaction_signals, PROC_REF(on_contact)) + +/datum/material_property/teleporting/proc/on_contact(datum/material/source, atom/object, atom/target, mob/living/user, def_zone, skin_contact) + SIGNAL_HANDLER + + if (!ismovable(target)) + return + + var/atom/movable/as_movable = target + // Don't teleport airlocks around please + if (as_movable.anchored || as_movable.move_resist >= INFINITY) + return + + // Floors don't trigger if you're wearing shoes because it'd be too cancer + if (isfloorturf(object) && !skin_contact && !source.get_property(MATERIAL_PENETRATING)) + return + + var/value = source.get_property(id) + do_teleport(target, get_turf(target), value, channel = TELEPORT_CHANNEL_BLUESPACE) + if (isstack(object)) + var/obj/item/stack/as_stack = object + as_stack.use(1) + else if (object.uses_integrity) + object.take_damage(object.max_integrity * value * 0.01) + +/// Makes all contact count as skin contact +/datum/material_property/penetrating + name = "Penetrating" + id = MATERIAL_PENETRATING + +/datum/material_property/penetrating/get_descriptor(value) + return "dimensionally penetrating" + +/datum/material_property/penetrating/get_tooltip(value) + return "Ignores all means of skin protection when triggering other material effects" diff --git a/code/datums/materials/requirements/_requirement.dm b/code/datums/materials/requirements/_requirement.dm index 2447add5960..d23e11c3fb9 100644 --- a/code/datums/materials/requirements/_requirement.dm +++ b/code/datums/materials/requirements/_requirement.dm @@ -57,6 +57,9 @@ property_minimums = list( MATERIAL_HARDNESS = 2, ) + property_maximums = list( + MATERIAL_FLEXIBILITY = 5, + ) /datum/material_requirement/rigid_material required_flags = MATERIAL_CLASS_RIGID diff --git a/code/game/atom/atom_examine.dm b/code/game/atom/atom_examine.dm index a19b83ad84d..02e66ab749e 100644 --- a/code/game/atom/atom_examine.dm +++ b/code/game/atom/atom_examine.dm @@ -145,8 +145,9 @@ if (isnull(prop_value)) // Error? continue var/descriptor = property?.get_descriptor(prop_value) + var/tooltip_hint = property?.get_tooltip(prop_value) if (descriptor) // Overriden derivative property? - material_string += span_tooltip("[property]: [prop_value < 0 ? "-" : ""]\Roman[round(abs(prop_value), 1)]", descriptor) + material_string += span_tooltip("[property]: [tooltip_hint]", descriptor) if (length(material_string)) . += span_info("[capitalize(material.name)] is [english_list(material_string)].") diff --git a/code/game/atom/atom_materials.dm b/code/game/atom/atom_materials.dm index 16153284345..05a1801ee6f 100644 --- a/code/game/atom/atom_materials.dm +++ b/code/game/atom/atom_materials.dm @@ -1,11 +1,13 @@ /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]. + /// 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. + /// 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. + /// 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) @@ -81,8 +83,101 @@ 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. * @@ -93,6 +188,12 @@ * 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) @@ -104,13 +205,36 @@ 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] - apply_single_mat_effect(custom_material, mat_amount, multiplier) - custom_material.on_applied(src, mat_amount, 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)) @@ -118,7 +242,8 @@ var/added_alpha = custom_material.alpha * (custom_material.alpha / 255) total_alpha += GET_MATERIAL_MODIFIER(added_alpha, multiplier) - apply_main_material_effects(main_material, main_mat_amount, main_mat_mult) + 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 @@ -219,20 +344,15 @@ /atom/proc/apply_single_mat_effect(datum/material/material, amount, multiplier) SHOULD_CALL_PARENT(TRUE) + // 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) - return - - var/base_modifier = material.get_property(MATERIAL_INTEGRITY) - var/integrity_mod = GET_MATERIAL_MODIFIER(base_modifier, multiplier) - modify_max_integrity(ceil(max_integrity * integrity_mod)) - var/list/armor_mods = material.get_armor_modifiers(multiplier) - set_armor(get_armor().generate_new_with_multipliers(armor_mods)) + 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) @@ -249,22 +369,44 @@ 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 + 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] - 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) + 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) - remove_main_material_effects(main_material, main_mat_amount, main_mat_mult) + 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) @@ -297,17 +439,8 @@ if(beauty_modifier >= 0.15 && HAS_TRAIT(src, TRAIT_FISHING_BAIT)) RemoveElement(/datum/element/shiny_bait) - if(!(material_flags & MATERIAL_AFFECT_STATISTICS) || !uses_integrity) - return - - var/base_modifier = material.get_property(MATERIAL_INTEGRITY) - var/integrity_mod = GET_MATERIAL_MODIFIER(base_modifier, multiplier) - modify_max_integrity(floor(max_integrity / integrity_mod)) - var/list/armor_mods = material.get_armor_modifiers(multiplier) - 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)) + 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) @@ -333,6 +466,42 @@ 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. * diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index c28f6817330..fff9be1ef6b 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -159,9 +159,15 @@ customMaterials = FALSE continue - var/datum/material_requirement/requirement = SSmaterials.requirements[mat] - if (!requirement) - stack_trace("Design [design] has an invalid material requirement [requirement]") + var/datum/material_requirement/requirement = null + if (ispath(mat, /datum/material_slot)) + var/datum/material_slot/slot = SSmaterials.material_slots[mat] + requirement = SSmaterials.requirements[slot.requirement_type] + else + requirement = SSmaterials.requirements[mat] + + if (!istype(requirement)) + stack_trace("Design [design] has an invalid material requirement: [mat]") continue cost[requirement.get_description()] = design_cost @@ -257,14 +263,20 @@ // Check for materials required. For custom material items decode their required materials var/list/materials_needed = list() + var/list/slots_chosen = null var/mat_choice = FALSE for(var/material, amount_needed in design.materials) - if(!ispath(material, /datum/material_requirement)) // Material requirement + if(!ispath(material, /datum/material_requirement) && !ispath(material, /datum/material_slot)) // Material requirement if(!istype(material, /datum/material)) CRASH("Autolathe ui_act got passed an invalid material id: [material]") materials_needed[material] += amount_needed continue + var/datum/material_slot/slot = null + if (ispath(material, /datum/material_slot)) + slot = SSmaterials.material_slots[material] + material = slot.requirement_type + var/list/choices = list() for(var/datum/material/valid_candidate as anything in SSmaterials.get_materials_by_req(material)) if(materials.get_material_amount(valid_candidate) >= (amount_needed + materials_needed[valid_candidate])) @@ -276,7 +288,7 @@ var/chosen = tgui_input_list( ui.user, - "Select the material to use", + "Select the material to use[slot ? " for [LOWER_TEXT(slot.name)]" : ""]", "Material Selection", sort_list(choices), ) @@ -284,6 +296,9 @@ return // user cancelled material = choices[chosen] + if (slot) + var/datum/material/proper_mat = material + LAZYSET(slots_chosen, slot.type, proper_mat.id) if(isnull(material)) CRASH("A player chose an invalid custom material in autolathe ui_act: [material]") @@ -323,7 +338,7 @@ if(!istype(material, /datum/material/glass) && !istype(material, /datum/material/iron)) ui.user.client.give_award(/datum/award/achievement/misc/getting_an_upgrade, ui.user) break - addtimer(CALLBACK(src, PROC_REF(do_make_item), design, build_count, build_time_per_item, material_cost_coefficient, charge_per_item, materials_needed, target_location), build_time_per_item) + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, build_count, build_time_per_item, material_cost_coefficient, charge_per_item, materials_needed, target_location, slots_chosen), build_time_per_item) return TRUE @@ -339,7 +354,7 @@ * * list/materials_needed - the list of materials to print 1 item * * turf/target - the location to drop the printed item on */ -/obj/machinery/autolathe/proc/do_make_item(datum/design/design, items_remaining, build_time_per_item, material_cost_coefficient, charge_per_item, list/materials_needed, turf/target) +/obj/machinery/autolathe/proc/do_make_item(datum/design/design, items_remaining, build_time_per_item, material_cost_coefficient, charge_per_item, list/materials_needed, turf/target, list/slots_chosen) PROTECTED_PROC(TRUE) if(items_remaining <= 0) // how @@ -386,6 +401,8 @@ created = design.create_result(target, materials_needed, amount = number_to_make) else created = design.create_result(target, materials_needed) + if (length(slots_chosen)) + created.set_material_slots(slots_chosen) split_materials_uniformly(materials_needed, material_cost_coefficient, created) if(isitem(created)) @@ -401,7 +418,7 @@ if(items_remaining <= 0) finalize_build() return - addtimer(CALLBACK(src, PROC_REF(do_make_item), design, items_remaining, build_time_per_item, material_cost_coefficient, charge_per_item, materials_needed, target), build_time_per_item) + addtimer(CALLBACK(src, PROC_REF(do_make_item), design, items_remaining, build_time_per_item, material_cost_coefficient, charge_per_item, materials_needed, target, slots_chosen), build_time_per_item) /** * Resets the icon state and busy flag diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index dc485a807d2..fd7a3f3058c 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -2001,11 +2001,7 @@ if (!(material_flags & MATERIAL_AFFECT_STATISTICS)) return - // [0 ~ 1] is fully insulating, (1 ~ 6] maps to (0 ~ 1] and [6 ~ 10] maps to [1 ~ 2] - // 1.18 and 0.15 here are to allow 6 to map to 1 and 10 to map to 2 and are pulled out of my ass (system in the desmos below) - // See https://www.desmos.com/calculator/rdbv1x8oty - var/conductivity = material.get_property(MATERIAL_ELECTRICAL) - var/siemens_modifier = round(max(0, conductivity - 1) ** 1.18 * 0.15, 0.01) + var/siemens_modifier = material.get_property(MATERIAL_INSULATION) // Cannot use the base formula as it would make any item with glass not conduct electricity if (siemens_modifier > 1) siemens_coefficient *= 1 + (siemens_modifier - 1) * multiplier @@ -2015,30 +2011,15 @@ if (siemens_coefficient == 0) obj_flags &= ~CONDUCTS_ELECTRICITY - if (material_flags & MATERIAL_NO_SLOWDOWN) - return - - // Density above 6 adds slowdown, density below 3 can reduce existing slowdown - var/density = material.get_property(MATERIAL_DENSITY) - var/slowdown_change = 0 - - if (density > 6) - slowdown_change = (density - 6) * MATERIAL_DENSITY_SLOWDOWN * mat_amount / SHEET_MATERIAL_AMOUNT - else if (density < 3) - slowdown_change = (3 - density) * -MATERIAL_DENSITY_SLOWDOWN * mat_amount / SHEET_MATERIAL_AMOUNT - - // Slowdown cannot be reduced below 0 if the item slows you down, or at all if the item speeds you up - if (slowdown_change) - slowdown = max(slowdown >= 0 ? 0 : slowdown, slowdown + slowdown_change * multiplier) + if (!(material_flags & MATERIAL_NO_SLOWDOWN)) + change_material_slowdown(material, mat_amount, multiplier) /obj/item/remove_single_mat_effect(datum/material/material, mat_amount, multiplier) . = ..() if (!(material_flags & MATERIAL_AFFECT_STATISTICS)) return - var/conductivity = material.get_property(MATERIAL_ELECTRICAL) - // 0 ~ 1 count as perfect insulators - var/siemens_modifier = round(max(conductivity - 1, 0) ** 1.18 * 0.15, 0.01) + var/siemens_modifier = material.get_property(MATERIAL_INSULATION) // Cannot use the base formula as it would make any item with glass not conduct electricity if (siemens_modifier > 1) siemens_coefficient /= 1 + (siemens_modifier - 1) * multiplier @@ -2050,16 +2031,24 @@ if (siemens_coefficient > 0 && (initial(obj_flags) & CONDUCTS_ELECTRICITY) && !(obj_flags & CONDUCTS_ELECTRICITY)) obj_flags |= CONDUCTS_ELECTRICITY - if (material_flags & MATERIAL_NO_SLOWDOWN) - return + if (!(material_flags & MATERIAL_NO_SLOWDOWN)) + change_material_slowdown(material, mat_amount, multiplier, removing = TRUE) +/obj/item/proc/change_material_slowdown(datum/material/material, mat_amount, multiplier, removing = FALSE) + // Density above 6 adds slowdown, density below 3 can reduce existing slowdown var/density = material.get_property(MATERIAL_DENSITY) var/slowdown_change = 0 if (density > 6) slowdown_change = (density - 6) * MATERIAL_DENSITY_SLOWDOWN * mat_amount / SHEET_MATERIAL_AMOUNT - else if (density < 3) - slowdown_change = (3 - density) * -MATERIAL_DENSITY_SLOWDOWN * mat_amount / SHEET_MATERIAL_AMOUNT + else if (density < 4) + slowdown_change = (4 - density) * -MATERIAL_DENSITY_SLOWDOWN * mat_amount / SHEET_MATERIAL_AMOUNT + + if (!removing) + // Slowdown cannot be reduced below 0 if the item slows you down, or at all if the item speeds you up + if (slowdown_change) + slowdown = max(slowdown >= 0 ? 0 : slowdown, slowdown + slowdown_change * multiplier) + return if (slowdown_change > 0) slowdown -= slowdown_change * multiplier @@ -2069,11 +2058,12 @@ /obj/item/finalize_remove_material_effects(list/materials) . = ..() + if (!(material_flags & MATERIAL_AFFECT_STATISTICS) || initial(siemens_coefficient) == 0 || siemens_coefficient != 0) + return // If we were made from an insulator we cannot restore via division - if (initial(siemens_coefficient) != 0 && siemens_coefficient == 0) - siemens_coefficient = initial(siemens_coefficient) - if (siemens_coefficient > 0 && (initial(obj_flags) & CONDUCTS_ELECTRICITY) && !(obj_flags & CONDUCTS_ELECTRICITY)) - obj_flags |= CONDUCTS_ELECTRICITY + siemens_coefficient = initial(siemens_coefficient) + if (siemens_coefficient > 0 && (initial(obj_flags) & CONDUCTS_ELECTRICITY) && !(obj_flags & CONDUCTS_ELECTRICITY)) + obj_flags |= CONDUCTS_ELECTRICITY /obj/item/change_material_strength(datum/material/material, mat_amount, multiplier, remove = FALSE) var/density = material.get_property(MATERIAL_DENSITY) diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm index 64d594b8baf..d7bf67cf730 100644 --- a/code/game/objects/items/stacks/bscrystal.dm +++ b/code/game/objects/items/stacks/bscrystal.dm @@ -7,7 +7,8 @@ singular_name = "bluespace crystal" dye_color = DYE_COSMIC w_class = WEIGHT_CLASS_TINY - mats_per_unit = list(/datum/material/bluespace=SHEET_MATERIAL_AMOUNT) + material_flags = MATERIAL_NO_DESCRIPTORS // Handles in-hand/thrown teleports by itself + mats_per_unit = list(/datum/material/bluespace = SHEET_MATERIAL_AMOUNT) points = 50 refined_type = /obj/item/stack/sheet/bluespace_crystal scan_state = "rock_bscrystal" @@ -52,6 +53,9 @@ blink_mob(hit_atom) use(1) +/obj/item/stack/ore/bluespace_crystal/attack_self_secondary(mob/user, modifiers) + interact(user) + //Artificial bluespace crystal, doesn't give you much research. /obj/item/stack/ore/bluespace_crystal/artificial name = "artificial bluespace crystal" @@ -67,12 +71,12 @@ // Polycrystals, aka stacks /obj/item/stack/sheet/bluespace_crystal name = "bluespace polycrystal" - icon = 'icons/obj/stack_objects.dmi' - icon_state = "polycrystal" - inhand_icon_state = null - gulag_valid = TRUE singular_name = "bluespace polycrystal" desc = "A stable polycrystal, made of fused-together bluespace crystals. You could probably break one off." + icon_state = "polycrystal" + inhand_icon_state = null + material_flags = MATERIAL_NO_DESCRIPTORS + gulag_valid = TRUE mats_per_unit = list(/datum/material/bluespace=SHEET_MATERIAL_AMOUNT) attack_verb_continuous = list("bluespace polybashes", "bluespace polybatters", "bluespace polybludgeons", "bluespace polythrashes", "bluespace polysmashes") attack_verb_simple = list("bluespace polybash", "bluespace polybatter", "bluespace polybludgeon", "bluespace polythrash", "bluespace polysmash") @@ -81,23 +85,21 @@ material_type = /datum/material/bluespace var/crystal_type = /obj/item/stack/ore/bluespace_crystal/refined -/obj/item/stack/sheet/bluespace_crystal/attack_self(mob/user)// to prevent the construction menu from ever happening - to_chat(user, span_warning("You cannot crush the polycrystal in-hand, try breaking one off.")) - //ATTACK HAND IGNORING PARENT RETURN VALUE /obj/item/stack/sheet/bluespace_crystal/attack_hand(mob/user, list/modifiers) - if(user.get_inactive_held_item() == src) - if(is_zero_amount(delete_if_zero = TRUE)) - return - var/BC = new crystal_type(src) - user.put_in_hands(BC) - use(1) - if(!amount) - to_chat(user, span_notice("You break the final crystal off.")) - else - to_chat(user, span_notice("You break off a crystal.")) + if(user.get_inactive_held_item() != src) + return ..() + + if(is_zero_amount(delete_if_zero = TRUE)) + return + + var/BC = new crystal_type(src) + user.put_in_hands(BC) + use(1) + if(!amount) + to_chat(user, span_notice("You break the final crystal off.")) else - ..() + to_chat(user, span_notice("You break off a crystal.")) /obj/item/stack/sheet/bluespace_crystal/fifty amount = 50 diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 17fd93bc5bf..d9a5e5c4bcc 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -63,7 +63,7 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \ ) AddElement(/datum/element/contextual_screentip_tools, tool_behaviors) - var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/spear, /datum/crafting_recipe/stunprod, /datum/crafting_recipe/teleprod) // snatcher prod isn't here as a spoopy secret + var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/spear, /datum/crafting_recipe/stunprod, /datum/crafting_recipe/teleprod, /datum/crafting_recipe/wireprod) // snatcher prod isn't here as a spoopy secret AddElement( /datum/element/slapcrafting,\ diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm index d4138155da2..83b202bc911 100644 --- a/code/game/objects/items/stacks/sheets/leather.dm +++ b/code/game/objects/items/stacks/sheets/leather.dm @@ -167,9 +167,9 @@ GLOBAL_LIST_INIT(monkey_recipes, list ( \ amount = 5 /obj/item/stack/sheet/animalhide/xeno - name = "alien hide" + name = "alien chitin" + singular_name = "alien chitin piece" desc = "The skin of a terrible creature." - singular_name = "alien hide piece" icon_state = "sheet-xeno" inhand_icon_state = null merge_type = /obj/item/stack/sheet/animalhide/xeno @@ -210,16 +210,6 @@ GLOBAL_LIST_INIT(carp_recipes, list ( \ /obj/item/stack/sheet/animalhide/carp/five amount = 5 -//don't see anywhere else to put these, maybe together they could be used to make the xenos suit? -/obj/item/stack/sheet/xenochitin - name = "alien chitin" - desc = "A piece of the hide of a terrible creature." - singular_name = "alien hide piece" - icon = 'icons/mob/nonhuman-player/alien.dmi' - icon_state = "chitin" - novariants = TRUE - merge_type = /obj/item/stack/sheet/xenochitin - /obj/item/xenos_claw name = "alien claw" desc = "The claw of a terrible creature." diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index a8169715d35..8ec2a97520a 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -950,7 +950,7 @@ GLOBAL_LIST_INIT(paperframe_recipes, list( desc = "Something's bloody meat compressed into a nice solid sheet." singular_name = "meat sheet" icon_state = "sheet-meat" - material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR + material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR | MATERIAL_NO_DESCRIPTORS mats_per_unit = list(/datum/material/meat = SHEET_MATERIAL_AMOUNT) merge_type = /obj/item/stack/sheet/meat material_type = /datum/material/meat @@ -997,7 +997,7 @@ GLOBAL_LIST_INIT(pizza_sheet_recipes, list( desc = "These sheets seem cursed." singular_name = "haunted sheet" icon_state = "sheet-meat" - material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR + material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR | MATERIAL_NO_DESCRIPTORS mats_per_unit = list(/datum/material/hauntium = SHEET_MATERIAL_AMOUNT) merge_type = /obj/item/stack/sheet/hauntium material_type = /datum/material/hauntium diff --git a/code/game/objects/items/stacks/sheets/sheets.dm b/code/game/objects/items/stacks/sheets/sheets.dm index 862d255133e..3d2b1f52030 100644 --- a/code/game/objects/items/stacks/sheets/sheets.dm +++ b/code/game/objects/items/stacks/sheets/sheets.dm @@ -56,8 +56,9 @@ if (isnull(prop_value)) // Error? continue var/descriptor = property?.get_descriptor(prop_value) + var/tooltip_hint = property?.get_tooltip(prop_value) if (descriptor) // Overriden derivative property? - material_string += span_tooltip("[property]: [prop_value < 0 ? "-" : ""]\Roman[round(abs(prop_value), 1)]", descriptor) + material_string += span_tooltip("[property]: [tooltip_hint]", descriptor) if (length(material_string)) . += span_info("[capitalize(material.name)] is [english_list(material_string)].") diff --git a/code/game/objects/items/stacks/telecrystal.dm b/code/game/objects/items/stacks/telecrystal.dm index dd1b74eb106..4988a9748a8 100644 --- a/code/game/objects/items/stacks/telecrystal.dm +++ b/code/game/objects/items/stacks/telecrystal.dm @@ -1,18 +1,20 @@ /obj/item/stack/telecrystal name = "telecrystal" - desc = "It seems to be pulsing with suspiciously enticing energies." + desc = "Covered in a web of finely engraved geometrical patterns, pulsing with suspiciously enticing energies." singular_name = "telecrystal" - icon = 'icons/obj/stack_objects.dmi' icon_state = "telecrystal" dye_color = DYE_SYNDICATE + full_w_class = WEIGHT_CLASS_TINY w_class = WEIGHT_CLASS_TINY max_amount = 50 item_flags = NOBLUDGEON merge_type = /obj/item/stack/telecrystal novariants = FALSE + material_type = /datum/material/telecrystal + mats_per_unit = list(/datum/material/telecrystal = SHEET_MATERIAL_AMOUNT) /obj/item/stack/telecrystal/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) - if(interacting_with != user) //You can't go around smacking people with crystals to find out if they have an uplink or not. + if(interacting_with != user) // You can't go around smacking people with crystals to find out if they have an uplink or not. return NONE for(var/obj/item/implant/uplink/uplink in interacting_with) @@ -20,14 +22,30 @@ continue var/datum/component/uplink/hidden_uplink = uplink.GetComponent(/datum/component/uplink) - if(hidden_uplink) - hidden_uplink.uplink_handler.add_telecrystals(amount) - use(amount) - to_chat(user, span_notice("You press [src] onto yourself and charge your hidden uplink.")) - return ITEM_INTERACT_SUCCESS + if(!hidden_uplink) + continue + hidden_uplink.uplink_handler.add_telecrystals(amount) + use(amount) + to_chat(user, span_notice("You press [src] onto yourself and charge your hidden uplink.")) + return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_BLOCKING /obj/item/stack/telecrystal/five amount = 5 /obj/item/stack/telecrystal/twenty amount = 20 + +/obj/item/stack/sheet/telepolycrystal + name = "telelocational podcrystal" + singular_name = "telelocational podcrystal" + desc = "A \"somewhat\" stable chunk of telecrystal. It lacks the precision-carved tuning channels, making it useless for long-range matter teleportation." + icon_state = "telepolycrystal" + inhand_icon_state = null + full_w_class = WEIGHT_CLASS_TINY + w_class = WEIGHT_CLASS_TINY + dye_color = DYE_SYNDICATE + novariants = TRUE + merge_type = /obj/item/stack/sheet/telepolycrystal + material_type = /datum/material/telecrystal + mats_per_unit = list(/datum/material/telecrystal = SHEET_MATERIAL_AMOUNT) diff --git a/code/game/objects/items/weaponry/melee/baton.dm b/code/game/objects/items/weaponry/melee/baton.dm index 07a40afabdc..3c5f0a4991a 100644 --- a/code/game/objects/items/weaponry/melee/baton.dm +++ b/code/game/objects/items/weaponry/melee/baton.dm @@ -933,6 +933,7 @@ slot_flags = null throw_stun_chance = 50 //I think it'd be funny can_upgrade = FALSE + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 1.15, /datum/material/telecrystal = SHEET_MATERIAL_AMOUNT, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 2) /obj/item/melee/baton/security/cattleprod/telecrystalprod/baton_effect(mob/living/target, mob/living/user, stun_override, clumsy) . = ..() diff --git a/code/game/objects/items/weaponry/melee/misc.dm b/code/game/objects/items/weaponry/melee/misc.dm index 8bb68ee396e..11d951f22a9 100644 --- a/code/game/objects/items/weaponry/melee/misc.dm +++ b/code/game/objects/items/weaponry/melee/misc.dm @@ -238,8 +238,10 @@ greyscale_config_worn = /datum/greyscale_config/cleric_mace greyscale_colors = COLOR_WHITE + COLOR_BROWN - material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_AFFECT_STATISTICS //Material type changes the prefix as well as the color. - custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 4.5, /datum/material/wood = SHEET_MATERIAL_AMOUNT * 1.5) //Defaults to an Iron Mace. + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE | MATERIAL_AFFECT_STATISTICS + // Defaults to an iron head, wooden handle mace + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 4.5, /datum/material/wood = SHEET_MATERIAL_AMOUNT * 1.5) + material_slots = list(/datum/material_slot/weapon_head/mace = /datum/material/iron, /datum/material_slot/handle = /datum/material/wood) slot_flags = ITEM_SLOT_BELT force = 16 w_class = WEIGHT_CLASS_BULKY @@ -250,31 +252,28 @@ attack_verb_continuous = list("smacks", "strikes", "cracks", "beats") attack_verb_simple = list("smack", "strike", "crack", "beat") -///Cleric maces are made of two custom materials: one is handle, and the other is the mace itself. -/obj/item/melee/cleric_mace/get_material_multiplier(datum/material/custom_material, list/materials, index) - if(length(materials) <= 1) - return 1.2 - if(index == 1) - return 1 - else - return 0.3 - +// It only inherits the name of the main material it's made of. The secondary is in the description. /obj/item/melee/cleric_mace/get_material_prefixes(list/materials) - var/datum/material/material = materials[1] - return material.name //It only inherits the name of the main material it's made of. The secondary is in the description. + var/datum/material/material = get_material_from_slot(/datum/material_slot/weapon_head) + return material?.name /obj/item/melee/cleric_mace/finalize_material_effects(list/materials) . = ..() - if(length(materials) == 1) - return - var/datum/material/material = materials[2] - desc = "[initial(desc)] Its handle is made of [material.name]." + var/datum/material/material = get_material_from_slot(/datum/material_slot/handle) + if (material) + desc = "[initial(desc)] Its handle is made of [material.name]." /obj/item/melee/cleric_mace/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) + // Don't bring a...mace to a gunfight, and also you aren't going to really block someone full body tackling you with a mace. + // Or a road roller, if one happened to hit you. if(attack_type == PROJECTILE_ATTACK || attack_type == LEAP_ATTACK || attack_type == OVERWHELMING_ATTACK) - final_block_chance = 0 //Don't bring a...mace to a gunfight, and also you aren't going to really block someone full body tackling you with a mace. Or a road roller, if one happened to hit you. + final_block_chance = 0 return ..() +/datum/material_slot/weapon_head/mace + name = "mace head" + material_amount = 3 + /obj/item/sord name = "\improper SORD" desc = "This thing is so unspeakably shitty you are having a hard time even holding it." diff --git a/code/game/objects/items/weaponry/melee/spear.dm b/code/game/objects/items/weaponry/melee/spear.dm index 00035afbc59..c3a87f4f0d6 100644 --- a/code/game/objects/items/weaponry/melee/spear.dm +++ b/code/game/objects/items/weaponry/melee/spear.dm @@ -1,4 +1,5 @@ -//spears +#define SPEAR_CUSTOM_TIP_PREFIX "spearblank" + /obj/item/spear name = "spear" desc = "A haphazardly-constructed yet still deadly weapon of ancient design." @@ -17,6 +18,7 @@ embed_type = /datum/embedding/spear armour_penetration = 5 custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 0.65, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 1.15) + material_slots = list(/datum/material_slot/weapon_head/speartip = /datum/material/glass, /datum/material_slot/handle/spear = /datum/material/iron) hitsound = 'sound/items/weapons/bladeslice.ogg' attack_verb_continuous = list("attacks", "pokes", "jabs", "tears", "lacerates", "gores") attack_verb_simple = list("attack", "poke", "jab", "tear", "lacerate", "gore") @@ -25,7 +27,7 @@ armor_type = /datum/armor/item_spear wound_bonus = -15 exposed_wound_bonus = 15 - material_flags = MATERIAL_EFFECTS + material_flags = MATERIAL_EFFECTS | MATERIAL_AFFECT_STATISTICS /// The icon prefix for this flavor of spear var/icon_prefix = "spearglass" /// How much damage to do unwielded @@ -36,8 +38,6 @@ var/improvised_construction = TRUE /// What is left over when a spear breaks var/spear_leftovers = /obj/item/stack/rods - /// Material type from which our tip is made - var/tip_mat_type = null /// What pike do we construct if someone kills themselves with us? var/pike_type = /obj/structure/headpike @@ -84,6 +84,10 @@ /obj/item/spear/update_icon_state() icon_state = "[icon_prefix]0" + if (icon_prefix == SPEAR_CUSTOM_TIP_PREFIX) + worn_icon_state = "spearglass0" + else + worn_icon_state = null return ..() /obj/item/spear/suicide_act(mob/living/carbon/user) @@ -107,87 +111,79 @@ var/obj/item/stack/rods/rod = locate() in components if (rod) spear_leftovers = rod.type + set_material_slot(/datum/material_slot/handle/spear, rod.get_master_material()) var/obj/item/shard/tip = locate() in components if (!tip) return ..() var/datum/material/tip_material = tip.get_master_material() - // For master material effects. As on_craft_completion is ran before set_custom_materials, this will allow the speartip to be treated as our master material no matter what - tip_mat_type = tip_material.id - switch (tip_mat_type) + set_material_slot(/datum/material_slot/weapon_head/speartip, tip_material) + return ..() + +/obj/item/spear/set_material_slot(slot_type, new_material) + . = ..() + if (slot_type != /datum/material_slot/weapon_head/speartip) + return + + if (istype(new_material, /datum/material)) + var/datum/material/as_material = new_material + new_material = as_material.type + + switch (new_material) if (/datum/material/alloy/plasmaglass) icon_prefix = "spearplasma" if (/datum/material/alloy/titaniumglass) icon_prefix = "speartitanium" if (/datum/material/alloy/plastitaniumglass) icon_prefix = "spearplastitanium" + else + icon_prefix = SPEAR_CUSTOM_TIP_PREFIX + + AddComponent(/datum/component/two_handed, \ + icon_wielded = "[icon_prefix]1", \ + wield_callback = CALLBACK(src, PROC_REF(on_wield)), \ + unwield_callback = CALLBACK(src, PROC_REF(on_unwield)), \ + ) update_appearance() - return ..() + +/obj/item/spear/finalize_material_effects(list/materials) + . = ..() + update_appearance() + +/obj/item/spear/update_overlays() + . = ..() + if (icon_prefix != SPEAR_CUSTOM_TIP_PREFIX) + return + var/datum/material/tip_material = get_master_material() + var/mutable_appearance/tip_overlay = mutable_appearance(icon, "speartip", appearance_flags = KEEP_APART | RESET_COLOR) + tip_overlay.color = tip_material.color + . += tip_overlay + +/obj/item/spear/separate_worn_overlays(mutable_appearance/standing, mutable_appearance/draw_target, isinhands, icon_file) + . = ..() + if (icon_prefix != SPEAR_CUSTOM_TIP_PREFIX || !isinhands) + return + var/datum/material/tip_material = get_master_material() + var/mutable_appearance/tip_overlay = mutable_appearance(icon_file, "speartip[HAS_TRAIT(src, TRAIT_WIELDED)]", appearance_flags = RESET_COLOR) + tip_overlay.color = tip_material.color + . += tip_overlay /obj/item/spear/get_master_material() - if (tip_mat_type && custom_materials[tip_mat_type]) - return SSmaterials.get_material(tip_mat_type) - return ..() - -/obj/item/spear/apply_main_material_effects(datum/material/main_material, amount, multiplier) - . = ..() - var/density = main_material.get_property(MATERIAL_DENSITY) - var/hardness = main_material.get_property(MATERIAL_HARDNESS) - // If a spear is too hard its unwieldy, if it is too light it doesn't have enough weight behind it - var/force_change = (hardness - 4) - max(0, density - 4) - max(0, 4 - density) * 2 - force_unwielded += force_change - force_wielded += force_change - force = force_unwielded - throwforce += force_change - wound_bonus += force_change * 5 - modify_max_integrity(max_integrity + (hardness - 4) * 10) - throw_range += (hardness - 4) - (density - 4) * 2 - throw_speed += floor(((hardness - 4) - (density - 4) * 2) / 2) - // These try to keep parity with titanium/plastitanium spears as armorpen boost was exclusive to them - armour_penetration += MATERIAL_PROPERTY_DIVERGENCE(hardness, 4, 6) * 5 - exposed_wound_bonus += (MATERIAL_PROPERTY_DIVERGENCE(hardness, 4, 6) - (density - 4)) * 5 - AddComponent(/datum/component/two_handed, \ - force_unwielded = force_unwielded, \ - force_wielded = force_wielded, \ - icon_wielded = "[icon_prefix]1", \ - wield_callback = CALLBACK(src, PROC_REF(on_wield)), \ - unwield_callback = CALLBACK(src, PROC_REF(on_unwield)), \ - ) - -/obj/item/spear/remove_main_material_effects(datum/material/main_material, amount, multiplier) - . = ..() - var/density = main_material.get_property(MATERIAL_DENSITY) - var/hardness = main_material.get_property(MATERIAL_HARDNESS) - var/force_change = (hardness - 4) - (density - 4) - force_unwielded -= force_change - force_wielded -= force_change - force = force_unwielded - throwforce -= force_change - wound_bonus -= force_change * 5 - modify_max_integrity(max_integrity - (hardness - 4) * 10) - throw_range -= (hardness - 4) - (density - 4) * 2 - throw_speed -= floor(((hardness - 4) - (density - 4) * 2) / 2) - // These try to keep parity with titanium/plastitanium spears as armorpen boost was exclusive to them - armour_penetration -= MATERIAL_PROPERTY_DIVERGENCE(hardness, 4, 6) * 5 - exposed_wound_bonus -= (MATERIAL_PROPERTY_DIVERGENCE(hardness, 4, 6) - (density - 4)) * 5 - AddComponent(/datum/component/two_handed, \ - force_unwielded = force_unwielded, \ - force_wielded = force_wielded, \ - icon_wielded = "[icon_prefix]1", \ - wield_callback = CALLBACK(src, PROC_REF(on_wield)), \ - unwield_callback = CALLBACK(src, PROC_REF(on_unwield)), \ - ) + var/datum/material/tip_material = get_material_from_slot(/datum/material_slot/weapon_head/speartip) + if (!tip_material) + return ..() + return custom_materials[tip_material] ? tip_material : ..() /obj/item/spear/afterattack(atom/target, mob/user, list/modifiers, list/attack_modifiers) - if(improvised_construction) + if(improvised_construction && !QDELETED(src)) take_damage(force / 2, sound_effect = FALSE) /obj/item/spear/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) . = ..() if (.) //spear was caught return - if(improvised_construction) + if(improvised_construction && !QDELETED(src)) take_damage(throwforce / 2, sound_effect = FALSE) /obj/item/spear/atom_destruction(damage_flag) @@ -197,6 +193,10 @@ loc.balloon_alert(loc, "spear broken!") return ..() +/obj/item/spear/get_material_prefixes(list/materials) + var/datum/material/material = get_material_from_slot(/datum/material_slot/weapon_head/speartip) + return material?.name + /obj/item/spear/proc/on_wield(obj/item/source, mob/living/carbon/user) reach = 1 armour_penetration *= 2 @@ -205,6 +205,111 @@ reach = 2 armour_penetration /= 2 +/datum/material_slot/weapon_head/speartip + name = "tip" + material_amount = 1.75 + +/datum/material_slot/weapon_head/speartip/on_applied(obj/item/spear/target, datum/material/material, amount, multiplier) + . = ..() + if (!(target.material_flags & MATERIAL_AFFECT_STATISTICS)) + return FALSE + + var/density = material.get_property(MATERIAL_DENSITY) + var/hardness = material.get_property(MATERIAL_HARDNESS) + // If a spear is too hard its unwieldy, if it is too light it doesn't have enough weight behind it + var/material_effect = (hardness - 4) - max(0, density - 4) - max(0, 4 - density) * 2 + target.wound_bonus += material_effect * 5 + // These try to keep parity with titanium/plastitanium spears as armorpen boost was exclusive to them + target.armour_penetration += MATERIAL_PROPERTY_DIVERGENCE(hardness, 4, 6) * 5 + target.exposed_wound_bonus += (MATERIAL_PROPERTY_DIVERGENCE(hardness, 4, 6) - (density - 4)) * 5 + return FALSE + +/datum/material_slot/weapon_head/spearhead/on_removed(obj/item/spear/target, datum/material/material, amount, multiplier) + . = ..() + if (!(target.material_flags & MATERIAL_AFFECT_STATISTICS)) + return FALSE + + var/density = material.get_property(MATERIAL_DENSITY) + var/hardness = material.get_property(MATERIAL_HARDNESS) + var/material_effect = (hardness - 4) - max(0, density - 4) - max(0, 4 - density) * 2 + target.wound_bonus -= material_effect * 5 + target.armour_penetration -= MATERIAL_PROPERTY_DIVERGENCE(hardness, 4, 6) * 5 + target.exposed_wound_bonus -= (MATERIAL_PROPERTY_DIVERGENCE(hardness, 4, 6) - (density - 4)) * 5 + return FALSE + +/datum/material_slot/handle/spear + +/datum/material_slot/handle/spear/on_applied(obj/item/target, datum/material/material, amount, multiplier) + . = ..() + if (!(target.material_flags & MATERIAL_AFFECT_STATISTICS)) + return FALSE + + var/density = material.get_property(MATERIAL_DENSITY) + var/hardness = material.get_property(MATERIAL_HARDNESS) + target.throw_range += (hardness - 4) - (density - 4) * 2 + target.throw_speed += floor((hardness - 4) / 2) - (density - 4) * 2 + return FALSE + +/datum/material_slot/handle/spear/on_removed(obj/item/target, datum/material/material, amount, multiplier) + . = ..() + if (!(target.material_flags & MATERIAL_AFFECT_STATISTICS)) + return FALSE + + var/density = material.get_property(MATERIAL_DENSITY) + var/hardness = material.get_property(MATERIAL_HARDNESS) + target.throw_range -= (hardness - 4) - (density - 4) * 2 + target.throw_speed -= floor((hardness - 4) / 2) - (density - 4) * 2 + return FALSE + +/obj/item/wireprod + name = "wireprod" + desc = "A metal rod with some wire attached to one of the ends, waiting for something sharp." + icon = 'icons/obj/weapons/spear.dmi' + icon_state = "wireprod" + inhand_icon_state = "spearblank0" + lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' + icon_angle = -45 + force = 5 + w_class = WEIGHT_CLASS_BULKY + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 0.65, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 1.5) + attack_verb_continuous = list("attacks", "pokes", "jabs", "tears", "lacerates", "gores") + attack_verb_simple = list("attack", "poke", "jab", "tear", "lacerate", "gore") + material_flags = MATERIAL_EFFECTS | MATERIAL_AFFECT_STATISTICS + +/obj/item/wireprod/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + var/datum/material/shard_mat = null + if (istype(tool, /obj/item/shard)) + shard_mat = tool.get_master_material() + else if (istype(tool, /obj/item/stack)) + shard_mat = tool.get_master_material() + if (!(shard_mat.mat_flags & MATERIAL_CLASS_CRYSTAL)) + shard_mat = null + + if (!shard_mat) + return NONE + + var/obj/item/spear/spear = new(drop_location()) + var/datum/material/rod_material = get_master_material() + spear.material_flags |= MATERIAL_ADD_PREFIX + spear.set_material_slot(/datum/material_slot/handle/spear, get_master_material()) + spear.set_material_slot(/datum/material_slot/weapon_head/speartip, shard_mat) + spear.set_custom_materials(list((rod_material) = custom_materials[rod_material], (shard_mat) = tool.custom_materials[shard_mat])) + to_chat(user, span_notice("You attach [tool] to [src]'s tip.")) + + if (istype(tool, /obj/item/stack)) + var/obj/item/stack/stack = tool + stack.use(1) + else + qdel(tool) + + var/was_holding = user.get_held_index_of_item(src) + qdel(src) + if (was_holding) + user.put_in_hands(spear) + +#undef SPEAR_CUSTOM_TIP_PREFIX + /obj/item/spear/explosive name = "explosive lance" icon_state = "spearbomb0" @@ -337,6 +442,7 @@ righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' demolition_mod = 0.5 resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + material_flags = MATERIAL_EFFECTS force = 13 throwforce = 23 throw_range = 9 @@ -350,7 +456,9 @@ custom_materials = list( /datum/material/iron = SHEET_MATERIAL_AMOUNT * 42, /datum/material/alloy/plasteel = SHEET_MATERIAL_AMOUNT * 15, - /datum/material/titanium = SHEET_MATERIAL_AMOUNT * 5) + /datum/material/titanium = SHEET_MATERIAL_AMOUNT * 5, + ) + material_slots = list(/datum/material_slot/weapon_head/speartip = /datum/material/titanium, /datum/material_slot/handle/spear = /datum/material/alloy/plasteel) /obj/item/spear/dragonator/Initialize(mapload) . = ..() @@ -366,6 +474,7 @@ icon_state = "speardragonraw0" icon_prefix = "speardragonraw" base_icon_state = "speardragonraw" + material_flags = MATERIAL_EFFECTS demolition_mod = 0.5 wound_bonus = 0 exposed_wound_bonus = 0 @@ -375,10 +484,14 @@ custom_materials = list( /datum/material/iron = SHEET_MATERIAL_AMOUNT * 42, /datum/material/alloy/plasteel = SHEET_MATERIAL_AMOUNT * 15, - /datum/material/titanium = SHEET_MATERIAL_AMOUNT * 5) + /datum/material/titanium = SHEET_MATERIAL_AMOUNT * 5, + ) + material_slots = list(/datum/material_slot/weapon_head/speartip = /datum/material/titanium, /datum/material_slot/handle/spear = /datum/material/alloy/plasteel) /obj/item/spear/dragonator_untreated/fire_act(exposed_temperature, exposed_volume) var/obj/item/spear/dragonator/dragonator = new(loc) + dragonator.set_material_slots(material_slots) + dragonator.set_custom_materials(custom_materials.Copy()) playsound(dragonator.loc, 'sound/effects/magic/staff_change.ogg',5) qdel(src) @@ -394,6 +507,7 @@ throwforce = 22 armour_penetration = 20 //Enhanced armor piercing custom_materials = list(/datum/material/bone = SHEET_MATERIAL_AMOUNT * 4) + material_slots = list(/datum/material_slot/weapon_head/speartip = /datum/material/bone, /datum/material_slot/handle/spear = /datum/material/bone) force_unwielded = 12 force_wielded = 20 spear_leftovers = /obj/item/stack/sheet/bone @@ -419,6 +533,7 @@ throwforce = 23 //Better to throw custom_materials = list(/datum/material/bamboo = SHEET_MATERIAL_AMOUNT * 25) + material_slots = list(/datum/material_slot/weapon_head/speartip = /datum/material/bamboo, /datum/material_slot/handle/spear = /datum/material/bamboo) spear_leftovers = /obj/item/stack/sheet/mineral/bamboo pike_type = /obj/structure/headpike/bamboo @@ -445,11 +560,12 @@ attack_verb_simple = list("attack", "poke", "jab", "tear", "gore", "lance") throwforce = 24 embed_type = null //no embedding - + material_flags = MATERIAL_EFFECTS custom_materials = list( /datum/material/diamond = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/alloy/plastitaniumglass = SHEET_MATERIAL_AMOUNT, ) + material_slots = list(/datum/material_slot/weapon_head/speartip = /datum/material/diamond, /datum/material_slot/handle/spear = /datum/material/alloy/plastitaniumglass) action_slots = ITEM_SLOT_HANDS actions_types = list(/datum/action/item_action/skybulge) improvised_construction = FALSE diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 3e572f7fb2e..167ca422109 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -330,15 +330,13 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag) /obj/apply_single_mat_effect(datum/material/material, mat_amount, multiplier) . = ..() - if(!(material_flags & MATERIAL_AFFECT_STATISTICS)) - return - change_material_strength(material, mat_amount, multiplier) + if(material_flags & MATERIAL_AFFECT_STATISTICS) + change_material_strength(material, mat_amount, multiplier) /obj/remove_single_mat_effect(datum/material/material, mat_amount, multiplier) . = ..() - if(!(material_flags & MATERIAL_AFFECT_STATISTICS)) - return - change_material_strength(material, mat_amount, multiplier, remove = TRUE) + if(material_flags & MATERIAL_AFFECT_STATISTICS) + change_material_strength(material, mat_amount, multiplier, remove = TRUE) /// Changes force and throwforce of an item based on its properties. Split into a separate proc as to allow items to change theirs based on sharpness and behavior /obj/proc/change_material_strength(datum/material/material, mat_amount, multiplier, remove = FALSE) diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm index b70d6af0072..342bfc981fd 100644 --- a/code/modules/fishing/fish/fish_traits.dm +++ b/code/modules/fishing/fish/fish_traits.dm @@ -259,7 +259,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/carnivore/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type) . = ..() - if(istype(rod.get_master_material(), /datum/material/meat)) //who cares about the bait, that fishing rod is yummy! + if(IS_EDIBLE(rod)) //who cares about the bait, that fishing rod is yummy! return if(!rod.bait) .[MULTIPLICATIVE_FISHING_MOD] = 0 diff --git a/code/modules/manufactorio/machines/lathe.dm b/code/modules/manufactorio/machines/lathe.dm index 559b92b7c10..c248bc6e47c 100644 --- a/code/modules/manufactorio/machines/lathe.dm +++ b/code/modules/manufactorio/machines/lathe.dm @@ -112,8 +112,17 @@ return //check for materials required. For custom material items decode their required materials var/list/materials_needed = list() + var/list/slots_chosen = null for(var/material, amount_needed in design.materials) - if(ispath(material, /datum/material_requirement)) // Material requirement + var/datum/material_requirement/requirement = null + var/datum/material_slot/slot = null + if(ispath(material, /datum/material_requirement)) + requirement = material + else if (ispath(material, /datum/material_slot)) + slot = SSmaterials.material_slots[material] + requirement = slot.requirement_type + + if(requirement) // Material requirement for(var/datum/material/valid_candidate as anything in SSmaterials.get_materials_by_req(material)) if(materials.get_material_amount(valid_candidate) >= amount_needed) material = valid_candidate @@ -121,6 +130,9 @@ if(isnull(material)) return materials_needed[material] = amount_needed + if (slot) + var/datum/material/proper_mat = material + LAZYSET(slots_chosen, slot.type, proper_mat.id) if(!materials.has_materials(materials_needed)) return @@ -129,9 +141,9 @@ flick_overlay_view(mutable_appearance(icon, "lathe_printing"), craft_time) print_sound.start() add_load(power_cost) - busy = addtimer(CALLBACK(src, PROC_REF(do_make_item), design, materials_needed), craft_time, TIMER_UNIQUE | TIMER_STOPPABLE | TIMER_DELETE_ME) + busy = addtimer(CALLBACK(src, PROC_REF(do_make_item), design, materials_needed, slots_chosen), craft_time, TIMER_UNIQUE | TIMER_STOPPABLE | TIMER_DELETE_ME) -/obj/machinery/power/manufacturing/lathe/proc/do_make_item(datum/design/design, list/materials_needed) +/obj/machinery/power/manufacturing/lathe/proc/do_make_item(datum/design/design, list/materials_needed, list/slots_chosen) finalize_build() if(surplus() < power_cost) return @@ -154,7 +166,10 @@ created = new stack_item(drop_location(), amount) else created = design.create_result(drop_location(), materials_needed) + if (length(slots_chosen)) + created.set_material_slots(slots_chosen) split_materials_uniformly(materials_needed, target_object = created) + if(isitem(created)) created.pixel_x = created.base_pixel_x + rand(-6, 6) created.pixel_y = created.base_pixel_y + rand(-6, 6) diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm index 9a16feceebb..bd8dd61d250 100644 --- a/code/modules/projectiles/guns/ballistic/pistol.dm +++ b/code/modules/projectiles/guns/ballistic/pistol.dm @@ -189,7 +189,7 @@ accepted_magazine_type = /obj/item/ammo_box/magazine/r10mm actions_types = list(/datum/action/item_action/toggle_firemode) obj_flags = UNIQUE_RENAME // if you did the sidequest, you get the customization - custom_materials = list(/datum/material/gold = SHEET_MATERIAL_AMOUNT * 30, /datum/material/silver = SHEET_MATERIAL_AMOUNT * 25, /datum/material/iron = SHEET_MATERIAL_AMOUNT * 11.5) + custom_materials = list(/datum/material/gold = SHEET_MATERIAL_AMOUNT * 30, /datum/material/silver = SHEET_MATERIAL_AMOUNT * 25, /datum/material/iron = SHEET_MATERIAL_AMOUNT * 11.5, /datum/material/telecrystal = SHEET_MATERIAL_AMOUNT * 4) /obj/item/gun/ballistic/automatic/pistol/aps name = "\improper Stechkin APS machine pistol" diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 1105c0a611c..78f39dc9ee5 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -3023,6 +3023,12 @@ target.set_custom_materials() var/list/metal_dat = list((metal_ref) = metal_amount) target.material_flags = applied_material_flags + if (target.has_material_slots()) + var/list/new_slots = target.get_material_slots() + for (var/slot_type in new_slots) + new_slots[slot_type] = metal_ref + // Safe to call and doesn't do anything as no materials are currently present on the target + target.set_material_slots(new_slots) target.set_custom_materials(metal_dat) /datum/reagent/gravitum diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm index a6e3c103417..bc1d5eaee0f 100644 --- a/code/modules/research/designs.dm +++ b/code/modules/research/designs.dm @@ -70,7 +70,7 @@ other types of metals and chemistry for reagents). var/list/temp_list = list() // Go through all of our materials, get the subsystem instance, and then replace the list. for(var/mat_type, amount in materials) - if(ispath(mat_type, /datum/material_requirement)) + if(ispath(mat_type, /datum/material_requirement) || ispath(mat_type, /datum/material_slot)) temp_list[mat_type] = amount continue diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm index e83b0b77fb4..b09378ea49b 100644 --- a/code/modules/research/designs/weapon_designs.dm +++ b/code/modules/research/designs/weapon_designs.dm @@ -626,7 +626,7 @@ desc = "A mace fit for a cleric. Useful for bypassing plate armor, but too bulky for much else." id = "cleric_mace" build_type = AUTOLATHE - materials = list(/datum/material_requirement/solid_material = SHEET_MATERIAL_AMOUNT * 4.5, /datum/material_requirement/rigid_material = SHEET_MATERIAL_AMOUNT * 1.5) + materials = list(/datum/material_slot/weapon_head = SHEET_MATERIAL_AMOUNT * 4.5, /datum/material_slot/handle = SHEET_MATERIAL_AMOUNT * 1.5) build_path = /obj/item/melee/cleric_mace category = list(RND_CATEGORY_IMPORTED) diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index 727518bef72..594094deefd 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -1122,7 +1122,7 @@ GLOBAL_LIST_INIT(slime_extract_auto_activate_reactions, init_slime_auto_activate ///Definitions for slime products that don't have anywhere else to go (Floor tiles, blueprints). /obj/item/stack/tile/bluespace - name = "bluespace floor tile" + name = "stabilized bluespace floor tile" singular_name = "floor tile" desc = "Through a series of micro-teleports these tiles let people move at incredible speeds." icon_state = "tile_bluespace" diff --git a/icons/mob/inhands/weapons/polearms_lefthand.dmi b/icons/mob/inhands/weapons/polearms_lefthand.dmi index a882482331f..9cf37e16e04 100644 Binary files a/icons/mob/inhands/weapons/polearms_lefthand.dmi and b/icons/mob/inhands/weapons/polearms_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/polearms_righthand.dmi b/icons/mob/inhands/weapons/polearms_righthand.dmi index 985e1188932..8107d6533a9 100644 Binary files a/icons/mob/inhands/weapons/polearms_righthand.dmi and b/icons/mob/inhands/weapons/polearms_righthand.dmi differ diff --git a/icons/obj/stack_objects.dmi b/icons/obj/stack_objects.dmi index 0daa5fc8dd0..80845395803 100644 Binary files a/icons/obj/stack_objects.dmi and b/icons/obj/stack_objects.dmi differ diff --git a/icons/obj/weapons/spear.dmi b/icons/obj/weapons/spear.dmi index e654cbde28d..6a2f95f78b5 100644 Binary files a/icons/obj/weapons/spear.dmi and b/icons/obj/weapons/spear.dmi differ diff --git a/icons/turf/composite.dmi b/icons/turf/composite.dmi index a40c4121d25..51938161ed1 100644 Binary files a/icons/turf/composite.dmi and b/icons/turf/composite.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 05586a9e481..717fc41a448 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1258,6 +1258,7 @@ #include "code\datums\components\manual_heart.dm" #include "code\datums\components\marionette.dm" #include "code\datums\components\martial_art_giver.dm" +#include "code\datums\components\material_turf_tracking.dm" #include "code\datums\components\mind_linker.dm" #include "code\datums\components\mind_martial_art.dm" #include "code\datums\components\mirv.dm" @@ -1822,6 +1823,8 @@ #include "code\datums\materials\hauntium.dm" #include "code\datums\materials\meat.dm" #include "code\datums\materials\pizza.dm" +#include "code\datums\materials\material_slots\_slot.dm" +#include "code\datums\materials\material_slots\generic.dm" #include "code\datums\materials\properties\_properties.dm" #include "code\datums\materials\properties\derived.dm" #include "code\datums\materials\properties\optional.dm"