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."