diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 69a3fb941dd..fcd819098e1 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -135,6 +135,14 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( #define EMBED_THROWSPEED_THRESHOLD 4 //The minimum value of an item's throw_speed for it to embed (Unless it has embedded_ignore_throwspeed_threshold set to 1) #define EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER 8 //Coefficient of multiplication for the damage the item does when removed without a surgery (this*item.w_class) #define EMBEDDED_UNSAFE_REMOVAL_TIME 30 //A Time in ticks, total removal time = (this*item.w_class) +#define EMBEDDED_JOSTLE_CHANCE 5 //Chance for embedded objects to cause pain every time they move (jostle) +#define EMBEDDED_JOSTLE_PAIN_MULTIPLIER 1 //Coefficient of multiplication for the damage the item does while +#define EMBEDDED_PAIN_STAM_PCT 0.0 //This percentage of all pain will be dealt as stam damage rather than brute (0-1) + +#define EMBED_HARMLESS list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE) +#define EMBED_HARMLESS_SUPERIOR list("pain_mult" = 0, "jostle_pain_mult" = 0, "ignore_throwspeed_threshold" = TRUE, "embed_chance" = 100, "fall_chance" = 0.1) +#define EMBED_POINTY list("ignore_throwspeed_threshold" = TRUE) +#define EMBED_POINTY_SUPERIOR list("embed_chance" = 100, "ignore_throwspeed_threshold" = TRUE) //Gun weapon weight #define WEAPON_LIGHT 1 diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 99637bb28c9..5949530aba6 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -309,6 +309,8 @@ #define COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY "human_melee_unarmed_attackby" //from mob/living/carbon/human/UnarmedAttack(): (mob/living/carbon/human/attacker) #define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" //Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted) #define COMSIG_JOB_RECEIVED "job_received" //Whenever EquipRanked is called, called after job is set +#define COMSIG_HUMAN_EMBED_RIP "item_embed_removing" // called on human when said human tries to rip out this embedded item (mob/living/carbon/human/target, /obj/item, /obj/item/bodypart/L) +#define COMSIG_HUMAN_EMBED_REMOVAL "item_embed_remove_surgery" // called on human from /datum/surgery_step/remove_object/success (mob/living/carbon/human/target, /obj/item, /obj/item/bodypart/L) // /datum/species signals #define COMSIG_SPECIES_GAIN "species_gain" //from datum/species/on_species_gain(): (datum/species/new_species, datum/species/old_species) diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index f00146d7848..5200002dbe4 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -258,3 +258,4 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define SLEEPING_CARP_TRAIT "sleeping_carp" #define MADE_UNCLONEABLE "made-uncloneable" #define TIMESTOP_TRAIT "timestop" +#define STICKY_NODROP "sticky-nodrop" //sticky nodrop sounds like a bad soundcloud rapper's name diff --git a/code/datums/components/embedded.dm b/code/datums/components/embedded.dm new file mode 100644 index 00000000000..eb1d4d106fd --- /dev/null +++ b/code/datums/components/embedded.dm @@ -0,0 +1,308 @@ +/* + This component is responsible for handling individual instances of embedded objects. The embeddable element is what allows an item to be embeddable and stores its embedding stats, + and when it impacts and meets the requirements to stick into something, it instantiates an embedded component. Once the item falls out, the component is destroyed, while the + element survives to embed another day. + + There are 2 different things that can be embedded presently: humans, and turfs (walls) + + - Human embedding has all the classical embedding behavior, and tracks more events and signals. The main behaviors and hooks to look for are: + -- Every process tick, there is a chance to randomly proc pain, controlled by pain_chance. There may also be a chance for the object to fall out randomly, per fall_chance + -- Every time the mob moves, there is a chance to proc jostling pain, controlled by jostle_chance (and only 50% as likely if the mob is walking or crawling) + -- Various signals hooking into human topic() and the embed removal surgery in order to handle removals. + + - Turf embedding is much simpler. All we do here is draw an overlay of the item's inhand on the turf, hide the item, and create an HTML link in the turf's inspect + that allows you to rip the item out. There's nothing dynamic about this, so far less checks. + + + In addition, there are 2 cases of embedding: embedding, and sticking + + - Embedding involves harmful and dangerous embeds, whether they cause brute damage, stamina damage, or a mix. This is the default behavior for embeddings, for when something is "pointy" + + - Sticking occurs when an item should not cause any harm while embedding (imagine throwing a sticky ball of tape at someone, rather than a shuriken). An item is considered "sticky" + when it has 0 random pain chance and 0 jostling chance. It's a bit arbitrary, but fairly straightforward. See /obj/item/is_embed_harmless() + + Stickables differ from embeds in the following ways: + -- Text descriptors use phrasing like "X is stuck to Y" rather than "X is embedded in Y" + -- There is no slicing sound on impact + -- All damage checks and bloodloss are skipped for humans + -- Pointy objects create sparks when embedding into a turf + +*/ + + +/datum/component/embedded + dupe_mode = COMPONENT_DUPE_ALLOWED + var/obj/item/bodypart/L + var/obj/item/weapon + + var/embed_chance // not like we really need it once we're already stuck in but hey + var/fall_chance + var/pain_chance + var/pain_mult + var/remove_pain_mult + var/rip_time + var/ignore_throwspeed_threshold + var/jostle_chance + var/jostle_pain_mult + var/pain_stam_pct + + var/harmful + var/mutable_appearance/overlay + +// figure out if im gonna drop args +/datum/component/embedded/Initialize(datum/parent, + obj/item/I, + datum/thrownthing/throwingdatum, + embed_chance = EMBED_CHANCE, + fall_chance = EMBEDDED_ITEM_FALLOUT, + pain_chance = EMBEDDED_PAIN_CHANCE, + pain_mult = EMBEDDED_PAIN_MULTIPLIER, + remove_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER, + //also fall pain + impact_pain_mult = EMBEDDED_IMPACT_PAIN_MULTIPLIER, + //rip_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER, + rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME, + ignore_throwspeed_threshold = FALSE, + jostle_chance = EMBEDDED_JOSTLE_CHANCE, + jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER, + pain_stam_pct = EMBEDDED_PAIN_STAM_PCT) + + + . = ..() + + if((!ishuman(parent) && !isturf(parent)) || !isitem(weapon)) + return COMPONENT_INCOMPATIBLE + + src.embed_chance = embed_chance + src.fall_chance = fall_chance + src.pain_chance = pain_chance + src.pain_mult = pain_mult + src.remove_pain_mult = remove_pain_mult + src.rip_time = rip_time + src.ignore_throwspeed_threshold = ignore_throwspeed_threshold + src.jostle_chance = jostle_chance + src.jostle_pain_mult = jostle_pain_mult + src.pain_stam_pct = pain_stam_pct + + src.weapon = I + + if(src.pain_chance || src.jostle_chance) + harmful = TRUE + + if(ishuman(parent)) + initHuman() + else if(isturf(parent)) + initTurf(throwingdatum) + + +/datum/component/embedded/RegisterWithParent() + if(ishuman(parent)) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/jostleCheck) + RegisterSignal(parent, COMSIG_HUMAN_EMBED_RIP, .proc/ripOutHuman) + RegisterSignal(parent, COMSIG_HUMAN_EMBED_REMOVAL, .proc/safeRemoveHuman) + else if(isturf(parent)) + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examineTurf) + RegisterSignal(parent, COMSIG_TOPIC, .proc/ripOutTurf) + //RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/destroyedTurf) + +/datum/component/embedded/UnregisterFromParent() + if(ishuman(parent)) + UnregisterSignal(parent, COMSIG_MOVABLE_MOVED) + UnregisterSignal(parent, COMSIG_HUMAN_EMBED_RIP) + UnregisterSignal(parent, COMSIG_HUMAN_EMBED_REMOVAL) + else if(isturf(parent)) + UnregisterSignal(parent, COMSIG_PARENT_EXAMINE) + UnregisterSignal(parent, COMSIG_TOPIC) + UnregisterSignal(parent, COMSIG_PARENT_QDELETING) + + +/datum/component/embedded/process() + if(ishuman(parent)) + processHuman() + //else + //processTurf() + + return ..() + +//////////////////////////////////////// +/////////////HUMAN PROCS//////////////// +//////////////////////////////////////// + +/// Harmful embeds have some extra behavior over harmless sticks, like creating blood, playing a slice, and dealing damage. +/datum/component/embedded/proc/initHuman() + var/mob/living/carbon/human/victim = parent + L = pick(victim.bodyparts) + L.embedded_objects |= weapon // on the inside... on the inside... + weapon.forceMove(victim) + + if(harmful) + victim.visible_message("[weapon] embeds itself in [victim]'s [L.name]!","[weapon] embeds itself in your [L.name]!") + victim.throw_alert("embeddedobject", /obj/screen/alert/embeddedobject) + playsound(victim,'sound/weapons/bladeslice.ogg', 40) + weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody! + //var/damage = weapon.w_class * impact_pain_mult + //L.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) + SEND_SIGNAL(victim, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded) + else + victim.visible_message("[weapon] sticks itself to [victim]'s [L.name]!","[weapon] sticks itself to your [L.name]!") + + +/// Called every time a human with a harmful embed moves, rolling a chance for the item to cause pain. The chance is halved if the human is crawling or walking. +/datum/component/embedded/proc/jostleCheck() + var/mob/living/carbon/human/victim = parent + + var/chance = jostle_chance + if(victim.m_intent == MOVE_INTENT_WALK || victim.lying) + chance *= 0.5 + + if(prob(chance)) + var/damage = weapon.w_class * jostle_pain_mult + L.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) + to_chat(victim, "[weapon] embedded in your [L.name] jostles and stings!") + + +/// Called when then item randomly falls out of a human. This handles the damage and descriptors, then calls safe_remove() +/datum/component/embedded/proc/fallOutHuman() + var/mob/living/carbon/human/victim = parent + + if(harmful) + var/damage = weapon.w_class * remove_pain_mult + L.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) + victim.visible_message("[weapon] falls out of [victim.name]'s [L.name]!","[weapon] falls out of your [L.name]!") + else + victim.visible_message("[weapon] falls off of [victim.name]'s [L.name]!","[weapon] falls off of your [L.name]!") + + safeRemoveHuman() + + +/// Called when a human with an object embedded/stuck to them inspects themselves and clicks the appropriate link to begin ripping the item out. This handles the ripping attempt, descriptors, and dealing damage, then calls safe_remove() +/datum/component/embedded/proc/ripOutHuman() + var/mob/living/carbon/human/victim = parent + var/time_taken = rip_time * weapon.w_class + + victim.visible_message("[victim] attempts to remove [weapon] from [victim.p_their()] [L.name].","You attempt to remove [weapon] from your [L.name]... (It will take [DisplayTimeText(time_taken)].)") + if(do_after(victim, time_taken, target = victim)) + if(!weapon || !L || weapon.loc != victim || !(weapon in L.embedded_objects)) + qdel(src) + + if(harmful) + var/damage = weapon.w_class * remove_pain_mult + L.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) //It hurts to rip it out, get surgery you dingus. + victim.emote("scream") + victim.visible_message("[victim] successfully rips [weapon] out of [victim.p_their()] [L.name]!", "You successfully remove [weapon] from your [L.name].") + else + victim.visible_message("[victim] successfully rips [weapon] off of [victim.p_their()] [L.name]!", "You successfully remove [weapon] from your [L.name].") + + safeRemoveHuman(TRUE) + + +/// This proc handles the final step and actual removal of an embedded/stuck item from a human, whether or not it was actually removed safely. +/datum/component/embedded/proc/safeRemoveHuman(to_hands) + var/mob/living/carbon/human/victim = parent + L.embedded_objects -= weapon + + if(to_hands) + victim.put_in_hands(weapon) + else + weapon.forceMove(get_turf(victim)) + + if(!victim.has_embedded_objects()) + victim.clear_alert("embeddedobject") + SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "embedded") + + +/// Items embedded/stuck to humans both check whether they randomly fall out (if applicable), as well as if the target mob and limb still exists. +/// Items harmfully embedded in humans have an additional check for random pain (if applicable) +/datum/component/embedded/proc/processHuman() + var/mob/living/carbon/human/victim = parent + + if(!victim || !L) // in case the victim and/or their limbs exploded (say, due to a sticky bomb) + weapon.forceMove(get_turf(weapon)) + qdel(src) + + if(victim.stat == DEAD) + return + + if(prob(pain_chance)) + var/damage = weapon.w_class * pain_mult + L.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) + to_chat(victim, "[weapon] embedded in your [L.name] hurts!") + + if(prob(fall_chance)) + fallOutHuman() + + + +//////////////////////////////////////// +//////////////TURF PROCS//////////////// +//////////////////////////////////////// + +/// Turfs are much lower maintenance, since we don't care if they're in pain, but since they don't bleed or scream, we draw an overlay to show their status. +/// The only difference pointy/sticky items make here is text descriptors and pointy objects making a spark shower on impact. +/datum/component/embedded/proc/initTurf(datum/thrownthing/throwingdatum) + var/turf/closed/hit = parent + + // we can't store the item IN the turf (cause turfs are just kinda... there), so we fake it by making the item invisible and bailing if it moves due to a blast + weapon.forceMove(hit) + weapon.invisibility = INVISIBILITY_ABSTRACT + RegisterSignal(weapon, COMSIG_MOVABLE_MOVED, .proc/itemMoved) + + var/pixelX = rand(-6, 6) + var/pixelY = rand(-6, 6) + + // can probably do this with a ? : + switch(throwingdatum.init_dir) + if(NORTH) + pixelY -= 4 + if(SOUTH) + pixelY += 4 + if(WEST) + pixelX -= 4 + if(EAST) + pixelX += 4 + + if(throwingdatum.init_dir in list(NORTH, WEST, NORTHWEST, SOUTHWEST)) + overlay = mutable_appearance(icon=weapon.lefthand_file,icon_state=weapon.item_state) + else + overlay = mutable_appearance(icon=weapon.righthand_file,icon_state=weapon.item_state) + + hit.add_overlay(overlay) + var/matrix/M = matrix() + M.Translate(pixelX, pixelY) + M.Scale(2.5) + overlay.transform = M + + if(harmful) + hit.visible_message("[weapon] embeds itself in [hit]!") + playsound(hit,'sound/weapons/bladeslice.ogg', 70) + + var/datum/effect_system/spark_spread/sparks = new + sparks.set_up(1, 1, parent) + sparks.attach(parent) + sparks.start() + else + hit.visible_message("[weapon] sticks itself to [hit]!") + + +/datum/component/embedded/proc/examineTurf(datum/source, mob/user, list/examine_list) + if(harmful) + examine_list += "\t There is \a [weapon] embedded in [parent]!" + else + examine_list += "\t There is \a [weapon] stuck to [parent]!" + + +/// Someone is ripping out the item from the turf by hand +/datum/component/embedded/proc/ripOutTurf(datum/source, user, href_list) + var/mob/living/us = user + if(locate(href_list["embedded_object"]) == weapon) + us.visible_message("[us] begins unwedging [weapon] from [parent].", "You begin unwedging [weapon] from [parent]...") + if(do_after(us, 30, target = parent)) + us.put_in_hands(weapon) + qdel(src) + + +/// This proc handles if something knocked the invisible item loose from the turf somehow (probably an explosion). Just make it visible and say it fell loose, then get outta here. +/datum/component/embedded/proc/itemMoved() + weapon.invisibility = initial(weapon.invisibility) + weapon.visible_message("[weapon] falls loose from [parent].") + qdel(src) diff --git a/code/datums/elements/embed.dm b/code/datums/elements/embed.dm new file mode 100644 index 00000000000..ad731dedb6d --- /dev/null +++ b/code/datums/elements/embed.dm @@ -0,0 +1,117 @@ +/* + The presence of this element allows an item to embed itself in a human or turf when it is thrown into a target (whether by hand, gun, or explosive wave) + with either at least 4 throwspeed (EMBED_THROWSPEED_THRESHOLD) or ignore_throwspeed_threshold set to TRUE. + + This element is granted primarily to any /obj/item that has something in its /embedding var, which should be formatted as a list. If you wish to be able to + grant/rescind the ability for an item to embed (say, when activating and deactivating an edagger), you can do so in two ways: + + 1. Drop the throw_speed var below EMBED_THROWSPEED_THRESHOLD (object will still be able to otherwise embed if thrown at high speed by something else like a blast) + 2. Add/Remove the embed element as needed (won't be able to embed at all) + + Otherwise non-embeddable or stickable items can be made embeddable/stickable through wizard events/sticky tape/admin memes. + +*/ + +/datum/element/embed + element_flags = ELEMENT_BESPOKE + id_arg_index = 2 + + var/embed_chance + var/fall_chance + var/pain_chance + var/pain_mult + var/remove_pain_mult + //src.impact_pain_mult + //src.rip_pain_mult + var/rip_time + var/ignore_throwspeed_threshold + var/jostle_chance + var/jostle_pain_mult + var/pain_stam_pct + +/datum/element/embed/Attach(datum/target, + embed_chance = EMBED_CHANCE, + fall_chance = EMBEDDED_ITEM_FALLOUT, + pain_chance = EMBEDDED_PAIN_CHANCE, + pain_mult = EMBEDDED_PAIN_MULTIPLIER, + remove_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER, + //also fall pain + //impact_pain_mult = EMBEDDED_IMPACT_PAIN_MULTIPLIER, + //rip_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER, + rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME, + ignore_throwspeed_threshold = FALSE, + jostle_chance = EMBEDDED_JOSTLE_CHANCE, + jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER, + pain_stam_pct = EMBEDDED_PAIN_STAM_PCT) + + src.embed_chance = embed_chance + src.fall_chance = fall_chance + src.pain_chance = pain_chance + src.pain_mult = pain_mult + src.remove_pain_mult = remove_pain_mult + //src.impact_pain_mult = impact_pain_mult + //src.rip_pain_mult = rip_pain_mult + src.rip_time = rip_time + src.ignore_throwspeed_threshold = ignore_throwspeed_threshold + src.jostle_chance = jostle_chance + src.jostle_pain_mult = jostle_pain_mult + src.pain_stam_pct = pain_stam_pct + + . = ..() + + if(!isitem(target)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(target, COMSIG_MOVABLE_IMPACT_ZONE, .proc/checkEmbedMob) + RegisterSignal(target, COMSIG_MOVABLE_IMPACT, .proc/checkEmbedOther) + + +/datum/element/embed/Detach(obj/item/target) + . = ..() + UnregisterSignal(target, COMSIG_MOVABLE_IMPACT_ZONE) + + +/// Checking to see if we're gonna embed into a human +/datum/element/embed/proc/checkEmbedMob(obj/item/weapon, mob/living/carbon/human/victim, hit_zone, datum/thrownthing/throwingdatum) + if(!istype(victim)) + return + + if(((throwingdatum ? throwingdatum.speed : weapon.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || ignore_throwspeed_threshold) + if(prob(embed_chance) && !HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE)) + victim.AddComponent(/datum/component/embedded, + weapon, + throwingdatum, + embed_chance = embed_chance, + fall_chance = fall_chance, + pain_chance = pain_chance, + pain_mult = pain_mult, + remove_pain_mult = remove_pain_mult, + //rip_pain_mult = rip_pain_mult, + rip_time = rip_time, + ignore_throwspeed_threshold = ignore_throwspeed_threshold, + jostle_chance = jostle_chance, + jostle_pain_mult = jostle_pain_mult, + pain_stam_pct = pain_stam_pct) + + +/// We need the hit_zone if we're embedding into a human, so this proc only handled if we're embedding into a turf +/datum/element/embed/proc/checkEmbedOther(obj/item/weapon, turf/hit, datum/thrownthing/throwingdatum) + if(!istype(hit)) + return + + if(((throwingdatum ? throwingdatum.speed : weapon.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || ignore_throwspeed_threshold) + if(prob(embed_chance)) + hit.AddComponent(/datum/component/embedded, + weapon, + throwingdatum, + embed_chance = embed_chance, + fall_chance = fall_chance, + pain_chance = pain_chance, + pain_mult = pain_mult, + remove_pain_mult = remove_pain_mult, + //rip_pain_mult = rip_pain_mult, + rip_time = rip_time, + ignore_throwspeed_threshold = ignore_throwspeed_threshold, + jostle_chance = jostle_chance, + jostle_pain_mult = jostle_pain_mult, + pain_stam_pct = pain_stam_pct) diff --git a/code/datums/embedding_behavior.dm b/code/datums/embedding_behavior.dm index d4181f94344..449fb3ba1cc 100644 --- a/code/datums/embedding_behavior.dm +++ b/code/datums/embedding_behavior.dm @@ -1,58 +1,73 @@ -#define EMBEDID "embed-[embed_chance]-[embedded_fall_chance]-[embedded_pain_chance]-[embedded_pain_multiplier]-[embedded_fall_pain_multiplier]-[embedded_impact_pain_multiplier]-[embedded_unsafe_removal_pain_multiplier]-[embedded_unsafe_removal_time]-[embedded_ignore_throwspeed_threshold]" +#define EMBEDID "embed-[embed_chance]-[fall_chance]-[pain_chance]-[pain_mult]-[fall_pain_mult]-[impact_pain_mult]-[rip_pain_mult]-[rip_time]-[ignore_throwspeed_threshold]-[jostle_chance]-[jostle_pain_mult]-[pain_stam_pct]" /proc/getEmbeddingBehavior(embed_chance = EMBED_CHANCE, - embedded_fall_chance = EMBEDDED_ITEM_FALLOUT, - embedded_pain_chance = EMBEDDED_PAIN_CHANCE, - embedded_pain_multiplier = EMBEDDED_PAIN_MULTIPLIER, - embedded_fall_pain_multiplier = EMBEDDED_FALL_PAIN_MULTIPLIER, - embedded_impact_pain_multiplier = EMBEDDED_IMPACT_PAIN_MULTIPLIER, - embedded_unsafe_removal_pain_multiplier = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER, - embedded_unsafe_removal_time = EMBEDDED_UNSAFE_REMOVAL_TIME, - embedded_ignore_throwspeed_threshold = FALSE) + fall_chance = EMBEDDED_ITEM_FALLOUT, + pain_chance = EMBEDDED_PAIN_CHANCE, + pain_mult = EMBEDDED_PAIN_MULTIPLIER, + fall_pain_mult = EMBEDDED_FALL_PAIN_MULTIPLIER, + impact_pain_mult = EMBEDDED_IMPACT_PAIN_MULTIPLIER, + rip_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER, + rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME, + ignore_throwspeed_threshold = FALSE, + jostle_chance = EMBEDDED_JOSTLE_CHANCE, + jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER, + pain_stam_pct = EMBEDDED_PAIN_STAM_PCT) . = locate(EMBEDID) if (!.) - . = new /datum/embedding_behavior(embed_chance, embedded_fall_chance, embedded_pain_chance, embedded_pain_multiplier, embedded_fall_pain_multiplier, embedded_impact_pain_multiplier, embedded_unsafe_removal_pain_multiplier, embedded_unsafe_removal_time, embedded_ignore_throwspeed_threshold) + . = new /datum/embedding_behavior(embed_chance, fall_chance, pain_chance, pain_mult, fall_pain_mult, impact_pain_mult, rip_pain_mult, rip_time, ignore_throwspeed_threshold, jostle_chance, jostle_pain_mult, pain_stam_pct) /datum/embedding_behavior var/embed_chance - var/embedded_fall_chance - var/embedded_pain_chance - var/embedded_pain_multiplier //The coefficient of multiplication for the damage this item does while embedded (this*w_class) - var/embedded_fall_pain_multiplier //The coefficient of multiplication for the damage this item does when falling out of a limb (this*w_class) - var/embedded_impact_pain_multiplier //The coefficient of multiplication for the damage this item does when first embedded (this*w_class) - var/embedded_unsafe_removal_pain_multiplier //The coefficient of multiplication for the damage removing this without surgery causes (this*w_class) - var/embedded_unsafe_removal_time //A time in ticks, multiplied by the w_class. - var/embedded_ignore_throwspeed_threshold //if we don't give a damn about EMBED_THROWSPEED_THRESHOLD + var/fall_chance + var/pain_chance + var/pain_mult //The coefficient of multiplication for the damage this item does while embedded (this*w_class) + var/fall_pain_mult //The coefficient of multiplication for the damage this item does when falling out of a limb (this*w_class) + var/impact_pain_mult //The coefficient of multiplication for the damage this item does when first embedded (this*w_class) + var/rip_pain_mult //The coefficient of multiplication for the damage removing this without surgery causes (this*w_class) + var/rip_time //A time in ticks, multiplied by the w_class. + var/ignore_throwspeed_threshold //if we don't give a damn about EMBED_THROWSPEED_THRESHOLD + var/jostle_chance //Chance to cause pain every time the victim moves (1/2 chance if they're walking or crawling) + var/jostle_pain_mult //The coefficient of multiplication for the damage when jostle damage is applied (this*w_class) + var/pain_stam_pct //Percentage of all pain damage dealt as stamina instead of brute (none by default) /datum/embedding_behavior/New(embed_chance = EMBED_CHANCE, - embedded_fall_chance = EMBEDDED_ITEM_FALLOUT, - embedded_pain_chance = EMBEDDED_PAIN_CHANCE, - embedded_pain_multiplier = EMBEDDED_PAIN_MULTIPLIER, - embedded_fall_pain_multiplier = EMBEDDED_FALL_PAIN_MULTIPLIER, - embedded_impact_pain_multiplier = EMBEDDED_IMPACT_PAIN_MULTIPLIER, - embedded_unsafe_removal_pain_multiplier = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER, - embedded_unsafe_removal_time = EMBEDDED_UNSAFE_REMOVAL_TIME, - embedded_ignore_throwspeed_threshold = FALSE) + fall_chance = EMBEDDED_ITEM_FALLOUT, + pain_chance = EMBEDDED_PAIN_CHANCE, + pain_mult = EMBEDDED_PAIN_MULTIPLIER, + fall_pain_mult = EMBEDDED_FALL_PAIN_MULTIPLIER, + impact_pain_mult = EMBEDDED_IMPACT_PAIN_MULTIPLIER, + rip_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER, + rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME, + ignore_throwspeed_threshold = FALSE, + jostle_chance = EMBEDDED_JOSTLE_CHANCE, + jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER, + pain_stam_pct = EMBEDDED_PAIN_STAM_PCT) src.embed_chance = embed_chance - src.embedded_fall_chance = embedded_fall_chance - src.embedded_pain_chance = embedded_pain_chance - src.embedded_pain_multiplier = embedded_pain_multiplier - src.embedded_fall_pain_multiplier = embedded_fall_pain_multiplier - src.embedded_impact_pain_multiplier = embedded_impact_pain_multiplier - src.embedded_unsafe_removal_pain_multiplier = embedded_unsafe_removal_pain_multiplier - src.embedded_unsafe_removal_time = embedded_unsafe_removal_time - src.embedded_ignore_throwspeed_threshold = embedded_ignore_throwspeed_threshold + src.fall_chance = fall_chance + src.pain_chance = pain_chance + src.pain_mult = pain_mult + src.fall_pain_mult = fall_pain_mult + src.impact_pain_mult = impact_pain_mult + src.rip_pain_mult = rip_pain_mult + src.rip_time = rip_time + src.ignore_throwspeed_threshold = ignore_throwspeed_threshold + src.jostle_chance = jostle_chance + src.jostle_pain_mult = jostle_pain_mult + src.pain_stam_pct = pain_stam_pct tag = EMBEDID -/datum/embedding_behavior/proc/setRating(embed_chance, embedded_fall_chance, embedded_pain_chance, embedded_pain_multiplier, embedded_fall_pain_multiplier, embedded_impact_pain_multiplier, embedded_unsafe_removal_pain_multiplier, embedded_unsafe_removal_time, embedded_ignore_throwspeed_threshold) +/datum/embedding_behavior/proc/setRating(embed_chance, fall_chance, pain_chance, pain_mult, fall_pain_mult, impact_pain_mult, rip_pain_mult, rip_time, ignore_throwspeed_threshold) return getEmbeddingBehavior((isnull(embed_chance) ? src.embed_chance : embed_chance),\ - (isnull(embedded_fall_chance) ? src.embedded_fall_chance : embedded_fall_chance),\ - (isnull(embedded_pain_chance) ? src.embedded_pain_chance : embedded_pain_chance),\ - (isnull(embedded_pain_multiplier) ? src.embedded_pain_multiplier : embedded_pain_multiplier),\ - (isnull(embedded_fall_pain_multiplier) ? src.embedded_fall_pain_multiplier : embedded_fall_pain_multiplier),\ - (isnull(embedded_impact_pain_multiplier) ? src.embedded_impact_pain_multiplier : embedded_impact_pain_multiplier),\ - (isnull(embedded_unsafe_removal_pain_multiplier) ? src.embedded_unsafe_removal_pain_multiplier : embedded_unsafe_removal_pain_multiplier),\ - (isnull(embedded_unsafe_removal_time) ? src.embedded_unsafe_removal_time : embedded_unsafe_removal_time),\ - (isnull(embedded_ignore_throwspeed_threshold) ? src.embedded_ignore_throwspeed_threshold : embedded_ignore_throwspeed_threshold)) + (isnull(fall_chance) ? src.fall_chance : fall_chance),\ + (isnull(pain_chance) ? src.pain_chance : pain_chance),\ + (isnull(pain_mult) ? src.pain_mult : pain_mult),\ + (isnull(fall_pain_mult) ? src.fall_pain_mult : fall_pain_mult),\ + (isnull(impact_pain_mult) ? src.impact_pain_mult : impact_pain_mult),\ + (isnull(rip_pain_mult) ? src.rip_pain_mult : rip_pain_mult),\ + (isnull(rip_time) ? src.rip_time : rip_time),\ + (isnull(ignore_throwspeed_threshold) ? src.ignore_throwspeed_threshold : ignore_throwspeed_threshold),\ + (isnull(jostle_chance) ? src.jostle_chance : jostle_chance),\ + (isnull(jostle_pain_mult) ? src.jostle_pain_mult : jostle_pain_mult),\ + (isnull(pain_stam_pct) ? src.pain_stam_pct : pain_stam_pct)) #undef EMBEDID diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 6eba8746a31..268a07c952f 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -4,6 +4,9 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) // if true, everyone item when created will have its name changed to be // more... RPG-like. +GLOBAL_VAR_INIT(stickpocalypse, FALSE) // if true, all non-embeddable items will be able to harmlessly stick to people when thrown +GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to embed in people, takes precedence over stickpocalypse + /obj/item name = "item" icon = 'icons/obj/items_and_weapons.dmi' @@ -149,12 +152,23 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) if(damtype == "brute") hitsound = "swing_hit" - if (!embedding) - embedding = getEmbeddingBehavior() - else if (islist(embedding)) - embedding = getEmbeddingBehavior(arglist(embedding)) - else if (!istype(embedding, /datum/embedding_behavior)) - stack_trace("Invalid type [embedding.type] found in .embedding during /obj/item Initialize()") + if(embedding) + var/list/temp = embedding + temp.Insert(0, /datum/element/embed) // i dunno about this + AddElement(arglist(temp)) + else if(GLOB.embedpocalypse) + embedding = EMBED_POINTY + var/list/temp = embedding + temp.Insert(0, /datum/element/embed) + AddElement(arglist(temp)) + name = "pointy [name]" + else if(GLOB.stickpocalypse) + embedding = EMBED_HARMLESS + var/list/temp = embedding + temp.Insert(0, /datum/element/embed) + AddElement(arglist(temp)) + name = "sticky [name]" + if(sharpness) //give sharp objects butchering functionality, for consistency AddComponent(/datum/component/butchering, 80 * toolspeed) @@ -222,6 +236,12 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) if(resistance_flags & FIRE_PROOF) . += "[src] is made of fire-retardant materials." + if(embedding) + if(is_embed_harmless()) + . += "[src] feels sticky, and could probably get stuck to someone if thrown properly!" + else + . += "[src] has a fine point, and could probably get caught in someone if thrown properly!" + if(!user.research_scanner) return @@ -863,3 +883,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) /obj/item/proc/doStrip(mob/stripper, mob/owner) return owner.dropItemToGround(src) + +/obj/item/proc/is_embed_harmless() + if(embedding) + return (embedding.pain_mult == 0 && embedding.jostle_pain_mult == 0) diff --git a/code/game/objects/items/grenades/ghettobomb.dm b/code/game/objects/items/grenades/ghettobomb.dm index 1295fe56f32..eb7e79a1104 100644 --- a/code/game/objects/items/grenades/ghettobomb.dm +++ b/code/game/objects/items/grenades/ghettobomb.dm @@ -45,7 +45,7 @@ /obj/item/grenade/iedcasing/attack_self(mob/user) // if(!active) - if(clown_check(user)) + if(!botch_check(user)) to_chat(user, "You light the [name]!") cut_overlay("improvised_grenade_filled") preprime(user, null, FALSE) diff --git a/code/game/objects/items/grenades/grenade.dm b/code/game/objects/items/grenades/grenade.dm index 4871a90f1ee..c8eb7fdd31f 100644 --- a/code/game/objects/items/grenades/grenade.dm +++ b/code/game/objects/items/grenades/grenade.dm @@ -17,6 +17,7 @@ var/det_time = 50 var/display_timer = 1 var/clumsy_check = GRENADE_CLUMSY_FUMBLE + var/sticky = FALSE /obj/item/grenade/suicide_act(mob/living/carbon/user) user.visible_message("[user] primes [src], then eats it! It looks like [user.p_theyre()] trying to commit suicide!") @@ -32,19 +33,20 @@ if(!QDELETED(src)) qdel(src) -/obj/item/grenade/proc/clown_check(mob/living/carbon/human/user) +/obj/item/grenade/proc/botch_check(mob/living/carbon/human/user) var/clumsy = HAS_TRAIT(user, TRAIT_CLUMSY) if(clumsy && (clumsy_check == GRENADE_CLUMSY_FUMBLE)) if(prob(50)) to_chat(user, "Huh? How does this thing work?") preprime(user, 5, FALSE) - return FALSE + return TRUE else if(!clumsy && (clumsy_check == GRENADE_NONCLUMSY_FUMBLE)) to_chat(user, "You pull the pin on [src]. Attached to it is a pink ribbon that says, \"HONK\"") preprime(user, 5, FALSE) - return FALSE - return TRUE - + return TRUE + else if(sticky && prob(50)) // to add risk to sticky tape grenade cheese, no return cause we still prime as normal after + to_chat(user, "What the... [src] is stuck to your hand!") + ADD_TRAIT(src, TRAIT_NODROP, STICKY_NODROP) /obj/item/grenade/examine(mob/user) . = ..() @@ -56,8 +58,16 @@ /obj/item/grenade/attack_self(mob/user) + if(HAS_TRAIT(src, TRAIT_NODROP)) + to_chat(user, "You try prying [src] off your hand...") + if(do_after(user, 70, target=src)) + to_chat(user, "You manage to remove [src] from your hand.") + REMOVE_TRAIT(src, TRAIT_NODROP, STICKY_NODROP) + + return + if(!active) - if(clown_check(user)) + if(!botch_check(user)) // if they botch the prime, it'll be handled in botch_check preprime(user) /obj/item/grenade/proc/log_grenade(mob/user, turf/T) diff --git a/code/game/objects/items/grenades/plastic.dm b/code/game/objects/items/grenades/plastic.dm index 2830f08755d..2a857c92d51 100644 --- a/code/game/objects/items/grenades/plastic.dm +++ b/code/game/objects/items/grenades/plastic.dm @@ -100,7 +100,7 @@ var/obj/item/I = AM I.throw_speed = max(1, (I.throw_speed - 3)) I.throw_range = max(1, (I.throw_range - 3)) - I.embedding = I.embedding.setRating(embed_chance = 0) + //I.embedding = I.embedding.setRating(embed_chance = 0) else if(istype(AM, /mob/living)) plastic_overlay.layer = FLOAT_LAYER diff --git a/code/game/objects/items/kitchen.dm b/code/game/objects/items/kitchen.dm index 670b59f063d..a95683d9d21 100644 --- a/code/game/objects/items/kitchen.dm +++ b/code/game/objects/items/kitchen.dm @@ -157,7 +157,7 @@ name = "combat knife" icon_state = "buckknife" desc = "A military combat utility survival knife." - embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 65, "embedded_fall_chance" = 10, "embedded_ignore_throwspeed_threshold" = TRUE) + embedding = list("pain_mult" = 4, "embed_chance" = 65, "fall_chance" = 10, "ignore_throwspeed_threshold" = TRUE) force = 20 throwforce = 20 attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "cut") @@ -166,7 +166,7 @@ /obj/item/kitchen/knife/combat/survival name = "survival knife" icon_state = "survivalknife" - embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 35, "embedded_fall_chance" = 10) + embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10) desc = "A hunting grade survival knife." force = 15 throwforce = 15 @@ -179,7 +179,7 @@ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' desc = "A sharpened bone. The bare minimum in survival." - embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 35, "embedded_fall_chance" = 10) + embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10) force = 15 throwforce = 15 custom_materials = null diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index 74e524ff041..7f178e43b98 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -99,7 +99,7 @@ throw_speed = 3 throw_range = 5 sharpness = IS_SHARP - embedding = list("embed_chance" = 75, "embedded_impact_pain_multiplier" = 10) + embedding = list("embed_chance" = 75, "impact_pain_mult" = 10) armour_penetration = 35 block_chance = 50 diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 32a21c2a1f5..0e8ec6c2bc5 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -21,6 +21,7 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \ max_amount = 50 attack_verb = list("hit", "bludgeoned", "whacked") hitsound = 'sound/weapons/gun/general/grenade_launch.ogg' + embedding = list() novariants = TRUE /obj/item/stack/rods/suicide_act(mob/living/carbon/user) diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm index 41171ba8575..a543b45a859 100644 --- a/code/game/objects/items/stacks/sheets/glass.dm +++ b/code/game/objects/items/stacks/sheets/glass.dm @@ -253,6 +253,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( sharpness = IS_SHARP var/icon_prefix var/obj/item/stack/sheet/weld_material = /obj/item/stack/sheet/glass + embedding = list("embed_chance" = 65) /obj/item/shard/suicide_act(mob/user) diff --git a/code/game/objects/items/stacks/tape.dm b/code/game/objects/items/stacks/tape.dm new file mode 100644 index 00000000000..5c23c42c30b --- /dev/null +++ b/code/game/objects/items/stacks/tape.dm @@ -0,0 +1,59 @@ + + +/obj/item/stack/sticky_tape + name = "sticky tape" + singular_name = "sticky tape" + desc = "Used for sticking to things for sticking said things to people." + icon = 'icons/obj/tapes.dmi' + icon_state = "tape_w" + var/prefix = "sticky" + item_flags = NOBLUDGEON + amount = 5 + max_amount = 5 + resistance_flags = FLAMMABLE + grind_results = list(/datum/reagent/cellulose = 5) + + var/datum/embedding_behavior/conferred_embed = EMBED_HARMLESS + var/overwrite_existing = FALSE + +/obj/item/stack/sticky_tape/afterattack(obj/item/I, mob/living/user) + if(I.embedding && I.embedding == getEmbeddingBehavior(arglist(conferred_embed))) + to_chat(user, "[I] is already coated in [src]!") + return + + user.visible_message("[user] begins wrapping [I] with [src].", "You begin wrapping [I] with [src].") + + if(do_after(user, 30, target=I)) + I.embedding = getEmbeddingBehavior(arglist(conferred_embed)) + I.AddElement(/datum/element/embed) + to_chat(user, "You finish wrapping [I] with [src].") + use(1) + I.name = "[prefix] [I.name]" + + if(istype(I, /obj/item/grenade)) + var/obj/item/grenade/sticky_bomb = I + sticky_bomb.sticky = TRUE + +/obj/item/stack/sticky_tape/super + name = "super sticky tape" + singular_name = "super sticky tape" + desc = "Quite possibly the most mischevious substance in the galaxy. Use with extreme lack of caution." + icon_state = "tape_y" + prefix = "super sticky" + conferred_embed = EMBED_HARMLESS_SUPERIOR + +/obj/item/stack/sticky_tape/pointy + name = "pointy tape" + singular_name = "pointy tape" + desc = "Used for sticking to things for sticking said things inside people." + icon_state = "tape_evil" + prefix = "pointy" + conferred_embed = EMBED_POINTY + +/obj/item/stack/sticky_tape/pointy/super + name = "super pointy tape" + singular_name = "super pointy tape" + desc = "You didn't know tape could look so sinister. Welcome to Space Station 13." + icon_state = "tape_spikes" + prefix = "super pointy" + conferred_embed = EMBED_POINTY_SUPERIOR diff --git a/code/game/objects/items/twohanded.dm b/code/game/objects/items/twohanded.dm index 8ecefff8186..f03ea924ba0 100644 --- a/code/game/objects/items/twohanded.dm +++ b/code/game/objects/items/twohanded.dm @@ -448,7 +448,7 @@ force_wielded = 18 throwforce = 20 throw_speed = 4 - embedding = list("embedded_impact_pain_multiplier" = 3) + embedding = list("impact_pain_mult" = 3) armour_penetration = 10 custom_materials = list(/datum/material/iron=1150, /datum/material/glass=2075) hitsound = 'sound/weapons/bladeslice.ogg' diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index cdc16465bcf..529526dbd7f 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -280,12 +280,28 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 force = 2 throwforce = 20 //20 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 28 damage on hit due to guaranteed embedding throw_speed = 4 - embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 100, "embedded_fall_chance" = 0) + embedding = list("pain_mult" = 4, "embed_chance" = 100, "fall_chance" = 0) + + w_class = WEIGHT_CLASS_SMALL sharpness = IS_SHARP custom_materials = list(/datum/material/iron=500, /datum/material/glass=500) resistance_flags = FIRE_PROOF +/obj/item/throwing_star/stamina + name = "shock throwing star" + desc = "An aerodynamic disc designed to cause excruciating pain when stuck inside fleeing targets, hopefully without causing fatal harm." + throwforce = 5 + embedding = list("pain_chance" = 5, "embed_chance" = 100, "fall_chance" = 0, "jostle_chance" = 10, "pain_stam_pct" = 0.8, "jostle_pain_mult" = 3) + +/obj/item/throwing_star/toy + name = "toy throwing star" + desc = "An aerodynamic disc strapped with adhesive for sticking to people, good for playing pranks and getting yourself killed by security." + sharpness = IS_BLUNT + force = 0 + throwforce = 0 + embedding = list("pain_mult" = 0, "jostle_pain_mult" = 0, "embed_chance" = 100, "fall_chance" = 0) + /obj/item/throwing_star/magspear name = "magnetic spear" desc = "A reusable spear that is typically loaded into kinetic spearguns." @@ -295,7 +311,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 force = 10 throw_range = 0 //throwing these invalidates the speargun attack_verb = list("stabbed", "ripped", "gored", "impaled") - embedding = list("embedded_pain_multiplier" = 8, "embed_chance" = 100, "embedded_fall_chance" = 0, "embedded_impact_pain_multiplier" = 15) //55 damage+embed on hit + embedding = list("pain_mult" = 8, "embed_chance" = 100, "fall_chance" = 0, "impact_pain_mult" = 15) //55 damage+embed on hit /obj/item/switchblade name = "switchblade" diff --git a/code/modules/events/wizard/embeddies.dm b/code/modules/events/wizard/embeddies.dm new file mode 100644 index 00000000000..3571e457847 --- /dev/null +++ b/code/modules/events/wizard/embeddies.dm @@ -0,0 +1,50 @@ +/datum/round_event_control/wizard/embedpocalypse + name = "Make Everything Embeddable" + weight = 2 + typepath = /datum/round_event/wizard/embedpocalypse + max_occurrences = 1 + earliest_start = 0 MINUTES + +/datum/round_event/wizard/embedpocalypse/start() + for(var/obj/item/I in world) + CHECK_TICK + + if(!(I.flags_1 & INITIALIZED_1)) + continue + + if(!I.embedding || I.embedding == EMBED_HARMLESS) + I.embedding = EMBED_POINTY + var/list/temp = I.embedding + temp.Insert(0, /datum/element/embed) + I.AddElement(arglist(temp)) + I.name = "pointy [I.name]" + + GLOB.embedpocalypse = TRUE + GLOB.stickpocalypse = FALSE // embedpocalypse takes precedence over stickpocalypse + +/datum/round_event_control/wizard/embedpocalypse/sticky + name = "Make Everything Sticky" + weight = 6 + typepath = /datum/round_event/wizard/embedpocalypse/sticky + max_occurrences = 1 + earliest_start = 0 MINUTES + +/datum/round_event_control/wizard/embedpocalypse/sticky/canSpawnEvent(players_amt, gamemode) + if(GLOB.embedpocalypse) + return FALSE + +/datum/round_event/wizard/embedpocalypse/sticky/start() + for(var/obj/item/I in world) + CHECK_TICK + + if(!(I.flags_1 & INITIALIZED_1)) + continue + + if(!I.embedding) + I.embedding = EMBED_HARMLESS + var/list/temp = I.embedding + temp.Insert(0, /datum/element/embed) + I.AddElement(arglist(temp)) + I.name = "sticky [I.name]" + + GLOB.stickpocalypse = TRUE diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 4542de9ae0d..948c63fd5eb 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -89,7 +89,9 @@ //We want an accurate reading of .len listclearnulls(BP.embedded_objects) - temp_bleed += 0.5*BP.embedded_objects.len + for(var/obj/item/embeddies in BP.embedded_objects) + if(!embeddies.is_embed_harmless()) + temp_bleed += 0.5 if(brutedamage >= 20) temp_bleed += (brutedamage * 0.013) diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 20f188715b9..63e69ac0cc0 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -143,7 +143,10 @@ disabled += BP missing -= BP.body_zone for(var/obj/item/I in BP.embedded_objects) - msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!\n" + if(I.is_embed_harmless()) + msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] stuck to [t_his] [BP.name]!\n" + else + msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!\n" for(var/X in disabled) var/obj/item/bodypart/BP = X diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index f9bbab9ad37..2f2208cbaf5 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -213,21 +213,7 @@ var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore return - var/time_taken = I.embedding.embedded_unsafe_removal_time*I.w_class - usr.visible_message("[usr] attempts to remove [I] from [usr.p_their()] [L.name].","You attempt to remove [I] from your [L.name]... (It will take [DisplayTimeText(time_taken)].)") - if(do_after(usr, time_taken, needhand = 1, target = src)) - if(!I || !L || I.loc != src || !(I in L.embedded_objects)) - return - L.embedded_objects -= I - L.receive_damage(I.embedding.embedded_unsafe_removal_pain_multiplier*I.w_class)//It hurts to rip it out, get surgery you dingus. - I.forceMove(get_turf(src)) - I.unembedded() - usr.put_in_hands(I) - usr.emote("scream") - usr.visible_message("[usr] successfully rips [I] out of [usr.p_their()] [L.name]!", "You successfully remove [I] from your [L.name].") - if(!has_embedded_objects()) - clear_alert("embeddedobject") - SEND_SIGNAL(usr, COMSIG_CLEAR_MOOD_EVENT, "embedded") + SEND_SIGNAL(src, COMSIG_HUMAN_EMBED_RIP, I, L) return if(href_list["item"]) //canUseTopic check for this is handled by mob/Topic() diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index b4a6139b8a8..503330230e0 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -140,21 +140,6 @@ hitpush = FALSE skipcatch = TRUE blocked = TRUE - else if(I) - if(((throwingdatum ? throwingdatum.speed : I.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || I.embedding.embedded_ignore_throwspeed_threshold) - if(can_embed(I)) - if(prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE)) - throw_alert("embeddedobject", /obj/screen/alert/embeddedobject) - var/obj/item/bodypart/L = pick(bodyparts) - L.embedded_objects |= I - I.add_mob_blood(src)//it embedded itself in you, of course it's bloody! - I.forceMove(src) - I.embedded(src) - L.receive_damage(I.w_class*I.embedding.embedded_impact_pain_multiplier) - visible_message("[I] embeds itself in [src]'s [L.name]!","[I] embeds itself in your [L.name]!") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded) - hitpush = FALSE - skipcatch = TRUE //can't catch the now embedded item return ..() @@ -746,7 +731,10 @@ to_chat(src, "\t Your [LB.name][isdisabled][self_aware ? " has " : " is "][status].") for(var/obj/item/I in LB.embedded_objects) - to_chat(src, "\t There is \a [I] embedded in your [LB.name]!") + if(I.is_embed_harmless()) + to_chat(src, "\t There is \a [I] stuck to your [LB.name]!") + else + to_chat(src, "\t There is \a [I] embedded in your [LB.name]!") for(var/t in missing) to_chat(src, "Your [parse_zone(t)] is missing!") diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 65a15feea84..4d17cc45631 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -39,10 +39,6 @@ handle_heart() handle_liver() - if(stat != DEAD) - //Stuff jammed in your limbs hurts - handle_embedded_objects() - dna.species.spec_life(src) // for mutantraces //Update our name based on whether our face is obscured/disfigured @@ -296,25 +292,6 @@ return TRUE return ..() - -/mob/living/carbon/human/proc/handle_embedded_objects() - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - for(var/obj/item/I in BP.embedded_objects) - if(prob(I.embedding.embedded_pain_chance)) - BP.receive_damage(I.w_class*I.embedding.embedded_pain_multiplier) - to_chat(src, "[I] embedded in your [BP.name] hurts!") - - if(prob(I.embedding.embedded_fall_chance)) - BP.receive_damage(I.w_class*I.embedding.embedded_fall_pain_multiplier) - BP.embedded_objects -= I - I.forceMove(drop_location()) - I.unembedded() - visible_message("[I] falls out of [name]'s [BP.name]!","[I] falls out of your [BP.name]!") - if(!has_embedded_objects()) - clear_alert("embeddedobject") - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded") - /mob/living/carbon/human/proc/handle_heart() var/we_breath = !HAS_TRAIT_FROM(src, TRAIT_NOBREATH, SPECIES_TRAIT) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index ee78a97f0ab..774e8a70772 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -69,7 +69,11 @@ var/obj/item/I = AM var/zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest var/dtype = BRUTE - SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, zone) + var/nosell_hit = SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, zone, throwingdatum) // TODO: find a better way to handle hitpush and skipcatch for humans + if(nosell_hit) + skipcatch = TRUE + hitpush = FALSE + dtype = I.damtype if(!blocked) visible_message("[src] is hit by [I]!", \ diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm index 31ac62d9fc5..ccce6c3af2b 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm @@ -15,4 +15,4 @@ /obj/item/throwing_star/ninja name = "ninja throwing star" throwforce = 30 - embedding = list("embedded_pain_multiplier" = 6, "embed_chance" = 100, "embedded_fall_chance" = 0) + embedding = list("pain_mult" = 6, "embed_chance" = 100, "fall_chance" = 0) diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index f7a1aa14463..7070a709191 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -27,6 +27,7 @@ var/colour = "black" //what colour the ink is! var/degrees = 0 var/font = PEN_FONT + embedding = list() /obj/item/pen/suicide_act(mob/user) user.visible_message("[user] is scribbling numbers all over [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit sudoku...") @@ -104,6 +105,7 @@ "Black and Silver" = "pen-fountain-b", "Command Blue" = "pen-fountain-cb" ) + embedding = list("embed_chance" = 75) /obj/item/pen/fountain/captain/Initialize() . = ..() diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm index 3d552d1ca82..92321152d32 100644 --- a/code/modules/research/designs/misc_designs.dm +++ b/code/modules/research/designs/misc_designs.dm @@ -560,3 +560,36 @@ build_path = /obj/item/construction/plumbing category = list("Equipment") departmental_flags = DEPARTMENTAL_FLAG_MEDICAL + + + +///////////////////////////////////////// +/////////////////Tape//////////////////// +///////////////////////////////////////// + +/datum/design/sticky_tape + name = "Sticky Tape" + id = "sticky_tape" + build_type = PROTOLATHE + materials = list(/datum/material/plastic = 500) + build_path = /obj/item/stack/sticky_tape + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/super_sticky_tape + name = "Super Sticky Tape" + id = "super_sticky_tape" + build_type = PROTOLATHE + materials = list(/datum/material/plastic = 3000) + build_path = /obj/item/stack/sticky_tape/super + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE + +/datum/design/pointy_tape + name = "Pointy Tape" + id = "pointy_tape" + build_type = PROTOLATHE + materials = list(/datum/material/iron = 1500, /datum/material/plastic = 1000) + build_path = /obj/item/stack/sticky_tape + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SERVICE diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 0fb5f42a166..8dd63cc6d3e 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -1145,6 +1145,30 @@ hidden = TRUE experimental = TRUE +/datum/techweb_node/sticky_basic + id = "sticky_basic" + display_name = "Basic Sticky Technology" + description = "The only thing left to do after researching this tech is to start printing out a bunch of 'kick me' signs." + prereq_ids = list("base") + design_ids = list("sticky_tape") + + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + export_price = 2500 + hidden = TRUE + experimental = TRUE + +// Can be researched after getting the basic sticky technology from the BEPIS major reward +/datum/techweb_node/sticky_advanced + id = "sticky_advanced" + display_name = "Advanced Sticky Technology" + description = "Taking a good joke too far? Nonsense!" + prereq_ids = list("sticky_basic") + design_ids = list("super_sticky_tape", "pointy_tape") + + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000) + export_price = 2500 + hidden = TRUE + //Helpers for debugging/balancing the techweb in its entirety! /proc/total_techweb_exports() var/list/datum/techweb_node/processing = list() diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index 43449b972c0..2837155191e 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -151,12 +151,14 @@ clear_alert("embeddedobject") SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded") -/mob/living/carbon/proc/has_embedded_objects() - . = FALSE +/mob/living/carbon/proc/has_embedded_objects(include_harmless=FALSE) + . = 0 for(var/X in bodyparts) var/obj/item/bodypart/L = X for(var/obj/item/I in L.embedded_objects) - return TRUE + if(!include_harmless && I.is_embed_harmless()) + continue + return 1 //Helper for quickly creating a new limb - used by augment code in species.dm spec_attacked_by diff --git a/code/modules/surgery/remove_embedded_object.dm b/code/modules/surgery/remove_embedded_object.dm index 39f9bcfff54..cac7924c846 100644 --- a/code/modules/surgery/remove_embedded_object.dm +++ b/code/modules/surgery/remove_embedded_object.dm @@ -30,11 +30,7 @@ var/objects = 0 for(var/obj/item/I in L.embedded_objects) objects++ - I.forceMove(get_turf(H)) - L.embedded_objects -= I - if(!H.has_embedded_objects()) - H.clear_alert("embeddedobject") - SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "embedded") + SEND_SIGNAL(H, COMSIG_HUMAN_EMBED_REMOVAL, I, L) if(objects > 0) display_results(user, target, "You successfully remove [objects] objects from [H]'s [L.name].", diff --git a/icons/obj/tapes.dmi b/icons/obj/tapes.dmi new file mode 100644 index 00000000000..a406a4dd1f9 Binary files /dev/null and b/icons/obj/tapes.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 51cc465ebdb..9e5db917110 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -386,6 +386,7 @@ #include "code\datums\components\earprotection.dm" #include "code\datums\components\edible.dm" #include "code\datums\components\edit_complainer.dm" +#include "code\datums\components\embedded.dm" #include "code\datums\components\empprotection.dm" #include "code\datums\components\explodable.dm" #include "code\datums\components\footstep.dm" @@ -523,6 +524,7 @@ #include "code\datums\elements\digitalcamo.dm" #include "code\datums\elements\dunkable.dm" #include "code\datums\elements\earhealing.dm" +#include "code\datums\elements\embed.dm" #include "code\datums\elements\firestacker.dm" #include "code\datums\elements\forced_gravity.dm" #include "code\datums\elements\selfknockback.dm" @@ -1050,6 +1052,7 @@ #include "code\game\objects\items\stacks\medical.dm" #include "code\game\objects\items\stacks\rods.dm" #include "code\game\objects\items\stacks\stack.dm" +#include "code\game\objects\items\stacks\tape.dm" #include "code\game\objects\items\stacks\telecrystal.dm" #include "code\game\objects\items\stacks\tickets.dm" #include "code\game\objects\items\stacks\wrap.dm" @@ -1770,6 +1773,7 @@ #include "code\modules\events\wizard\blobies.dm" #include "code\modules\events\wizard\curseditems.dm" #include "code\modules\events\wizard\departmentrevolt.dm" +#include "code\modules\events\wizard\embeddies.dm" #include "code\modules\events\wizard\fakeexplosion.dm" #include "code\modules\events\wizard\ghost.dm" #include "code\modules\events\wizard\greentext.dm"