mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-09 16:05:07 +00:00
## 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! /🆑
620 lines
25 KiB
Plaintext
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
|