Files
SmArtKar e49e13b795 Reworks phobias to use the new fear system instead of handling all the code via traumas (#94455)
## About The Pull Request

Phobias have been refactored to use the new fear system instead of
handling their effects via simple triggers on themselves. The overall
idea is still the same, triggering upon seeing/hearing/saying a scary
word or thing, but instead of instantly sending you into a panic it adds
some fear and has a chance of triggering a new startle fear effect,
which mirrors some of the previous phobia effects that weren't already
included in other handlers.
To complete the phobic trio, I've added monophobia as a quirk - its
quite interesting compared to nycto- and claustrophobia, as it forces
you to stick to other people, thus indirectly incentivizing social
interactions, and could make for actually interesting moments.
Additionally, I've cleaned up some of the related code and added message
CDs to all other handlers that were missing them as to avoid chat spam
which could sometimes occur when the effect triggered far too
frequently.

## Why It's Good For The Game

Using the new system makes phobias much more immersive and interesting
to play around, as they no longer absolutely cripple you to the point of
making the game unplayable in case of some of them due to random stuns
or knockouts. It also makes them natively interact with other phobias
and quirks, which is a neat thing for character building.
Currently getting a phobia basically forces you to go to medbay as to
not risk randomly falling unconscious every once in a while if you roll
a common one (like cyborgs, blood, doctors or authority), and picking
one as a quirk is basically not an option.

## Changelog
🆑
add: Monophobia is now availible as a negative (-3) quirk.
balance: Phobias now have accumulating, and less debilitating effects
due to conversion to the new fear system.
qol: Fear effects now have a cooldown between messages as to avoid chat
spam.
code: Cleaned up some of fear code.
refactor: Refactored phobias to use the fear system
/🆑
2025-12-20 22:57:58 +00:00

218 lines
7.8 KiB
Plaintext

// Terror effect handlers
#define FEAR_SCALING(base, min, max) clamp(base * (terror_buildup - min) / (max - min), 0, base)
/// Causes mild jittering, scaling with current terror level
/datum/terror_handler/jittering
handler_type = TERROR_HANDLER_EFFECT
default = TRUE
COOLDOWN_DECLARE(message_cd)
/datum/terror_handler/jittering/tick(seconds_per_tick, terror_buildup)
. = ..()
if (owner.stat >= UNCONSCIOUS)
return
if (terror_buildup < TERROR_BUILDUP_FEAR)
return
// If we're terrified, keep constant jittering and dizzyness
if (terror_buildup > TERROR_BUILDUP_TERROR)
owner.adjust_dizzy_up_to(10 SECONDS * seconds_per_tick, 10 SECONDS)
owner.adjust_jitter_up_to(10 SECONDS * seconds_per_tick, 10 SECONDS)
return
if (!SPT_PROB(1 + FEAR_SCALING(4, TERROR_BUILDUP_FEAR, TERROR_BUILDUP_TERROR), seconds_per_tick)) // 1% to 5% chance
return
if (COOLDOWN_FINISHED(src, message_cd) && !owner.has_status_effect(/datum/status_effect/jitter)) // Don't display the message if we're already shaking
to_chat(owner, span_warning("You can't stop shaking..."))
COOLDOWN_START(src, message_cd, TERROR_MESSAGE_CD)
owner.set_jitter_if_lower(20 SECONDS)
owner.set_dizzy_if_lower(20 SECONDS)
/// Stutter when afraid
/datum/terror_handler/stuttering
handler_type = TERROR_HANDLER_EFFECT
default = TRUE
/datum/terror_handler/stuttering/tick(seconds_per_tick, terror_buildup)
. = ..()
if (owner.stat >= UNCONSCIOUS)
return
if (terror_buildup < TERROR_BUILDUP_FEAR)
return
if (terror_buildup > TERROR_BUILDUP_TERROR || SPT_PROB(1 + FEAR_SCALING(4, TERROR_BUILDUP_FEAR, TERROR_BUILDUP_TERROR), seconds_per_tick))
owner.set_stutter_if_lower(10 SECONDS)
/// Can randomly give you some oxyloss, and cause a heart attack past TERROR_BUILDUP_HEART_ATTACK
/datum/terror_handler/heart_problems
handler_type = TERROR_HANDLER_EFFECT
default = TRUE
COOLDOWN_DECLARE(message_cd)
/datum/terror_handler/heart_problems/tick(seconds_per_tick, terror_buildup)
. = ..()
if (owner.stat >= UNCONSCIOUS)
return
if (terror_buildup < TERROR_BUILDUP_FEAR)
return
if (!SPT_PROB(1 + FEAR_SCALING(4, TERROR_BUILDUP_FEAR, TERROR_BUILDUP_PANIC), seconds_per_tick)) // 1% to 5% chance
return
if (terror_buildup < TERROR_BUILDUP_HEART_ATTACK || !prob(15))
owner.adjust_oxy_loss(8)
if (!COOLDOWN_FINISHED(src, message_cd))
return
COOLDOWN_START(src, message_cd, TERROR_MESSAGE_CD)
if (terror_buildup < TERROR_BUILDUP_FEAR)
to_chat(owner, span_warning("Your heart skips a beat."))
else
to_chat(owner, span_userdanger("You feel your heart lurching in your chest..."))
return
owner.visible_message(
span_warning("[owner] clutches [owner.p_their()] chest for a moment, then collapses to the floor."),
span_alert("The shadows begin to creep up from the corners of your vision, and then there is nothing..."),
span_hear("You hear something heavy collide with the ground."),
)
owner.apply_status_effect(/datum/status_effect/heart_attack)
owner.Unconscious(20 SECONDS)
/// Low chance to vomit when terrified, increases significantly during panic attacks
/datum/terror_handler/vomiting
handler_type = TERROR_HANDLER_EFFECT
/datum/terror_handler/vomiting/tick(seconds_per_tick, terror_buildup)
. = ..()
if (owner.stat >= UNCONSCIOUS)
return
if (terror_buildup < TERROR_BUILDUP_TERROR)
return
if (SPT_PROB((terror_buildup >= TERROR_BUILDUP_PANIC) ? 3 : 1, seconds_per_tick))
to_chat(owner, span_warning("You feel sick..."))
// Vomit blood if we're *really* freaking out
addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living/carbon, vomit), terror_buildup >= TERROR_BUILDUP_PASSIVE_MAXIMUM), 5 SECONDS)
/// Causes tunnel vision, blurry eyes and periodic panic attacks when panicking
/datum/terror_handler/panic
handler_type = TERROR_HANDLER_EFFECT
default = TRUE
/// Has the panic message been shown yet?
var/active = FALSE
/// Are we in a state of a panic attack currently? Only really used for tracking our breath loop
var/active_attack = FALSE
/// Breath loop used during a panic attack
var/datum/looping_sound/breathing/breath_loop
/// Timer that will stop our panic attack
var/panic_end_timer = null
/datum/terror_handler/panic/New(mob/living/new_owner, datum/component/fearful/new_component)
. = ..()
breath_loop = new(owner, _direct = TRUE)
/datum/terror_handler/panic/Destroy(force)
owner.remove_fov_trait(type, FOV_270_DEGREES)
QDEL_NULL(breath_loop)
deltimer(panic_end_timer)
return ..()
/datum/terror_handler/panic/tick(seconds_per_tick, terror_buildup)
. = ..()
if (owner.stat >= UNCONSCIOUS)
stop_panic_attack()
active = FALSE
owner.remove_fov_trait(type, FOV_270_DEGREES)
return
if (terror_buildup < TERROR_BUILDUP_PANIC)
if (active_attack) // No you don't
return TERROR_BUILDUP_PANIC - terror_buildup
active = FALSE
owner.remove_fov_trait(type, FOV_270_DEGREES)
return
if (!active)
active = TRUE
to_chat(owner, span_userdanger("You feel your heart racing!"))
owner.add_fov_trait(type, FOV_270_DEGREES) // Terror induced tunnel vision
owner.playsound_local(owner, 'sound/effects/health/slowbeat.ogg', 40, FALSE, channel = CHANNEL_HEARTBEAT, use_reverb = FALSE)
if (SPT_PROB(5, seconds_per_tick))
owner.set_eye_blur_if_lower(10 SECONDS)
if (active_attack)
owner.losebreath += 0.25 // Miss 1/4 breaths
return
if (SPT_PROB(2 + FEAR_SCALING(3, TERROR_BUILDUP_PANIC, TERROR_BUILDUP_MAXIMUM), seconds_per_tick))
. += panic_attack(terror_buildup)
/datum/terror_handler/panic/proc/panic_attack(terror_buildup)
active_attack = TRUE
owner.emote("gasp")
owner.Knockdown(0.5 SECONDS)
breath_loop.start()
panic_end_timer = addtimer(CALLBACK(src, PROC_REF(stop_panic_attack)), rand(3 SECONDS, 5 SECONDS), TIMER_UNIQUE|TIMER_STOPPABLE)
owner.visible_message(span_warning("[owner] drops to the floor for a moment, clutching their chest."), span_alert("Your heart lurches in your chest. You can't take much more of this!"))
return PANIC_ATTACK_TERROR_AMOUNT
/datum/terror_handler/panic/proc/stop_panic_attack()
breath_loop.stop()
active_attack = FALSE
deltimer(panic_end_timer)
panic_end_timer = null
/datum/terror_handler/startle
handler_type = TERROR_HANDLER_EFFECT
COOLDOWN_DECLARE(startle_cd)
/datum/terror_handler/startle/tick(seconds_per_tick, terror_buildup)
. = ..()
if (owner.stat >= UNCONSCIOUS || !COOLDOWN_FINISHED(src, startle_cd))
return
if (terror_buildup < TERROR_BUILDUP_FEAR || terror_buildup - component.last_tick_buildup < TERROR_STARTLE_MINIMUM_DIFFERENCE)
return
// The more scared we are, and the more fear we acquired at once last tick, the higher the probability of us being startled is
// Not SPT_PROB or FEAR_SCALING because this is only triggered during ticks when we actually acquire fear
if (!prob(15 * (terror_buildup - component.last_tick_buildup) / TERROR_STARTLE_MINIMUM_DIFFERENCE * terror_buildup / TERROR_BUILDUP_FEAR))
return
COOLDOWN_START(src, startle_cd, TERROR_STARTLE_COOLDOWN)
switch (rand(1, 3))
if (1)
to_chat(owner, span_warning("You are startled!"))
owner.emote("jump")
owner.Immobilize(0.1 SECONDS * (terror_buildup / TERROR_BUILDUP_FEAR))
if (2)
owner.emote("scream")
owner.say("AAAAH!!", forced = "phobia")
if (!prob(15 * (terror_buildup / TERROR_BUILDUP_FEAR)))
return
var/held_item = owner.get_active_held_item()
if (owner.dropItemToGround(held_item))
owner.visible_message(
span_danger("[owner.name] drops \the [held_item]!"),
span_warning("You drop \the [held_item]!"), null, COMBAT_MESSAGE_RANGE)
if (3)
to_chat(owner, span_warning("You lose your balance!"))
owner.adjust_staggered_up_to(2 SECONDS * (terror_buildup / TERROR_BUILDUP_FEAR), 20 SECONDS)
owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/spooked)
addtimer(CALLBACK(src, PROC_REF(speed_up)), 3 SECONDS, TIMER_STOPPABLE | TIMER_DELETE_ME)
/datum/terror_handler/startle/proc/speed_up()
owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/spooked)
#undef FEAR_SCALING