Files
Bubberstation/code/datums/embedding.dm
die 0204ab8fdd Canreach refactor (#93165)
## About The Pull Request
ports https://github.com/DaedalusDock/daedalusdock/pull/1144
ports https://github.com/DaedalusDock/daedalusdock/pull/1147

full credit to @Kapu1178 for the juice

instead of `reacher.CanReach(target)` we now do
`target.CanBeReachedBy(reacher)`, this allows us to give special
behavior to atoms which we want to reach, which is exactly what I need
for a feature I'm working on.
## Why It's Good For The Game
allows us to be more flexible with reachability
## Changelog
🆑
refactor: refactored how reaching items works, report any oddities with
being unable to reach something you should be able to!
/🆑
2025-10-07 20:28:59 +02:00

620 lines
25 KiB
Plaintext

/// How quicker is it for someone else to rip out an item?
#define RIPPING_OUT_HELP_TIME_MULTIPLIER 0.75
/// How much safer is it for someone else to rip out an item?
#define RIPPING_OUT_HELP_DAMAGE_MULTIPLIER 0.75
/*
* The magical embedding datum which is a container for all embedding interactions an item (or a projectile) can have.
* Whenever an item with an embedding datum is thrown into a carbon with either EMBED_THROWSPEED_THRESHOLD throwspeed or ignore_throwspeed_threshold set to TRUE, it will
* embed into them, with latter option reserved for sticky items and shrapnel.
* Whenever a projectile embeds, the datum is copied onto the shrapnel
*/
/datum/embedding
/// Chance for an object to embed into somebody when thrown
var/embed_chance = 45
/// Chance for embedded object to fall out (causing pain but removing the object)
var/fall_chance = 5
/// Chance for embedded objects to cause pain (damage user)
var/pain_chance = 15
/// Coefficient of multiplication for the damage the item does while embedded (this*item.w_class)
var/pain_mult = 2
/// Coefficient of multiplication for the damage the item does when it first embeds (this*item.w_class)
var/impact_pain_mult = 4
/// Coefficient of multiplication for the damage the item does when it falls out or is removed without a surgery (this*item.w_class)
var/remove_pain_mult = 6
/// Time in ticks, total removal time = (this*item.w_class)
var/rip_time = 3 SECONDS
/// If this should ignore throw speed threshold of 4
var/ignore_throwspeed_threshold = FALSE
/// Chance for embedded objects to cause pain every time they move (jostle)
var/jostle_chance = 5
/// Coefficient of multiplication for the damage the item does while
var/jostle_pain_mult = 1
/// This percentage of all pain will be dealt as stam damage rather than brute (0-1)
var/pain_stam_pct = 0
/// Traits which make target immune to us embedding into them, any trait from the list works
var/list/immune_traits = list(TRAIT_PIERCEIMMUNE)
/// The embed doesn't show up on examine, only on health analyzers.
/// (Note: This means you can't rip it out)
/// It will also hide its name (and downplay its severity) when referring to in messages.
var/stealthy_embed = FALSE
/// Thing that we're attached to
VAR_FINAL/obj/item/parent
/// Mob we've embedded into, if any
VAR_FINAL/mob/living/carbon/owner
/// Limb we've embedded into in whose contents we reside
VAR_FINAL/obj/item/bodypart/owner_limb
/datum/embedding/New(obj/item/creator)
. = ..()
if (creator)
register_on(creator)
/// Registers ourselves with an item
/datum/embedding/proc/register_on(obj/item/new_parent)
if(!isitem(new_parent))
CRASH("Embedding datum attempted to register on a non-item object [new_parent] ([new_parent?.type])")
parent = new_parent
RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_qdel))
RegisterSignal(parent, COMSIG_MOVABLE_IMPACT_ZONE, PROC_REF(try_embed))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE_TAGS, PROC_REF(examined_tags))
/datum/embedding/Destroy(force)
if (!parent)
return ..()
parent.set_embed(null)
UnregisterSignal(parent, list(COMSIG_QDELETING, COMSIG_MOVABLE_IMPACT_ZONE, COMSIG_ATOM_EXAMINE))
owner = null
owner_limb = null
parent = null
return ..()
/// Creates a copy and sets all of its *relevant* variables
/// Children should override this with new variables if they add any "generic" ones
/datum/embedding/proc/create_copy(atom/movable/new_owner)
var/datum/embedding/brother = new type(new_owner)
brother.embed_chance = embed_chance
brother.fall_chance = fall_chance
brother.pain_chance = pain_chance
brother.pain_mult = pain_mult
brother.impact_pain_mult = impact_pain_mult
brother.remove_pain_mult = remove_pain_mult
brother.rip_time = rip_time
brother.ignore_throwspeed_threshold = ignore_throwspeed_threshold
brother.jostle_chance = jostle_chance
brother.jostle_pain_mult = jostle_pain_mult
brother.pain_stam_pct = pain_stam_pct
brother.immune_traits = immune_traits.Copy()
brother.stealthy_embed = stealthy_embed
return brother
///Someone inspected our embeddable item
/datum/embedding/proc/examined_tags(obj/item/source, mob/user, list/examine_list)
SIGNAL_HANDLER
if(is_harmless())
examine_list["sticky"] = "[parent] looks sticky, and could probably get stuck to someone if thrown properly!"
else
examine_list["embeddable"] = "[parent] has a fine point, and could probably embed in someone if thrown properly!"
/// Is passed victim a valid target for us to embed into?
/datum/embedding/proc/can_embed(atom/movable/source, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum)
if (!istype(victim))
return FALSE
if (HAS_TRAIT(victim, TRAIT_GODMODE))
return
if (immune_traits)
for (var/immunity_trait in immune_traits)
if (HAS_TRAIT(victim, immunity_trait))
return FALSE
if (isitem(source))
var/flying_speed = throwingdatum?.speed || source.throw_speed
if(flying_speed < EMBED_THROWSPEED_THRESHOLD && !ignore_throwspeed_threshold)
return FALSE
return TRUE
/// Attempts to embed an object
/datum/embedding/proc/try_embed(obj/item/weapon, mob/living/carbon/victim, hit_zone, blocked, datum/thrownthing/throwingdatum)
SIGNAL_HANDLER
if (blocked || !can_embed(parent, victim, hit_zone, throwingdatum))
failed_embed(victim, hit_zone)
return
if (!roll_embed_chance(victim, hit_zone, throwingdatum))
failed_embed(victim, hit_zone, random = TRUE)
return
var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || victim.bodyparts[1]
embed_into(victim, limb)
return MOVABLE_IMPACT_ZONE_OVERRIDE
/// Attempts to embed shrapnel from a projectile
/datum/embedding/proc/try_embed_projectile(obj/projectile/source, atom/hit, hit_zone, blocked, pierce_hit)
if (pierce_hit)
return
if (blocked >= 100 || !can_embed(source, hit))
failed_embed(hit, hit_zone)
return
var/mob/living/carbon/victim = hit
var/obj/item/payload = setup_shrapnel(source, victim)
if (!roll_embed_chance(victim, hit_zone))
failed_embed(victim, hit_zone, random = TRUE)
return
var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || victim.bodyparts[1]
embed_into(victim, limb)
SEND_SIGNAL(source, COMSIG_PROJECTILE_ON_EMBEDDED, payload, hit)
/// Used for custom logic while setting up shrapnel payload
/datum/embedding/proc/setup_shrapnel(obj/projectile/source, mob/living/carbon/victim)
var/shrapnel_type = source.shrapnel_type
var/obj/item/payload = new shrapnel_type(get_turf(victim))
// Detach from parent, we don't want em to delete us
source.set_embed(null, dont_delete = TRUE)
// Hook signals up first, as payload sends a comsig upon embed update
register_on(payload)
payload.set_embed(src)
if(istype(payload, /obj/item/shrapnel/bullet))
payload.name = source.name
SEND_SIGNAL(source, COMSIG_PROJECTILE_ON_SPAWN_EMBEDDED, payload, victim)
/// Calculates the actual chance to embed based on armour penetration and throwing speed, then returns true if we pass that probability check
/datum/embedding/proc/roll_embed_chance(mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum)
var/chance = embed_chance
// Something threw us really, really fast
if (throwingdatum?.speed > parent.throw_speed)
chance += (throwingdatum.speed - parent.throw_speed) * EMBED_CHANCE_SPEED_BONUS
if (is_harmless())
return prob(embed_chance)
// We'll be nice and take the better of bullet and bomb armor, halved
var/armor = max(victim.run_armor_check(hit_zone, BULLET, armour_penetration = parent.armour_penetration, silent = TRUE), victim.run_armor_check(hit_zone, BOMB, armour_penetration = parent.armour_penetration, silent = TRUE)) * 0.5
// We only care about armor penetration if there's actually armor to penetrate
if(!armor)
return prob(chance)
if (parent.weak_against_armour)
armor *= ARMOR_WEAKENED_MULTIPLIER
chance -= armor
if (chance < 0)
victim.visible_message(span_danger("[parent] bounces off [victim]'s armor, unable to embed!"),
span_notice("[parent] bounces off your armor, unable to embed!"), vision_distance = COMBAT_MESSAGE_RANGE)
return FALSE
return prob(chance)
/// We've tried to embed into something and failed
/// Random being TRUE means we've lost the roulette, FALSE means we've either been blocked or the target is invalid
/datum/embedding/proc/failed_embed(mob/living/carbon/victim, hit_zone, random = FALSE)
if (!istype(parent))
return
SEND_SIGNAL(parent, COMSIG_ITEM_FAILED_EMBED, victim, hit_zone)
if((parent.item_flags & DROPDEL) && !QDELETED(parent))
qdel(parent)
/// Does this item deal any damage when embedding or jostling inside of someone?
/datum/embedding/proc/is_harmless(consider_stamina = FALSE)
return pain_mult == 0 && jostle_pain_mult == 0 && (consider_stamina || pain_stam_pct < 1)
//Handles actual embedding logic.
/datum/embedding/proc/embed_into(mob/living/carbon/victim, obj/item/bodypart/target_limb)
SHOULD_NOT_OVERRIDE(TRUE)
set_owner(victim, target_limb)
START_PROCESSING(SSprocessing, src)
owner_limb._embed_object(parent)
parent.forceMove(owner)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(weapon_disappeared))
RegisterSignal(parent, COMSIG_MAGIC_RECALL, PROC_REF(magic_pull))
owner.visible_message(span_danger("[parent] [is_harmless() ? "sticks itself to" : "embeds itself in"] [owner]'s [owner_limb.plaintext_zone]!"),
span_userdanger("[parent] [is_harmless() ? "sticks itself to" : "embeds itself in"] your [owner_limb.plaintext_zone]!"))
var/damage = parent.throwforce
if (!is_harmless(consider_stamina = TRUE))
if(!stealthy_embed)
owner.throw_alert(ALERT_EMBEDDED_OBJECT, /atom/movable/screen/alert/embeddedobject)
owner.add_mood_event("embedded", /datum/mood_event/embedded)
if (!is_harmless())
playsound(owner,'sound/items/weapons/bladeslice.ogg', 40)
if (owner_limb.can_bleed())
parent.add_mob_blood(owner) // it embedded itself in you, of course it's bloody!
damage += parent.w_class * impact_pain_mult
SEND_SIGNAL(parent, COMSIG_ITEM_EMBEDDED, victim, target_limb)
on_successful_embed(victim, target_limb)
if (damage <= 0)
return TRUE
var/armor = owner.run_armor_check(owner_limb.body_zone, MELEE, "Your armor has protected your [owner_limb.plaintext_zone].",
"Your armor has softened a hit to your [owner_limb.plaintext_zone].", parent.armour_penetration,
weak_against_armour = parent.weak_against_armour,
)
owner.apply_damage(
damage = (1 - pain_stam_pct) * damage,
damagetype = BRUTE,
def_zone = owner_limb.body_zone,
blocked = armor,
wound_bonus = parent.wound_bonus,
exposed_wound_bonus = parent.exposed_wound_bonus,
sharpness = parent.get_sharpness(),
attacking_item = parent,
)
owner.apply_damage(
damage = pain_stam_pct * damage,
damagetype = STAMINA,
)
return TRUE
/// Proc which is called upon successfully embedding into someone/something, for children to override
/datum/embedding/proc/on_successful_embed(mob/living/carbon/victim, obj/item/bodypart/target_limb)
return
/// Registers signals that our owner should have
/// Handles jostling, tweezing embedded items out and grenade chain reactions
/datum/embedding/proc/set_owner(mob/living/carbon/victim, obj/item/bodypart/target_limb)
owner = victim
owner_limb = target_limb
RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(owner_moved))
RegisterSignal(owner, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby))
RegisterSignal(owner, COMSIG_ATOM_EX_ACT, PROC_REF(on_ex_act))
RegisterSignal(owner_limb, COMSIG_BODYPART_REMOVED, PROC_REF(on_removed))
/// Avoid calling this directly as this doesn't move the object from its owner's contents
/// Returns TRUE if the item got deleted due to DROPDEL flag
/datum/embedding/proc/stop_embedding()
STOP_PROCESSING(SSprocessing, src)
if (owner_limb)
UnregisterSignal(owner_limb, COMSIG_BODYPART_REMOVED)
owner_limb._unembed_object(parent)
if (owner)
UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_EX_ACT))
if (!owner.has_embedded_objects())
owner.clear_alert(ALERT_EMBEDDED_OBJECT)
owner.clear_mood_event("embedded")
UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MAGIC_RECALL))
SEND_SIGNAL(parent, COMSIG_ITEM_UNEMBEDDED, owner, owner_limb)
owner = null
owner_limb = null
if((parent.item_flags & DROPDEL) && !QDELETED(parent))
qdel(parent)
return TRUE
return FALSE
/datum/embedding/proc/on_qdel(atom/movable/source)
SIGNAL_HANDLER
if (owner_limb)
weapon_disappeared()
qdel(src)
/// Move self to owner's turf when our limb gets removed
/datum/embedding/proc/on_removed(datum/source, mob/living/carbon/old_owner)
SIGNAL_HANDLER
if (!stop_embedding()) // Dropdel?
parent.forceMove(old_owner.drop_location())
/// Someone attempted to pull us out! Either the owner by inspecting themselves, or someone else by examining the owner and clicking the link.
/datum/embedding/proc/rip_out(mob/living/jack_the_ripper)
if (!owner.IsReachableBy(jack_the_ripper))
return
if (!jack_the_ripper.can_perform_action(owner, FORBID_TELEKINESIS_REACH | NEED_HANDS | ALLOW_RESTING))
return
var/time_taken = rip_time * parent.w_class
var/damage_mult = 1
if (jack_the_ripper != owner)
time_taken *= RIPPING_OUT_HELP_TIME_MULTIPLIER
damage_mult *= RIPPING_OUT_HELP_DAMAGE_MULTIPLIER
owner.visible_message(span_warning("[jack_the_ripper] attempts to remove [parent] from [owner]'s [owner_limb.plaintext_zone]!"),
span_userdanger("[jack_the_ripper] attempt to remove [parent] from your [owner_limb.plaintext_zone]!"), ignored_mobs = jack_the_ripper)
to_chat(jack_the_ripper, span_notice("You attempt to remove [parent] from [owner]'s [owner_limb.plaintext_zone]..."))
else
owner.visible_message(span_warning("[owner] attempts to remove [parent] from [owner.p_their()] [owner_limb.plaintext_zone]."),
span_notice("You attempt to remove [parent] from your [owner_limb.plaintext_zone]..."))
if (!do_after(jack_the_ripper, time_taken, owner, extra_checks = CALLBACK(src, PROC_REF(still_in))))
return
if (parent.loc != owner || !(parent in owner_limb?.embedded_objects))
return
if (jack_the_ripper == owner)
owner.visible_message(span_notice("[owner] successfully rips [parent] [is_harmless() ? "off" : "out"] of [owner.p_their()] [owner_limb.plaintext_zone]!"),
span_notice("You successfully remove [parent] from your [owner_limb.plaintext_zone]."))
else
owner.visible_message(span_notice("[jack_the_ripper] successfully rips [parent] [is_harmless() ? "off" : "out"] of [owner]'s [owner_limb.plaintext_zone]!"),
span_userdanger("[jack_the_ripper] removes [parent] from your [owner_limb.plaintext_zone]!"), ignored_mobs = jack_the_ripper)
to_chat(jack_the_ripper, span_notice("You successfully remove [parent] from [owner]'s [owner_limb.plaintext_zone]."))
if (!is_harmless())
damaging_removal_effect(damage_mult)
remove_embedding(jack_the_ripper)
/// Handles damage effects upon forceful removal
/datum/embedding/proc/damaging_removal_effect(ouchies_multiplier)
var/damage = parent.w_class * remove_pain_mult * ouchies_multiplier
owner.apply_damage(
damage = (1 - pain_stam_pct) * damage,
damagetype = BRUTE,
def_zone = owner_limb,
wound_bonus = max(0, parent.wound_bonus), // It hurts to rip it out, get surgery you dingus. unlike the others, this CAN wound + increase slash bloodflow
sharpness = parent.get_sharpness() || SHARP_EDGED, // always sharp, even if the object isn't
attacking_item = parent,
)
owner.apply_damage(
damage = pain_stam_pct * damage,
damagetype = STAMINA,
)
owner.emote("scream")
/// The proper proc to call when you want to remove something. If a mob is passed, the item will be put in its hands - otherwise it's just dumped onto the ground
/datum/embedding/proc/remove_embedding(mob/living/to_hands)
var/mob/living/carbon/stored_owner = owner
if (stop_embedding()) // Dropdel?
return
parent.forceMove(stored_owner.drop_location())
if (!isnull(to_hands))
to_hands.put_in_hands(parent)
/// When owner moves around, attempt to jostle the item
/datum/embedding/proc/owner_moved(mob/living/carbon/source, atom/old_loc, dir, forced, list/old_locs)
SIGNAL_HANDLER
var/chance = jostle_chance
if(!forced && (owner.move_intent == MOVE_INTENT_WALK || owner.body_position == LYING_DOWN) && !CHECK_MOVE_LOOP_FLAGS(source, MOVEMENT_LOOP_OUTSIDE_CONTROL))
chance *= 0.5
if(is_harmless(consider_stamina = TRUE) || !prob(chance))
return
var/damage = parent.w_class * jostle_pain_mult
owner.apply_damage(
damage = (1 - pain_stam_pct) * damage,
damagetype = BRUTE,
def_zone = owner_limb,
wound_bonus = CANT_WOUND,
sharpness = parent.get_sharpness(),
attacking_item = parent,
)
owner.apply_damage(
damage = pain_stam_pct * damage,
damagetype = STAMINA,
)
if(stealthy_embed)
to_chat(owner, span_danger("Something in your [owner_limb.plaintext_zone] jostles and stings!"))
else
to_chat(owner, span_userdanger("[parent] embedded in your [owner_limb.plaintext_zone] jostles and stings!"))
jostle_effects()
/// Effects which should occur when the owner moves, sometimes
/datum/embedding/proc/jostle_effects()
return
/// When someone attempts to pluck us with tweezers or wirecutters
/datum/embedding/proc/on_attackby(mob/living/carbon/victim, obj/item/tool, mob/user)
SIGNAL_HANDLER
if (user.zone_selected != owner_limb.body_zone || (tool.tool_behaviour != TOOL_HEMOSTAT && tool.tool_behaviour != TOOL_WIRECUTTER))
return
if (parent != owner_limb.embedded_objects[1]) // Don't pluck everything at the same time
return
// Ensure that we can actually
if (!owner.try_inject(user, owner_limb.body_zone, INJECT_CHECK_IGNORE_SPECIES | INJECT_TRY_SHOW_ERROR_MESSAGE))
return COMPONENT_NO_AFTERATTACK
INVOKE_ASYNC(src, PROC_REF(try_pluck), tool, user)
return COMPONENT_NO_AFTERATTACK
/datum/embedding/process(seconds_per_tick)
if (!owner || !owner_limb || owner_limb.owner != owner)
stack_trace("Attempted to process embedding on [parent] ([parent.type]) without an owner, owner_limb or owner-less limb!")
parent.forceMove(get_turf(parent))
return
if (process_effect(seconds_per_tick))
return
if (owner.stat == DEAD)
return
var/fall_chance_current = SPT_PROB_RATE(fall_chance / 100, seconds_per_tick) * 100
if(owner.body_position == LYING_DOWN)
fall_chance_current *= 0.2
if(prob(fall_chance_current))
fall_out()
return
var/damage = parent.w_class * pain_mult
var/pain_chance_current = SPT_PROB_RATE(pain_chance / 100, seconds_per_tick) * 100
if(pain_stam_pct && HAS_TRAIT_FROM(owner, TRAIT_INCAPACITATED, STAMINA)) //if it's a less-lethal embed, give them a break if they're already stamcritted
pain_chance_current *= 0.2
damage *= 0.5
else if(owner.body_position == LYING_DOWN)
pain_chance_current *= 0.2
if (is_harmless(consider_stamina = TRUE) || !prob(pain_chance_current))
return
owner.apply_damage(
damage = (1 - pain_stam_pct) * damage,
damagetype = BRUTE,
def_zone = owner_limb,
wound_bonus = CANT_WOUND,
sharpness = parent.get_sharpness(),
attacking_item = parent,
)
owner.apply_damage(
damage = pain_stam_pct * damage,
damagetype = STAMINA,
)
if(stealthy_embed)
to_chat(owner, span_danger("Something in your [owner_limb.plaintext_zone] [pain_stam_pct < 1 ? "hurts!" : "weighs you down."]"))
else
to_chat(owner, span_userdanger("[parent] embedded in your [owner_limb.plaintext_zone] [pain_stam_pct < 1 ? "hurts!" : "weighs you down."]"))
/// Called every process, return TRUE in order to abort further processing - if it falls out, etc
/datum/embedding/proc/process_effect(seconds_per_tick)
return
/// Attempt to pluck out the embedded item using tweezers of some kind
/datum/embedding/proc/try_pluck(obj/item/tool, mob/user)
var/pluck_time = rip_time * (parent.w_class * 0.3) * tool.toolspeed
var/self_pluck = (user == owner)
var/safe_pluck = tool.tool_behaviour != TOOL_HEMOSTAT
// Don't harm ourselves if we're just stuck
if (is_harmless())
safe_pluck = TRUE
if (self_pluck)
pluck_time *= 1.5
// Wirecutters are harder to use for this
if (safe_pluck)
pluck_time *= 1.5
if (self_pluck)
owner.visible_message(span_danger("[owner] begins plucking [parent] from [owner.p_their()] [owner_limb.plaintext_zone] with [tool]..."),
span_notice("You start plucking [parent] from your [owner_limb.plaintext_zone] with [tool]..."), visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE)
else
user.visible_message(span_danger("[user] begins plucking [parent] from [owner]'s [owner_limb.plaintext_zone] with [tool]..."),
span_notice("You start plucking [parent] from [owner]'s [owner_limb.plaintext_zone] with [tool]..."), ignored_mobs = owner)
to_chat(owner, span_userdanger("[user] begins plucking [parent] from your [owner_limb.plaintext_zone] with [tool]... "))
if (!do_after(user, pluck_time, owner, extra_checks = CALLBACK(src, PROC_REF(still_in))))
if (self_pluck)
to_chat(user, span_danger("You fail to pluck [parent] from your [owner_limb.plaintext_zone]."))
else
to_chat(user, span_danger("You fail to pluck [parent] from [owner]'s [owner_limb.plaintext_zone]."))
to_chat(owner, span_danger("[user] fails to pluck [parent] from your [owner_limb.plaintext_zone]."))
return
if (self_pluck)
to_chat(span_notice("You pluck [parent] from your [owner_limb.plaintext_zone][safe_pluck ? "." : span_danger(", but it hurts like hell")]"))
if(!safe_pluck)
damaging_removal_effect(min(self_pluck ? 1 : RIPPING_OUT_HELP_DAMAGE_MULTIPLIER, 0.4 * tool.w_class))
remove_embedding(user)
/// Called when then item randomly falls out of a carbon. This handles the damage and descriptors, then calls remove_embedding()
/datum/embedding/proc/fall_out()
if(is_harmless())
owner.visible_message(span_warning("[parent] falls off of [owner.name]'s [owner_limb.plaintext_zone]!"),
span_warning("[parent] falls off of your [owner_limb.plaintext_zone]!"))
remove_embedding()
return
var/damage = parent.w_class * remove_pain_mult
owner.apply_damage(
damage = (1 - pain_stam_pct) * damage,
damagetype = BRUTE,
def_zone = owner_limb,
wound_bonus = CANT_WOUND,
sharpness = parent.get_sharpness(),
attacking_item = parent,
)
owner.apply_damage(
damage = pain_stam_pct * damage,
damagetype = STAMINA,
)
owner.visible_message(span_danger("[parent] falls out of [owner.name]'s [owner_limb.plaintext_zone]!"),
span_userdanger("[parent] falls out of your [owner_limb.plaintext_zone]!"))
remove_embedding()
/// Whenever the parent item is forcefully moved by some weird means
/datum/embedding/proc/weapon_disappeared(atom/old_loc, dir, forced)
SIGNAL_HANDLER
// If something moved it to their limb, its not really *disappearing*, is it?
if (owner && parent.loc != owner_limb)
to_chat(owner, span_userdanger("[parent] that was embedded in your [owner_limb.plaintext_zone] disappears!"))
stop_embedding()
/// So the sticky grenades chain-detonate, because mobs are very careful with which of their contents they blow up
/datum/embedding/proc/on_ex_act(atom/source, severity)
SIGNAL_HANDLER
// In the process of owner's ex_act
if (QDELETED(parent))
return
switch(severity)
if(EXPLODE_DEVASTATE)
SSexplosions.high_mov_atom += parent
if(EXPLODE_HEAVY)
SSexplosions.med_mov_atom += parent
if(EXPLODE_LIGHT)
SSexplosions.low_mov_atom += parent
/// Called when an object is ripped out of someone's body by magic or other abnormal means
/datum/embedding/proc/magic_pull(obj/item/weapon, mob/living/caster)
SIGNAL_HANDLER
if(is_harmless())
owner.visible_message(span_danger("[parent] vanishes from [owner]'s [owner_limb.plaintext_zone]!"), span_userdanger("[parent] vanishes from [owner_limb.plaintext_zone]!"))
return
var/damage = parent.w_class * remove_pain_mult
owner.apply_damage(
damage = (1 - pain_stam_pct) * damage * 1.5,
damagetype = BRUTE,
def_zone = owner_limb,
wound_bonus = max(0, parent.wound_bonus), // Performs exit wounds and flings the user to the caster if nearby
sharpness = parent.get_sharpness() || SHARP_EDGED,
attacking_item = parent,
)
owner.apply_damage(
damage = pain_stam_pct * damage,
damagetype = STAMINA,
)
owner.cause_wound_of_type_and_severity(WOUND_PIERCE, owner_limb, WOUND_SEVERITY_MODERATE)
playsound(owner, 'sound/effects/wounds/blood2.ogg', 50, TRUE)
var/dist = get_dist(caster, owner) //Check if the caster is close enough to yank them in
if(dist >= 7)
owner.visible_message(span_danger("[parent] is violently torn from [owner]'s [owner_limb.plaintext_zone]!"), span_userdanger("[parent] is violently torn from your [owner_limb.plaintext_zone]!"))
return
owner.throw_at(caster, get_dist(owner, caster) - 1, 1, caster)
owner.Paralyze(1 SECONDS)
owner.visible_message(span_alert("[owner] is sent flying towards [caster] as the [parent] tears out of them!"), span_alert("You are launched at [caster] as the [parent] tears from your body and towards their hand!"))
/datum/embedding/proc/still_in()
if (parent.loc != owner)
return FALSE
if (!(parent in owner_limb?.embedded_objects))
return FALSE
if (owner_limb?.owner != owner)
return FALSE
return TRUE
#undef RIPPING_OUT_HELP_TIME_MULTIPLIER
#undef RIPPING_OUT_HELP_DAMAGE_MULTIPLIER