Files
JacquerelandGitHub b942cec267 Audit dismemberment to make sure we don't forget it might fail (#94808)
## About The Pull Request

Fixes #94778

There are a bunch of places where we dismember a limb (or head) and then
do something with it assuming that we actually dismembered it, but that
isn't actually true.
Many of these places then move your not-actually-dismembered head
somewhere else. If you have an unremovable limb (such as if you have
TRAIT_NODISMEMBER) this means that it actually gets removed anyway
because it's physically moved somewhere else.
In all of these cases, if we failed to dismember the limb we should just
leave it alone.

I left anything where after dismembering the limb it immediately deletes
it in though because those are generally supposed to be invariably
fatal.

## Changelog

🆑
fix: Chainsaws can't behead people with unremovable heads.
fix: You can only put your own head on a spear or into a box if it is
possible to remove your head.
fix: Heretic rifts can only remove your arms if your arms can be
removed.
fix: Paperwork can only cut off your head if your head can be cut off.
fix: Smite being reflected can only blow off your arm if your arm can be
removed.
/🆑
2026-01-10 22:56:25 -07:00

196 lines
7.6 KiB
Plaintext

/obj/item/grenade/flashbang
name = "flashbang"
icon_state = "flashbang"
inhand_icon_state = "flashbang"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
possible_fuse_time = list("3", "4", "5")
//how many tiles away the mob will be affected by the flashbang.
var/flashbang_range = 7
//The devision for the sweetspot culations. if set to 1, the sweetspot is ostensibly the flashbang range.
var/sweetspot_divider = 3
//The light emitted by this flashbang to indicate the sweetspot.
var/flashbang_light = LIGHT_COLOR_INTENSE_RED
/obj/item/grenade/flashbang/apply_grenade_fantasy_bonuses(quality)
flashbang_range = modify_fantasy_variable("flashbang_range", flashbang_range, quality)
/obj/item/grenade/flashbang/remove_grenade_fantasy_bonuses(quality)
flashbang_range = reset_fantasy_variable("flashbang_range", flashbang_range)
/obj/item/grenade/flashbang/arm_grenade(mob/user, delayoverride, msg = TRUE, volume = 60)
. = ..()
if(!.)
return
var/sweetspot_range = clamp(CEILING(flashbang_range/sweetspot_divider, 1), 0, flashbang_range)
set_light(sweetspot_range, sweetspot_range, flashbang_light)
/obj/item/grenade/flashbang/detonate(mob/living/lanced_by)
. = ..()
if(!.)
return
update_mob()
var/turf/flashbang_turf = get_turf(src)
if(!flashbang_turf)
return
//Check that there's enough pressure on the detonation turf for the 'bang' part of the flashbang to work.
var/datum/gas_mixture/environment = flashbang_turf.return_air()
var/pressure = environment?.return_pressure()
var/soundbang = pressure >= SOUND_MINIMUM_PRESSURE
do_sparks(rand(5, 9), FALSE, src)
playsound(flashbang_turf, 'sound/items/weapons/flashbang.ogg', 100, TRUE, 8, 0.9)
new /obj/effect/dummy/lighting_obj (flashbang_turf, flashbang_range + 2, 4, COLOR_WHITE, 2)
for(var/mob/living/living_mob in get_hearers_in_view(flashbang_range, flashbang_turf))
bang(get_turf(living_mob), living_mob, soundbang)
qdel(src)
/obj/item/grenade/flashbang/proc/bang(turf/turf, mob/living/living_mob, soundbang = TRUE)
if(living_mob.stat == DEAD) //They're dead!
return
living_mob.show_message(span_warning("BANG"), MSG_AUDIBLE)
var/distance = get_dist(get_turf(src), turf)
var/sweetspot_range = clamp(CEILING(flashbang_range/sweetspot_divider, 1), 0, flashbang_range)
//Flash
var/attempt_flash = living_mob.flash_act(affect_silicon = 1)
if(attempt_flash == FLASH_COMPLETED)
if(distance <= sweetspot_range || issilicon(living_mob))
living_mob.Paralyze(max(20/max(1, distance), 5))
living_mob.Knockdown(max(200/max(1, distance), 60))
else
living_mob.adjust_dizzy_up_to(max(200/max(1, distance), 5), 20 SECONDS)
living_mob.dropItemToGround(living_mob.get_active_held_item())
living_mob.dropItemToGround(living_mob.get_inactive_held_item())
//Bang
if(!soundbang && distance)
return
if(!distance)
living_mob.soundbang_act(SOUNDBANG_OVERWHELMING, 20 SECONDS, 10, 15)
return
if(distance <= 1) // Adds more stun as to not prime n' pull (#45381)
living_mob.soundbang_act(SOUNDBANG_STRONG, 3 SECONDS, 5)
return
if(distance <= sweetspot_range)
living_mob.soundbang_act(SOUNDBANG_NORMAL, max(20 SECONDS / max(1, distance), 60), rand(0, 5))
return
if(!living_mob.soundbang_act(SOUNDBANG_NORMAL, 0, rand(0, 2)))
return
living_mob.adjust_staggered_up_to(max(200/max(1, distance), 5), 10 SECONDS)
living_mob.dropItemToGround(living_mob.get_active_held_item())
living_mob.dropItemToGround(living_mob.get_inactive_held_item())
/obj/item/grenade/stingbang
name = "stingbang"
icon_state = "timeg_locked"
base_icon_state = "timeg"
inhand_icon_state = "flashbang"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
var/flashbang_range = 1 //how many tiles away the mob will be stunned.
shrapnel_type = /obj/projectile/bullet/pellet/stingball
shrapnel_radius = 5
custom_premium_price = PAYCHECK_COMMAND * 3.5 // mostly gotten through cargo, but throw in one for the sec vendor ;)
/obj/item/grenade/stingbang/mega
name = "mega stingbang"
icon_state = "timeg_mega_locked"
base_icon_state = "timeg_mega"
shrapnel_type = /obj/projectile/bullet/pellet/stingball/mega
shrapnel_radius = 12
/obj/item/grenade/stingbang/detonate(mob/living/lanced_by)
if(dud_flags)
active = FALSE
update_appearance()
return FALSE
if(iscarbon(loc))
var/mob/living/carbon/user = loc
var/obj/item/bodypart/bodypart = user.get_holding_bodypart_of_item(src)
if(bodypart)
forceMove(get_turf(user))
var/did_dismember = bodypart.dismember()
user.visible_message("<b>[span_danger("[src] goes off in [user]'s hand[did_dismember ? ", blowing [user.p_their()] [bodypart.plaintext_zone] to bloody shreds" : ""]!")]</b>", span_userdanger("[src] goes off in your hand[did_dismember ? ", blowing your [bodypart.plaintext_zone] to bloody shreds" : ""]!"))
. = ..()
if(!.)
return
update_mob()
var/flashbang_turf = get_turf(src)
if(!flashbang_turf)
return
do_sparks(rand(5, 9), FALSE, src)
playsound(flashbang_turf, 'sound/items/weapons/flashbang.ogg', 50, TRUE, 8, 0.9)
new /obj/effect/dummy/lighting_obj (flashbang_turf, flashbang_range + 2, 2, COLOR_WHITE, 1)
for(var/mob/living/living_mob in get_hearers_in_view(flashbang_range, flashbang_turf))
pop(get_turf(living_mob), living_mob)
qdel(src)
/obj/item/grenade/stingbang/proc/pop(turf/turf, mob/living/living_mob)
if(living_mob.stat == DEAD) //They're dead!
return
living_mob.show_message(span_warning("POP"), MSG_AUDIBLE)
var/distance = get_dist(get_turf(src), turf)
//Flash
if(living_mob.flash_act(affect_silicon = 1))
living_mob.Paralyze(max(10/max(1, distance), 5))
living_mob.Knockdown(max(100/max(1, distance), 60))
//Bang
if(!distance)
living_mob.Paralyze(2 SECONDS)
living_mob.Knockdown(20 SECONDS)
living_mob.soundbang_act(SOUNDBANG_NORMAL, 200, 10, 15)
if(living_mob.apply_damages(brute = 10, burn = 10))
to_chat(living_mob, span_userdanger("The blast from \the [src] bruises and burns you!"))
// only checking if they're on top of the tile, cause being one tile over will be its own punishment
// Grenade that releases more shrapnel the more times you use it in hand between priming and detonation (sorta like the 9bang from MW3), for admin goofs
/obj/item/grenade/primer
name = "rotfrag grenade"
desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases shrapnel shards."
icon_state = "timeg_locked"
base_icon_state = "timeg"
inhand_icon_state = "flashbang"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
var/rots_per_mag = 3 /// how many times we need to "rotate" the charge in hand per extra tile of magnitude
shrapnel_type = /obj/projectile/bullet/shrapnel
var/rots = 1 /// how many times we've "rotated" the charge
/obj/item/grenade/primer/attack_self(mob/user)
. = ..()
if(active)
user.playsound_local(user, 'sound/misc/box_deploy.ogg', 50, TRUE)
rots++
user.changeNext_move(CLICK_CD_RAPID)
/obj/item/grenade/primer/detonate(mob/living/lanced_by)
shrapnel_radius = round(rots / rots_per_mag)
. = ..()
if(!.)
return
qdel(src)
/obj/item/grenade/primer/stingbang
name = "rotsting"
desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases stingballs."
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
rots_per_mag = 2
shrapnel_type = /obj/projectile/bullet/pellet/stingball