Hallucination revamps and additions (#89865)

## About The Pull Request

1. Hallucination effects are now tiered

Hallucinations now all have tiers, ranging from common to special. 
If you are just hallucinating a teeny bit, you will not experience the
more extreme hallucinations, like bubblegum or your mom.
But if you're hallucinating off your butt, you will be a bit more likely
to experience them.

2. Hallucination rate has been tweaked

Default hallucination cooldown is now 20-80 seconds, up from 10-60
seconds.
However the cooldown will *also* vary depending on just how much you're
hallucinating, going down to 10-40 seconds.

3. RDS is now capped a bit lower (meaning you don't see the higher tiers
like bubblegum).

But I added a preference to uncap it. For the people who actually like
bubblegum visits.

4. If a hallucination fails to trigger, the cooldown will partially
reset. (by 75%)

5. "Fake chat" hallucination will pick more viable subjects.

Fake chat will try to find someone who can actually speak your language,
rather than make a monkey speak mothic or something.
(I may revisit this so if you're super-hallucinating it reverts to old
behavior though.)

6. Adds a hallucination: Fake blood

You hallucinate that you start bleeding, very simple.

7. Adds a hallucination: Fake telepathy

You hallucinate a random telepathic message, similar to fake chat.

8. Adds a hallucination: Eyes in the Dark

A nearby dark turf will have a set of glowing red eyes shine through the
dark. A classic.

9. Adds some new sub-hallucination: PDA ringtone (fake sound), summon
guns/magic (fake item)

Funny prank.

10. Makes mindbreaker a bit more effective at combating RDS.

Pretty much does nothing right now unless you gulp like 50u.

## Why It's Good For The Game

Hallucinations are pretty one note if you experience them for longer
than 10 minutes.

This is due to two fold:
- Many hallucinations are goofy, and not subtle
- Hallucinations trigger very rapidly

You will never fall for a hallucination because in between "You see John
Greytide put the blueprints away", you get your mom yelling at you,
everyone looking like syndies (again), and bubblegum

This pr addresses it by
- Limiting the wacky hallucinations for when you're really off your
gourd
- Reducing the period between triggers
- Adding a few hallucinations

If the wackier hallucinations are reserved for when you're really off
your rocker, this lets the more subtle ones sink in over time, leaves
more room for second guessing

## Changelog

🆑 Melbert
add: Adds 4-5 new hallucinations. Collect them all.
balance: If you are only hallucinating a little bit, the game will
prefer to pick more subtle hallucinations. If you are hallucinating a
ton, it will prefer the more wacky hallucinations.
balance: If you are only hallucinating a little bit, the cooldown
between hallucinations is longer. If you are hallucinating a ton, it
will be shorter.
balance: If a hallucination fails to trigger (such as a deaf person
getting a sound hallucination) the next one will be a lot sooner.
balance: RDS hallucination amount is capped at mid tier hallucinations.
This means bubblegum and co. will be a lot rarer, or will even never
show. HOWEVER, there is now a preference allowing you to uncap your RDS
hallucinations.
balance: Mindbreaker toxin is more effective at suppressing RDS.
balance: Some hallucinations effects have been tweaked up or down
according to the new thresholds. Madness mask as an example.
fix: "Fake Fire" Hallucination works again, and now has a unique message
for if you stop-drop-roll that other people see.
/🆑
This commit is contained in:
MrMelbert
2025-03-20 21:36:56 +00:00
committed by GitHub
parent a30e5d5539
commit eb0e842320
45 changed files with 591 additions and 105 deletions
+57 -26
View File
@@ -1,6 +1,18 @@
/// A global list of all ongoing hallucinations, primarily for easy access to be able to stop (delete) hallucinations.
GLOBAL_LIST_EMPTY(all_ongoing_hallucinations)
// Hallucination tiers
/// Very common hallucinations, minor stuff that'll make you double-take but is otherwise very subtle.
#define HALLUCINATION_TIER_COMMON 1
/// Uncommon hallucinations, more noticeable and potentially more impactful (causing temporary stuns or stamina damage).
#define HALLUCINATION_TIER_UNCOMMON 2
/// Rarer hallucinations which are usually pretty obvious, but also pretty impactful.
#define HALLUCINATION_TIER_RARE 3
/// Hallucinations which are generally just for laughs and are obviously fake
#define HALLUCINATION_TIER_VERYSPECIAL 4
/// Hallucinations which are never picked, only forced
#define HALLUCINATION_TIER_NEVER 5
/// What typepath of the hallucination
#define HALLUCINATION_ARG_TYPE 1
/// Where the hallucination came from, for logging
@@ -111,7 +123,7 @@ GLOBAL_LIST_EMPTY(all_ongoing_hallucinations)
to_chat(nearby_living, pick(optional_messages))
/// Global weighted list of all hallucinations that can show up randomly.
GLOBAL_LIST_INIT(random_hallucination_weighted_list, generate_hallucination_weighted_list())
GLOBAL_LIST_INIT_TYPED(random_hallucination_weighted_list, /list, generate_hallucination_weighted_list())
/// Generates the global weighted list of random hallucinations.
/proc/generate_hallucination_weighted_list()
@@ -124,49 +136,68 @@ GLOBAL_LIST_INIT(random_hallucination_weighted_list, generate_hallucination_weig
if(weight <= 0)
continue
weighted_list[hallucination_type] = weight
LAZYSET(weighted_list["[initial(hallucination_type.hallucination_tier)]"], hallucination_type, weight)
return weighted_list
/// Select a random hallucination from the hallucination pool
///
/// * tier - the tier of hallucination to select from
/// * strict - if true, only select from the passed tier. If false, select from the passed tier and all tiers below it.
/proc/get_random_hallucination(tier = HALLUCINATION_TIER_COMMON, strict = FALSE)
if(!GLOB.random_hallucination_weighted_list[tier])
CRASH("get_random_hallucination - No hallucinations in tier \[[tier]\].")
var/list/pool = GLOB.random_hallucination_weighted_list["[tier]"].Copy()
if(!strict)
tier -= 1
while(tier >= HALLUCINATION_TIER_COMMON)
pool += GLOB.random_hallucination_weighted_list["[tier]"]
tier -= 1
return pick_weight(pool)
/// Debug proc for getting the total weight of the random_hallucination_weighted_list
/proc/debug_hallucination_weighted_list()
var/total_weight = 0
for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list)
total_weight += GLOB.random_hallucination_weighted_list[hallucination_type]
for(var/tier in GLOB.random_hallucination_weighted_list)
for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list[tier])
total_weight += GLOB.random_hallucination_weighted_list[tier][hallucination_type]
to_chat(usr, span_boldnotice("The total weight of the hallucination weighted list is [total_weight]."))
return total_weight
ADMIN_VERB(debug_hallucination_weighted_list_per_type, R_DEBUG, "Show Hallucination Weights", "View the weight of each hallucination subtype in the random weighted list.", ADMIN_CATEGORY_DEBUG)
var/header = "<tr><th>Type</th> <th>Weight</th> <th>Percent</th>"
var/header = "<tr><th>Type</th> <th>Weight</th> <th>Tier</th> <th>Percent</th>"
var/total_weight = debug_hallucination_weighted_list()
var/list/all_weights = list()
var/datum/hallucination/last_type
var/last_type_weight = 0
for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list)
var/this_weight = GLOB.random_hallucination_weighted_list[hallucination_type]
// Last_type is the abstract parent of the last hallucination type we iterated over
if(last_type)
// If this hallucination is the same path as the last type (subtype), add it to the total of the last type weight
if(ispath(hallucination_type, last_type))
last_type_weight += this_weight
continue
for(var/tier in GLOB.random_hallucination_weighted_list)
for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list[tier])
var/this_weight = GLOB.random_hallucination_weighted_list[tier][hallucination_type]
// Last_type is the abstract parent of the last hallucination type we iterated over
if(last_type)
// If this hallucination is the same path as the last type (subtype), add it to the total of the last type weight
if(ispath(hallucination_type, last_type))
last_type_weight += this_weight
continue
// Otherwise we moved onto the next hallucination subtype so we can stop
// Otherwise we moved onto the next hallucination subtype so we can stop
else
all_weights["<tr><td>[last_type]</td> <td>[last_type_weight] / [total_weight]</td> <td>[initial(hallucination_type.hallucination_tier)]</td> <td>[round(100 * (last_type_weight / total_weight), 0.01)]% chance</td></tr>"] = last_type_weight
// Set last_type to the abstract parent of this hallucination
last_type = initial(hallucination_type.abstract_hallucination_parent)
// If last_type is the base hallucination it has no distinct subtypes so we can total it up immediately
if(last_type == /datum/hallucination)
all_weights["<tr><td>[hallucination_type]</td> <td>[this_weight] / [total_weight]</td> <td>[initial(hallucination_type.hallucination_tier)]</td> <td>[round(100 * (this_weight / total_weight), 0.01)]% chance</td></tr>"] = this_weight
last_type = null
// Otherwise we start the weight sum for the next entry here
else
all_weights["<tr><td>[last_type]</td> <td>[last_type_weight] / [total_weight]</td> <td>[round(100 * (last_type_weight / total_weight), 0.01)]% chance</td></tr>"] = last_type_weight
// Set last_type to the abstract parent of this hallucination
last_type = initial(hallucination_type.abstract_hallucination_parent)
// If last_type is the base hallucination it has no distinct subtypes so we can total it up immediately
if(last_type == /datum/hallucination)
all_weights["<tr><td>[hallucination_type]</td> <td>[this_weight] / [total_weight]</td> <td>[round(100 * (this_weight / total_weight), 0.01)]% chance</td></tr>"] = this_weight
last_type = null
// Otherwise we start the weight sum for the next entry here
else
last_type_weight = this_weight
last_type_weight = this_weight
// Sort by weight descending, where weight is the values (not the keys). We assoc_to_keys later to get JUST the text
sortTim(all_weights, GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE)
+4 -1
View File
@@ -310,7 +310,10 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
if(!(living_owner.mobility_flags & MOBILITY_MOVE))
return FALSE
return living_owner.resist_fire()
return handle_stop_drop_roll(owner)
/atom/movable/screen/alert/fire/proc/handle_stop_drop_roll(mob/living/roller)
return roller.resist_fire()
/atom/movable/screen/alert/give // information set when the give alert is made
icon_state = "default"
+6 -4
View File
@@ -11,17 +11,19 @@
scan_desc = "schizophrenia"
gain_text = span_warning("You feel your grip on reality slipping...")
lose_text = span_notice("You feel more grounded.")
/// Whether the hallucinations we give are uncapped, ie all the wacky ones
var/uncapped = FALSE
/datum/brain_trauma/mild/hallucinations/on_life(seconds_per_tick, times_fired)
if(owner.stat != CONSCIOUS || owner.IsSleeping() || owner.IsUnconscious())
if(owner.stat >= UNCONSCIOUS)
return
if(HAS_TRAIT(owner, TRAIT_RDS_SUPPRESSED))
owner.remove_language(/datum/language/aphasia, source = LANGUAGE_APHASIA)
owner.adjust_hallucinations(-10 SECONDS * seconds_per_tick)
return
if(!HAS_TRAIT(owner, TRAIT_RDS_SUPPRESSED))
owner.grant_language(/datum/language/aphasia, source = LANGUAGE_APHASIA)
owner.adjust_hallucinations_up_to(10 SECONDS * seconds_per_tick, 100 SECONDS)
owner.grant_language(/datum/language/aphasia, source = LANGUAGE_APHASIA)
owner.adjust_hallucinations_up_to(((uncapped ? 12 SECONDS : 5 SECONDS) * seconds_per_tick), (uncapped ? 240 SECONDS : 60 SECONDS))
/datum/brain_trauma/mild/hallucinations/on_lose()
owner.remove_status_effect(/datum/status_effect/hallucination)
@@ -17,7 +17,7 @@
if(!iscarbon(quirk_holder))
return
var/mob/living/carbon/carbon_quirk_holder = quirk_holder
// Setup our special RDS mild hallucination.
// Not a unique subtype so not to plague subtypesof,
// also as we inherit the names and values from our quirk.
@@ -28,6 +28,7 @@
added_trauma.scan_desc = LOWER_TEXT(name)
added_trauma.gain_text = null
added_trauma.lose_text = null
added_trauma.uncapped = client_source?.prefs?.read_preference(/datum/preference/toggle/rds_limit)
carbon_quirk_holder.gain_trauma(added_trauma)
added_trama_ref = WEAKREF(added_trauma)
@@ -39,3 +40,7 @@
/datum/quirk/insanity/remove()
QDEL_NULL(added_trama_ref)
/datum/quirk_constant_data/rds_limit
associated_typepath = /datum/quirk/insanity
customization_options = list(/datum/preference/toggle/rds_limit)
@@ -17,13 +17,7 @@
RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(body_position_changed))
ADD_TRAIT(owner, TRAIT_HANDS_BLOCKED, TRAIT_STATUS_EFFECT(id)) // they're kinda busy!
owner.visible_message(
span_danger("[owner] rolls on the floor, trying to put [owner.p_them()]self out!"),
span_notice("You stop, drop, and roll!"),
)
// Start with one weaker roll
owner.spin(spintime = actual_interval, speed = actual_interval / 4)
owner.adjust_fire_stacks(-0.25)
start_rolling()
for (var/obj/item/dropped in owner.loc)
dropped.extinguish() // Effectively extinguish your items by rolling on them
@@ -33,6 +27,14 @@
UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_LIVING_SET_BODY_POSITION))
REMOVE_TRAIT(owner, TRAIT_HANDS_BLOCKED, TRAIT_STATUS_EFFECT(id))
/datum/status_effect/stop_drop_roll/proc/start_rolling()
owner.visible_message(
span_danger("[owner] rolls on the floor, trying to put [owner.p_them()]self out!"),
span_notice("You stop, drop, and roll!"),
)
// Start with one weaker roll
reduce_firestacks(0.25)
/datum/status_effect/stop_drop_roll/tick(seconds_between_ticks)
if(HAS_TRAIT(owner, TRAIT_IMMOBILIZED) || HAS_TRAIT(owner, TRAIT_INCAPACITATED))
qdel(src)
@@ -44,17 +46,17 @@
return
owner.spin(spintime = actual_interval, speed = actual_interval / 4)
owner.adjust_fire_stacks(-1)
if(owner.fire_stacks > 0)
if(!reduce_firestacks(1))
return
owner.visible_message(
span_danger("[owner] successfully extinguishes [owner.p_them()]self!"),
span_notice("You extinguish yourself."),
)
qdel(src)
stop_rolling_successful()
/// Return TRUE to stop the us from rolling.
/datum/status_effect/stop_drop_roll/proc/reduce_firestacks(amt = 1)
owner.adjust_fire_stacks(-1 * amt)
return owner.fire_stacks <= 0
/// Called when we just, stop rolling, due to movement or other reasons. Maybe still on fire, maybe not.
/datum/status_effect/stop_drop_roll/proc/stop_rolling(datum/source, ...)
SIGNAL_HANDLER
@@ -62,8 +64,54 @@
to_chat(owner, span_notice("You stop rolling around."))
qdel(src)
/// Called when we've successfully extinguished ourselves.
/datum/status_effect/stop_drop_roll/proc/stop_rolling_successful()
owner.visible_message(
span_danger("[owner] successfully extinguishes [owner.p_them()]self!"),
span_notice("You extinguish yourself."),
)
qdel(src)
/datum/status_effect/stop_drop_roll/proc/body_position_changed(datum/source, new_value, old_value)
SIGNAL_HANDLER
if(new_value != LYING_DOWN)
stop_rolling()
/// Subtype of rolling triggered when someone hallucinating fire tries to stop, drop, and roll.
/datum/status_effect/stop_drop_roll/hallucinating
/// Weakref to the fire hallucination
var/datum/weakref/hallucination_weakref
/datum/status_effect/stop_drop_roll/hallucinating/on_creation(mob/living/new_owner, datum/weakref/hallucination_weakref)
src.hallucination_weakref = hallucination_weakref
return ..()
/datum/status_effect/stop_drop_roll/hallucinating/start_rolling()
owner.visible_message(
span_danger("[owner] starts rolling around on the floor, flailing about!"),
span_notice("You stop, drop, and roll!"),
)
reduce_firestacks(1) // more effective cause it's not real
/datum/status_effect/stop_drop_roll/hallucinating/reduce_firestacks(amt = 1)
var/datum/hallucination/fire/hallucination = hallucination_weakref?.resolve()
if(!istype(hallucination))
return TRUE
hallucination.fake_firestacks += (-1 * amt)
if(hallucination.fake_firestacks <= 0)
hallucination.clear_fire()
return TRUE
return FALSE
/datum/status_effect/stop_drop_roll/hallucinating/stop_rolling_successful()
var/datum/hallucination/fire/hallucination = hallucination_weakref?.resolve()
if(istype(hallucination))
hallucination.clear_fire()
owner.visible_message(
span_danger("[owner] stops flailing around on the ground."),
span_notice("You extinguish yourself."),
)
qdel(src)
@@ -5,22 +5,26 @@
alert_type = null
tick_interval = 2 SECONDS
remove_on_fullheal = TRUE
processing_speed = STATUS_EFFECT_NORMAL_PROCESS
/// Biotypes which cannot hallucinate.
var/barred_biotypes = NO_HALLUCINATION_BIOTYPES
/// The lower range of when the next hallucination will trigger after one occurs.
var/lower_tick_interval = 10 SECONDS
var/lower_tick_interval = 20 SECONDS
/// The upper range of when the next hallucination will trigger after one occurs.
var/upper_tick_interval = 60 SECONDS
var/upper_tick_interval = 80 SECONDS
/// The maximum hallucination tier that can be picked.
var/max_hallucination_tier = HALLUCINATION_TIER_COMMON
/// If TRUE, we only select hallucinations from the hallucination_tier.
/// If FALSE, it will also include anything below the hallucination_tier.
var/strict_tier = FALSE
/// Tier can be variable, based on the duration of the hallucination.
var/variable_tier = TRUE
/// The cooldown for when the next hallucination can occur
COOLDOWN_DECLARE(hallucination_cooldown)
/datum/status_effect/hallucination/on_creation(mob/living/new_owner, duration, lower_tick_interval, upper_tick_interval)
if(isnum(duration))
src.duration = duration
if(isnum(lower_tick_interval))
src.lower_tick_interval = lower_tick_interval
if(isnum(upper_tick_interval))
src.upper_tick_interval = upper_tick_interval
/datum/status_effect/hallucination/on_creation(mob/living/new_owner, new_duration)
if(isnum(new_duration))
src.duration = new_duration
return ..()
/datum/status_effect/hallucination/on_apply()
@@ -86,15 +90,39 @@
if(!COOLDOWN_FINISHED(src, hallucination_cooldown))
return
var/datum/hallucination/picked_hallucination = pick_weight(GLOB.random_hallucination_weighted_list)
owner.cause_hallucination(picked_hallucination, "[id] status effect")
COOLDOWN_START(src, hallucination_cooldown, rand(lower_tick_interval, upper_tick_interval))
var/lower_cd = lower_tick_interval
var/upper_cd = upper_tick_interval
if(!variable_tier)
var/seconds_left = (duration - world.time) / 10
switch(seconds_left)
if(0 to 20)
max_hallucination_tier = HALLUCINATION_TIER_COMMON
lower_tick_interval *= 1.2
upper_tick_interval *= 1.2
if(20 to 60)
max_hallucination_tier = prob(10) ? HALLUCINATION_TIER_RARE : HALLUCINATION_TIER_UNCOMMON
if(60 to 120)
max_hallucination_tier = HALLUCINATION_TIER_RARE
lower_cd *= 0.75
upper_cd *= 0.75
if(120 to INFINITY)
max_hallucination_tier = HALLUCINATION_TIER_VERYSPECIAL
lower_cd *= 0.5
upper_cd *= 0.5
var/datum/hallucination/picked_hallucination = get_random_hallucination(max_hallucination_tier, strict_tier)
if(!owner.cause_hallucination(picked_hallucination, "[id] status effect"))
lower_cd *= 0.25
upper_cd *= 0.25
COOLDOWN_START(src, hallucination_cooldown, rand(lower_cd, upper_cd))
// Sanity related hallucinations
/datum/status_effect/hallucination/sanity
id = "low sanity"
status_type = STATUS_EFFECT_REFRESH
duration = STATUS_EFFECT_PERMANENT // This lasts "forever", only goes away with sanity gain
max_hallucination_tier = HALLUCINATION_TIER_UNCOMMON
variable_tier = FALSE
/datum/status_effect/hallucination/sanity/on_health_scan(datum/source, list/render_list, advanced, mob/user, mode, tochat)
return
@@ -130,3 +158,18 @@
else
stack_trace("[type] was assigned a mob which was not crazy or insane. (was: [owner.mob_mood.sanity_level])")
qdel(src)
/datum/status_effect/hallucination/perceptomatrix
id = "perceptomatrix_hallucination"
status_type = STATUS_EFFECT_REFRESH
strict_tier = TRUE
variable_tier = FALSE
/datum/status_effect/hallucination/perceptomatrix/refresh(mob/living/refresh_owner, new_duration)
src.duration += new_duration
/datum/status_effect/hallucination/perceptomatrix/on_creation(mob/living/new_owner, new_duration)
if(isnum(new_duration))
src.lower_tick_interval = new_duration * 0.2
src.upper_tick_interval = new_duration
return ..()
+2 -2
View File
@@ -52,7 +52,7 @@
living_mob.Paralyze(10)
living_mob.Knockdown(100)
to_chat(living_mob, span_hypnophrase("The sound echoes in your brain..."))
living_mob.adjust_hallucinations(100 SECONDS)
living_mob.adjust_hallucinations(150 SECONDS)
else
if(distance <= 1)
@@ -60,7 +60,7 @@
living_mob.Knockdown(30)
if(hypno_sound)
to_chat(living_mob, span_hypnophrase("The sound echoes in your brain..."))
living_mob.adjust_hallucinations(100 SECONDS)
living_mob.adjust_hallucinations(150 SECONDS)
//Flash
if(living_mob.flash_act(affect_silicon = 1))
@@ -28,7 +28,7 @@
target.adjust_confusion(15 SECONDS)
target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 160)
if(3)
target.adjust_hallucinations(120 SECONDS)
target.adjust_hallucinations(150 SECONDS)
/obj/item/organ/heart/gland/mindshock/mind_control(command, mob/living/user)
if(!ownerCheck() || !mind_control_uses || active_mind_control)
@@ -62,7 +62,7 @@
human_in_range.mob_mood.direct_sanity_drain(rand(-2, -20) * seconds_per_tick)
if(SPT_PROB(60, seconds_per_tick))
human_in_range.adjust_hallucinations_up_to(10 SECONDS, 240 SECONDS)
human_in_range.adjust_hallucinations_up_to(10 SECONDS, 120 SECONDS)
if(SPT_PROB(40, seconds_per_tick))
human_in_range.set_jitter_if_lower(10 SECONDS)
@@ -0,0 +1,11 @@
/datum/preference/toggle/rds_limit
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
savefile_key = "rds_limit"
savefile_identifier = PREFERENCE_CHARACTER
default_value = FALSE
/datum/preference/toggle/rds_limit/apply_to_human(mob/living/carbon/human/target, value)
return
/datum/preference/toggle/rds_limit/is_accessible(datum/preferences/preferences)
return ..() && (/datum/quirk/insanity::name in preferences.all_quirks)
+1 -4
View File
@@ -240,10 +240,7 @@
cast_on.emote("scream")
cast_on.set_eye_blur_if_lower(eye_blur_duration)
cast_on.adjust_staggered(stagger_duration)
cast_on.apply_status_effect(/datum/status_effect/hallucination, hallucination_duration, \
hallucination_duration * 0.2, hallucination_duration) // lower/upper hallucination freq. bound
return
cast_on.apply_status_effect(/datum/status_effect/hallucination/perceptomatrix, hallucination_duration, HALLUCINATION_TIER_RARE)
#undef PERCEPTOMATRIX_INACTIVE_FLAGS
#undef PERCEPTOMATRIX_ACTIVE_FLAGS
@@ -349,7 +349,7 @@
/obj/item/clothing/suit/armor/reactive/hallucinating/emp_activation(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
owner.visible_message(span_danger("[src] blocks [attack_text], but pulls a massive charge of mental energy into [owner] from the surrounding environment!"))
owner.adjust_hallucinations_up_to(50 SECONDS, 300 SECONDS)
owner.adjust_hallucinations_up_to(50 SECONDS, 240 SECONDS)
reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration
return TRUE
@@ -9,6 +9,8 @@
/datum/hallucination
/// What is this hallucination's weight in the random hallucination pool?
var/random_hallucination_weight = 0
/// What tier of hallucination is this? Rarer ones should be higher
var/hallucination_tier = HALLUCINATION_TIER_NEVER
/// Who's our next highest abstract parent type?
var/abstract_hallucination_parent = /datum/hallucination
/// Extra info about the hallucination displayed in the log.
+1
View File
@@ -2,6 +2,7 @@
/datum/hallucination/battle
abstract_hallucination_parent = /datum/hallucination/battle
random_hallucination_weight = 3
hallucination_tier = HALLUCINATION_TIER_COMMON
/// Subtype of battle hallucination for gun based battles, where it sounds like someone is being shot.
/datum/hallucination/battle/gun
+69
View File
@@ -0,0 +1,69 @@
/datum/hallucination/blood_flow
random_hallucination_weight = 3
hallucination_tier = HALLUCINATION_TIER_COMMON
/// The bleeding hallucination's image
var/image/bleeding
/datum/hallucination/blood_flow/start()
if(!hallucinator.client || !iscarbon(hallucinator))
return FALSE
var/mob/living/carbon/carb_hallucinator = hallucinator
if(!length(carb_hallucinator.bodyparts) || HAS_TRAIT(carb_hallucinator, TRAIT_NOBLOOD))
return FALSE
var/obj/item/bodypart/picked
var/list/bodyparts = carb_hallucinator.bodyparts.Copy()
while(isnull(picked) && length(bodyparts))
picked = pick_n_take(bodyparts)
if(!picked.can_bleed())
picked = null
if(isnull(picked))
return FALSE
feedback_details += "Bleeding: [picked]"
RegisterSignals(picked, list(COMSIG_QDELETING, COMSIG_BODYPART_REMOVED), PROC_REF(stop_bleeding))
RegisterSignal(hallucinator, SIGNAL_ADDTRAIT(TRAIT_NOBLOOD), PROC_REF(stop_bleeding))
to_chat(hallucinator, span_warning("Your [picked.plaintext_zone] looses a spray of blood!"))
var/bleed_duration = rand(16 SECONDS, 40 SECONDS)
addtimer(CALLBACK(src, PROC_REF(stop_bleeding), picked), bleed_duration)
if(prob(25))
addtimer(CALLBACK(src, PROC_REF(by_god), picked), bleed_duration * pick(0.5, 0.66))
stamina_loop()
hallucinator.playsound_local(get_turf(hallucinator), pick('sound/effects/wounds/blood1.ogg', 'sound/effects/wounds/blood2.ogg', 'sound/effects/wounds/blood3.ogg'), 50, TRUE)
bleeding = image(
icon = 'icons/mob/effects/bleed_overlays.dmi',
icon_state = "[picked.body_zone]_[pick(2, 3)]",
loc = hallucinator,
)
bleeding.layer = -WOUND_LAYER
hallucinator.client?.images += bleeding
return TRUE
/datum/hallucination/blood_flow/Destroy()
hallucinator.client?.images -= bleeding
return ..()
/datum/hallucination/blood_flow/proc/by_god(obj/item/bodypart/picked)
if(QDELETED(src) || QDELETED(hallucinator) || QDELETED(picked))
return
to_chat(hallucinator, span_warning("The blood doesn't stop flowing, yet [picked.plaintext_zone] doesn't seem to hurt..."))
/datum/hallucination/blood_flow/proc/stop_bleeding(obj/item/bodypart/source)
SIGNAL_HANDLER
UnregisterSignal(source, list(COMSIG_QDELETING, COMSIG_BODYPART_REMOVED))
UnregisterSignal(hallucinator, SIGNAL_ADDTRAIT(TRAIT_NOBLOOD))
if(!QDELETED(source))
to_chat(hallucinator, span_warning("Your [source.plaintext_zone] stops bleeding."))
if(!QDELETED(src))
qdel(src)
/datum/hallucination/blood_flow/proc/stamina_loop()
set waitfor = FALSE
while(!QDELETED(src) && !QDELETED(hallucinator))
hallucinator.adjustStaminaLoss(5)
sleep(4 SECONDS)
+5
View File
@@ -1,6 +1,7 @@
/// Makes a random body appear and disappear quickly in view of the hallucinator.
/datum/hallucination/body
abstract_hallucination_parent = /datum/hallucination/body
hallucination_tier = HALLUCINATION_TIER_COMMON
/// The file to make the body image from.
var/body_image_file
/// The icon state to make the body image form.
@@ -126,6 +127,7 @@
/datum/hallucination/body/weird
random_hallucination_weight = 0.1 // These are very uncommon
abstract_hallucination_parent = /datum/hallucination/body/weird
hallucination_tier = HALLUCINATION_TIER_RARE
/datum/hallucination/body/weird/alien
body_image_file = 'icons/mob/nonhuman-player/alien.dmi'
@@ -140,6 +142,7 @@
body_image_file = 'icons/mob/simple/mob.dmi'
body_image_state = "chronostuck"
body_floats = TRUE
hallucination_tier = HALLUCINATION_TIER_VERYSPECIAL
/datum/hallucination/body/weird/god
body_image_file = 'icons/mob/simple/mob.dmi'
@@ -158,6 +161,7 @@
/datum/hallucination/body/weird/bones
body_image_file = 'icons/obj/trader_signs.dmi'
body_image_state = "mrbones"
hallucination_tier = HALLUCINATION_TIER_VERYSPECIAL
/datum/hallucination/body/weird/freezer
random_hallucination_weight = 0.3 // Slightly more common since it's cool (heh)
@@ -165,6 +169,7 @@
body_image_state = "the_freezer"
body_layer = ABOVE_ALL_MOB_LAYER
spawn_under_hallucinator = TRUE
hallucination_tier = HALLUCINATION_TIER_VERYSPECIAL
/datum/hallucination/body/weird/freezer/make_body_image(turf/location)
var/image/body = ..()
@@ -1,5 +1,6 @@
/datum/hallucination/bolts
random_hallucination_weight = 7
hallucination_tier = HALLUCINATION_TIER_COMMON
/// A list of weakrefs to airlocks we bolt down around us
var/list/datum/weakref/airlocks_to_hit
/// A list of weakrefs to fake lock hallucinations we've created
@@ -1,6 +1,7 @@
/// Sends a fake bubblegum charging through a nearby wall to our target.
/datum/hallucination/oh_yeah
random_hallucination_weight = 1
hallucination_tier = HALLUCINATION_TIER_RARE
/// An image overlayed to the wall bubblegum comes out of, to look destroyed.
var/image/fake_broken_wall
/// An image put where bubblegum is expected to land, to mimic his charge "rune" icon.
+2
View File
@@ -1,6 +1,7 @@
/// A hallucination that makes us and (possibly) other people look like something else.
/datum/hallucination/delusion
abstract_hallucination_parent = /datum/hallucination/delusion
hallucination_tier = HALLUCINATION_TIER_UNCOMMON
/// The duration of the delusions
var/duration = 30 SECONDS
@@ -249,6 +250,7 @@
delusion_name = "Syndicate"
affects_others = TRUE
affects_us = FALSE
hallucination_tier = HALLUCINATION_TIER_RARE
/datum/hallucination/delusion/preset/syndies/make_delusion_image(mob/over_who)
delusion_appearance = get_dynamic_human_appearance(
@@ -0,0 +1,91 @@
/datum/hallucination/eyes_in_dark
random_hallucination_weight = 2
hallucination_tier = HALLUCINATION_TIER_COMMON
/// The floating eye effect, somewhere in the world
var/obj/effect/abstract/floating_eyes/eyes
/datum/hallucination/eyes_in_dark/Destroy()
if(QDELETED(eyes))
eyes = null
else
QDEL_NULL(eyes)
return ..()
/datum/hallucination/eyes_in_dark/start()
if(!hallucinator.client)
return FALSE
if(hallucinator.lighting_cutoff >= 2.5)
return FALSE
var/list/valid = list()
for(var/turf/open/nearby in view(hallucinator))
if(nearby.get_lumcount() > LIGHTING_TILE_IS_DARK)
continue
valid += nearby
if(!length(valid))
return FALSE
if(prob(5))
to_chat(hallucinator, span_warning("You feel like you're being watched..."))
var/turf/selected = pick(valid)
feedback_details += "Eye coords: [selected.x], [selected.y], [selected.z]"
eyes = new(selected, hallucinator)
RegisterSignal(eyes, COMSIG_QDELETING, PROC_REF(end_hallucination))
addtimer(CALLBACK(src, PROC_REF(end_hallucination_gracefully)), rand(60 SECONDS, 180 SECONDS))
return TRUE
/datum/hallucination/eyes_in_dark/proc/end_hallucination_gracefully()
animate(eyes, alpha = 0, time = 1 SECONDS)
QDEL_IN(src, 1.2 SECONDS)
/datum/hallucination/eyes_in_dark/proc/end_hallucination()
SIGNAL_HANDLER
if(!QDELETED(src))
qdel(src)
/obj/effect/abstract/floating_eyes
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
// Who sees the eyes?
var/datum/weakref/seer_ref
/obj/effect/abstract/floating_eyes/Initialize(mapload, mob/seer)
. = ..()
if(isnull(seer))
return INITIALIZE_HINT_QDEL
seer_ref = WEAKREF(seer)
var/image/make_invis = image(icon = null, icon_state = null, loc = src)
make_invis.override = TRUE
add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/one_person/reversed, "hallucination", make_invis, null, seer)
START_PROCESSING(SSfastprocess, src)
update_appearance()
/obj/effect/abstract/floating_eyes/Destroy()
STOP_PROCESSING(SSfastprocess, src)
return ..()
/obj/effect/abstract/floating_eyes/update_overlays()
. = ..()
var/mutable_appearance/r_eye = mutable_appearance(icon = 'icons/mob/human/human_face.dmi', icon_state = "eyes_glow_r")
r_eye.color = COLOR_DARK_RED
. += r_eye
var/mutable_appearance/l_eye = mutable_appearance(icon = 'icons/mob/human/human_face.dmi', icon_state = "eyes_glow_l")
l_eye.color = COLOR_DARK_RED
. += l_eye
. += emissive_appearance('icons/mob/human/human_face.dmi', "eyes_glow_l", src)
. += emissive_appearance('icons/mob/human/human_face.dmi', "eyes_glow_r", src)
/obj/effect/abstract/floating_eyes/process(seconds_per_tick)
var/turf/below_us = get_turf(src)
var/mob/seer = seer_ref?.resolve()
if(below_us.get_lumcount() < LIGHTING_TILE_IS_DARK || seer?.lighting_cutoff >= 2.5 || get_dist(seer, src) <= 1)
graceful_delete()
/obj/effect/abstract/floating_eyes/proc/graceful_delete()
STOP_PROCESSING(SSfastprocess, src)
animate(src, alpha = 0, time = 0.5 SECONDS)
QDEL_IN(src, 0.75 SECONDS)
+1
View File
@@ -2,6 +2,7 @@
/datum/hallucination/fake_alert
abstract_hallucination_parent = /datum/hallucination/fake_alert
random_hallucination_weight = 1
hallucination_tier = HALLUCINATION_TIER_COMMON
var/del_timer_id
/// The duration of the alert being thrown.
+46 -16
View File
@@ -1,6 +1,7 @@
/// Sends a fake chat message to the hallucinator.
/datum/hallucination/chat
random_hallucination_weight = 100
hallucination_tier = HALLUCINATION_TIER_COMMON
/// If TRUE, we force the message to be hallucinated from common radio. Only set in New()
var/force_radio
@@ -12,30 +13,59 @@
src.specific_message = specific_message
return ..()
/// When passed a mob, returns a list of languages that mob could theoretically speak IF a blank slate.
/datum/hallucination/chat/proc/get_hallucinating_spoken_languages(atom/movable/who)
var/override_typepath
if(iscarbon(who))
var/mob/living/carbon/human_who = who
override_typepath = human_who.dna?.species?.species_language_holder
var/datum/language_holder/what_they_speak = GLOB.prototype_language_holders[override_typepath || who.initial_language_holder]
return what_they_speak?.spoken_languages?.Copy() || list()
/datum/hallucination/chat/start()
var/mob/living/carbon/human/speaker
var/datum/language/understood_language = hallucinator.get_random_understood_language()
for(var/mob/living/carbon/nearby_human in view(hallucinator))
if(nearby_human == hallucinator)
continue
var/list/datum/language/understood_languages = hallucinator.get_language_holder().understood_languages
var/understood_language
if(!speaker)
speaker = nearby_human
else if(get_dist(hallucinator, nearby_human) < get_dist(hallucinator, speaker))
if(!force_radio)
var/list/valid_humans = list()
var/list/valid_corpses = list()
for(var/mob/living/carbon/nearby_human in view(hallucinator))
if(nearby_human == hallucinator)
continue
if(nearby_human.stat == DEAD)
valid_corpses += nearby_human
continue
valid_humans += nearby_human
// pick a nearby human which can speak a language the hallucinator understands
for(var/mob/living/carbon/nearby_human in shuffle(valid_humans))
var/list/shared_languages = get_hallucinating_spoken_languages(nearby_human) & understood_languages
if(!length(shared_languages)) // future idea : have people hallucinating off their minds ignore this check
continue
speaker = nearby_human
understood_language = pick(shared_languages)
break
// corpses disrespect language because they're... dead
if(isnull(speaker) && length(valid_corpses))
speaker = pick(valid_corpses)
// Get person to affect if radio hallucination
var/is_radio = !speaker || force_radio
var/is_radio = force_radio || isnull(speaker)
if(is_radio)
var/list/humans = list()
for(var/datum/mind/crew_mind in shuffle(get_crewmember_minds()))
if(crew_mind == hallucinator.mind)
continue
var/list/shared_languages = get_hallucinating_spoken_languages(crew_mind.current) & understood_languages
if(!length(shared_languages))
continue
speaker = crew_mind.current
understood_language = pick(shared_languages)
break
for(var/datum/mind/crew_mind in get_crewmember_minds())
if(crew_mind.current)
humans += crew_mind.current
if(humans.len)
speaker = pick(humans)
if(!speaker)
if(isnull(speaker))
return
// Time to generate a message.
+1
View File
@@ -1,6 +1,7 @@
// This hallucinations makes us suddenly think we died, stopping us / changing our hud / sending a fake deadchat message.
/datum/hallucination/death
random_hallucination_weight = 1
hallucination_tier = HALLUCINATION_TIER_UNCOMMON
/// Determines whether we floor them or just immobilize them
var/floor_them = TRUE
@@ -1,5 +1,6 @@
/datum/hallucination/message
random_hallucination_weight = 60
hallucination_tier = HALLUCINATION_TIER_COMMON
/datum/hallucination/message/start()
var/list/nearby_humans = list()
@@ -5,6 +5,7 @@
/// Plasma starts flooding from the nearby vent
/datum/hallucination/fake_flood
random_hallucination_weight = 7
hallucination_tier = HALLUCINATION_TIER_UNCOMMON
var/list/image/flood_images = list()
var/list/obj/effect/plasma_image_holder/flood_image_holders = list()
+37 -1
View File
@@ -1,6 +1,7 @@
/// Hallucination that plays a fake sound somewhere nearby.
/datum/hallucination/fake_sound
abstract_hallucination_parent = /datum/hallucination/fake_sound
hallucination_tier = HALLUCINATION_TIER_COMMON
/// Volume of the fake sound
var/volume = 50
@@ -10,6 +11,9 @@
var/sound_type
/datum/hallucination/fake_sound/start()
if(!hallucinator.can_hear())
return FALSE
var/sound_to_play = islist(sound_type) ? pick(sound_type) : sound_type
play_fake_sound(random_far_turf(), sound_to_play)
feedback_details += "Sound: [sound_to_play]"
@@ -148,9 +152,31 @@
volume = 90
sound_type = 'sound/items/weapons/flash.ogg'
/datum/hallucination/fake_sound/normal/ringtone
volume = 50
/datum/hallucination/fake_sound/normal/ringtone/New(mob/living/hallucinator)
. = ..()
if(HAS_TRAIT(SSstation, STATION_TRAIT_PDA_GLITCHED))
sound_type = pick(
'sound/machines/beep/twobeep_voice1.ogg',
'sound/machines/beep/twobeep_voice2.ogg',
)
else
sound_type = 'sound/machines/beep/twobeep_high.ogg'
/datum/hallucination/fake_sound/normal/ringtone/play_fake_sound(turf/source, sound_to_play = sound_type)
if(prob(33))
source = get_turf(hallucinator)
var/obj/item/modular_computer/pda/pda = locate() in hallucinator.get_all_contents()
var/datum/computer_file/program/messenger/messenger_app = locate() in pda?.stored_files
hallucinator.balloon_alert(hallucinator, "*[messenger_app?.ringtone || MESSENGER_RINGTONE_DEFAULT]*")
return ..()
/datum/hallucination/fake_sound/weird
abstract_hallucination_parent = /datum/hallucination/fake_sound/weird
random_hallucination_weight = 1
hallucination_tier = HALLUCINATION_TIER_VERYSPECIAL
/// if FALSE, we will pass "null" in as the turf source, meaning the sound will just play without direction / etc.
var/no_source = FALSE
@@ -187,6 +213,7 @@
sound_type = 'sound/effects/magic/clockwork/invoke_general.ogg'
/datum/hallucination/fake_sound/weird/creepy
hallucination_tier = HALLUCINATION_TIER_COMMON
/datum/hallucination/fake_sound/weird/creepy/New(mob/living/hallucinator)
. = ..()
@@ -202,6 +229,7 @@
/datum/hallucination/fake_sound/weird/game_over
sound_vary = FALSE
sound_type = 'sound/machines/compiler/compiler-failure.ogg'
hallucination_tier = HALLUCINATION_TIER_RARE
/datum/hallucination/fake_sound/weird/hallelujah
sound_vary = FALSE
@@ -216,8 +244,10 @@
sound_vary = FALSE
no_source = TRUE
sound_type = 'sound/runtime/hyperspace/hyperspace_begin.ogg'
hallucination_tier = HALLUCINATION_TIER_COMMON
/datum/hallucination/fake_sound/weird/laugher
hallucination_tier = HALLUCINATION_TIER_COMMON
sound_type = list(
'sound/mobs/humanoids/human/laugh/womanlaugh.ogg',
'sound/mobs/humanoids/human/laugh/manlaugh1.ogg',
@@ -228,6 +258,7 @@
volume = 15
sound_vary = FALSE
sound_type = 'sound/items/weapons/ring.ogg'
hallucination_tier = HALLUCINATION_TIER_RARE
/datum/hallucination/fake_sound/weird/phone/play_fake_sound(turf/source, sound_to_play)
for(var/next_ring in 1 to 3)
@@ -236,6 +267,7 @@
return ..()
/datum/hallucination/fake_sound/weird/spell
hallucination_tier = HALLUCINATION_TIER_RARE
sound_type = list(
'sound/effects/magic/disintegrate.ogg',
'sound/effects/magic/ethereal_enter.ogg',
@@ -247,15 +279,18 @@
)
/datum/hallucination/fake_sound/weird/spell/just_jaunt // A few antags use jaunts, so this sound specifically is fun to isolate
hallucination_tier = HALLUCINATION_TIER_RARE
sound_type = 'sound/effects/magic/ethereal_enter.ogg'
/datum/hallucination/fake_sound/weird/summon_sound // Heretic circle sound, notably
volume = 75
hallucination_tier = HALLUCINATION_TIER_RARE
sound_type = 'sound/effects/magic/castsummon.ogg'
/datum/hallucination/fake_sound/weird/tesloose
volume = 35
sound_type = 'sound/effects/magic/lightningbolt.ogg'
hallucination_tier = HALLUCINATION_TIER_RARE
/datum/hallucination/fake_sound/weird/tesloose/play_fake_sound(turf/source, sound_to_play)
. = ..()
@@ -265,6 +300,7 @@
/datum/hallucination/fake_sound/weird/xeno
random_hallucination_weight = 2 // Some of these are ambience sounds too
volume = 25
hallucination_tier = HALLUCINATION_TIER_RARE
sound_type = list(
'sound/mobs/non-humanoids/hiss/lowHiss1.ogg',
'sound/mobs/non-humanoids/hiss/lowHiss2.ogg',
@@ -283,7 +319,7 @@
sound_type = 'sound/effects/hallucinations/radio_static.ogg'
/datum/hallucination/fake_sound/weird/ice_crack
random_hallucination_weight = 2
random_hallucination_weight = 0
volume = 100
no_source = TRUE
sound_type = 'sound/effects/ice_shovel.ogg'
+1
View File
@@ -2,6 +2,7 @@
/datum/hallucination/hazard
abstract_hallucination_parent = /datum/hallucination/hazard
random_hallucination_weight = 5
hallucination_tier = HALLUCINATION_TIER_UNCOMMON
/// The type of effect we create
var/hazard_type = /obj/effect/client_image_holder/hallucination/danger
+1
View File
@@ -2,6 +2,7 @@
/datum/hallucination/screwy_hud
abstract_hallucination_parent = /datum/hallucination/screwy_hud
random_hallucination_weight = 4
hallucination_tier = HALLUCINATION_TIER_COMMON
/// The type of hud we give to the hallucinator
var/screwy_hud_type = SCREWYHUD_NONE
+1
View File
@@ -1,6 +1,7 @@
/// Causes the hallucinator to believe themselves frozen in ice. Man am I glad he's frozen in there etc etc
/datum/hallucination/ice
random_hallucination_weight = 3
hallucination_tier = HALLUCINATION_TIER_COMMON
/// What icon file to use for our hallucinator
var/ice_icon = 'icons/effects/freeze.dmi'
@@ -2,6 +2,7 @@
/datum/hallucination/fake_item
abstract_hallucination_parent = /datum/hallucination/fake_item
random_hallucination_weight = 1
hallucination_tier = HALLUCINATION_TIER_COMMON
/// A flag of slots this fake item can appear in.
var/valid_slots = ITEM_SLOT_HANDS|ITEM_SLOT_BELT|ITEM_SLOT_LPOCKET|ITEM_SLOT_RPOCKET
@@ -114,6 +115,24 @@
return hallucinated_item
/datum/hallucination/fake_item/summon_guns
hallucination_tier = HALLUCINATION_TIER_RARE
valid_slots = ITEM_SLOT_HANDS
/datum/hallucination/fake_item/summon_guns/make_fake_item(where_to_put_it, equip_flags)
template_item_type = pick(GLOB.summoned_guns)
. = ..()
hallucinator.playsound_local(get_turf(hallucinator), 'sound/effects/magic/summon_guns.ogg', 50, TRUE)
/datum/hallucination/fake_item/summon_magic
hallucination_tier = HALLUCINATION_TIER_RARE
valid_slots = ITEM_SLOT_HANDS
/datum/hallucination/fake_item/summon_magic/make_fake_item(where_to_put_it, equip_flags)
template_item_type = pick(GLOB.summoned_magic + GLOB.summoned_special_magic)
. = ..()
hallucinator.playsound_local(get_turf(hallucinator), 'sound/effects/magic/summon_magic.ogg', 50, TRUE)
/obj/item/hallucinated
name = "mirage"
plane = ABOVE_HUD_PLANE
+2
View File
@@ -1,6 +1,8 @@
/// Your mother appears to scold you.
/datum/hallucination/your_mother
random_hallucination_weight = 2
hallucination_tier = HALLUCINATION_TIER_VERYSPECIAL
var/obj/effect/client_image_holder/hallucination/your_mother/mother
/datum/hallucination/your_mother/start()
@@ -2,6 +2,7 @@
/datum/hallucination/nearby_fake_item
abstract_hallucination_parent = /datum/hallucination/nearby_fake_item
random_hallucination_weight = 1
hallucination_tier = HALLUCINATION_TIER_COMMON
/// The icon file to draw from for left hand icons
var/left_hand_file
+36 -10
View File
@@ -3,6 +3,7 @@
/datum/hallucination/fire
random_hallucination_weight = 3
hallucination_tier = HALLUCINATION_TIER_UNCOMMON
/// Are we currently burning our mob?
var/active = TRUE
@@ -27,21 +28,29 @@
/// How long have we spent on fire?
var/time_spent = 0
/datum/hallucination/fire/New(mob/living/hallucinator)
if(ismonkey(hallucinator))
fire_icon_state = "monkey_big_fire"
var/fake_firestacks = 0
else if(!ishuman(hallucinator))
fire_icon_state = "generic_fire"
/datum/hallucination/fire/proc/make_overlay()
var/mutable_appearance/real_overlay = hallucinator.get_fire_overlay(fake_firestacks)
if(!real_overlay)
return null
if(real_overlay.icon_state == fire_overlay?.icon_state)
return fire_overlay
return ..()
var/image/new_overlay = image(real_overlay.icon, hallucinator, real_overlay.icon_state, real_overlay.layer)
new_overlay.appearance_flags = real_overlay.appearance_flags
return new_overlay
/datum/hallucination/fire/start()
fire_overlay = image(fire_icon, hallucinator, fire_icon_state, ABOVE_MOB_LAYER)
SET_PLANE_EXPLICIT(fire_overlay, ABOVE_GAME_PLANE, hallucinator)
fake_firestacks = rand(5, 15)
fire_overlay = make_overlay()
if(!fire_overlay)
return FALSE
hallucinator.client?.images |= fire_overlay
to_chat(hallucinator, span_userdanger("You're set on fire!"))
hallucinator.throw_alert(ALERT_FIRE, /atom/movable/screen/alert/fire, override = TRUE)
var/atom/movable/screen/alert/fire/fake/alert = hallucinator.throw_alert(ALERT_FIRE, /atom/movable/screen/alert/fire/fake, override = TRUE)
alert.hallucination_weakref = WEAKREF(src)
times_to_lower_stamina = rand(5, 10)
addtimer(CALLBACK(src, PROC_REF(start_expanding)), 2 SECONDS)
return TRUE
@@ -66,8 +75,15 @@
if(QDELETED(src))
return
if(hallucinator.fire_stacks <= 0)
fake_firestacks -= (0.25 * seconds_per_tick)
if(fake_firestacks <= 0)
clear_fire()
else
var/new_overlay = make_overlay()
if(new_overlay && new_overlay != fire_overlay)
hallucinator.client?.images -= fire_overlay
fire_overlay = new_overlay
hallucinator.client?.images |= fire_overlay
time_spent += seconds_per_tick
@@ -100,6 +116,8 @@
/datum/hallucination/fire/proc/update_temp()
if(stage <= 0)
hallucinator.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE)
if(!active)
qdel(src)
else
hallucinator.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE)
hallucinator.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, stage, override = TRUE)
@@ -117,3 +135,11 @@
#undef RAISE_FIRE_COUNT
#undef RAISE_FIRE_TIME
/// This alert is thrown when hallucinating fire
/atom/movable/screen/alert/fire/fake
/// We need to track the original hallucination so we can pass it to the status effect
var/datum/weakref/hallucination_weakref
/atom/movable/screen/alert/fire/fake/handle_stop_drop_roll(mob/living/roller)
return !!roller.apply_status_effect(/datum/status_effect/stop_drop_roll/hallucinating, hallucination_weakref)
@@ -1,6 +1,7 @@
///Causes the target to see incorrect health damages on the healthdoll
/datum/hallucination/fake_health_doll
random_hallucination_weight = 12
hallucination_tier = HALLUCINATION_TIER_COMMON
/// The duration of the hallucination
var/duration
+2 -1
View File
@@ -1,6 +1,7 @@
/// Causes a fake "zap" to the hallucinator.
/datum/hallucination/shock
random_hallucination_weight = 1
random_hallucination_weight = 1 // really low weight, as it also has a snowflake check to trigger when bumping airlocks
hallucination_tier = HALLUCINATION_TIER_COMMON
var/electrocution_icon = 'icons/mob/human/human.dmi'
var/electrocution_icon_state = "electrocuted_base"
@@ -1,6 +1,7 @@
/datum/hallucination/station_message
abstract_hallucination_parent = /datum/hallucination/station_message
random_hallucination_weight = 1
hallucination_tier = HALLUCINATION_TIER_RARE
/datum/hallucination/station_message/start()
qdel(src) // To be implemented by subtypes, call parent for easy cleanup
@@ -1,6 +1,7 @@
/// Shoots a random, fake projectile to the hallucinator
/datum/hallucination/stray_bullet
random_hallucination_weight = 7
hallucination_tier = HALLUCINATION_TIER_UNCOMMON
/datum/hallucination/stray_bullet/start()
var/list/turf/starting_locations = list()
+36
View File
@@ -0,0 +1,36 @@
/datum/hallucination/telepathy
random_hallucination_weight = 4
hallucination_tier = HALLUCINATION_TIER_COMMON
/datum/hallucination/telepathy/start()
var/datum/action/cooldown/spell/list_target/telepathy/mimiced_type = pick(typesof(/datum/action/cooldown/spell/list_target/telepathy))
hallucinator.balloon_alert(hallucinator, "you hear a voice")
to_chat(hallucinator, "\
<span class='[initial(mimiced_type.bold_telepathy_span)]'>You hear a voice in your head...</span>\
<span class='[initial(mimiced_type.telepathy_span)]'> [get_telepath_message()]</span>\
")
return TRUE
/datum/hallucination/telepathy/proc/get_telepath_message()
if(prob(0.001))
return "horse"
var/memo = pick(
pick_list_replacements(HALLUCINATION_FILE, "advice"),
pick_list_replacements(HALLUCINATION_FILE, "aggressive"),
pick_list_replacements(HALLUCINATION_FILE, "conversation"),
pick_list_replacements(HALLUCINATION_FILE, "didyouhearthat"),
pick_list_replacements(HALLUCINATION_FILE, "doubt"),
pick_list_replacements(HALLUCINATION_FILE, "escape"),
pick_list_replacements(HALLUCINATION_FILE, "getout"),
pick_list_replacements(HALLUCINATION_FILE, "greetings"),
pick_list_replacements(HALLUCINATION_FILE, "suspicion"),
)
var/names = pick(
first_name(hallucinator.name),
last_name(hallucinator.name),
first_name(hallucinator.real_name),
last_name(hallucinator.real_name),
)
return replacetext(memo, "%TARGETNAME%", names)
@@ -1,6 +1,7 @@
/// Xeno crawls from nearby vent, jumps at you, and goes back in.
/datum/hallucination/xeno_attack
random_hallucination_weight = 2
hallucination_tier = HALLUCINATION_TIER_RARE
/datum/hallucination/xeno_attack/start()
var/turf/xeno_attack_source
@@ -884,7 +884,7 @@
active_hallucination_weakref = WEAKREF(affected_mob.cause_hallucination(greatest_fear, name, duration = 5 MINUTES, skip_nearby = !overdosed))
else
// if they're just some random schmuck, give them random hallucinations
affected_mob.adjust_hallucinations_up_to(4 SECONDS * REM * seconds_per_tick, 20 SECONDS)
affected_mob.adjust_hallucinations_up_to(4 SECONDS * REM * seconds_per_tick, 30 SECONDS)
/datum/reagent/drug/syndol/on_mob_end_metabolize(mob/living/affected_mob)
. = ..()
@@ -2640,10 +2640,9 @@
/datum/reagent/bz_metabolites/on_mob_life(mob/living/carbon/target, seconds_per_tick, times_fired)
. = ..()
if(target.mind)
var/datum/antagonist/changeling/changeling = IS_CHANGELING(target)
if(changeling)
changeling.adjust_chemicals(-2 * REM * seconds_per_tick)
target.adjust_hallucinations(5 SECONDS * REM * seconds_per_tick)
var/datum/antagonist/changeling/changeling = IS_CHANGELING(target)
changeling?.adjust_chemicals(-2 * REM * seconds_per_tick)
/datum/reagent/pax/peaceborg
name = "Synthpax"
@@ -378,8 +378,7 @@
/// Too much funny gas, time to get brain damage
/obj/item/organ/lungs/proc/too_much_bz(mob/living/carbon/breather, datum/gas_mixture/breath, bz_pp, old_bz_pp)
if(bz_pp > BZ_trip_balls_min)
breather.adjust_hallucinations(20 SECONDS)
breather.reagents.add_reagent(/datum/reagent/bz_metabolites, 5)
breather.reagents.add_reagent(/datum/reagent/bz_metabolites, clamp(bz_pp, 1, 5))
if(bz_pp > BZ_brain_damage_min && prob(33))
breather.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150, ORGAN_ORGANIC)
+3 -2
View File
@@ -1,9 +1,10 @@
{
"suspicion": [
"@pick(add_name)i'm watching you...",
"@pick(add_name)i know what you're doing",
"@pick(add_name)i'm watching you...",
"@pick(add_name)what are you hiding?",
"I saw that"
"I saw that",
"I'm watching you"
],
"conversation": [
+4
View File
@@ -3857,6 +3857,7 @@
#include "code\modules\client\preferences\prosthetic_limb.dm"
#include "code\modules\client\preferences\prosthetic_organ.dm"
#include "code\modules\client\preferences\random.dm"
#include "code\modules\client\preferences\rds_limit.dm"
#include "code\modules\client\preferences\runechat.dm"
#include "code\modules\client\preferences\scaling_method.dm"
#include "code\modules\client\preferences\scarred_eye.dm"
@@ -4306,10 +4307,12 @@
#include "code\modules\forensics\forensics_helpers.dm"
#include "code\modules\hallucination\_hallucination.dm"
#include "code\modules\hallucination\battle.dm"
#include "code\modules\hallucination\blood_flow.dm"
#include "code\modules\hallucination\body.dm"
#include "code\modules\hallucination\bolted_airlocks.dm"
#include "code\modules\hallucination\bubblegum_attack.dm"
#include "code\modules\hallucination\delusions.dm"
#include "code\modules\hallucination\eyes_in_dark.dm"
#include "code\modules\hallucination\fake_alert.dm"
#include "code\modules\hallucination\fake_chat.dm"
#include "code\modules\hallucination\fake_death.dm"
@@ -4327,6 +4330,7 @@
#include "code\modules\hallucination\shock.dm"
#include "code\modules\hallucination\station_message.dm"
#include "code\modules\hallucination\stray_bullet.dm"
#include "code\modules\hallucination\telepathy.dm"
#include "code\modules\hallucination\xeno_attack.dm"
#include "code\modules\holiday\foreign_calendar.dm"
#include "code\modules\holiday\holidays.dm"
@@ -0,0 +1,9 @@
import { CheckboxInput, FeatureToggle } from '../base';
export const rds_limit: FeatureToggle = {
name: 'Unlimit Hallucinations',
description:
'Checking this box will remove limitations on hallucinations, \
causing them to be more frequent, intrusive, and (generally) wacky.',
component: CheckboxInput,
};