Biodegrade dissolves all restraints instead of one single restraint; including grabs (#94135)

## About The Pull Request
@Metekillot 

Continues https://github.com/tgstation/tgstation/pull/92053
https://github.com/tgstation/tgstation/pull/92442
https://github.com/tgstation/tgstation/pull/92911

Biodegrade destroys all restraints instead of one single restraint.

Biodegrade will now dissolve all* restraints (fixes niche use-cases such
as strait jackets, cuffed prisoner shoes, etc).

Biodegrade includes grabs (unless it's another ling): The manhandler
gets splashed acid, dealing some damage (this damage is not that bad);
the damage dealt depends on BIO protection (for reference - the default
security uniform halves the damage). They will also be stunned briefly
to give the ling a short chance to leg it.

## Why It's Good For The Game

Same reasons as Metek's

This change is meant to bring changelings in line with their categorized
"uncontainable" valid salad demarcation.
Biodegrade kinda sucks, and this makes it not-suck. 

Makes biodegrade a more attractive ability for changeling by
significantly increasing its use as a method to escape confinement of
any sort. Hopefully it has the gumption to close up cheesy edge cases as
well.

## Changelog

🆑 ArchBTW, Bisar
balance: Changeling's Biodegrade works against grabs.
balance: Changeling's Biodegrade destroys all restraints instead of one
single restraint.
fix: Changeling's Biodegrade shows the effect when used inside lockers. 
fix: Changeling's Biodegrade properly covers restraint types (cuffed
prisoner shoes, strait jackets, etc).
/🆑

---------

Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArchBTW <230859540+glue0000@users.noreply.github.com>
Co-authored-by: Aki Ito <11748095+ExcessiveUseOfCobblestone@users.noreply.github.com>
This commit is contained in:
archbtw
2025-12-06 00:29:52 +00:00
committed by GitHub
parent 0b5d8ead8d
commit 4d32af1508
4 changed files with 115 additions and 97 deletions
@@ -0,0 +1,25 @@
/datum/reagent/toxin/acid/bio_acid
name = "adaptive bio-acid"
description = "An immensely strong, acidic substance of seemingly biological origin. It is teeming with microscopic\
organisms that seem to alter its composition to most adaptively dissolve whatever it comes into contact with."
color = "#9455ff"
creation_purity = 100
toxpwr = 0
acidpwr = 0
ph = 0.0
penetrates_skin = TOUCH
/datum/reagent/toxin/acid/bio_acid/expose_mob(mob/living/exposed_mob, methods = TOUCH, reac_volume, show_message = TRUE, touch_protection)
if(IS_CHANGELING(exposed_mob))
to_chat(exposed_mob, span_changeling("We excrete a bio-agent to neutralize the bio-acid. It is routine and reflexive to do so."))
volume = min(0.1, volume)
holder.update_total()
return
. = ..()
exposed_mob.adjust_fire_loss(round(reac_volume * min(1 - touch_protection), 0.1) * 3, required_bodytype = BODYTYPE_ORGANIC) // full bio protection = 100% damage reduction
exposed_mob.acid_act(10, 50)
/datum/reagent/toxin/acid/bio_acid/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
if(SPT_PROB(50, seconds_per_tick))
affected_mob.emote(pick("screech", "cry"))
@@ -3,114 +3,106 @@
desc = "Dissolves restraints or other objects preventing free movement. Costs 30 chemicals."
helptext = "This is obvious to nearby people, and can destroy standard restraints and closets."
button_icon_state = "biodegrade"
chemical_cost = 30 //High cost to prevent spam
chemical_cost = 30
dna_cost = 2
req_human = TRUE
disabled_by_fire = FALSE
var/static/bio_acid_path = /datum/reagent/toxin/acid/bio_acid
var/static/bio_acid_amount_per_spray = 6
var/static/bio_acid_color = "#9455ff"
/datum/action/changeling/biodegrade/sting_action(mob/living/carbon/human/user)
if(user.handcuffed)
var/obj/O = user.get_item_by_slot(ITEM_SLOT_HANDCUFFED)
if(!istype(O))
return FALSE
user.visible_message(span_warning("[user] vomits a glob of acid on [user.p_their()] [O]!"), \
span_warning("We vomit acidic ooze onto our restraints!"))
. = FALSE
var/list/obj/restraints = list()
var/obj/handcuffs = user.get_item_by_slot(ITEM_SLOT_HANDCUFFED)
var/obj/legcuffs = user.get_item_by_slot(ITEM_SLOT_LEGCUFFED)
var/obj/item/clothing/suit/straitjacket = user.get_item_by_slot(ITEM_SLOT_OCLOTHING)
var/obj/item/clothing/shoes/sneakers/orange/prisoner_shoes = user.get_item_by_slot(ITEM_SLOT_FEET)
var/obj/item/clothing/shoes/knotted_shoes = user.get_item_by_slot(ITEM_SLOT_FEET)
var/obj/some_manner_of_cage = astype(user.loc, /obj)
var/mob/living/space_invader = user.pulledby
addtimer(CALLBACK(src, PROC_REF(dissolve_handcuffs), user, O), 3 SECONDS)
log_combat(user, user.handcuffed, "melted handcuffs", addition = "(biodegrade)")
..()
return TRUE
if(!istype(prisoner_shoes, /obj/item/clothing/shoes/sneakers/orange) || !prisoner_shoes?.attached_cuffs)
prisoner_shoes = null
if(!istype(knotted_shoes) || istype(knotted_shoes) && !knotted_shoes.tied == SHOES_KNOTTED)
knotted_shoes = null
if(!straitjacket?.breakouttime)
straitjacket = null
if(user.legcuffed)
var/obj/O = user.get_item_by_slot(ITEM_SLOT_LEGCUFFED)
if(!istype(O))
return FALSE
user.visible_message(span_warning("[user] vomits a glob of acid on [user.p_their()] [O]!"), \
span_warning("We vomit acidic ooze onto our restraints!"))
if(!handcuffs && !legcuffs && !straitjacket && !prisoner_shoes && !knotted_shoes && !some_manner_of_cage && !space_invader)
user.balloon_alert(user, "already free!")
return .
..()
addtimer(CALLBACK(src, PROC_REF(dissolve_legcuffs), user, O), 3 SECONDS)
log_combat(user, user.legcuffed, "melted legcuffs", addition = "(biodegrade)")
..()
return TRUE
if(handcuffs)
restraints.Add(handcuffs)
if(legcuffs)
restraints.Add(legcuffs)
if(straitjacket)
restraints.Add(straitjacket)
if(prisoner_shoes)
restraints.Add(prisoner_shoes)
if(knotted_shoes)
restraints.Add(knotted_shoes)
if(some_manner_of_cage)
restraints.Add(some_manner_of_cage)
if(user.wear_suit?.breakouttime)
var/obj/item/clothing/suit/S = user.get_item_by_slot(ITEM_SLOT_OCLOTHING)
if(!istype(S))
return FALSE
user.visible_message(span_warning("[user] vomits a glob of acid across the front of [user.p_their()] [S]!"), \
span_warning("We vomit acidic ooze onto our [user.wear_suit.name]!"))
addtimer(CALLBACK(src, PROC_REF(dissolve_straightjacket), user, S), 3 SECONDS)
log_combat(user, user.wear_suit, "melted [user.wear_suit]", addition = "(biodegrade)")
..()
return TRUE
for(var/obj/restraint as anything in restraints)
if(restraint.obj_flags & (INDESTRUCTIBLE | ACID_PROOF | UNACIDABLE))
to_chat(user, span_changeling("We cannot use bio-acid to destroy [restraint]!"))
continue
if(istype(user.loc, /obj/structure/closet))
var/obj/structure/closet/C = user.loc
if(!istype(C))
return FALSE
C.visible_message(span_warning("[C]'s hinges suddenly begin to melt and run!"))
to_chat(user, span_warning("We vomit acidic goop onto the interior of [C]!"))
addtimer(CALLBACK(src, PROC_REF(open_closet), user, C), 7 SECONDS)
log_combat(user, user.loc, "melted locker", addition = "(biodegrade)")
..()
return TRUE
if(istype(user.loc, /obj/structure/spider/cocoon))
var/obj/structure/spider/cocoon/C = user.loc
if(!istype(C))
return FALSE
C.visible_message(span_warning("[src] shifts and starts to fall apart!"))
to_chat(user, span_warning("We secrete acidic enzymes from our skin and begin melting our cocoon..."))
addtimer(CALLBACK(src, PROC_REF(dissolve_cocoon), user, C), 25) //Very short because it's just webs
log_combat(user, user.loc, "melted cocoon", addition = "(biodegrade)")
..()
return TRUE
var/obj/item/clothing/shoes/shoes = user.shoes
if(istype(shoes) && shoes.tied == SHOES_KNOTTED && !(shoes.resistance_flags & (INDESTRUCTIBLE|UNACIDABLE|ACID_PROOF)))
new /obj/effect/decal/cleanable/greenglow(shoes.drop_location())
if(restraint == user.loc)
restraint.visible_message(span_warning("Bubbling acid start spewing out of [restraint]..."))
addtimer(CALLBACK(restraint, TYPE_PROC_REF(/atom, atom_destruction), ACID), 4 SECONDS)
for(var/beat in 1 to 3)
addtimer(CALLBACK(src, PROC_REF(make_puddle), restraint), beat SECONDS)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), restraint, 'sound/items/tools/welder.ogg', 50, TRUE), beat SECONDS)
log_combat(user = user, target = restraint, what_done = "melted restraining container", addition = "(biodegrade)")
return
addtimer(CALLBACK(restraint, TYPE_PROC_REF(/atom, atom_destruction), ACID), 1.5 SECONDS)
log_combat(user = user, target = restraint, what_done = "melted restraining item", addition = "(biodegrade)")
user.visible_message(
span_warning("[user] vomits a glob of acid on [user.p_their()] tied up [shoes.name], melting [shoes.p_them()] into a pool of goo!"),
span_warning("We vomit acidic ooze onto our tied up [shoes.name], melting [shoes.p_them()] into a pool of goo!"),
)
log_combat(user, shoes, "melted own shoes", addition = "(biodegrade)")
qdel(shoes)
..()
return TRUE
span_warning("[user] spews torrents of acid onto [restraint], melting them with horrifying ease."),
user.balloon_alert(user, "melting restraints..."),
span_danger("You hear retching, then the sizzling of powerful acid, closer to the sound of hissing steam."))
playsound(user, 'sound/items/tools/welder.ogg', 50, TRUE)
. = TRUE
user.balloon_alert(user, "already free!")
return FALSE
if(space_invader)
punish_with_acid(user, space_invader)
. = TRUE
return .
/datum/action/changeling/biodegrade/proc/dissolve_handcuffs(mob/living/carbon/human/user, obj/O)
if(O && user.handcuffed == O)
user.visible_message(span_warning("[O] dissolve[O.gender == PLURAL?"":"s"] into a puddle of sizzling goop."))
new /obj/effect/decal/cleanable/greenglow(O.drop_location())
qdel(O)
/// Spawn green acid puddle underneath obj, used for callback
/datum/action/changeling/biodegrade/proc/make_puddle(obj/melted_restraint)
if (melted_restraint) // just incase obj gets qdel'd
return new /obj/effect/decal/cleanable/greenglow(get_turf(melted_restraint))
/datum/action/changeling/biodegrade/proc/dissolve_legcuffs(mob/living/carbon/human/user, obj/O)
if(O && user.legcuffed == O)
user.visible_message(span_warning("[O] dissolve[O.gender == PLURAL?"":"s"] into a puddle of sizzling goop."))
new /obj/effect/decal/cleanable/greenglow(O.drop_location())
qdel(O)
/datum/action/changeling/biodegrade/proc/acid_blast(atom/movable/user, atom/movable/target)
var/datum/reagents/ephemeral_acid = new
ephemeral_acid.add_reagent(bio_acid_path, bio_acid_amount_per_spray)
var/mutable_appearance/splash_animation = mutable_appearance('icons/effects/effects.dmi', "splash")
splash_animation.color = bio_acid_color
target.flick_overlay_view(splash_animation, 3 SECONDS)
ephemeral_acid.expose(target, TOUCH)
/datum/action/changeling/biodegrade/proc/dissolve_straightjacket(mob/living/carbon/human/user, obj/S)
if(S && user.wear_suit == S)
user.visible_message(span_warning("[S] dissolves into a puddle of sizzling goop."))
new /obj/effect/decal/cleanable/greenglow(S.drop_location())
qdel(S)
/datum/action/changeling/biodegrade/proc/open_closet(mob/living/carbon/human/user, obj/structure/closet/C)
if(C && user.loc == C)
C.visible_message(span_warning("[C]'s door breaks and opens!"))
new /obj/effect/decal/cleanable/greenglow(C.drop_location())
C.welded = FALSE
C.locked = FALSE
C.broken = TRUE
C.open()
to_chat(user, span_warning("We open the container restraining us!"))
/datum/action/changeling/biodegrade/proc/dissolve_cocoon(mob/living/carbon/human/user, obj/structure/spider/cocoon/C)
if(C && user.loc == C)
new /obj/effect/decal/cleanable/greenglow(C.drop_location())
qdel(C) //The cocoon's destroy will move the changeling outside of it without interference
to_chat(user, span_warning("We dissolve the cocoon!"))
/datum/action/changeling/biodegrade/proc/punish_with_acid(mob/living/carbon/human/user, mob/living/hapless_manhandler)
acid_blast(user, hapless_manhandler)
playsound(user, 'sound/mobs/non-humanoids/bileworm/bileworm_spit.ogg', 50, TRUE)
if(IS_CHANGELING(hapless_manhandler))
user.visible_message(
span_danger("[user] spews a mist of sizzling acid onto [hapless_manhandler]... but nothing happens!"),
span_changeling("We prepare our escape, spraying bio-acid on our captor... [span_danger("But nothing happened?!")]"),
span_danger("You hear retching, then a sizzling that terminates quite abruptly.")
)
to_chat(hapless_manhandler, span_changeling("Our prey attempts to dissuade us with one of our biology's simplest adaptions. Quaint."))
return
user.visible_message(
span_danger("[user] spews a mist of sizzling acid onto [hapless_manhandler], using the opportunity to wrestle away."),
user.balloon_alert(user, "dissuading captor..."),
span_danger("You hear retching, then sizzling, quickly muffled by a loud keening of pain."))
hapless_manhandler.Stun(2 SECONDS)
hapless_manhandler.emote("scream")
hapless_manhandler.stop_pulling()
log_combat(user = user, target = hapless_manhandler, what_done = "acid-spewed to escape a grab", addition = "(biodegrade)")
+1 -1
View File
@@ -439,7 +439,7 @@
///Get the item on the mob in the storage slot identified by the id passed in
/mob/proc/get_item_by_slot(slot_id)
/mob/proc/get_item_by_slot(slot_id) as /obj/item
return null
/// Gets what slot the item on the mob is held in.
+1
View File
@@ -3301,6 +3301,7 @@
#include "code\modules\antagonists\changeling\changeling_power.dm"
#include "code\modules\antagonists\changeling\fallen_changeling.dm"
#include "code\modules\antagonists\changeling\headslug_eggs.dm"
#include "code\modules\antagonists\changeling\powers\_biodegrade_bioacid.dm"
#include "code\modules\antagonists\changeling\powers\absorb.dm"
#include "code\modules\antagonists\changeling\powers\adrenaline.dm"
#include "code\modules\antagonists\changeling\powers\augmented_eyesight.dm"