mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
## About The Pull Request Currently, projectiles cannot embed if there is any armor present at all. This is due to the fact that the ``blocked`` argument passed to this proc isn't actually ``TRUE/FALSE``, as is expected of the typical embed proc and how the deceptively similarly named argument found on hitby would suggest. For projectiles, however, It is the value of armor that the mob being hit has when they were shot. Therefore, it will never embed unless that value being passed along is equal to 0, such as from being reduced by armour penetration (which is why flechettes work currently on live) Now we only fail if the value is 100. Which means it was fully mitigated. ## Why It's Good For The Game oughe ## Changelog 🆑 fix: Projectiles can once again embed targets that are armoured. /🆑 --------- Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
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
|
|
stop_embedding()
|
|
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 (!jack_the_ripper.CanReach(owner))
|
|
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
|