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, +};