Forcing an embed means that it will always happen (Effects trapped posters, accidental consumption, and bullet shrapnel) (#73729)

## About The Pull Request

Addresses #73105
I confidently asserted in the issue that the current behaviour was not a
bug, but lo and behold when I went to investigate this on March 1st what
did I find... a bug! Force embed actually never worked in the first
place because the passed arguments were in the wrong order. Now they are
named, so the order doesn't matter any more.
Also there was an argument called "silent" which did literally nothing,
I just deleted it.

Sending the force flag will now mean that the forced embed _always_
embeds unless it is literally impossible. The only other place this
effects is when you accidentally eat glass, so a similar case to this
one.

While I was in there, I made posters _very slightly_ more discerning
about what gloves block glass. Unfortunately there's no widely applied
and useful trait like `THICK_MATERIAL` for gloves so I mostly just
exluded latex and fingerless gloves... but that's better than nothing.

Anyway now if you bother to place a glass shard inside a poster and
someone tries to pull it down and they're not wearing thin or fingerless
gloves it will _always_ embed in their hand.

Oh and I put some balloon alerts in there.

**Unrelatedly to this change** fixing this seems to also fix an ancient
bug where bullet shrapnel would roll its embed chance twice in a row,
meaning that it had an (x/100)^2% chance of applying rather than an
(x/100)% chance, significantly less than written.
It is weird for that to go in this PR, but seems to be an effect of
fixing this bug? Not sure there's a way of not fixing both at the same
time.

## Why It's Good For The Game

You go to the effort of putting a glass shard behind a poster (the only
way to associate your own fingerprints with the deed) and are lucky
enough that the person who pulls it down isn't wearing gloves (almost
everyone does) and it _still_ only has a 50% chance to embed the glass?
The trap should work at least _some_ of the time.

## Changelog

