From be6b35485dbea25e05288f7d2707949113e62113 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Mon, 18 May 2026 15:18:23 -0400 Subject: [PATCH] Psi-Protect Pills (#22449) requires #22442 This PR adds the new Psi-Protect pills required by the current Lemurian Sea lore arc. They can be obtained exclusively via the psychiatric medicine selection, and are a new prescription drug marketed by Zeng-Hu Pharmaceuticals as a solution for ADPI exposure that doesn't require a more invasive implant. When taken, these pills provide 10 minutes of temporary protection from telepathy, as well as a reduction in psi-sensitivity. In the loadouts: image Spawning in: image Messages playing when taking the pill image Verifying in testing that the component correctly attaches image And verifying that component TTD is set, as well as Signals are being correctly registered. image --- aurorastation.dme | 2 + code/__DEFINES/is_helpers.dm | 6 + .../components/psionics/psiblock_drugs.dm | 139 ++++++++++++++++++ .../datums/components/timed_life_component.dm | 45 ++++++ .../items/weapons/storage/pill_bottle.dm | 30 ++++ .../loadout/items/drugs_meds.dm | 3 + .../Chemistry-Reagents-Drugs.dm | 28 +++- .../reagents/reagent_containers/pill.dm | 15 ++ .../changelogs/hellfirejag-psiblock-drugs.yml | 4 + 9 files changed, 270 insertions(+), 2 deletions(-) create mode 100644 code/datums/components/psionics/psiblock_drugs.dm create mode 100644 code/datums/components/timed_life_component.dm create mode 100644 html/changelogs/hellfirejag-psiblock-drugs.yml diff --git a/aurorastation.dme b/aurorastation.dme index ac44de05ce8..5e6732c5cce 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -498,6 +498,7 @@ #include "code\datums\components\local_network.dm" #include "code\datums\components\orbiter.dm" #include "code\datums\components\overlay_lighting.dm" +#include "code\datums\components\timed_life_component.dm" #include "code\datums\components\armor\_armor.dm" #include "code\datums\components\armor\synthetic_armor.dm" #include "code\datums\components\base_name\base_name.dm" @@ -514,6 +515,7 @@ #include "code\datums\components\overhead_emote\overhead_emote_singleton.dm" #include "code\datums\components\psionics\psi_sensitivity.dm" #include "code\datums\components\psionics\psi_suppression.dm" +#include "code\datums\components\psionics\psiblock_drugs.dm" #include "code\datums\components\skills\skill_component.dm" #include "code\datums\components\skills\combat\armed_combat_skill_component.dm" #include "code\datums\components\skills\combat\firearms_skill_component.dm" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index b94f144e493..b3af9c8a204 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -22,6 +22,12 @@ #define isEye(A) istype(A, /mob/abstract/eye) #define ishuman(A) istype(A, /mob/living/carbon/human) +/** + * Astype() variant of ishuman(), for use in inlining the check when you only need it once. + * This is equal to null if the target is not of the type /mob/living/carbon/human. + * And is automatically typecast as a /mob/living/carbon/human if the check passes. + */ +#define ashuman(A) astype(A, /mob/living/carbon/human) #define ismech(A) istype(A, /mob/living/heavy_vehicle) diff --git a/code/datums/components/psionics/psiblock_drugs.dm b/code/datums/components/psionics/psiblock_drugs.dm new file mode 100644 index 00000000000..d8bfadcc99c --- /dev/null +++ b/code/datums/components/psionics/psiblock_drugs.dm @@ -0,0 +1,139 @@ +/datum/component/timed_life/psiblock_drugs + /// Typecasted parent of this component. + var/mob/living/carbon/human/owner + + /// The set of messages to choose from when this drug is first taken. + var/initial_effect_message_list = list("Everything feels dulled and distant.", "You feel like you can't focus on anything.", "Your thoughts feel sluggish.", "Why should you care about others?") + + /// The set of messages to choose from when this drug wears off. + var/removal_message_list = list("You feel more sensitive to your surroundings.", "Your thoughts feel clearer.", "You feel more aware of others around you.", "You can focus better.") + + /// How much this drug modifies the user's psi sensitivity. + var/psi_sensitivity_modifier = -1 + + /// The percent chance that this drug will cancel a psionic power when the user is targeted by one. + var/telepathy_cancel_probability = 95 + +/datum/component/timed_life/psiblock_drugs/Initialize(lifetime_seconds = 5 MINUTES) + . = ..() + if (!parent) + return + + owner = ashuman(parent) + + if (length(initial_effect_message_list)) + to_chat(parent, SPAN_NOTICE(pick(initial_effect_message_list))) + + RegisterSignal(parent, COMSIG_PSI_CHECK_SENSITIVITY, PROC_REF(modify_sensitivity), override = TRUE) + RegisterSignal(parent, COMSIG_PSI_MIND_POWER, PROC_REF(cancel_power), override = TRUE) + +/datum/component/timed_life/psiblock_drugs/Destroy() + owner = null + if (!parent) + return ..() + + // This is here and not on the reagent because the reagent's duration is handled by this component rather than metabolism. + // If the parent is being deleted, then there's no need to send a message about the drug wearing off. + if (!QDELING(parent) && length(removal_message_list)) + to_chat(parent, SPAN_NOTICE(pick(removal_message_list))) + + UnregisterSignal(parent, COMSIG_PSI_CHECK_SENSITIVITY) + UnregisterSignal(parent, COMSIG_PSI_MIND_POWER) + return ..() + +/datum/component/timed_life/psiblock_drugs/proc/modify_sensitivity(parent, effective_sensitivity) + SIGNAL_HANDLER + + *effective_sensitivity = *effective_sensitivity + psi_sensitivity_modifier + +/datum/component/timed_life/psiblock_drugs/proc/cancel_power(parent, caster, cancelled, cancel_return, wide_field) + SIGNAL_HANDLER + if (prob(telepathy_cancel_probability)) + *cancelled = TRUE + +/datum/component/timed_life/psiblock_drugs/process(seconds_per_tick) + . = ..() + +// Variants of psiblocking drugs +/datum/component/timed_life/psiblock_drugs/yomi_genetics + /// The next time (in real life seconds) that a hand tremor will occur. + var/next_tremor_time = 0 + + /// The minimum time between hand tremors, in real life seconds. + var/min_tremor_time = 1 MINUTE + + /// The maximum time between hand tremors, in real life seconds. + var/max_tremor_time = 3 MINUTES + + /// The next time (in real life seconds) that a seizure will occur. + var/next_seizure_time = 0 + + /// The minimum time between seizures, in real life seconds. + var/min_seizure_time = 9 MINUTES + 30 SECONDS + + /// The maximum time between seizures, in real life seconds. + var/max_seizure_time = 2 HOURS // Intentionally far greater than the actual duration, seizures are meant to be rare. + + /// The penalty to accuracy for gun attacks due to hand tremors. + var/accuracy_penalty = 0.25 + + /// The penalty to dispersion for gun attacks due to hand tremors. + var/dispersion_penalty = 5 + + /// The penalty to surgery success chance due to hand tremors. + var/surgery_success_penalty = -10 + +/datum/component/timed_life/psiblock_drugs/yomi_genetics/Initialize(lifetime_seconds = 5 MINUTES) + . = ..() + next_tremor_time = REALTIMEOFDAY + rand(min_tremor_time, max_tremor_time) + next_seizure_time = REALTIMEOFDAY + rand(min_seizure_time, max_seizure_time) + + // Some skill check penalties related to hand tremors. Tremors give a small penalty to some skill checks. + RegisterSignal(parent, COMSIG_BEFORE_GUN_FIRE, PROC_REF(check_tremor_gun), override = TRUE) + RegisterSignal(parent, COMSIG_GET_SURGERY_SUCCESS_MODIFIERS, PROC_REF(check_tremor_surgery), override = TRUE) + +/datum/component/timed_life/psiblock_drugs/yomi_genetics/Destroy() + UnregisterSignal(parent, COMSIG_BEFORE_GUN_FIRE) + UnregisterSignal(parent, COMSIG_GET_SURGERY_SUCCESS_MODIFIERS) + return ..() + +/datum/component/timed_life/psiblock_drugs/yomi_genetics/proc/check_tremor_gun(mob/shooter, accuracy_decrease, dispersion_increase) + SIGNAL_HANDLER + *accuracy_decrease = *accuracy_decrease + accuracy_penalty + *dispersion_increase = *dispersion_increase + dispersion_penalty + +/datum/component/timed_life/psiblock_drugs/yomi_genetics/proc/check_tremor_surgery(mob/living/user, success_rate) + SIGNAL_HANDLER + *success_rate = *success_rate + surgery_success_penalty + +/datum/component/timed_life/psiblock_drugs/yomi_genetics/process(seconds_per_tick) + . = ..() + var/time_of_day = REALTIMEOFDAY + if (time_of_day >= next_tremor_time) + owner.visible_message(SPAN_NOTICE("[owner]'s hand shakes..."), SPAN_NOTICE("Your hand shakes...")) + next_tremor_time = time_of_day + rand(min_tremor_time, max_tremor_time) + + if (time_of_day >= next_seizure_time) + owner.seizure() + next_seizure_time = time_of_day + rand(min_seizure_time, max_seizure_time) + +/datum/component/timed_life/psiblock_drugs/yomi_genetics/cheap + min_tremor_time = 30 SECONDS + max_tremor_time = 2 MINUTES + min_seizure_time = 5 MINUTES + max_seizure_time = 30 MINUTES + telepathy_cancel_probability = 75 + psi_sensitivity_modifier = -0.75 + accuracy_penalty = 0.35 + dispersion_penalty = 10 + surgery_success_penalty = -20 + +/datum/component/timed_life/psiblock_drugs/yomi_genetics/expensive + min_tremor_time = 2 MINUTES + max_tremor_time = 5 MINUTES + min_seizure_time = 30 MINUTES // Will never induce a seizure. + telepathy_cancel_probability = 100 + psi_sensitivity_modifier = -1.25 + accuracy_penalty = 0.15 + dispersion_penalty = 2 + surgery_success_penalty = -5 diff --git a/code/datums/components/timed_life_component.dm b/code/datums/components/timed_life_component.dm new file mode 100644 index 00000000000..4b409e91ca7 --- /dev/null +++ b/code/datums/components/timed_life_component.dm @@ -0,0 +1,45 @@ +/** + * A component that will delete itself after a certain amount of time has passed. + * This is useful for things like temporary buffs, or temporary objects that should be cleaned up after a while. + * + * As an abstract type, this component is not meant to be used directly. Instead, other components should extend this one and set the lifetime variable to the desired amount of time. + * For example, a component that gives a temporary speed boost might set lifetime to 30 seconds. + */ +ABSTRACT_TYPE(/datum/component/timed_life) + /** + * The amount of time, in seconds, that this component will last before it deletes itself. + * This is set during initialization, or by calling set_lifetime() on the component. + * This is a final variable because it should really only be set during initialization, and not by any external code. The component will handle updating this variable as needed. + */ + VAR_FINAL/lifetime = 0 + + /** + * The target time in real life seconds that the component will delete itself. + * This is calculated as REALTIMEOFDAY + lifetime when the component is initialized, and can be refreshed by calling refresh() on it. + * This is a final variable because it should only be set by the component itself, and not by any external code. The component will handle updating this variable as needed. + */ + VAR_FINAL/time_to_die = 0 + +/datum/component/timed_life/Initialize(lifetime_seconds = 5 MINUTES) + . = ..() + if (!parent) + return + + lifetime = lifetime_seconds + time_to_die = REALTIMEOFDAY + lifetime + START_PROCESSING(SSprocessing, src) + +/datum/component/timed_life/Destroy() + STOP_PROCESSING(SSprocessing, src) + return ..() + +/datum/component/timed_life/proc/refresh() + time_to_die = REALTIMEOFDAY + lifetime + +/datum/component/timed_life/proc/set_lifetime(new_lifetime) + lifetime = new_lifetime + refresh() + +/datum/component/timed_life/process(seconds_per_tick) + if (REALTIMEOFDAY >= time_to_die) + qdel(src) diff --git a/code/game/objects/items/weapons/storage/pill_bottle.dm b/code/game/objects/items/weapons/storage/pill_bottle.dm index 1a5751d6f5f..8e6d0dcc054 100644 --- a/code/game/objects/items/weapons/storage/pill_bottle.dm +++ b/code/game/objects/items/weapons/storage/pill_bottle.dm @@ -210,3 +210,33 @@ name = "bottle of Psilocybin pills" desc = "Contains psychotropic pills derived from certain species of mushroom." starts_with = list(/obj/item/reagent_containers/pill/psilocybin = 4) + +/** + * The base psi-protect pills. Childs of this have variations of side effects. + * This version of the pill has no side effects, and is not intended to be spawned directly. + */ +/obj/item/storage/pill_bottle/psi_protect + name = "bottle of Psi-protect pills" + desc = "A high-precision, personalised drug tailored to a patient's genetic, neurological, and psionic profile. " \ + + "It is marketed towards those with psionic disorders, such as psionic echoes, and made available at discounted prices to Zeng-Hu-affiliated explorers of the Lemurian Sea." + starts_with = list(/obj/item/reagent_containers/pill/psi_protect = 6) + +/obj/item/storage/pill_bottle/psi_protect/yomi_genetics + name = "bottle of Psi-protect Personalized pills" + desc = "A YomiGenetics high-precision, personalised drug tailored to a patient's genetic, neurological, and psionic profile. " \ + + "It is marketed towards those with psionic disorders, such as psionic echoes, and made available at discounted prices to Zeng-Hu-affiliated explorers of the Lemurian Sea." + starts_with = list(/obj/item/reagent_containers/pill/psi_protect/yomi_genetics = 6) + +/obj/item/storage/pill_bottle/psi_protect/yomi_genetics/cheap + name = "bottle of Psi-protect Broad-Use pills" + desc = "A cheaper, broad-targetting drug under the PsiProtect brand, with a more brute-force mechanism and only requiring the most basic of psionic profiling tests. " \ + + "This one has long stopped being prescribed or marketed, but is available at clearance prices." + starts_with = list(/obj/item/reagent_containers/pill/psi_protect/yomi_genetics/cheap = 6) + +/obj/item/storage/pill_bottle/psi_protect/yomi_genetics/expensive + name = "bottle of Psi-protect Personalized Gold pills" + desc = "A high-precision, personalised drug tailored to a patient's genetic, neurological, and psionic profile. " \ + + "It is marketed towards those with psionic disorders, such as psionic echoes, and made available at discounted prices to Zeng-Hu-affiliated explorers of the Lemurian Sea. " \ + + "This one is thrice as expensive, for more affluent customers, and requiring more genetic and neurological profiling. " \ + + "Nonetheless, it remains cheaper than the more elegant ZHP-MSV3 'Mindblankers'." + starts_with = list(/obj/item/reagent_containers/pill/psi_protect/yomi_genetics/expensive = 6) diff --git a/code/modules/client/preference_setup/loadout/items/drugs_meds.dm b/code/modules/client/preference_setup/loadout/items/drugs_meds.dm index b6d7d253d23..eaf562c0ee6 100644 --- a/code/modules/client/preference_setup/loadout/items/drugs_meds.dm +++ b/code/modules/client/preference_setup/loadout/items/drugs_meds.dm @@ -179,6 +179,9 @@ psych_meds["Orastabin pills"] = /obj/item/storage/pill_bottle/orastabin psych_meds["Parvosil pills"] = /obj/item/storage/pill_bottle/parvosil psych_meds["Corophenidate pills"] = /obj/item/storage/pill_bottle/corophenidate + psych_meds["Psi-protect pills"] = /obj/item/storage/pill_bottle/psi_protect/yomi_genetics + psych_meds["Psi-protect pills (cheap)"] = /obj/item/storage/pill_bottle/psi_protect/yomi_genetics/cheap + psych_meds["Psi-protect pills (expensive)"] = /obj/item/storage/pill_bottle/psi_protect/yomi_genetics/expensive gear_tweaks += new /datum/gear_tweak/path(psych_meds) /datum/gear/drugs_meds/otc diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Drugs.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Drugs.dm index 0d64b7933cf..20bdfe7433f 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Drugs.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Drugs.dm @@ -20,7 +20,7 @@ var/ignores_drug_resistance = FALSE /singleton/reagent/drugs/initial_effect(mob/living/carbon/human/M, alien, datum/reagents/holder) - if (effect_messages && REALTIMEOFDAY >= M.next_drug_message) + if (length(initial_effect_message_list) && effect_messages && REALTIMEOFDAY >= M.next_drug_message) var/msg = pick(initial_effect_message_list) to_chat(M, SPAN_GOOD("[msg]")) M.next_drug_message = REALTIMEOFDAY + DRUG_MESSAGE_COOLDOWN @@ -35,7 +35,7 @@ power = max(power - 2, 0) /singleton/reagent/drugs/final_effect(mob/living/carbon/M, datum/reagents/holder) - if (effect_messages && REALTIMEOFDAY >= M.next_sober_message) + if (length(sober_message_list) && effect_messages && REALTIMEOFDAY >= M.next_sober_message) var/msg = pick(sober_message_list) to_chat(M, SPAN_WARNING("[msg]")) M.next_sober_message = REALTIMEOFDAY + SOBER_MESSAGE_COOLDOWN @@ -847,4 +847,28 @@ return . = ..() +/singleton/reagent/drugs/psiblock + name = "PsiProtect" + description = "A drug that provides temporary protection against psionic effects. It is marketed towards explorers intending to enter or travel near to the Lemurian Sea." + color = "#b0b0b0" + taste_description = "inexplicably the color grey itself, then all other tastes fade into nothingness" + initial_effect_message_list = null + sober_message_list = null + var/component_to_add = /datum/component/timed_life/psiblock_drugs + var/component_timer = 10 MINUTES + +/singleton/reagent/drugs/psiblock/affect_blood(mob/living/carbon/M, alien, removed, datum/reagents/holder) + var/datum/component/timed_life/psiblock_drugs/psiblock_comp = M.LoadComponent(component_to_add, component_timer) + psiblock_comp.refresh() + +/singleton/reagent/drugs/psiblock/yomi_genetics + component_to_add = /datum/component/timed_life/psiblock_drugs/yomi_genetics + +/singleton/reagent/drugs/psiblock/yomi_genetics/cheap + component_to_add = /datum/component/timed_life/psiblock_drugs/yomi_genetics/cheap + +/singleton/reagent/drugs/psiblock/yomi_genetics/expensive + component_to_add = /datum/component/timed_life/psiblock_drugs/yomi_genetics/expensive + component_timer = 8 MINUTES + #undef DRUG_MESSAGE_DELAY diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm index 9b8a45d5541..220fb9b88a7 100644 --- a/code/modules/reagents/reagent_containers/pill.dm +++ b/code/modules/reagents/reagent_containers/pill.dm @@ -411,3 +411,18 @@ desc = "A strong psychotropic derived from certain species of mushroom." icon_state = "pill10" reagents_to_add = list(/singleton/reagent/drugs/psilocybin = 5) + +/obj/item/reagent_containers/pill/psi_protect + name = "5u Psi-protect Pill" + desc = "A pill used to protect against ADPI and treat various psionic disorders" + icon_state = "pill10" + reagents_to_add = list(/singleton/reagent/drugs/psiblock = 5) + +/obj/item/reagent_containers/pill/psi_protect/yomi_genetics + reagents_to_add = list(/singleton/reagent/drugs/psiblock/yomi_genetics = 5) + +/obj/item/reagent_containers/pill/psi_protect/yomi_genetics/cheap + reagents_to_add = list(/singleton/reagent/drugs/psiblock/yomi_genetics/cheap = 5) + +/obj/item/reagent_containers/pill/psi_protect/yomi_genetics/expensive + reagents_to_add = list(/singleton/reagent/drugs/psiblock/yomi_genetics/expensive = 5) diff --git a/html/changelogs/hellfirejag-psiblock-drugs.yml b/html/changelogs/hellfirejag-psiblock-drugs.yml new file mode 100644 index 00000000000..ecc21364594 --- /dev/null +++ b/html/changelogs/hellfirejag-psiblock-drugs.yml @@ -0,0 +1,4 @@ +author: Hellfirejag +delete-after: True +changes: + - rscadd: "Added new Yomi Genetics brand Psi-protect drugs to the Psychiatric Drugs loadout selection. These pills provide temporary protection against psionic effects."