Adds a new passive changeling ability - the Defibrillator Grasp. Adds support for 0 cost changeling abilities. (#76301)

## About The Pull Request


![image](https://github.com/tgstation/tgstation/assets/51863163/190d0dda-6899-468d-9a3e-5df05d7b1414)

- Adds a new PASSIVE changeling ability, totally not stolen from The
Thing, called Defibrillator Grasp. If someone attempts to defibrillate
your body while you are in stasis or dead, you will instantly be revived
at full strength. Additionally, the person doing the defibbing will have
both of their arms removed (or if they're a cyborg they'll just be
stunned).

- Adds support for 0 cost changeling abilities. DNA sting is now 0 cost
/ optionally learned.

## Why It's Good For The Game

The intent of this ability to add a new avenue for stealthier
changelings to spring upon their victims - faking their death only to
get brought to medical, then springing up like a bat out of hell and
pouncing upon a now vulnerable medical doctor.

Or maybe pretending to be another type of criminal to get brought to the
brig medbay and revived, then suddenly jumping up and striking out.

As for the DNA sting change - Ling gets a ton of buttons to start and
people don't often use a lot of them. DNA sting is a prime example. By
moving some to just "zero cost, optional", trims down on some action
ability bloat.

## Changelog

🆑 Melbert
add: Added a new 0 cost passive changeling ability, the Defibrillator
Grasp.
add: DNA sing is now no longer innate, but 0 cost, allowing changelings
to not take it if they don't plan on using it.
/🆑
This commit is contained in:
MrMelbert
2023-07-02 03:44:19 +01:00
committed by GitHub
parent 7f3d763285
commit 80c954c6a0
18 changed files with 215 additions and 43 deletions
+8
View File
@@ -338,6 +338,14 @@ GLOBAL_LIST_INIT(human_invader_antagonists, list(
#define HUNTER_PACK_BOUNTY "Bounty Fugitive Hunters"
#define HUNTER_PACK_PSYKER "Psyker Fugitive Hunters"
/// Changeling abilities with DNA cost = this are innately given to all changelings
#define CHANGELING_POWER_INNATE -1
/// Changeling abilities with DNA cost = this are not obtainable by changelings - either used for secret unlockable or abstract abilities
#define CHANGELING_POWER_UNOBTAINABLE -2
/// For changelings, this is how many recent say lines are retained when absorbing a mob
#define LING_ABSORB_RECENT_SPEECH 8
// Various abductor equipment modes.
#define VEST_STEALTH 1
-4
View File
@@ -1,4 +0,0 @@
/// The duration of the fakedeath coma.
#define LING_FAKEDEATH_TIME (40 SECONDS)
/// The number of recent spoken lines to gain on absorbing a mob
#define LING_ABSORB_RECENT_SPEECH 8
@@ -4,9 +4,14 @@
/// From /datum/surgery_step/success(): (datum/surgery_step/step, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results)
#define COMSIG_MOB_SURGERY_STEP_SUCCESS "mob_surgery_step_success"
/// From /obj/item/shockpaddles/do_help, after the defib do_after is complete, but before any effects are applied: (mob/living/defibber, obj/item/shockpaddles/source)
#define COMSIG_DEFIBRILLATOR_PRE_HELP_ZAP "carbon_being_defibbed"
/// Return to stop default defib handling
#define COMPONENT_DEFIB_STOP (1<<0)
/// From /obj/item/shockpaddles/proc/do_success(): (obj/item/shockpaddles/source)
#define COMSIG_DEFIBRILLATOR_SUCCESS "defib_success"
#define COMPONENT_DEFIB_STOP (1<<0)
// #define COMPONENT_DEFIB_STOP (1<<0) // Same return, to stop default defib handling
/// From /datum/surgery/can_start(): (mob/source, datum/surgery/surgery, mob/living/patient)
#define COMSIG_SURGERY_STARTING "surgery_starting"
+3
View File
@@ -592,6 +592,9 @@
playsound(src, 'sound/machines/defib_failed.ogg', 50, FALSE)
do_cancel()
return
if(SEND_SIGNAL(H, COMSIG_DEFIBRILLATOR_PRE_HELP_ZAP, user, src) & COMPONENT_DEFIB_STOP)
do_cancel()
return
if(H.stat == DEAD)
H.visible_message(span_warning("[H]'s body convulses a bit."))
playsound(src, SFX_BODYFALL, 50, TRUE)
@@ -41,7 +41,7 @@
var/dna_cost = initial(ability_path.dna_cost)
if(dna_cost <= 0)
if(dna_cost < 0)
continue
var/list/ability_data = list()
@@ -57,7 +57,7 @@
can_purchase = FALSE
if(initial(ability_path.req_dna) > changeling.absorbed_count)
can_purchase = FALSE
if(dna_cost > genetic_points_remaining)
if(dna_cost > 0 && dna_cost > genetic_points_remaining)
can_purchase = FALSE
ability_data["can_purchase"] = can_purchase
@@ -229,11 +229,11 @@
/*
* Instantiate all the default actions of a ling (transform, dna sting, absorb, etc)
* Any Changeling action with `dna_cost == 0` will be added here automatically
* Any Changeling action with dna_cost = CHANGELING_POWER_INNATE will be added here automatically
*/
/datum/antagonist/changeling/proc/create_innate_actions()
for(var/datum/action/changeling/path as anything in all_powers)
if(initial(path.dna_cost) != 0)
if(initial(path.dna_cost) != CHANGELING_POWER_INNATE)
continue
var/datum/action/changeling/innate_ability = new path()
@@ -7,17 +7,33 @@
background_icon_state = "bg_changeling"
overlay_icon_state = "bg_changeling_border"
button_icon = 'icons/mob/actions/actions_changeling.dmi'
var/needs_button = TRUE//for passive abilities like hivemind that dont need a button
var/helptext = "" // Details
var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands)
var/dna_cost = -1 //cost of the sting in dna points. 0 = auto-purchase (see changeling.dm), -1 = cannot be purchased
var/req_dna = 0 //amount of dna needed to use this ability. Changelings always have atleast 1
var/req_human = FALSE //if you need to be human to use this ability
var/req_absorbs = 0 //similar to req_dna, but only gained from absorbing, not DNA sting
///Maximum stat before the ability is blocked. For example, `UNCONSCIOUS` prevents it from being used when in hard crit or dead, while `DEAD` allows the ability to be used on any stat values.
/// For passive abilities like hivemind that dont need an action button
var/needs_button = TRUE
/// Details displayed in fine print within the changling emporium
var/helptext = ""
/// How many changeling chems it costs to use
var/chemical_cost = 0
/**
* Cost of the ability in dna points, negative values are not valid
*
* Special numbers include [CHANGELING_POWER_INNATE], which are given to changeling for free without bring prompted
* and [CHANGELING_POWER_UNOBTAINABLE], which are not available for purchase in the changeling emporium
*/
var/dna_cost = CHANGELING_POWER_UNOBTAINABLE
/// Amount of dna needed to use this ability. Note, changelings always have atleast 1
var/req_dna = 0
/// If you need to be humanoid to use this ability (disincludes monkeys)
var/req_human = FALSE
/// Similar to req_dna, but only gained from absorbing, not DNA sting
var/req_absorbs = 0
/// Maximum stat before the ability is blocked.
/// For example, `UNCONSCIOUS` prevents it from being used when in hard crit or dead,
/// while `DEAD` allows the ability to be used on any stat values.
var/req_stat = CONSCIOUS
var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag
var/active = FALSE//used by a few powers that toggle
/// usable when the changeling is in death coma
var/ignores_fakedeath = FALSE
/// used by a few powers that toggle
var/active = FALSE
/*
changeling code now relies on on_purchase to grant powers.
@@ -3,7 +3,7 @@
desc = "Absorb the DNA of our victim. Requires us to strangle them."
button_icon_state = "absorb_dna"
chemical_cost = 0
dna_cost = 0
dna_cost = CHANGELING_POWER_INNATE
req_human = TRUE
///if we're currently absorbing, used for sanity
var/is_absorbing = FALSE
@@ -0,0 +1,92 @@
/datum/action/changeling/defib_grasp
name = "Defibrillator Grasp"
desc = "We prepare ourselves while in stasis. If one of our enemies attempts to defibrillate us, \
we will snatch their arms off and instantly finalize our stasis."
helptext = "This ability is passive, and will trigger when a defibrillator paddle is applied to our chest \
while we are dead or in stasis. Will also stun cyborgs momentarily."
needs_button = FALSE
dna_cost = 0
/// Flags to pass to fully heal when we get zapped
var/heal_flags = HEAL_DAMAGE|HEAL_BODY|HEAL_STATUS|HEAL_CC_STATUS
/datum/action/changeling/defib_grasp/on_purchase(mob/user, is_respec)
. = ..()
RegisterSignal(user, COMSIG_DEFIBRILLATOR_PRE_HELP_ZAP, PROC_REF(on_defibbed))
/// Signal proc for [COMSIG_DEFIBRILLATOR_PRE_HELP_ZAP].
/datum/action/changeling/defib_grasp/proc/on_defibbed(mob/living/carbon/source, mob/living/defibber, obj/item/shockpaddles/defib)
SIGNAL_HANDLER
if(source.stat != DEAD && !HAS_TRAIT_FROM(source, TRAIT_FAKEDEATH, CHANGELING_TRAIT))
return
INVOKE_ASYNC(src, PROC_REF(execute_defib), source, defibber, defib)
return COMPONENT_DEFIB_STOP
/// Executes the defib action, causing the changeling to fully heal and get up.
/datum/action/changeling/defib_grasp/proc/execute_defib(mob/living/carbon/changeling, mob/living/defibber, obj/item/shockpaddles/defib)
remove_arms(changeling, defibber, defib)
if(changeling.stat == DEAD)
changeling.revive(heal_flags)
else
changeling.fully_heal(heal_flags)
changeling.cure_fakedeath(CHANGELING_TRAIT) // rips us out of revival stasis (if we're in it)
changeling.buckled?.unbuckle_mob(changeling) // get us off of stasis beds please
changeling.set_resting(FALSE)
changeling.adjust_jitter(20 SECONDS)
changeling.emote("scream")
playsound(changeling, 'sound/magic/demon_consume.ogg', 50, TRUE)
// Mimics some real defib stuff (wish this was more generalized)
playsound(defib, SFX_BODYFALL, 50, TRUE)
playsound(defib, 'sound/machines/defib_zap.ogg', 75, TRUE, -1)
playsound(defib, 'sound/machines/defib_success.ogg', 50, FALSE) // I guess
defib.shock_pulling(30, changeling)
/// Removes the arms of the defibber if they're a carbon, and stuns them for a bit.
/// If they're a cyborg, they'll just get stunned instead.
/datum/action/changeling/defib_grasp/proc/remove_arms(mob/living/carbon/changeling, mob/living/defibber, obj/item/shockpaddles/defib)
if(iscyborg(defibber))
if(defibber.flash_act(affect_silicon = TRUE))
to_chat(defibber, span_userdanger("[changeling] awakens suddenly, overloading your sensors!"))
// run default visible message regardless, no overt indication of the cyborg being overloaded to watchers
else
defibber.Stun(4 SECONDS) // stuck defibbing
if(iscarbon(defibber))
var/removed_arms = 0
var/mob/living/carbon/carbon_defibber = defibber
for(var/obj/item/bodypart/arm/limb in carbon_defibber.bodyparts)
if(limb.dismember(silent = FALSE))
removed_arms++
qdel(limb)
if(removed_arms)
// OH GOOD HEAVENS
defibber.adjust_jitter(3 MINUTES)
defibber.adjust_dizzy(1 MINUTES)
defibber.adjust_stutter(1 MINUTES)
defibber.adjust_eye_blur(10 SECONDS)
defibber.emote("scream")
changeling.visible_message(
span_bolddanger("[changeling] awakens suddenly, snatching [defib] out of [defibber]'s hands while ripping off [removed_arms >= 2 ? "" : "one of "][defibber.p_their()] arms!"),
vision_distance = COMBAT_MESSAGE_RANGE,
ignored_mobs = list(changeling, defibber),
)
to_chat(changeling, span_changeling("The power of [defib] course through us, reviving us from our stasis! \
With this newfound energy, we snap [removed_arms >= 2 ? "" : "one of "][defibber]'s arms off!"))
to_chat(defibber, span_userdanger("[changeling] awakens suddenly, snapping [removed_arms >= 2 ? "" : "one of "]your arms off!"))
return // no default message if we got an arm
changeling.visible_message(
span_bolddanger("[changeling] awakens suddenly!"),
vision_distance = COMBAT_MESSAGE_RANGE,
ignored_mobs = changeling,
)
to_chat(changeling, span_changeling("The power of [defib] course through us, reviving us from our stasis!"))
@@ -3,10 +3,15 @@
desc = "We fall into a stasis, allowing us to regenerate and trick our enemies. Costs 15 chemicals."
button_icon_state = "fake_death"
chemical_cost = 15
dna_cost = 0
dna_cost = CHANGELING_POWER_INNATE
req_dna = 1
req_stat = DEAD
ignores_fakedeath = TRUE
/// How long it takes for revival to ready upon entering stasis.
/// The changelin can opt to stay in fakedeath for longer, though.
var/fakedeath_duration = 40 SECONDS
/// If TRUE, we're ready to revive and can click the button to heal.
var/revive_ready = FALSE
//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay.
@@ -14,25 +19,63 @@
..()
if(revive_ready)
INVOKE_ASYNC(src, PROC_REF(revive), user)
revive_ready = FALSE
chemical_cost = 15
to_chat(user, span_notice("We have revived ourselves."))
build_all_button_icons(UPDATE_BUTTON_NAME|UPDATE_BUTTON_ICON)
disable_revive(user) // this should be already called via signal, but just incase something wacky happens
else
to_chat(user, span_notice("We begin our stasis, preparing energy to arise once more."))
user.fakedeath(CHANGELING_TRAIT) //play dead
addtimer(CALLBACK(src, PROC_REF(ready_to_regenerate), user), LING_FAKEDEATH_TIME, TIMER_UNIQUE)
enable_fakedeath(user)
return TRUE
/// Used to enable fakedeath and register relevant signals / start timers
/datum/action/changeling/fakedeath/proc/enable_fakedeath(mob/living/changeling)
if(revive_ready || HAS_TRAIT_FROM(changeling, TRAIT_DEATHCOMA, CHANGELING_TRAIT))
return
changeling.fakedeath(CHANGELING_TRAIT)
addtimer(CALLBACK(src, PROC_REF(ready_to_regenerate), changeling), fakedeath_duration, TIMER_UNIQUE)
RegisterSignal(changeling, SIGNAL_REMOVETRAIT(TRAIT_DEATHCOMA), PROC_REF(fakedeath_reset))
/// Sets [revive_ready] to FALSE and updates the button icons.
/datum/action/changeling/fakedeath/proc/disable_revive(mob/living/changeling)
if(!revive_ready)
return
chemical_cost = 15
revive_ready = FALSE
build_all_button_icons(UPDATE_BUTTON_NAME|UPDATE_BUTTON_ICON)
UnregisterSignal(changeling, SIGNAL_REMOVETRAIT(TRAIT_DEATHCOMA))
/// Sets [revive_ready] to TRUE and updates the button icons.
/datum/action/changeling/fakedeath/proc/enable_revive(mob/living/changeling)
if(revive_ready)
return
chemical_cost = 0
revive_ready = TRUE
build_all_button_icons(UPDATE_BUTTON_NAME|UPDATE_BUTTON_ICON)
/// Signal proc to stop the revival process if the changeling exits their stasis early.
/datum/action/changeling/fakedeath/proc/fakedeath_reset(datum/source)
SIGNAL_HANDLER
if(HAS_TRAIT_FROM(source, TRAIT_DEATHCOMA, CHANGELING_TRAIT))
return
disable_revive(source)
/datum/action/changeling/fakedeath/proc/revive(mob/living/carbon/user)
if(!istype(user))
return
if(!HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, CHANGELING_TRAIT))
return
user.cure_fakedeath(CHANGELING_TRAIT)
// Heal all damage and some minor afflictions,
var/flags_to_heal = (HEAL_DAMAGE|HEAL_BODY|HEAL_STATUS|HEAL_CC_STATUS)
// but leave out limbs so we can do it specially
user.revive(flags_to_heal & ~HEAL_LIMBS)
to_chat(user, span_notice("We have revived ourselves."))
var/static/list/dont_regenerate = list(BODY_ZONE_HEAD) // headless changelings are funny
if(!length(user.get_missing_limbs() - dont_regenerate))
@@ -49,17 +92,17 @@
user.regenerate_limbs(dont_regenerate)
/datum/action/changeling/fakedeath/proc/ready_to_regenerate(mob/user)
if(!user?.mind)
if(QDELETED(src) || QDELETED(user))
return
var/datum/antagonist/changeling/ling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(!ling || !(src in ling.innate_powers))
var/datum/antagonist/changeling/ling = user.mind?.has_antag_datum(/datum/antagonist/changeling)
if(QDELETED(ling) || !(src in ling.innate_powers + ling.purchased_powers)) // checking both innate and purchased for full coverage
return
if(!HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, CHANGELING_TRAIT))
return
to_chat(user, span_notice("We are ready to revive."))
chemical_cost = 0
revive_ready = TRUE
build_all_button_icons(UPDATE_BUTTON_NAME|UPDATE_BUTTON_ICON)
enable_revive(user)
/datum/action/changeling/fakedeath/can_sting(mob/living/user)
if(revive_ready)
@@ -15,7 +15,7 @@
desc = "Go tell a coder if you see this"
helptext = "Yell at Miauw and/or Perakp"
chemical_cost = 1000
dna_cost = -1
dna_cost = CHANGELING_POWER_UNOBTAINABLE
var/silent = FALSE
var/weapon_type
@@ -88,7 +88,7 @@
desc = "Go tell a coder if you see this"
helptext = "Yell at Miauw and/or Perakp"
chemical_cost = 1000
dna_cost = -1
dna_cost = CHANGELING_POWER_UNOBTAINABLE
var/helmet_type = /obj/item
var/suit_type = /obj/item
@@ -4,7 +4,7 @@
helptext = "Will alert nearby crew if any external limbs are regenerated. Can be used while unconscious."
button_icon_state = "regenerate"
chemical_cost = 10
dna_cost = 0
dna_cost = CHANGELING_POWER_INNATE
req_stat = HARD_CRIT
/datum/action/changeling/regenerate/sting_action(mob/living/user)
@@ -3,7 +3,7 @@
desc = "We take on the appearance and voice of one we have absorbed. Costs 5 chemicals."
button_icon_state = "transform"
chemical_cost = 5
dna_cost = 0
dna_cost = CHANGELING_POWER_INNATE
req_dna = 1
req_human = TRUE
@@ -559,7 +559,6 @@
if(player_mind.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND)
ADD_TRAIT(player, TRAIT_DEFIB_BLACKLISTED, REF(src))
player.med_hud_set_status()
for(var/datum/job/job as anything in SSjob.joinable_occupations)
if(!(job.departments_bitflags & (DEPARTMENT_BITFLAG_SECURITY|DEPARTMENT_BITFLAG_COMMAND)))
@@ -626,7 +625,6 @@
for (var/datum/mind/rev_head as anything in ex_headrevs)
if(!isnull(rev_head.current))
ADD_TRAIT(rev_head.current, TRAIT_DEFIB_BLACKLISTED, REF(src))
rev_head.current.med_hud_set_status()
for(var/datum/objective/mutiny/head_tracker in objectives)
var/mob/living/head_of_staff = head_tracker.target?.current
+13 -1
View File
@@ -6,6 +6,14 @@
RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_DEATHCOMA), PROC_REF(on_deathcoma_trait_gain))
RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_DEATHCOMA), PROC_REF(on_deathcoma_trait_loss))
RegisterSignals(src, list(
SIGNAL_ADDTRAIT(TRAIT_FAKEDEATH),
SIGNAL_REMOVETRAIT(TRAIT_FAKEDEATH),
SIGNAL_ADDTRAIT(TRAIT_DEFIB_BLACKLISTED),
SIGNAL_REMOVETRAIT(TRAIT_DEFIB_BLACKLISTED),
), PROC_REF(update_medhud_on_signal))
RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED), PROC_REF(on_immobilized_trait_gain))
RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_IMMOBILIZED), PROC_REF(on_immobilized_trait_loss))
@@ -66,7 +74,6 @@
if(stat <= UNCONSCIOUS)
update_stat()
/// Called when [TRAIT_DEATHCOMA] is added to the mob.
/mob/living/proc/on_deathcoma_trait_gain(datum/source)
SIGNAL_HANDLER
@@ -77,6 +84,11 @@
SIGNAL_HANDLER
REMOVE_TRAIT(src, TRAIT_KNOCKEDOUT, TRAIT_DEATHCOMA)
/// Updates medhud when recieving relevant signals.
/mob/living/proc/update_medhud_on_signal(datum/source)
SIGNAL_HANDLER
med_hud_set_health()
med_hud_set_status()
/// Called when [TRAIT_IMMOBILIZED] is added to the mob.
/mob/living/proc/on_immobilized_trait_gain(datum/source)
-1
View File
@@ -524,7 +524,6 @@
add_traits(list(TRAIT_FAKEDEATH, TRAIT_DEATHCOMA), source)
tod = station_time_timestamp()
///Unignores all slowdowns that lack the IGNORE_NOSLOW flag.
/mob/living/proc/unignore_slowdown(source)
REMOVE_TRAIT(src, TRAIT_IGNORESLOWDOWN, source)
+1 -1
View File
@@ -60,7 +60,6 @@
#include "code\__DEFINES\callbacks.dm"
#include "code\__DEFINES\cameranets.dm"
#include "code\__DEFINES\cargo.dm"
#include "code\__DEFINES\changeling.dm"
#include "code\__DEFINES\chat.dm"
#include "code\__DEFINES\chat_filter.dm"
#include "code\__DEFINES\cleaning.dm"
@@ -2557,6 +2556,7 @@
#include "code\modules\antagonists\changeling\powers\augmented_eyesight.dm"
#include "code\modules\antagonists\changeling\powers\biodegrade.dm"
#include "code\modules\antagonists\changeling\powers\chameleon_skin.dm"
#include "code\modules\antagonists\changeling\powers\defib_grasp.dm"
#include "code\modules\antagonists\changeling\powers\digitalcamo.dm"
#include "code\modules\antagonists\changeling\powers\fakedeath.dm"
#include "code\modules\antagonists\changeling\powers\fleshmend.dm"
@@ -167,7 +167,7 @@ const AbilitiesSection = (props, context) => {
<Stack.Item basis={0} textColor="label" grow>
Your
<span style={absorbstyle}>&ensp;Absorb DNA</span> ability allows
you to steal the DNA and memories of a victim. Your
you to steal the DNA and memories of a victim. The
<span style={absorbstyle}>&ensp;Extract DNA Sting</span> ability
also steals the DNA of a victim, and is undetectable, but does not
grant you their memories or speech patterns.