🆑
fix: Something which is "forced" to embed will now always actually
embed. Resultingly, accidentally swallowing glass or encountering it
behind a poster without adequate protection will now always cause it to
embed into your body.
balance: Thin and fingerless gloves may no longer protect you from
having glass in your hand after pulling down a poster
fix: Bullets containing shrapnel now use their correct chance to embed,
which may cause bullet wounds to contain shrapnel more frequently.
/🆑
This commit is contained in:
Jacquerel
2023-03-09 00:55:12 +00:00
committed by GitHub
parent 03bb726a80
commit 4ffd487520
3 changed files with 59 additions and 48 deletions
+49 -36
View File
@@ -70,35 +70,26 @@
/datum/element/embed/proc/checkEmbed(obj/item/weapon, mob/living/carbon/victim, hit_zone, blocked, datum/thrownthing/throwingdatum, forced=FALSE)
SIGNAL_HANDLER
if((!forced && blocked) || !istype(victim) || HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE))
return
if(forced)
embed_object(weapon, victim, hit_zone, throwingdatum)
return TRUE
if(blocked || !istype(victim) || HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE))
return FALSE
var/flying_speed = throwingdatum?.speed || weapon.throw_speed
if(!forced && (flying_speed < EMBED_THROWSPEED_THRESHOLD && !ignore_throwspeed_threshold)) // check if it's a forced embed, and if not, if it's going fast enough to proc embedding
return
if(flying_speed < EMBED_THROWSPEED_THRESHOLD && !ignore_throwspeed_threshold)
return FALSE
var/actual_chance = embed_chance
var/penetrative_behaviour = 1 //Keep this above 1, as it is a multiplier for the pen_mod for determining actual embed chance.
if(weapon.weak_against_armour)
penetrative_behaviour = ARMOR_WEAKENED_MULTIPLIER
if(!roll_embed_chance(weapon, victim, hit_zone, throwingdatum))
return FALSE
if(throwingdatum?.speed > weapon.throw_speed)
actual_chance += (throwingdatum.speed - weapon.throw_speed) * EMBED_CHANCE_SPEED_BONUS
if(!weapon.isEmbedHarmless()) // all the armor in the world won't save you from a kick me sign
var/armor = max(victim.run_armor_check(hit_zone, BULLET, silent=TRUE), victim.run_armor_check(hit_zone, BOMB, silent=TRUE)) * 0.5 // we'll be nice and take the better of bullet and bomb armor, halved
if(armor) // we only care about armor penetration if there's actually armor to penetrate
var/pen_mod = -(armor * penetrative_behaviour) // if our shrapnel is weak into armor, then we restore our armor to the full value.
actual_chance += pen_mod // doing the armor pen as a separate calc just in case this ever gets expanded on
if(actual_chance <= 0)
victim.visible_message(span_danger("[weapon] bounces off [victim]'s armor, unable to embed!"), span_notice("[weapon] bounces off your armor, unable to embed!"), vision_distance = COMBAT_MESSAGE_RANGE)
return
if(!prob(actual_chance))
return
embed_object(weapon, victim, hit_zone, throwingdatum)
return TRUE
/// Actually sticks the object to a victim
/datum/element/embed/proc/embed_object(obj/item/weapon, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum)
var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || pick(victim.bodyparts)
victim.AddComponent(/datum/component/embedded,\
weapon,\
@@ -115,8 +106,6 @@
jostle_pain_mult = jostle_pain_mult,\
pain_stam_pct = pain_stam_pct)
return TRUE
///A different embed element has been attached, so we'll detach and let them handle things
/datum/element/embed/proc/severancePackage(obj/weapon, datum/element/E)
SIGNAL_HANDLER
@@ -148,7 +137,7 @@
/datum/element/embed/proc/checkEmbedProjectile(obj/projectile/P, atom/movable/firer, atom/hit, angle, hit_zone)
SIGNAL_HANDLER
if(!iscarbon(hit))
if(!iscarbon(hit) || HAS_TRAIT(hit, TRAIT_PIERCEIMMUNE))
Detach(P)
return // we don't care
@@ -172,28 +161,52 @@
* AGAIN if we actually embed. This way, we save on at least one message.
*
* Arguments:
* * I- the item we're trying to insert into the target
* * embedding_item- the item we're trying to insert into the target
* * target- what we're trying to shish-kabob, either a bodypart or a carbon
* * hit_zone- if our target is a carbon, try to hit them in this zone, if we don't have one, pick a random one. If our target is a bodypart, we already know where we're hitting.
* * forced- if we want this to succeed 100%
*/
/datum/element/embed/proc/tryForceEmbed(obj/item/I, atom/target, hit_zone, forced=FALSE)
/datum/element/embed/proc/tryForceEmbed(obj/item/embedding_item, atom/target, hit_zone, forced=FALSE)
SIGNAL_HANDLER
var/obj/item/bodypart/limb
var/mob/living/carbon/C
if(!forced && !prob(embed_chance))
return
var/mob/living/carbon/victim
if(iscarbon(target))
C = target
victim = target
if(!hit_zone)
limb = pick(C.bodyparts)
limb = pick(victim.bodyparts)
hit_zone = limb.body_zone
else if(isbodypart(target))
limb = target
hit_zone = limb.body_zone
C = limb.owner
victim = limb.owner
return checkEmbed(I, C, hit_zone, forced=TRUE)
if(!forced && !roll_embed_chance(embedding_item, victim, hit_zone))
return
return checkEmbed(embedding_item, victim, hit_zone, forced=TRUE) // Don't repeat the embed roll, we already did it
/// Calculates the actual chance to embed based on armour penetration and throwing speed, then returns true if we pass that probability check
/datum/element/embed/proc/roll_embed_chance(obj/item/embedding_item, mob/living/victim, hit_zone, datum/thrownthing/throwingdatum)
var/actual_chance = embed_chance
if(throwingdatum?.speed > embedding_item.throw_speed)
actual_chance += (throwingdatum.speed - embedding_item.throw_speed) * EMBED_CHANCE_SPEED_BONUS
if(embedding_item.isEmbedHarmless()) // all the armor in the world won't save you from a kick me sign
return prob(actual_chance)
var/armor = max(victim.run_armor_check(hit_zone, BULLET, silent=TRUE), victim.run_armor_check(hit_zone, BOMB, silent=TRUE)) * 0.5 // we'll be nice and take the better of bullet and bomb armor, halved
if(!armor) // we only care about armor penetration if there's actually armor to penetrate
return prob(actual_chance)
//Keep this above 1, as it is a multiplier for the pen_mod for determining actual embed chance.
var/penetrative_behaviour = embedding_item.weak_against_armour ? ARMOR_WEAKENED_MULTIPLIER : 1
var/pen_mod = -(armor * penetrative_behaviour) // if our shrapnel is weak into armor, then we restore our armor to the full value.
actual_chance += pen_mod // doing the armor pen as a separate calc just in case this ever gets expanded on
if(actual_chance <= 0)
victim.visible_message(span_danger("[embedding_item] bounces off [victim]'s armor, unable to embed!"), span_notice("[embedding_item] bounces off your armor, unable to embed!"), vision_distance = COMBAT_MESSAGE_RANGE)
return FALSE
return prob(actual_chance)
+7 -9
View File
@@ -57,7 +57,7 @@
return ..()
if (poster_structure.trap?.resolve())
to_chat(user, span_warning("This poster is already booby-trapped!"))
balloon_alert(user, "already trapped!")
return
if(!user.transferItemToLoc(I, poster_structure))
@@ -215,16 +215,16 @@
return
to_chat(user, span_warning("There's something sharp behind this! What the hell?"))
if(!can_embed_trap(user) || !payload.tryEmbed(user.get_active_hand(), TRUE))
if(!can_embed_trap(user) || !payload.tryEmbed(user.get_active_hand(), forced = TRUE))
visible_message(span_notice("A [payload.name] falls from behind the poster.") )
payload.forceMove(user.drop_location())
else
SEND_SIGNAL(src, COMSIG_POSTER_TRAP_SUCCEED, user)
/obj/structure/sign/poster/proc/can_embed_trap(mob/living/carbon/human/user)
if (!istype(user))
if (!istype(user) || HAS_TRAIT(user, TRAIT_PIERCEIMMUNE))
return FALSE
return (!user.gloves && !HAS_TRAIT(user, TRAIT_PIERCEIMMUNE))
return !user.gloves || !(user.gloves.body_parts_covered & HANDS) || HAS_TRAIT(user, TRAIT_FINGERPRINT_PASSTHROUGH) || HAS_TRAIT(user.gloves, TRAIT_FINGERPRINT_PASSTHROUGH)
/obj/structure/sign/poster/proc/roll_and_drop(atom/location)
pixel_x = 0
@@ -249,15 +249,14 @@
var/stuff_on_wall = 0
for(var/obj/contained_object in contents) //Let's see if it already has a poster on it or too much stuff
if(istype(contained_object, /obj/structure/sign/poster))
to_chat(user, span_warning("The wall is far too cluttered to place a poster!"))
balloon_alert(user, "no room!")
return
stuff_on_wall++
if(stuff_on_wall == 3)
to_chat(user, span_warning("The wall is far too cluttered to place a poster!"))
balloon_alert(user, "no room!")
return
to_chat(user, span_notice("You start placing the poster on the wall...") )
balloon_alert(user, "hanging poster...")
var/obj/structure/sign/poster/placed_poster = rolled_poster.poster_structure
flick("poster_being_set", placed_poster)
@@ -266,7 +265,6 @@
var/turf/user_drop_location = get_turf(user) //cache this so it just falls to the ground if they move. also no tk memes allowed.
if(!do_after(user, PLACE_SPEED, placed_poster, extra_checks = CALLBACK(placed_poster, TYPE_PROC_REF(/obj/structure/sign/poster, snowflake_wall_turf_check), src)))
to_chat(user, span_notice("The poster falls down!"))
placed_poster.roll_and_drop(user_drop_location)
return
+3 -3
View File
@@ -1169,13 +1169,13 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e
* * target- Either a body part or a carbon. What are we hitting?
* * forced- Do we want this to go through 100%?
*/
/obj/item/proc/tryEmbed(atom/target, forced=FALSE, silent=FALSE)
/obj/item/proc/tryEmbed(atom/target, forced=FALSE)
if(!isbodypart(target) && !iscarbon(target))
return NONE
if(!forced && !LAZYLEN(embedding))
return NONE
if(SEND_SIGNAL(src, COMSIG_EMBED_TRY_FORCE, target, forced, silent))
if(SEND_SIGNAL(src, COMSIG_EMBED_TRY_FORCE, target = target, forced = forced))
return COMPONENT_EMBED_SUCCESS
failedEmbed()
@@ -1235,7 +1235,7 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e
victim.apply_damage(max(15, force), BRUTE, BODY_ZONE_HEAD, wound_bonus = 10, sharpness = TRUE)
victim.losebreath += 2
if(tryEmbed(victim.get_bodypart(BODY_ZONE_CHEST), TRUE, TRUE)) //and if it embeds successfully in their chest, cause a lot of pain
if(tryEmbed(victim.get_bodypart(BODY_ZONE_CHEST), forced = TRUE)) //and if it embeds successfully in their chest, cause a lot of pain
victim.apply_damage(max(25, force*1.5), BRUTE, BODY_ZONE_CHEST, wound_bonus = 7, sharpness = TRUE)
victim.losebreath += 6
discover_after = FALSE