diff --git a/code/__HELPERS/hallucinations.dm b/code/__HELPERS/hallucinations.dm
index d3d4a2630ed..110a94e862d 100644
--- a/code/__HELPERS/hallucinations.dm
+++ b/code/__HELPERS/hallucinations.dm
@@ -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 = "
| Type | Weight | Percent | "
+ var/header = "
|---|
| Type | Weight | Tier | Percent | "
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["
|---|
| [last_type] | [last_type_weight] / [total_weight] | [initial(hallucination_type.hallucination_tier)] | [round(100 * (last_type_weight / total_weight), 0.01)]% chance |
"] = 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["| [hallucination_type] | [this_weight] / [total_weight] | [initial(hallucination_type.hallucination_tier)] | [round(100 * (this_weight / total_weight), 0.01)]% chance |
"] = this_weight
+ last_type = null
+
+ // Otherwise we start the weight sum for the next entry here
else
- all_weights["| [last_type] | [last_type_weight] / [total_weight] | [round(100 * (last_type_weight / total_weight), 0.01)]% chance |
"] = 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["| [hallucination_type] | [this_weight] / [total_weight] | [round(100 * (this_weight / total_weight), 0.01)]% chance |
"] = 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)
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index e0f1472b6a8..2178554bf00 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -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"
diff --git a/code/datums/brain_damage/mild.dm b/code/datums/brain_damage/mild.dm
index 846869901d6..d50de8ed943 100644
--- a/code/datums/brain_damage/mild.dm
+++ b/code/datums/brain_damage/mild.dm
@@ -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)
diff --git a/code/datums/quirks/negative_quirks/insanity.dm b/code/datums/quirks/negative_quirks/insanity.dm
index c5fc1afdf0d..9401d006b67 100644
--- a/code/datums/quirks/negative_quirks/insanity.dm
+++ b/code/datums/quirks/negative_quirks/insanity.dm
@@ -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)
diff --git a/code/datums/status_effects/buffs/stop_drop_roll.dm b/code/datums/status_effects/buffs/stop_drop_roll.dm
index 96db96372dd..e7ff393a1dd 100644
--- a/code/datums/status_effects/buffs/stop_drop_roll.dm
+++ b/code/datums/status_effects/buffs/stop_drop_roll.dm
@@ -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)
diff --git a/code/datums/status_effects/debuffs/hallucination.dm b/code/datums/status_effects/debuffs/hallucination.dm
index 66e85f1900a..c45dd1451b1 100644
--- a/code/datums/status_effects/debuffs/hallucination.dm
+++ b/code/datums/status_effects/debuffs/hallucination.dm
@@ -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 ..()
diff --git a/code/game/objects/items/grenades/hypno.dm b/code/game/objects/items/grenades/hypno.dm
index 0d7601df204..d77049419ab 100644
--- a/code/game/objects/items/grenades/hypno.dm
+++ b/code/game/objects/items/grenades/hypno.dm
@@ -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))
diff --git a/code/modules/antagonists/abductor/equipment/glands/mindshock.dm b/code/modules/antagonists/abductor/equipment/glands/mindshock.dm
index a4aa88b8da9..3480d20de4e 100644
--- a/code/modules/antagonists/abductor/equipment/glands/mindshock.dm
+++ b/code/modules/antagonists/abductor/equipment/glands/mindshock.dm
@@ -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)
diff --git a/code/modules/antagonists/heretic/items/madness_mask.dm b/code/modules/antagonists/heretic/items/madness_mask.dm
index 564715d7dd8..1be99d41012 100644
--- a/code/modules/antagonists/heretic/items/madness_mask.dm
+++ b/code/modules/antagonists/heretic/items/madness_mask.dm
@@ -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)
diff --git a/code/modules/client/preferences/rds_limit.dm b/code/modules/client/preferences/rds_limit.dm
new file mode 100644
index 00000000000..d035791bbb8
--- /dev/null
+++ b/code/modules/client/preferences/rds_limit.dm
@@ -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)
diff --git a/code/modules/clothing/head/perceptomatrix.dm b/code/modules/clothing/head/perceptomatrix.dm
index ed45ce0f9e3..9e31361e0c9 100644
--- a/code/modules/clothing/head/perceptomatrix.dm
+++ b/code/modules/clothing/head/perceptomatrix.dm
@@ -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
diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm
index eaa997607f0..813f296506f 100644
--- a/code/modules/clothing/suits/reactive_armour.dm
+++ b/code/modules/clothing/suits/reactive_armour.dm
@@ -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
diff --git a/code/modules/hallucination/_hallucination.dm b/code/modules/hallucination/_hallucination.dm
index df3bbe74757..12510d0230d 100644
--- a/code/modules/hallucination/_hallucination.dm
+++ b/code/modules/hallucination/_hallucination.dm
@@ -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.
diff --git a/code/modules/hallucination/battle.dm b/code/modules/hallucination/battle.dm
index 2a50093e3a0..6bcbafea52d 100644
--- a/code/modules/hallucination/battle.dm
+++ b/code/modules/hallucination/battle.dm
@@ -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
diff --git a/code/modules/hallucination/blood_flow.dm b/code/modules/hallucination/blood_flow.dm
new file mode 100644
index 00000000000..aaaba4ba2c9
--- /dev/null
+++ b/code/modules/hallucination/blood_flow.dm
@@ -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)
diff --git a/code/modules/hallucination/body.dm b/code/modules/hallucination/body.dm
index cba2ce0c31d..96cf60c66b7 100644
--- a/code/modules/hallucination/body.dm
+++ b/code/modules/hallucination/body.dm
@@ -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 = ..()
diff --git a/code/modules/hallucination/bolted_airlocks.dm b/code/modules/hallucination/bolted_airlocks.dm
index f50c8876100..fd8e13279f4 100644
--- a/code/modules/hallucination/bolted_airlocks.dm
+++ b/code/modules/hallucination/bolted_airlocks.dm
@@ -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
diff --git a/code/modules/hallucination/bubblegum_attack.dm b/code/modules/hallucination/bubblegum_attack.dm
index 529d67dcd35..66dfa606dc3 100644
--- a/code/modules/hallucination/bubblegum_attack.dm
+++ b/code/modules/hallucination/bubblegum_attack.dm
@@ -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.
diff --git a/code/modules/hallucination/delusions.dm b/code/modules/hallucination/delusions.dm
index c6795794bc1..c90b60d2f41 100644
--- a/code/modules/hallucination/delusions.dm
+++ b/code/modules/hallucination/delusions.dm
@@ -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(
diff --git a/code/modules/hallucination/eyes_in_dark.dm b/code/modules/hallucination/eyes_in_dark.dm
new file mode 100644
index 00000000000..60f54bb80ab
--- /dev/null
+++ b/code/modules/hallucination/eyes_in_dark.dm
@@ -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)
diff --git a/code/modules/hallucination/fake_alert.dm b/code/modules/hallucination/fake_alert.dm
index 6e10daf73aa..50ddf2e194f 100644
--- a/code/modules/hallucination/fake_alert.dm
+++ b/code/modules/hallucination/fake_alert.dm
@@ -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.
diff --git a/code/modules/hallucination/fake_chat.dm b/code/modules/hallucination/fake_chat.dm
index 10431724496..3f2d39e4661 100644
--- a/code/modules/hallucination/fake_chat.dm
+++ b/code/modules/hallucination/fake_chat.dm
@@ -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.
diff --git a/code/modules/hallucination/fake_death.dm b/code/modules/hallucination/fake_death.dm
index 9583418f232..7d0b603c9a4 100644
--- a/code/modules/hallucination/fake_death.dm
+++ b/code/modules/hallucination/fake_death.dm
@@ -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
diff --git a/code/modules/hallucination/fake_message.dm b/code/modules/hallucination/fake_message.dm
index de5616d83c6..b6fc640b0b2 100644
--- a/code/modules/hallucination/fake_message.dm
+++ b/code/modules/hallucination/fake_message.dm
@@ -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()
diff --git a/code/modules/hallucination/fake_plasmaflood.dm b/code/modules/hallucination/fake_plasmaflood.dm
index 5b3a67f5312..5019bec8912 100644
--- a/code/modules/hallucination/fake_plasmaflood.dm
+++ b/code/modules/hallucination/fake_plasmaflood.dm
@@ -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()
diff --git a/code/modules/hallucination/fake_sound.dm b/code/modules/hallucination/fake_sound.dm
index 1d93be83f94..7b2e2ee8825 100644
--- a/code/modules/hallucination/fake_sound.dm
+++ b/code/modules/hallucination/fake_sound.dm
@@ -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'
diff --git a/code/modules/hallucination/hazard.dm b/code/modules/hallucination/hazard.dm
index 34bcee62f6a..81596ee1ea7 100644
--- a/code/modules/hallucination/hazard.dm
+++ b/code/modules/hallucination/hazard.dm
@@ -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
diff --git a/code/modules/hallucination/hud_screw.dm b/code/modules/hallucination/hud_screw.dm
index 88a9fccc078..346c25f7e99 100644
--- a/code/modules/hallucination/hud_screw.dm
+++ b/code/modules/hallucination/hud_screw.dm
@@ -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
diff --git a/code/modules/hallucination/ice_cube.dm b/code/modules/hallucination/ice_cube.dm
index d5ec89649de..40c86ffcc4f 100644
--- a/code/modules/hallucination/ice_cube.dm
+++ b/code/modules/hallucination/ice_cube.dm
@@ -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'
diff --git a/code/modules/hallucination/inhand_fake_item.dm b/code/modules/hallucination/inhand_fake_item.dm
index 665c8811339..06e83375a3e 100644
--- a/code/modules/hallucination/inhand_fake_item.dm
+++ b/code/modules/hallucination/inhand_fake_item.dm
@@ -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
diff --git a/code/modules/hallucination/mother.dm b/code/modules/hallucination/mother.dm
index 12b31b04f05..4e65aed6a54 100644
--- a/code/modules/hallucination/mother.dm
+++ b/code/modules/hallucination/mother.dm
@@ -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()
diff --git a/code/modules/hallucination/nearby_fake_item.dm b/code/modules/hallucination/nearby_fake_item.dm
index 33ef6b14b25..a93c9ece396 100644
--- a/code/modules/hallucination/nearby_fake_item.dm
+++ b/code/modules/hallucination/nearby_fake_item.dm
@@ -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
diff --git a/code/modules/hallucination/on_fire.dm b/code/modules/hallucination/on_fire.dm
index 3e64618e719..048d8dcc96c 100644
--- a/code/modules/hallucination/on_fire.dm
+++ b/code/modules/hallucination/on_fire.dm
@@ -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)
diff --git a/code/modules/hallucination/screwy_health_doll.dm b/code/modules/hallucination/screwy_health_doll.dm
index 2a8eeba16e2..35f7356b4d4 100644
--- a/code/modules/hallucination/screwy_health_doll.dm
+++ b/code/modules/hallucination/screwy_health_doll.dm
@@ -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
diff --git a/code/modules/hallucination/shock.dm b/code/modules/hallucination/shock.dm
index 5c99328dafb..85f144b050e 100644
--- a/code/modules/hallucination/shock.dm
+++ b/code/modules/hallucination/shock.dm
@@ -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"
diff --git a/code/modules/hallucination/station_message.dm b/code/modules/hallucination/station_message.dm
index 55b44d18463..7f3ebdc6a54 100644
--- a/code/modules/hallucination/station_message.dm
+++ b/code/modules/hallucination/station_message.dm
@@ -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
diff --git a/code/modules/hallucination/stray_bullet.dm b/code/modules/hallucination/stray_bullet.dm
index 33462d2ab43..f3775671f2f 100644
--- a/code/modules/hallucination/stray_bullet.dm
+++ b/code/modules/hallucination/stray_bullet.dm
@@ -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()
diff --git a/code/modules/hallucination/telepathy.dm b/code/modules/hallucination/telepathy.dm
new file mode 100644
index 00000000000..df161579341
--- /dev/null
+++ b/code/modules/hallucination/telepathy.dm
@@ -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, "\
+ You hear a voice in your head...\
+ [get_telepath_message()]\
+ ")
+ 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)
diff --git a/code/modules/hallucination/xeno_attack.dm b/code/modules/hallucination/xeno_attack.dm
index 0d8b15490d9..33ebc6e593c 100644
--- a/code/modules/hallucination/xeno_attack.dm
+++ b/code/modules/hallucination/xeno_attack.dm
@@ -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
diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
index 175405211e7..d4003b8034e 100644
--- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
@@ -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)
. = ..()
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 562ad5d8190..54cdc5bc2cf 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -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"
diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm
index 00a80ac21b6..938b1ceaf8c 100644
--- a/code/modules/surgery/organs/internal/lungs/_lungs.dm
+++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm
@@ -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)
diff --git a/strings/hallucination.json b/strings/hallucination.json
index d6e13d570d9..e52ba2611f0 100644
--- a/strings/hallucination.json
+++ b/strings/hallucination.json
@@ -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": [
diff --git a/tgstation.dme b/tgstation.dme
index 8770518455f..8aacf2a5efa 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -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"
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/rds_limit.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/rds_limit.tsx
new file mode 100644
index 00000000000..fa32abe2d2e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/rds_limit.tsx
@@ -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,
+};