From 8f033ead91be2fdd49cfc6cb33c52f4acf564ad5 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Mon, 26 Sep 2022 01:43:45 +0200 Subject: [PATCH] [MIRROR] Completely refactors hallucinations, and also adds a few [MDB IGNORE] (#16348) * Completely refactors hallucinations, and also adds a few * delete 5 old hallucination types that should have been removed * Fixed old leftover tips conflicts * Fixes all the leftover conflicts and otherwise broken code Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: tastyfish Co-authored-by: GoldenAlpharex --- .../signals/signals_mob/signals_mob_carbon.dm | 20 +- code/__DEFINES/dcs/signals/signals_object.dm | 5 + code/__DEFINES/events.dm | 3 + code/__DEFINES/research/anomalies.dm | 3 + code/__DEFINES/status_effects.dm | 5 + code/__DEFINES/supermatter.dm | 2 +- code/__DEFINES/text.dm | 2 + code/__DEFINES/traits.dm | 4 +- code/__DEFINES/vv.dm | 3 +- code/__HELPERS/hallucinations.dm | 236 +++++++++++++ code/_globalvars/phobias.dm | 2 +- code/datums/brain_damage/hypnosis.dm | 14 +- code/datums/brain_damage/magic.dm | 12 +- code/datums/brain_damage/mild.dm | 26 +- code/datums/brain_damage/severe.dm | 4 +- code/datums/brain_damage/special.dm | 96 ++--- .../diseases/advance/symptoms/hallucigen.dm | 2 +- .../diseases/advance/symptoms/sensory.dm | 3 +- code/datums/mutations/body.dm | 2 +- code/datums/quirks/negative.dm | 37 +- code/datums/status_effects/buffs.dm | 23 +- .../status_effects/debuffs/hallucination.dm | 91 +++++ .../status_effects/debuffs/screwy_hud.dm | 58 +++ code/datums/voice_of_god_command.dm | 11 +- code/game/area/areas/shuttles.dm | 2 +- .../machinery/computer/arcade/orion_event.dm | 3 +- code/game/machinery/doors/airlock.dm | 21 +- code/game/machinery/medical_kiosk.dm | 18 +- code/game/objects/effects/anomalies.dm | 59 ++-- .../items/devices/portable_chem_mixer.dm | 17 +- .../items/devices/scanners/health_analyzer.dm | 3 - code/game/objects/items/food/misc.dm | 9 +- code/game/objects/items/grenades/hypno.dm | 5 +- code/modules/admin/admin_verbs.dm | 1 + code/modules/admin/force_event.dm | 3 +- .../abductor/equipment/glands/mindshock.dm | 2 +- .../blob/blobstrains/regenerative_materia.dm | 18 +- .../changeling/powers/tiny_prick.dm | 7 +- code/modules/antagonists/cult/blood_magic.dm | 2 +- .../antagonists/heretic/items/madness_mask.dm | 2 +- .../sacrifice_knowledge/sacrifice_buff.dm | 2 +- .../sacrifice_knowledge.dm | 6 +- .../heretic/knowledge/void_lore.dm | 2 +- .../atmospherics/gasmixtures/reactions.dm | 3 +- .../components/fusion/hfr_main_processes.dm | 9 +- .../machinery/components/fusion/hfr_procs.dm | 24 -- code/modules/cargo/centcom_podlauncher.dm | 26 +- .../modules/clothing/suits/reactive_armour.dm | 37 +- code/modules/events/_event.dm | 5 +- code/modules/events/false_alarm.dm | 2 +- code/modules/events/immovable_rod.dm | 2 +- code/modules/events/mass_hallucination.dm | 159 +++++++-- .../modules/events/wizard/departmentrevolt.dm | 2 +- code/modules/events/wizard/madness.dm | 2 +- code/modules/hallucination/HUD.dm | 156 -------- code/modules/hallucination/_hallucination.dm | 319 ++++++++++------- code/modules/hallucination/airlock.dm | 90 ----- code/modules/hallucination/battle.dm | 166 +++++++++ code/modules/hallucination/body.dm | 56 +++ code/modules/hallucination/bolted_airlocks.dm | 127 +++++++ .../modules/hallucination/bubblegum_attack.dm | 99 ++++++ code/modules/hallucination/chat.dm | 169 --------- code/modules/hallucination/death.dm | 34 -- code/modules/hallucination/delusions.dm | 177 ++++++++++ code/modules/hallucination/fake_alert.dm | 107 ++++++ code/modules/hallucination/fake_chat.dm | 89 +++++ code/modules/hallucination/fake_death.dm | 82 +++++ code/modules/hallucination/fake_message.dm | 74 ++++ .../modules/hallucination/fake_plasmaflood.dm | 103 ++++++ code/modules/hallucination/fake_sound.dm | 276 +++++++++++++++ code/modules/hallucination/fire.dm | 89 ----- code/modules/hallucination/hazard.dm | 176 ++++------ code/modules/hallucination/hostile_mob.dm | 171 --------- code/modules/hallucination/hud_screw.dm | 26 ++ code/modules/hallucination/husk.dm | 31 -- .../modules/hallucination/inhand_fake_item.dm | 146 ++++++++ code/modules/hallucination/item.dm | 172 --------- .../modules/hallucination/nearby_fake_item.dm | 129 +++++++ code/modules/hallucination/on_fire.dm | 120 +++++++ code/modules/hallucination/plasma_flood.dm | 83 ----- code/modules/hallucination/polymorph.dm | 101 ------ .../hallucination/screwy_health_doll.dm | 80 +++++ code/modules/hallucination/shock.dm | 70 +++- code/modules/hallucination/sound.dm | 243 ------------- code/modules/hallucination/station_message.dm | 118 +++++++ code/modules/hallucination/stray_bullet.dm | 332 +++++++++++++++++- code/modules/hallucination/xeno_attack.dm | 103 ++++++ .../modules/mining/lavaland/megafauna_loot.dm | 3 +- code/modules/mob/living/carbon/carbon.dm | 78 ++-- .../mob/living/carbon/carbon_defines.dm | 7 +- code/modules/mob/living/carbon/examine.dm | 2 +- .../mob/living/carbon/human/examine.dm | 4 +- code/modules/mob/living/carbon/human/human.dm | 84 ++--- .../mob/living/carbon/human/human_defense.dm | 79 +---- .../mob/living/carbon/human/human_defines.dm | 2 - .../mob/living/carbon/human/species.dm | 11 +- .../carbon/human/species_types/plasmamen.dm | 8 + code/modules/mob/living/carbon/life.dm | 7 +- code/modules/mob/living/living.dm | 44 ++- code/modules/mob/living/silicon/silicon.dm | 8 +- .../simple_animal/guardian/types/fire.dm | 20 +- code/modules/mob/living/taste.dm | 2 +- code/modules/mob/mob_helpers.dm | 5 - code/modules/plumbing/plumbers/synthesizer.dm | 5 +- .../delamination_effects.dm | 8 +- .../power/supermatter/supermatter_process.dm | 21 +- .../projectile/special/hallucination.dm | 230 ------------ .../projectile/special/mindflayer.dm | 6 +- code/modules/reagents/chemistry/holder.dm | 2 +- .../chemistry/machinery/chem_dispenser.dm | 15 +- .../chemistry/reagents/alcohol_reagents.dm | 28 +- .../chemistry/reagents/drug_reagents.dm | 9 +- .../chemistry/reagents/medicine_reagents.dm | 32 +- .../reagents/pyrotechnic_reagents.dm | 9 - .../chemistry/reagents/toxin_reagents.dm | 20 +- .../reagents/withdrawal/generic_addictions.dm | 80 +++-- code/modules/surgery/bodyparts/_bodyparts.dm | 78 ++++ .../surgery/bodyparts/dismemberment.dm | 1 + code/modules/surgery/organs/lungs.dm | 2 +- code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/create_and_destroy.dm | 7 +- .../modules/unit_tests/hallucination_icons.dm | 70 ++++ .../cortical_borer/code/cortical_borer.dm | 7 +- .../code/cortical_borer_abilities.dm | 2 +- .../modules/morenarcotics/code/opium.dm | 23 +- strings/hallucination.json | 22 +- strings/tips.txt | 48 +-- tgstation.dme | 33 +- 128 files changed, 4066 insertions(+), 2488 deletions(-) create mode 100644 code/__HELPERS/hallucinations.dm create mode 100644 code/datums/status_effects/debuffs/hallucination.dm create mode 100644 code/datums/status_effects/debuffs/screwy_hud.dm delete mode 100644 code/modules/hallucination/HUD.dm delete mode 100644 code/modules/hallucination/airlock.dm create mode 100644 code/modules/hallucination/battle.dm create mode 100644 code/modules/hallucination/body.dm create mode 100644 code/modules/hallucination/bolted_airlocks.dm create mode 100644 code/modules/hallucination/bubblegum_attack.dm delete mode 100644 code/modules/hallucination/chat.dm delete mode 100644 code/modules/hallucination/death.dm create mode 100644 code/modules/hallucination/delusions.dm create mode 100644 code/modules/hallucination/fake_alert.dm create mode 100644 code/modules/hallucination/fake_chat.dm create mode 100644 code/modules/hallucination/fake_death.dm create mode 100644 code/modules/hallucination/fake_message.dm create mode 100644 code/modules/hallucination/fake_plasmaflood.dm create mode 100644 code/modules/hallucination/fake_sound.dm delete mode 100644 code/modules/hallucination/fire.dm delete mode 100644 code/modules/hallucination/hostile_mob.dm create mode 100644 code/modules/hallucination/hud_screw.dm delete mode 100644 code/modules/hallucination/husk.dm create mode 100644 code/modules/hallucination/inhand_fake_item.dm delete mode 100644 code/modules/hallucination/item.dm create mode 100644 code/modules/hallucination/nearby_fake_item.dm create mode 100644 code/modules/hallucination/on_fire.dm delete mode 100644 code/modules/hallucination/plasma_flood.dm delete mode 100644 code/modules/hallucination/polymorph.dm create mode 100644 code/modules/hallucination/screwy_health_doll.dm delete mode 100644 code/modules/hallucination/sound.dm create mode 100644 code/modules/hallucination/station_message.dm create mode 100644 code/modules/hallucination/xeno_attack.dm delete mode 100644 code/modules/projectiles/projectile/special/hallucination.dm create mode 100644 code/modules/unit_tests/hallucination_icons.dm diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index b7caf31c2cf..4e3f5a4cf28 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -25,10 +25,24 @@ ///from base of /obj/item/bodypart/proc/attach_limb(): (new_limb, special) allows you to fail limb attachment #define COMSIG_CARBON_ATTACH_LIMB "carbon_attach_limb" #define COMPONENT_NO_ATTACH (1<<0) -#define COMSIG_CARBON_REMOVE_LIMB "carbon_remove_limb" //from base of /obj/item/bodypart/proc/drop_limb(lost_limb, dismembered) #define COMSIG_BODYPART_GAUZED "bodypart_gauzed" // from /obj/item/bodypart/proc/apply_gauze(/obj/item/stack/gauze) #define COMSIG_BODYPART_GAUZE_DESTROYED "bodypart_degauzed" // from [/obj/item/bodypart/proc/seep_gauze] when it runs out of absorption +/// Called from update_health_hud, whenever a bodypart is being updated on the health doll +#define COMSIG_BODYPART_UPDATING_HEALTH_HUD "bodypart_updating_health_hud" + /// Return to override that bodypart's health hud with your own icon + #define COMPONENT_OVERRIDE_BODYPART_HEALTH_HUD (1<<0) + +/// Called from /obj/item/bodypart/check_for_injuries (mob/living/carbon/examiner, list/check_list) +#define COMSIG_BODYPART_CHECKED_FOR_INJURY "bodypart_injury_checked" +/// Called from /obj/item/bodypart/check_for_injuries (obj/item/bodypart/examined, list/check_list) +#define COMSIG_CARBON_CHECKING_BODYPART "carbon_checking_injury" + +/// Called from carbon losing a limb /obj/item/bodypart/proc/drop_limb(obj/item/bodypart/lost_limb, dismembered) +#define COMSIG_CARBON_REMOVE_LIMB "carbon_remove_limb" +/// Called from bodypart being removed /obj/item/bodypart/proc/drop_limb(mob/living/carbon/old_owner, dismembered) +#define COMSIG_BODYPART_REMOVED "bodypart_removed" + ///from base of mob/living/carbon/soundbang_act(): (list(intensity)) #define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" ///from /item/organ/proc/Insert() (/obj/item/organ/) @@ -62,6 +76,10 @@ #define COMSIG_CARBON_LOSE_TRAUMA "carbon_lose_trauma" ///Called when a carbon updates their health (source = carbon) #define COMSIG_CARBON_HEALTH_UPDATE "carbon_health_update" +///Called when a carbon's health hud is updated. (source = carbon, shown_health_amount) +#define COMSIG_CARBON_UPDATING_HEALTH_HUD "carbon_health_hud_update" + /// Return if you override the carbon's health hud with something else + #define COMPONENT_OVERRIDE_HEALTH_HUD (1<<0) ///Called when a carbon updates their sanity (source = carbon) #define COMSIG_CARBON_SANITY_UPDATE "carbon_sanity_update" ///Called when a carbon breathes, before the breath has actually occured diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 5464fffdcec..6c7e26f6641 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -93,6 +93,11 @@ #define COMSIG_AIRLOCK_CLOSE "airlock_close" ///from /obj/machinery/door/airlock/set_bolt(): #define COMSIG_AIRLOCK_SET_BOLT "airlock_set_bolt" +///from /obj/machinery/door/airlock/bumpopen(), to the carbon who bumped: (airlock) +#define COMSIG_CARBON_BUMPED_AIRLOCK_OPEN "carbon_bumped_airlock_open" + /// Return to stop the door opening on bump. + #define STOP_BUMP (1<<0) + // /obj/item signals ///from base of obj/item/equipped(): (/mob/equipper, slot) diff --git a/code/__DEFINES/events.dm b/code/__DEFINES/events.dm index 2da887e95ad..d189969b8e3 100644 --- a/code/__DEFINES/events.dm +++ b/code/__DEFINES/events.dm @@ -32,3 +32,6 @@ #define EVENT_CATEGORY_SPACE "Space Threats" ///Events summoned by a wizard #define EVENT_CATEGORY_WIZARD "Wizard" + +/// Return from admin setup to stop the event from triggering entirely. +#define ADMIN_CANCEL_EVENT "cancel event" diff --git a/code/__DEFINES/research/anomalies.dm b/code/__DEFINES/research/anomalies.dm index 35ee0ec05ff..90be8dbb602 100644 --- a/code/__DEFINES/research/anomalies.dm +++ b/code/__DEFINES/research/anomalies.dm @@ -11,3 +11,6 @@ #define FLUX_NO_EXPLOSION 0 #define FLUX_EXPLOSIVE 1 #define FLUX_LOW_EXPLOSIVE 2 + +/// Chance of anomalies moving every process tick +#define ANOMALY_MOVECHANCE 45 diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 13220043713..68b65c54d65 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -38,3 +38,8 @@ #define STASIS_CHEMICAL_EFFECT "stasis_chemical" #define STASIS_SHAPECHANGE_EFFECT "stasis_shapechange" + +#define adjust_hallucinations(duration) adjust_timed_status_effect(duration, /datum/status_effect/hallucination) +#define adjust_hallucinations_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/hallucination, up_to) +#define set_hallucinations(duration) set_timed_status_effect(duration, /datum/status_effect/hallucination) +#define set_hallucinations_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/hallucination, TRUE) diff --git a/code/__DEFINES/supermatter.dm b/code/__DEFINES/supermatter.dm index e3c68f7265f..6b9d52d665e 100644 --- a/code/__DEFINES/supermatter.dm +++ b/code/__DEFINES/supermatter.dm @@ -36,7 +36,7 @@ #define MATTER_POWER_CONVERSION 10 //Crystal converts 1/this value of stored matter into energy. //These would be what you would get at point blank, decreases with distance -#define DETONATION_HALLUCINATION 600 +#define DETONATION_HALLUCINATION 20 MINUTES /// All humans within this range will be irradiated #define DETONATION_RADIATION_RANGE 20 diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm index c48dafc336a..4c4bec0e2a1 100644 --- a/code/__DEFINES/text.dm +++ b/code/__DEFINES/text.dm @@ -49,5 +49,7 @@ #define CULT_SHUTTLE_CURSE "cult_shuttle_curse.json" /// File location for eigenstasium lines #define EIGENSTASIUM_FILE "eigenstasium.json" +/// File location for hallucination lines +#define HALLUCINATION_FILE "hallucination.json" /// File location for ninja lines #define NINJA_FILE "ninja.json" diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 7e0166096d0..3a04a8817cd 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -312,6 +312,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_NICE_SHOT "nice_shot" /// prevents the damage done by a brain tumor #define TRAIT_TUMOR_SUPPRESSED "brain_tumor_suppressed" +/// Prevents hallucinations from the hallucination brain trauma (RDS) +#define TRAIT_HALLUCINATION_SUPPRESSED "hallucination_suppressed" /// overrides the update_fire proc to always add fire (for lava) #define TRAIT_PERMANENTLY_ONFIRE "permanently_onfire" /// Galactic Common Sign Language @@ -584,8 +586,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_EXTROVERT "extrovert" #define TRAIT_INTROVERT "introvert" #define TRAIT_ANXIOUS "anxious" -#define TRAIT_INSANITY "insanity" #define TRAIT_SMOKER "smoker" + /// Gives you the Shifty Eyes quirk, rarely making people who examine you think you examined them back even when you didn't #define TRAIT_SHIFTY_EYES "shifty_eyes" diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 0dcda8a6fa6..72799d7fcb9 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -126,12 +126,13 @@ #define VV_HK_GIVE_SPEECH_IMPEDIMENT "impede_speech" #define VV_HK_ADD_MOOD "addmood" #define VV_HK_REMOVE_MOOD "removemood" +#define VV_HK_GIVE_HALLUCINATION "give_hallucination" +#define VV_HK_GIVE_DELUSION_HALLUCINATION "give_hallucination_delusion" // /mob/living/carbon #define VV_HK_MAKE_AI "aiify" #define VV_HK_MODIFY_BODYPART "mod_bodypart" #define VV_HK_MODIFY_ORGANS "organs_modify" -#define VV_HK_HALLUCINATION "force_hallucinate" #define VV_HK_MARTIAL_ART "give_martial_art" #define VV_HK_GIVE_TRAUMA "give_trauma" #define VV_HK_CURE_TRAUMA "cure_trauma" diff --git a/code/__HELPERS/hallucinations.dm b/code/__HELPERS/hallucinations.dm new file mode 100644 index 00000000000..1c860ec616b --- /dev/null +++ b/code/__HELPERS/hallucinations.dm @@ -0,0 +1,236 @@ +/// A global list of all ongoing hallucinations, primarily for easy access to be able to stop (delete) hallucinations. +GLOBAL_LIST_EMPTY(all_ongoing_hallucinations) + +// Macro wrapper for _cause_hallucination so we can cheat in named arguments, like AddComponent. +/** + * Causes a hallucination of a certain type to the mob. + * + * First argument is always the type of halllucination, a /datum/hallucination, required. + * second argument is always the key source of the hallucination, used for admin logging, required. + * + * Additionally, named arguments are supported for passing them forward to the created hallucination's new(). + */ +#define cause_hallucination(arguments...) _cause_hallucination(list(##arguments)) + +/// Unless you need this for an explicit reason, use the cause_hallucination wrapper. +/mob/living/proc/_cause_hallucination(list/raw_args) + if(!length(raw_args)) + CRASH("cause_hallucination called with no arguments.") + + var/datum/hallucination/hallucination_type = raw_args[1] // first arg is the type always + if(!ispath(hallucination_type)) + CRASH("cause_hallucination was given a non-hallucination type.") + + var/hallucination_source = raw_args[2] // and second arg, the source + var/datum/hallucination/new_hallucination + + if(length(raw_args) > 2) + var/list/passed_args = raw_args.Copy(3) + passed_args.Insert(1, src) + + new_hallucination = new hallucination_type(arglist(passed_args)) + else + new_hallucination = new hallucination_type(src) + + // For some reason, we qdel'd in New, maybe something went wrong. + if(QDELETED(new_hallucination)) + return + // It's not guaranteed that the hallucination passed can successfully be initiated. + // This means there may be cases where someone should have a hallucination but nothing happens, + // notably if you pass a randomly picked hallucination type into this. + // Maybe there should be a separate proc to reroll on failure? + if(!new_hallucination.start()) + qdel(new_hallucination) + return + + investigate_log("was afflicted with a hallucination of type [hallucination_type] by: [hallucination_source]. \ + ([new_hallucination.feedback_details])", INVESTIGATE_HALLUCINATIONS) + return new_hallucination + +/** + * Emits a hallucinating pulse around the passed atom. + * Affects everyone in the passed radius who can view the center, + * except for those with TRAIT_MADNESS_IMMUNE, or those who are blind. + * + * center - required, the center of the pulse + * radius - the radius around that the pulse reaches + * hallucination_duration - how much hallucination is added by the pulse. reduced based on distance to the center. + * hallucination_max_duration - a cap on how much hallucination can be added + * optional_messages - optional list of messages passed. Those affected by pulses will be given one of the messages in said list. + */ +/proc/visible_hallucination_pulse(atom/center, radius = 7, hallucination_duration = 50 SECONDS, hallucination_max_duration, list/optional_messages) + for(var/mob/living/nearby_living in view(center, radius)) + if(HAS_TRAIT(nearby_living, TRAIT_MADNESS_IMMUNE) || (nearby_living.mind && HAS_TRAIT(nearby_living.mind, TRAIT_MADNESS_IMMUNE))) + continue + + if(nearby_living.is_blind()) + continue + + // Everyone else gets hallucinations. + var/dist = sqrt(1 / max(1, get_dist(nearby_living, center))) + nearby_living.adjust_hallucinations_up_to(hallucination_duration * dist, hallucination_max_duration) + if(length(optional_messages)) + 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()) + +/// Generates the global weighted list of random hallucinations. +/proc/generate_hallucination_weighted_list() + var/list/weighted_list = list() + + for(var/datum/hallucination/hallucination_type as anything in typesof(/datum/hallucination)) + if(hallucination_type == initial(hallucination_type.abstract_hallucination_parent)) + continue + var/weight = initial(hallucination_type.random_hallucination_weight) + if(weight <= 0) + continue + + weighted_list[hallucination_type] = weight + + return weighted_list + +/// 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] + + to_chat(usr, span_boldnotice("The total weight of the hallucination weighted list is [total_weight].")) + return total_weight + +/// Debug verb for getting the weight of each distinct type within the random_hallucination_weighted_list +/client/proc/debug_hallucination_weighted_list_per_type() + set name = "Show Hallucination Weights" + set category = "Debug" + + var/header = "Type Weight 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 + + // Otherwise we moved onto the next hallucination subtype so we can stop + 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 + + // Sort by weight descending, where weight is the values (not the keys). We assoc_to_keys later to get JUST the text + all_weights = sortTim(all_weights, /proc/cmp_numeric_dsc, associative = TRUE) + + var/page_style = "" + var/page_contents = "[page_style][header][jointext(assoc_to_keys(all_weights), "")]
" + var/datum/browser/popup = new(mob, "hallucinationdebug", "Hallucination Weights", 600, 400) + popup.set_content(page_contents) + popup.open() + +/// Gets a random subtype of the passed hallucination type that has a random_hallucination_weight > 0. +/// If no subtype is passed, it will get any random hallucination subtype that is not abstract and has weight > 0. +/// This can be used instead of picking from the global weighted list to just get a random valid hallucination. +/proc/get_random_valid_hallucination_subtype(passed_type = /datum/hallucination) + if(!ispath(passed_type, /datum/hallucination)) + CRASH("get_random_valid_hallucination_subtype - get_random_valid_hallucination_subtype passed not a hallucination subtype.") + + for(var/datum/hallucination/hallucination_type as anything in shuffle(subtypesof(passed_type))) + if(initial(hallucination_type.abstract_hallucination_parent) == hallucination_type) + continue + if(initial(hallucination_type.random_hallucination_weight) <= 0) + continue + + return hallucination_type + + return null + +/// Helper to give the passed mob the ability to select a hallucination from the list of all hallucination subtypes. +/proc/select_hallucination_type(mob/user, message = "Select a hallucination subtype", title = "Choose Hallucination") + var/static/list/hallucinations + if(!hallucinations) + hallucinations = typesof(/datum/hallucination) + for(var/datum/hallucination/hallucination_type as anything in hallucinations) + if(initial(hallucination_type.abstract_hallucination_parent) == hallucination_type) + hallucinations -= hallucination_type + + var/chosen = tgui_input_list(user, message, title, hallucinations) + if(!chosen || !ispath(chosen, /datum/hallucination)) + return null + + return chosen + +/// Helper to give the passed mob the ability to create a delusion hallucination (even a custom one). +/// Returns a list of arguments - pass these to _cause_hallucination to cause the desired hallucination +/proc/create_delusion(mob/user) + var/static/list/delusions + if(!delusions) + delusions = typesof(/datum/hallucination/delusion) + for(var/datum/hallucination/delusion_type as anything in delusions) + if(initial(delusion_type.abstract_hallucination_parent) == delusion_type) + delusions -= delusion_type + + var/chosen = tgui_input_list(user, "Select a delusion type. Custom will allow for custom icon entry.", "Select Delusion", delusions) + if(!chosen || !ispath(chosen, /datum/hallucination/delusion)) + return + + var/list/delusion_args = list() + var/static/list/options = list("Yes", "No") + var/duration = tgui_input_number(user, "How long should it last in seconds?", "Delusion: Duration", max_value = INFINITY, min_value = 1, default = 30) + var/affects_us = (tgui_alert(user, "Should they see themselves as the delusion?", "Delusion: Affects us", options) == "Yes") + var/affects_others = (tgui_alert(user, "Should they see everyone else delusion?", "Delusion: Affects others", options) == "Yes") + var/skip_nearby = (tgui_alert(user, "Should the delusion only affect people outside of their view?", "Delusion: Skip in view", options) == "Yes") + var/play_wabbajack = (tgui_alert(user, "Play the wabbajack sound when it happens?", "Delusion: Wabbajack sound", options) == "Yes") + + delusion_args = list( + chosen, + "forced delusion", + duration = duration * 1 SECONDS, + affects_us = affects_us, + affects_others = affects_others, + skip_nearby = skip_nearby, + play_wabbajack = play_wabbajack, + ) + + if(ispath(chosen, /datum/hallucination/delusion/custom)) + var/custom_icon_file = input(user, "Pick file for custom delusion:", "Custom Delusion: File") as null|file + if(!custom_icon_file) + return + + var/custom_icon_state = tgui_input_text(user, "What icon state do you wanna use from the file?", "Custom Delusion: Icon State") + if(!custom_icon_state) + return + + var/custom_name = tgui_input_text(user, "What name should it show up as? (Can be empty)", "Custom Delusion: Name") + + delusion_args += list( + custom_icon_file = custom_icon_file, + custom_icon_state = custom_icon_state, + custom_name = custom_name, + ) + + return delusion_args + +/// Lines the bubblegum hallucinatoin uses when it pops up +#define BUBBLEGUM_HALLUCINATION_LINES list( \ + span_colossus("I AM IMMORTAL."), \ + span_colossus("I SHALL TAKE YOUR WORLD."), \ + span_colossus("I SEE YOU."), \ + span_colossus("YOU CANNOT ESCAPE ME FOREVER."), \ + span_colossus("NOTHING CAN HOLD ME."), \ + ) diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index 678553e09d4..749b47de7da 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -107,7 +107,7 @@ GLOBAL_LIST_INIT(phobia_objs, list( )), "spiders" = typecacheof(list(/obj/structure/spider)), "security" = typecacheof(list( - /obj/effect/hallucination/simple/securitron, + /obj/effect/client_image_holder/securitron, /obj/item/clothing/under/rank/security/detective, /obj/item/clothing/under/rank/security/head_of_security, /obj/item/clothing/under/rank/security/officer, diff --git a/code/datums/brain_damage/hypnosis.dm b/code/datums/brain_damage/hypnosis.dm index 6e540489f93..d0e0bc2845a 100644 --- a/code/datums/brain_damage/hypnosis.dm +++ b/code/datums/brain_damage/hypnosis.dm @@ -58,11 +58,15 @@ /datum/brain_trauma/hypnosis/on_life(delta_time, times_fired) ..() if(DT_PROB(1, delta_time)) - switch(rand(1,2)) - if(1) - to_chat(owner, span_hypnophrase("...[lowertext(hypnotic_phrase)]...")) - if(2) - new /datum/hallucination/chat(owner, TRUE, FALSE, span_hypnophrase("[hypnotic_phrase]")) + if(prob(50)) + to_chat(owner, span_hypnophrase("...[lowertext(hypnotic_phrase)]...")) + else + owner.cause_hallucination( \ + /datum/hallucination/chat, \ + "hypnosis", \ + force_radio = TRUE, \ + specific_message = span_hypnophrase("[hypnotic_phrase]"), \ + ) /datum/brain_trauma/hypnosis/handle_hearing(datum/source, list/hearing_args) hearing_args[HEARING_RAW_MESSAGE] = target_phrase.Replace(hearing_args[HEARING_RAW_MESSAGE], span_hypnophrase("$1")) diff --git a/code/datums/brain_damage/magic.dm b/code/datums/brain_damage/magic.dm index 70f3fa73497..31d02b408ac 100644 --- a/code/datums/brain_damage/magic.dm +++ b/code/datums/brain_damage/magic.dm @@ -73,12 +73,16 @@ scan_desc = "extra-sensory paranoia" gain_text = "You feel like something wants to kill you..." lose_text = "You no longer feel eyes on your back." - var/obj/effect/hallucination/simple/stalker_phantom/stalker + var/obj/effect/client_image_holder/stalker_phantom/stalker var/close_stalker = FALSE //For heartbeat +/datum/brain_trauma/magic/stalker/Destroy() + QDEL_NULL(stalker) + return ..() + /datum/brain_trauma/magic/stalker/on_gain() create_stalker() - ..() + return ..() /datum/brain_trauma/magic/stalker/proc/create_stalker() var/turf/stalker_source = locate(owner.x + pick(-12, 12), owner.y + pick(-12, 12), owner.z) //random corner @@ -86,7 +90,7 @@ /datum/brain_trauma/magic/stalker/on_lose() QDEL_NULL(stalker) - ..() + return ..() /datum/brain_trauma/magic/stalker/on_life(delta_time, times_fired) // Dead and unconscious people are not interesting to the psychic stalker. @@ -115,7 +119,7 @@ close_stalker = FALSE ..() -/obj/effect/hallucination/simple/stalker_phantom +/obj/effect/client_image_holder/stalker_phantom name = "???" desc = "It's coming closer..." image_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' diff --git a/code/datums/brain_damage/mild.dm b/code/datums/brain_damage/mild.dm index 09262e5940d..52e4a0b2766 100644 --- a/code/datums/brain_damage/mild.dm +++ b/code/datums/brain_damage/mild.dm @@ -8,16 +8,20 @@ name = "Hallucinations" desc = "Patient suffers constant hallucinations." scan_desc = "schizophrenia" - gain_text = "You feel your grip on reality slipping..." - lose_text = "You feel more grounded." + gain_text = span_warning("You feel your grip on reality slipping...") + lose_text = span_notice("You feel more grounded.") /datum/brain_trauma/mild/hallucinations/on_life(delta_time, times_fired) - owner.hallucination = min(owner.hallucination + 10, 50) - ..() + if(owner.stat != CONSCIOUS || owner.IsSleeping() || owner.IsUnconscious()) + return + if(HAS_TRAIT(owner, TRAIT_HALLUCINATION_SUPPRESSED)) + return + + owner.adjust_hallucinations_up_to(10 SECONDS * delta_time, 100 SECONDS) /datum/brain_trauma/mild/hallucinations/on_lose() - owner.hallucination = 0 - ..() + owner.remove_status_effect(/datum/status_effect/hallucination) + return ..() /datum/brain_trauma/mild/stuttering name = "Stuttering" @@ -109,17 +113,15 @@ lose_text = "You no longer feel perfectly healthy." /datum/brain_trauma/mild/healthy/on_gain() - owner.set_screwyhud(SCREWYHUD_HEALTHY) - ..() + owner.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + return ..() /datum/brain_trauma/mild/healthy/on_life(delta_time, times_fired) - owner.set_screwyhud(SCREWYHUD_HEALTHY) //just in case of hallucinations owner.adjustStaminaLoss(-2.5 * delta_time) //no pain, no fatigue - ..() /datum/brain_trauma/mild/healthy/on_lose() - owner.set_screwyhud(SCREWYHUD_NONE) - ..() + owner.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + return ..() /datum/brain_trauma/mild/muscle_weakness name = "Muscle Weakness" diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm index 9aa71f91610..492712d39c4 100644 --- a/code/datums/brain_damage/severe.dm +++ b/code/datums/brain_damage/severe.dm @@ -200,7 +200,7 @@ if(3, 4) if(high_stress) to_chat(owner, span_warning("You're going mad with loneliness!")) - owner.hallucination += 30 + owner.adjust_hallucinations(60 SECONDS) else to_chat(owner, span_warning("You feel really lonely...")) @@ -307,7 +307,7 @@ scan_desc = "dyslexia" gain_text = "You have trouble reading or writing..." lose_text = "Your suddenly remember how to read and write." - + /datum/brain_trauma/severe/dyslexia/on_gain() ADD_TRAIT(owner, TRAIT_ILLITERATE, TRAUMA_TRAIT) ..() diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm index eddf9156af7..ee110a96853 100644 --- a/code/datums/brain_damage/special.dm +++ b/code/datums/brain_damage/special.dm @@ -87,61 +87,64 @@ if(!second_turf) return - var/obj/effect/hallucination/simple/bluespace_stream/first = new(first_turf, owner) - var/obj/effect/hallucination/simple/bluespace_stream/second = new(second_turf, owner) + var/obj/effect/client_image_holder/bluespace_stream/first = new(first_turf, owner) + var/obj/effect/client_image_holder/bluespace_stream/second = new(second_turf, owner) first.linked_to = second second.linked_to = first - first.seer = owner - second.seer = owner -/obj/effect/hallucination/simple/bluespace_stream +/obj/effect/client_image_holder/bluespace_stream name = "bluespace stream" desc = "You see a hidden pathway through bluespace..." image_icon = 'icons/effects/effects.dmi' image_state = "bluestream" image_layer = ABOVE_MOB_LAYER image_plane = GAME_PLANE_UPPER - var/obj/effect/hallucination/simple/bluespace_stream/linked_to - var/mob/living/carbon/seer + var/obj/effect/client_image_holder/bluespace_stream/linked_to -/obj/effect/hallucination/simple/bluespace_stream/Initialize(mapload) +/obj/effect/client_image_holder/bluespace_stream/Initialize(mapload, list/mobs_which_see_us) . = ..() - QDEL_IN(src, 300) + QDEL_IN(src, 30 SECONDS) -/obj/effect/hallucination/simple/bluespace_stream/Destroy() +/obj/effect/client_image_holder/bluespace_stream/Destroy() if(!QDELETED(linked_to)) qdel(linked_to) linked_to = null - seer = null return ..() -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/effect/hallucination/simple/bluespace_stream/attack_hand(mob/user, list/modifiers) - if(user != seer || !linked_to) +/obj/effect/client_image_holder/bluespace_stream/attack_hand(mob/user, list/modifiers) + . = ..() + if(.) return + + if(!(user in who_sees_us) || !linked_to) + return + var/slip_in_message = pick("slides sideways in an odd way, and disappears", "jumps into an unseen dimension",\ "sticks one leg straight out, wiggles [user.p_their()] foot, and is suddenly gone", "stops, then blinks out of reality", \ "is pulled into an invisible vortex, vanishing from sight") var/slip_out_message = pick("silently fades in", "leaps out of thin air","appears", "walks out of an invisible doorway",\ "slides out of a fold in spacetime") + to_chat(user, span_notice("You try to align with the bluespace stream...")) - if(do_after(user, 20, target = src)) - var/turf/source_turf = get_turf(src) - var/turf/destination_turf = get_turf(linked_to) + if(!do_after(user, 2 SECONDS, target = src)) + return - new /obj/effect/temp_visual/bluespace_fissure(source_turf) - new /obj/effect/temp_visual/bluespace_fissure(destination_turf) + var/turf/source_turf = get_turf(src) + var/turf/destination_turf = get_turf(linked_to) - user.visible_message(span_warning("[user] [slip_in_message]."), null, null, null, user) + new /obj/effect/temp_visual/bluespace_fissure(source_turf) + new /obj/effect/temp_visual/bluespace_fissure(destination_turf) - if(!do_teleport(user, destination_turf, no_effects = TRUE)) - user.visible_message(span_warning("[user] [slip_out_message], ending up exactly where they left."), null, null, null, user) - return + user.visible_message(span_warning("[user] [slip_in_message]."), ignored_mobs = user) + if(do_teleport(user, destination_turf, no_effects = TRUE)) user.visible_message(span_warning("[user] [slip_out_message]."), span_notice("...and find your way to the other side.")) + else + user.visible_message(span_warning("[user] [slip_out_message], ending up exactly where they left."), span_notice("...and find yourself where you started?")) -/obj/effect/hallucination/simple/bluespace_stream/attack_tk(mob/user) + +/obj/effect/client_image_holder/bluespace_stream/attack_tk(mob/user) to_chat(user, span_warning("\The [src] actively rejects your mind, and the bluespace energies surrounding it disrupt your telekinesis!")) return COMPONENT_CANCEL_ATTACK_CHAIN @@ -353,62 +356,73 @@ gain_text = "Justice is coming for you." lose_text = "You were absolved for your crimes." random_gain = FALSE - var/obj/effect/hallucination/simple/securitron/beepsky + /// A ref to our fake beepsky image that we chase the owner with + var/obj/effect/client_image_holder/securitron/beepsky + +/datum/brain_trauma/special/beepsky/Destroy() + QDEL_NULL(beepsky) + return ..() /datum/brain_trauma/special/beepsky/on_gain() create_securitron() - ..() + return ..() /datum/brain_trauma/special/beepsky/proc/create_securitron() + QDEL_NULL(beepsky) var/turf/where = locate(owner.x + pick(-12, 12), owner.y + pick(-12, 12), owner.z) beepsky = new(where, owner) - beepsky.victim = owner /datum/brain_trauma/special/beepsky/on_lose() QDEL_NULL(beepsky) - ..() + return ..() /datum/brain_trauma/special/beepsky/on_life() if(QDELETED(beepsky) || !beepsky.loc || beepsky.z != owner.z) - QDEL_NULL(beepsky) if(prob(30)) create_securitron() else return + if(get_dist(owner, beepsky) >= 10 && prob(20)) - QDEL_NULL(beepsky) create_securitron() + if(owner.stat != CONSCIOUS) if(prob(20)) owner.playsound_local(beepsky, 'sound/voice/beepsky/iamthelaw.ogg', 50) return + if(get_dist(owner, beepsky) <= 1) owner.playsound_local(owner, 'sound/weapons/egloves.ogg', 50) owner.visible_message(span_warning("[owner]'s body jerks as if it was shocked."), span_userdanger("You feel the fist of the LAW.")) owner.take_bodypart_damage(0,0,rand(40, 70)) QDEL_NULL(beepsky) + if(prob(20) && get_dist(owner, beepsky) <= 8) owner.playsound_local(beepsky, 'sound/voice/beepsky/criminal.ogg', 40) - ..() -/obj/effect/hallucination/simple/securitron +/obj/effect/client_image_holder/securitron name = "Securitron" desc = "The LAW is coming." image_icon = 'icons/mob/silicon/aibots.dmi' image_state = "secbot-c" - var/victim -/obj/effect/hallucination/simple/securitron/Initialize(mapload) +/obj/effect/client_image_holder/securitron/Initialize(mapload) . = ..() name = pick("Officer Beepsky", "Officer Johnson", "Officer Pingsky") START_PROCESSING(SSfastprocess, src) -/obj/effect/hallucination/simple/securitron/process() - if(prob(60)) - forceMove(get_step_towards(src, victim)) - if(prob(5)) - to_chat(victim, span_name("[name] exclaims, \"Level 10 infraction alert!\"")) - -/obj/effect/hallucination/simple/securitron/Destroy() +/obj/effect/client_image_holder/securitron/Destroy() STOP_PROCESSING(SSfastprocess,src) return ..() + +/obj/effect/client_image_holder/securitron/process() + if(prob(40)) + return + + var/mob/victim = pick(who_sees_us) + forceMove(get_step_towards(src, victim)) + if(prob(5)) + var/beepskys_cry = "Level 10 infraction alert!" + to_chat(victim, "[name] exclaims, \"[beepskys_cry]\"") + if(victim.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat)) + victim.create_chat_message(src, raw_message = beepskys_cry, spans = list("robotic")) diff --git a/code/datums/diseases/advance/symptoms/hallucigen.dm b/code/datums/diseases/advance/symptoms/hallucigen.dm index cbf594d5e0f..96466782b66 100644 --- a/code/datums/diseases/advance/symptoms/hallucigen.dm +++ b/code/datums/diseases/advance/symptoms/hallucigen.dm @@ -61,4 +61,4 @@ to_chat(M, span_userdanger("[pick("Oh, your head...", "Your head pounds.", "They're everywhere! Run!", "Something in the shadows...")]")) else to_chat(M, span_notice("[pick(healthy_messages)]")) - M.hallucination += (45 * power) + M.adjust_hallucinations(90 SECONDS * power) diff --git a/code/datums/diseases/advance/symptoms/sensory.dm b/code/datums/diseases/advance/symptoms/sensory.dm index 2eab2b6a5bb..b0a5a7787a5 100644 --- a/code/datums/diseases/advance/symptoms/sensory.dm +++ b/code/datums/diseases/advance/symptoms/sensory.dm @@ -57,7 +57,8 @@ M.reagents.remove_reagent(/datum/reagent/toxin/mindbreaker, 5) if(M.reagents.has_reagent(/datum/reagent/toxin/histamine)) M.reagents.remove_reagent(/datum/reagent/toxin/histamine, 5) - M.hallucination = max(0, M.hallucination - 10) + + M.adjust_hallucinations(-20 SECONDS) if(A.stage >= 5) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, -3) diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm index 07cc4833ee7..8e0f32d4d36 100644 --- a/code/datums/mutations/body.dm +++ b/code/datums/mutations/body.dm @@ -104,7 +104,7 @@ if(DT_PROB(2.5, delta_time) && owner.stat == CONSCIOUS) owner.emote("scream") if(prob(25)) - owner.hallucination += 20 + owner.adjust_hallucinations(40 SECONDS) //Dwarfism shrinks your body and lets you pass tables. /datum/mutation/human/dwarfism diff --git a/code/datums/quirks/negative.dm b/code/datums/quirks/negative.dm index 7dd8bac979e..3e0046dfb07 100644 --- a/code/datums/quirks/negative.dm +++ b/code/datums/quirks/negative.dm @@ -488,28 +488,41 @@ /datum/quirk/insanity name = "Reality Dissociation Syndrome" - desc = "You suffer from a severe disorder that causes very vivid hallucinations. Mindbreaker toxin can suppress its effects, and you are immune to mindbreaker's hallucinogenic properties. THIS IS NOT A LICENSE TO GRIEF." + desc = "You suffer from a severe disorder that causes very vivid hallucinations. \ + Mindbreaker toxin can suppress its effects, and you are immune to mindbreaker's hallucinogenic properties. \ + THIS IS NOT A LICENSE TO GRIEF." icon = "grin-tongue-wink" value = -8 - mob_trait = TRAIT_INSANITY - gain_text = "..." - lose_text = "You feel in tune with the world again." + gain_text = span_userdanger("...") + lose_text = span_notice("You feel in tune with the world again.") medical_record_text = "Patient suffers from acute Reality Dissociation Syndrome and experiences vivid hallucinations." hardcore_value = 6 - processing_quirk = TRUE -/datum/quirk/insanity/process(delta_time) - if(quirk_holder.stat != CONSCIOUS || quirk_holder.IsSleeping() || quirk_holder.IsUnconscious()) +/datum/quirk/insanity/add() + if(!iscarbon(quirk_holder)) return + var/mob/living/carbon/carbon_quirk_holder = quirk_holder - if(DT_PROB(2, delta_time)) - quirk_holder.hallucination += rand(10, 25) + // 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. + var/datum/brain_trauma/mild/hallucinations/added_trauma = new() + added_trauma.resilience = TRAUMA_RESILIENCE_ABSOLUTE + added_trauma.name = name + added_trauma.desc = medical_record_text + added_trauma.scan_desc = lowertext(name) + added_trauma.gain_text = null + added_trauma.lose_text = null -/datum/quirk/insanity/post_add() //I don't /think/ we'll need this but for newbies who think "roleplay as insane" = "license to kill" it's probably a good thing to have + carbon_quirk_holder.gain_trauma(added_trauma) + +/datum/quirk/insanity/post_add() if(!quirk_holder.mind || quirk_holder.mind.special_role) return - to_chat(quirk_holder, "Please note that your dissociation syndrome does NOT give you the right to attack people or otherwise cause any interference to \ - the round. You are not an antagonist, and the rules will treat you the same as other crewmembers.") + // I don't /think/ we'll need this, but for newbies who think "roleplay as insane" = "license to kill", + // it's probably a good thing to have. + to_chat(quirk_holder, "Please note that your [lowertext(name)] does NOT give you the right to attack people or otherwise cause any interference to \ + the round. You are not an antagonist, and the rules will treat you the same as other crewmembers.") /datum/quirk/social_anxiety name = "Social Anxiety" diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index c8af3967e64..a1f2fb0a69f 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -379,17 +379,30 @@ . = ..() to_chat(owner, "RIP AND TEAR") SEND_SOUND(owner, sound('sound/hallucinations/veryfar_noise.ogg')) - new /datum/hallucination/delusion(owner, forced = TRUE, force_kind = "demon", duration = duration, skip_nearby = FALSE) - chainsaw = new(get_turf(owner)) - owner.log_message("entered a blood frenzy", LOG_ATTACK) - ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT) + owner.cause_hallucination( \ + /datum/hallucination/delusion/preset/demon, \ + "[id] status effect", \ + duration = duration, \ + affects_us = FALSE, \ + affects_others = TRUE, \ + skip_nearby = FALSE, \ + play_wabbajack = FALSE, \ + ) + owner.drop_all_held_items() + + chainsaw = new(get_turf(owner)) + ADD_TRAIT(chainsaw, TRAIT_NODROP, CHAINSAW_FRENZY_TRAIT) owner.put_in_hands(chainsaw, forced = TRUE) chainsaw.attack_self(owner) - owner.reagents.add_reagent(/datum/reagent/medicine/adminordrazine,25) + + owner.log_message("entered a blood frenzy", LOG_ATTACK) + owner.reagents.add_reagent(/datum/reagent/medicine/adminordrazine, 25) to_chat(owner, span_warning("KILL, KILL, KILL! YOU HAVE NO ALLIES ANYMORE, KILL THEM ALL!")) + var/datum/client_colour/colour = owner.add_client_colour(/datum/client_colour/bloodlust) QDEL_IN(colour, 1.1 SECONDS) + return TRUE /datum/status_effect/mayhem/on_remove() . = ..() diff --git a/code/datums/status_effects/debuffs/hallucination.dm b/code/datums/status_effects/debuffs/hallucination.dm new file mode 100644 index 00000000000..123f2332bab --- /dev/null +++ b/code/datums/status_effects/debuffs/hallucination.dm @@ -0,0 +1,91 @@ +/// Hallucination status effect. How most hallucinations end up happening. +/// Hallucinations are drawn from the global weighted list, random_hallucination_weighted_list +/datum/status_effect/hallucination + id = "hallucination" + alert_type = null + tick_interval = 2 SECONDS + /// Can this hallucination apply to silicons? + var/affects_silicons = FALSE + /// The lower range of when the next hallucination will trigger after one occurs. + var/lower_tick_interval = 10 SECONDS + /// The upper range of when the next hallucination will trigger after one occurs. + var/upper_tick_interval = 60 SECONDS + /// The cooldown for when the next hallucination can occur + COOLDOWN_DECLARE(hallucination_cooldown) + +/datum/status_effect/hallucination/on_creation( + mob/living/new_owner, + duration = 10 SECONDS, + affects_silicons = FALSE, +) + + src.duration = duration + src.affects_silicons = affects_silicons + return ..() + +/datum/status_effect/hallucination/on_apply() + if(!affects_silicons && issilicon(owner)) + return FALSE + + RegisterSignal(owner, COMSIG_LIVING_POST_FULLY_HEAL, .proc/remove_hallucinations) + RegisterSignal(owner, COMSIG_LIVING_HEALTHSCAN, .proc/on_health_scan) + if(iscarbon(owner)) + RegisterSignal(owner, COMSIG_CARBON_CHECKING_BODYPART, .proc/on_check_bodypart) + RegisterSignal(owner, COMSIG_CARBON_BUMPED_AIRLOCK_OPEN, .proc/on_bump_airlock) + + return TRUE + +/datum/status_effect/hallucination/on_remove() + UnregisterSignal(owner, list( + COMSIG_LIVING_POST_FULLY_HEAL, + COMSIG_LIVING_HEALTHSCAN, + COMSIG_CARBON_CHECKING_BODYPART, + COMSIG_CARBON_BUMPED_AIRLOCK_OPEN, + )) + +/// Signal proc for [COMSIG_LIVING_POST_FULLY_HEAL], terminate on full heal +/datum/status_effect/hallucination/proc/remove_hallucinations(datum/source) + SIGNAL_HANDLER + + qdel(src) + +/// Signal proc for [COMSIG_LIVING_HEALTHSCAN]. Show we're hallucinating to (advanced) scanners. +/datum/status_effect/hallucination/proc/on_health_scan(datum/source, list/render_list, advanced, mob/user, mode) + SIGNAL_HANDLER + + if(!advanced) + return + + render_list += "Subject is hallucinating.\n" + +/// Signal proc for [COMSIG_CARBON_CHECKING_BODYPART], +/// checking bodyparts while hallucinating can cause them to appear more damaged than they are +/datum/status_effect/hallucination/proc/on_check_bodypart(mob/living/carbon/source, obj/item/bodypart/examined, list/check_list, list/limb_damage) + SIGNAL_HANDLER + + if(prob(30)) + limb_damage[BRUTE] += rand(30, 40) + if(prob(30)) + limb_damage[BURN] += rand(30, 40) + +/// Signal proc for [COMSIG_CARBON_BUMPED_AIRLOCK_OPEN], bumping an airlock can cause a fake zap. +/// This only happens on airlock bump, future TODO - make this chance roll for attack_hand opening airlocks too +/datum/status_effect/hallucination/proc/on_bump_airlock(mob/living/carbon/source, obj/machinery/door/airlock/bumped) + SIGNAL_HANDLER + + // 1% chance to fake a shock. + if(prob(99) || !source.should_electrocute() || bumped.operating) + return + + source.cause_hallucination(/datum/hallucination/shock, "hallucinated shock from [bumped]",) + return STOP_BUMP + +/datum/status_effect/hallucination/tick(delta_time, times_fired) + if(owner.stat == DEAD) + return + 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)) diff --git a/code/datums/status_effects/debuffs/screwy_hud.dm b/code/datums/status_effects/debuffs/screwy_hud.dm new file mode 100644 index 00000000000..a270652262a --- /dev/null +++ b/code/datums/status_effects/debuffs/screwy_hud.dm @@ -0,0 +1,58 @@ +/** + * Screwy hud status. + * + * Applied to carbons, it will make their health bar look like it's incorrect - + * in crit (SCREWYHUD_CRIT), dead (SCREWYHUD_DEAD), or fully healthy (SCREWYHUD_HEALTHY) + * + * Grouped status effect, so multiple sources can add a screwyhud without + * accidentally removing another source's hud. + */ +/datum/status_effect/grouped/screwy_hud + alert_type = null + /// The priority of this screwyhud over other screwyhuds. + var/priority = -1 + /// The icon we override our owner's healths.icon_state with + var/override_icon + +/datum/status_effect/grouped/screwy_hud/on_apply() + if(!iscarbon(owner)) + return FALSE + + RegisterSignal(owner, COMSIG_CARBON_UPDATING_HEALTH_HUD, .proc/on_health_hud_updated) + owner.update_health_hud() + return TRUE + +/datum/status_effect/grouped/screwy_hud/on_remove() + UnregisterSignal(owner, COMSIG_CARBON_UPDATING_HEALTH_HUD) + owner.update_health_hud() + +/datum/status_effect/grouped/screwy_hud/proc/on_health_hud_updated(mob/living/carbon/source, shown_health_amount) + SIGNAL_HANDLER + + // Shouldn't even be running if we're dead, but just in case... + if(source.stat == DEAD) + return + + // It's entirely possible we have multiple screwy huds on one mob. + // Defer to priority to determine which to show. If our's is lower, don't show it. + for(var/datum/status_effect/grouped/screwy_hud/other_screwy_hud in source.status_effects) + if(other_screwy_hud.priority > priority) + return + + source.hud_used.healths.icon_state = override_icon + return COMPONENT_OVERRIDE_HEALTH_HUD + +/datum/status_effect/grouped/screwy_hud/fake_dead + id = "fake_hud_dead" + priority = 100 // death is absolute + override_icon = "health7" + +/datum/status_effect/grouped/screwy_hud/fake_crit + id = "fake_hud_crit" + priority = 90 // crit is almost death, and death is absolute + override_icon = "health6" + +/datum/status_effect/grouped/screwy_hud/fake_healthy + id = "fake_hud_healthy" + priority = 10 // fully healthy is the opposite of death, which is absolute + override_icon = "health0" diff --git a/code/datums/voice_of_god_command.dm b/code/datums/voice_of_god_command.dm index 4f12b007ab5..18dcf9941b9 100644 --- a/code/datums/voice_of_god_command.dm +++ b/code/datums/voice_of_god_command.dm @@ -161,8 +161,15 @@ GLOBAL_LIST_INIT(voice_of_god_commands, init_voice_of_god_commands()) trigger = "see\\s*the\\s*truth|hallucinate" /datum/voice_of_god_command/hallucinate/execute(list/listeners, mob/living/user, power_multiplier = 1, message) - for(var/mob/living/carbon/target in listeners) - new /datum/hallucination/delusion(target, TRUE, null, 150 * power_multiplier, 0) + for(var/mob/living/target in listeners) + target.cause_hallucination( \ + get_random_valid_hallucination_subtype(/datum/hallucination/delusion/preset), \ + "voice of god", \ + duration = 15 SECONDS * power_multiplier, \ + affects_us = FALSE, \ + affects_others = TRUE, \ + skip_nearby = FALSE, \ + ) /// This command wakes up the listeners. /datum/voice_of_god_command/wake_up diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm index b19a303868a..7cec09b8717 100644 --- a/code/game/area/areas/shuttles.dm +++ b/code/game/area/areas/shuttles.dm @@ -264,7 +264,7 @@ qdel(L.pulling) var/turf/LA = get_turf(pick(warp_points)) L.forceMove(LA) - L.hallucination = 0 + L.remove_status_effect(/datum/status_effect/hallucination) to_chat(L, "The battle is won. Your bloodlust subsides.", confidential = TRUE) for(var/obj/item/chainsaw/doomslayer/chainsaw in L) qdel(chainsaw) diff --git a/code/game/machinery/computer/arcade/orion_event.dm b/code/game/machinery/computer/arcade/orion_event.dm index 9db3d00dab3..03da88e601b 100644 --- a/code/game/machinery/computer/arcade/orion_event.dm +++ b/code/game/machinery/computer/arcade/orion_event.dm @@ -260,7 +260,7 @@ /datum/orion_event/raiders/emag_effect(obj/machinery/computer/arcade/orion_trail/game, mob/living/gamer) if(prob(50-gamer_skill)) to_chat(usr, span_userdanger("You hear battle shouts. The tramping of boots on cold metal. Screams of agony. The rush of venting air. Are you going insane?")) - gamer.hallucination += 30 + gamer.adjust_hallucinations(60 SECONDS) else to_chat(usr, span_userdanger("Something strikes you from behind! It hurts like hell and feel like a blunt weapon, but nothing is there...")) gamer.take_bodypart_damage(30) @@ -529,4 +529,3 @@ spaceport_security.GiveTarget(usr) game.fuel += fuel game.food += food - diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 38bb8285330..bb47fb261dc 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -371,16 +371,17 @@ seal = null update_appearance() -/obj/machinery/door/airlock/bumpopen(mob/living/user) //Airlocks now zap you when you 'bump' them open when they're electrified. --NeoFite - if(!issilicon(usr)) - if(isElectrified() && shock(user, 100)) - return - else if(user.hallucinating() && iscarbon(user) && prob(1) && !operating) - var/mob/living/carbon/C = user - if(!C.wearing_shock_proof_gloves()) - new /datum/hallucination/shock(C) - return - ..() +/obj/machinery/door/airlock/bumpopen(mob/living/user) + if(issilicon(user) || !iscarbon(user)) + return ..() + + if(isElectrified() && shock(user, 100)) + return + + if(SEND_SIGNAL(user, COMSIG_CARBON_BUMPED_AIRLOCK_OPEN, src) & STOP_BUMP) + return + + return ..() /obj/machinery/door/airlock/proc/isElectrified() return (secondsElectrified != MACHINE_NOT_ELECTRIFIED) diff --git a/code/game/machinery/medical_kiosk.dm b/code/game/machinery/medical_kiosk.dm index 018a1d26454..f9718b70c76 100644 --- a/code/game/machinery/medical_kiosk.dm +++ b/code/game/machinery/medical_kiosk.dm @@ -281,7 +281,7 @@ for(var/datum/addiction/addiction_type as anything in patient.mind.active_addictions) addict_list += list(list("name" = initial(addiction_type.name))) - if (patient.hallucinating()) + if (patient.has_status_effect(/datum/status_effect/hallucination)) hallucination_status = "Subject appears to be hallucinating. Suggested treatments: bedrest, mannitol or psicodine." if(patient.stat == DEAD || HAS_TRAIT(patient, TRAIT_FAKEDEATH) || ((brute_loss+fire_loss+tox_loss+oxy_loss+clone_loss) >= 200)) //Patient status checks. @@ -292,8 +292,16 @@ patient_status = "Injured" else if((brute_loss+fire_loss+tox_loss+oxy_loss+clone_loss) >= 20) patient_status = "Lightly Injured" - if(pandemonium || user.hallucinating()) - patient_status = pick("The only kiosk is kiosk, but is the only patient, patient?", "Breathing manually.","Constact NTOS site admin.","97% carbon, 3% natural flavoring","The ebb and flow wears us all in time.","It's Lupus. You have Lupus.","Undergoing monkey disease.") + if(pandemonium || user.has_status_effect(/datum/status_effect/hallucination)) + patient_status = pick( + "The only kiosk is kiosk, but is the only patient, patient?", + "Breathing manually.", + "Constact NTOS site admin.", + "97% carbon, 3% natural flavoring", + "The ebb and flow wears us all in time.", + "It's Lupus. You have Lupus.", + "Undergoing monkey disease.", + ) if((brain_loss) >= 100) //Brain status checks. brain_status = "Grave brain damage detected." @@ -304,9 +312,9 @@ else if((brain_loss) >= 1) brain_status = "Mild brain damage detected." //You may have a miiiild case of severe brain damage. - if(pandemonium == TRUE) + if(pandemonium) chaos_modifier = 1 - else if (user.hallucinating()) + else if(user.has_status_effect(/datum/status_effect/hallucination)) chaos_modifier = 0.3 data["kiosk_cost"] = active_price + (chaos_modifier * (rand(1,25))) diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm index 7deb34cf378..fa3ed6efdce 100644 --- a/code/game/objects/effects/anomalies.dm +++ b/code/game/objects/effects/anomalies.dm @@ -1,8 +1,4 @@ //Anomalies, used for events. Note that these DO NOT work by themselves; their procs are called by the event datum. - -/// Chance of taking a step per second -#define ANOMALY_MOVECHANCE 45 - /obj/effect/anomaly name = "anomaly" desc = "A mysterious anomaly, seen commonly only in the region of space that the station orbits..." @@ -511,6 +507,13 @@ var/ticks = 0 /// How many seconds between each small hallucination pulses var/release_delay = 5 + /// Messages sent to people feeling the pulses + var/static/list/messages = list( + span_warning("You feel your conscious mind fall apart!"), + span_warning("Reality warps around you!"), + span_warning("Something's wispering around you!"), + span_warning("You are going insane!"), + ) /obj/effect/anomaly/hallucination/anomalyEffect(delta_time) . = ..() @@ -518,36 +521,28 @@ if(ticks < release_delay) return ticks -= release_delay - var/turf/open/our_turf = get_turf(src) - if(istype(our_turf)) - hallucination_pulse(our_turf, 5) + if(!isturf(loc)) + return + + visible_hallucination_pulse( + center = get_turf(src), + radius = 5, + hallucination_duration = 50 SECONDS, + hallucination_max_duration = 300 SECONDS, + optional_messages = messages, + ) /obj/effect/anomaly/hallucination/detonate() - var/turf/open/our_turf = get_turf(src) - if(istype(our_turf)) - hallucination_pulse(our_turf, 10) + if(!isturf(loc)) + return -/obj/effect/anomaly/hallucination/proc/hallucination_pulse(turf/open/location, range) - for(var/mob/living/carbon/human/near in view(location, range)) - // If they are immune to hallucinations. - if (HAS_TRAIT(near, TRAIT_MADNESS_IMMUNE) || (near.mind && HAS_TRAIT(near.mind, TRAIT_MADNESS_IMMUNE))) - continue - - // Blind people don't get hallucinations. - if (near.is_blind()) - continue - - // Everyone else gets hallucinations. - var/dist = sqrt(1 / max(1, get_dist(near, location))) - near.hallucination += 50 * dist - near.hallucination = clamp(near.hallucination, 0, 150) - var/list/messages = list( - "You feel your conscious mind fall apart!", - "Reality warps around you!", - "Something's wispering around you!", - "You are going insane!", - ) - to_chat(near, span_warning(pick(messages))) + visible_hallucination_pulse( + center = get_turf(src), + radius = 10, + hallucination_duration = 50 SECONDS, + hallucination_max_duration = 300 SECONDS, + optional_messages = messages, + ) ///////////////////// @@ -631,5 +626,3 @@ icon = 'icons/effects/effects.dmi' icon_state = "shield-flash" duration = 3 - -#undef ANOMALY_MOVECHANCE diff --git a/code/game/objects/items/devices/portable_chem_mixer.dm b/code/game/objects/items/devices/portable_chem_mixer.dm index cf1cd98f4d5..61a69a4f96f 100644 --- a/code/game/objects/items/devices/portable_chem_mixer.dm +++ b/code/game/objects/items/devices/portable_chem_mixer.dm @@ -145,9 +145,16 @@ ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "PortableChemMixer", name) - if(user.hallucinating()) + + var/is_hallucinating = FALSE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) + + if(is_hallucinating) // to not ruin the immersion by constantly changing the fake chemicals ui.set_autoupdate(FALSE) + ui.open() /obj/item/storage/portable_chem_mixer/ui_data(mob/user) @@ -159,9 +166,11 @@ data["beakerTransferAmounts"] = beaker ? list(1,5,10,30,50,100) : null data["showpH"] = show_ph var/chemicals[0] - var/is_hallucinating = user.hallucinating() - if(user.hallucinating()) - is_hallucinating = TRUE + var/is_hallucinating = FALSE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) + for(var/re in dispensable_reagents) var/value = dispensable_reagents[re] var/datum/reagent/temp = GLOB.chemical_reagents_list[re] diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index 657dc27e671..3d142bb45ea 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -202,9 +202,6 @@ if (HAS_TRAIT(target, TRAIT_IRRADIATED)) render_list += "Subject is irradiated. Supply toxin healing.\n" - if(advanced && target.hallucinating()) - render_list += "Subject is hallucinating.\n" - //Eyes and ears if(advanced && iscarbon(target)) var/mob/living/carbon/carbontarget = target diff --git a/code/game/objects/items/food/misc.dm b/code/game/objects/items/food/misc.dm index a1942c827b9..3373cfae381 100644 --- a/code/game/objects/items/food/misc.dm +++ b/code/game/objects/items/food/misc.dm @@ -442,8 +442,6 @@ food_reagents = list(/datum/reagent/blood = 15) tastes = list("hell" = 1, "people" = 1) metabolization_amount = REAGENTS_METABOLISM - /// What the player hears from the bubblegum hallucination, and also says one of these when suiciding - var/static/list/hallucination_lines = list("I AM IMMORTAL.", "I SHALL TAKE YOUR WORLD.", "I SEE YOU.", "YOU CANNOT ESCAPE ME FOREVER.", "NOTHING CAN HOLD ME.") /obj/item/food/bubblegum/bubblegum/process() . = ..() @@ -470,16 +468,15 @@ ///This proc has a 5% chance to have a bubblegum line appear, with an 85% chance for just text and 15% for a bubblegum hallucination and scarier text. /obj/item/food/bubblegum/bubblegum/proc/hallucinate(mob/living/carbon/victim) - if(!prob(5)) //cursed by bubblegum + if(prob(95)) //cursed by bubblegum return if(prob(15)) - new /datum/hallucination/oh_yeah(victim) - to_chat(victim, span_colossus("[pick(hallucination_lines)]")) + victim.cause_hallucination(/datum/hallucination/oh_yeah, "bubblegum bubblegum", haunt_them = TRUE) else to_chat(victim, span_warning("[pick("You hear faint whispers.", "You smell ash.", "You feel hot.", "You hear a roar in the distance.")]")) /obj/item/food/bubblegum/bubblegum/suicide_act(mob/user) - user.say(";[pick(hallucination_lines)]") + user.say(";[pick(BUBBLEGUM_HALLUCINATION_LINES)]") return ..() /obj/item/food/gumball diff --git a/code/game/objects/items/grenades/hypno.dm b/code/game/objects/items/grenades/hypno.dm index 36512b92ba2..074a94c6f12 100644 --- a/code/game/objects/items/grenades/hypno.dm +++ b/code/game/objects/items/grenades/hypno.dm @@ -46,14 +46,15 @@ living_mob.Paralyze(10) living_mob.Knockdown(100) to_chat(living_mob, span_hypnophrase("The sound echoes in your brain...")) - living_mob.hallucination += 50 + living_mob.adjust_hallucinations(100 SECONDS) + else if(distance <= 1) living_mob.Paralyze(5) living_mob.Knockdown(30) if(hypno_sound) to_chat(living_mob, span_hypnophrase("The sound echoes in your brain...")) - living_mob.hallucination += 50 + living_mob.adjust_hallucinations(100 SECONDS) //Flash if(living_mob.flash_act(affect_silicon = 1)) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index ac00350a4e2..3bd0d13ff62 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -229,6 +229,7 @@ GLOBAL_PROTECT(admin_verbs_debug) /client/proc/open_lua_editor, /client/proc/validate_puzzgrids, /client/proc/debug_spell_requirements, + /client/proc/debug_hallucination_weighted_list_per_type, ) GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release)) GLOBAL_PROTECT(admin_verbs_possess) diff --git a/code/modules/admin/force_event.dm b/code/modules/admin/force_event.dm index 86d400c3c9b..b4a099e4ff5 100644 --- a/code/modules/admin/force_event.dm +++ b/code/modules/admin/force_event.dm @@ -61,7 +61,8 @@ var/datum/round_event_control/event = locate(event_to_run_type) in SSevents.control if(!event) return - event.admin_setup(usr) + if(event.admin_setup(usr) == ADMIN_CANCEL_EVENT) + return var/always_announce_chance = 100 var/no_announce_chance = 0 event.runEvent(announce_chance_override = announce_event ? always_announce_chance : no_announce_chance, admin_forced = TRUE) diff --git a/code/modules/antagonists/abductor/equipment/glands/mindshock.dm b/code/modules/antagonists/abductor/equipment/glands/mindshock.dm index 381d7e11218..5b95c609e98 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_timed_status_effect(15 SECONDS, /datum/status_effect/confusion) target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 160) if(3) - target.hallucination += 60 + target.adjust_hallucinations(120 SECONDS) /obj/item/organ/internal/heart/gland/mindshock/mind_control(command, mob/living/user) if(!ownerCheck() || !mind_control_uses || active_mind_control) diff --git a/code/modules/antagonists/blob/blobstrains/regenerative_materia.dm b/code/modules/antagonists/blob/blobstrains/regenerative_materia.dm index 9b023936a55..d1583f2443d 100644 --- a/code/modules/antagonists/blob/blobstrains/regenerative_materia.dm +++ b/code/modules/antagonists/blob/blobstrains/regenerative_materia.dm @@ -24,13 +24,15 @@ exposed_mob.reagents.add_reagent(/datum/reagent/toxin/spore, 0.2*reac_volume) exposed_mob.apply_damage(0.7*reac_volume, TOX) -/datum/reagent/blob/regenerative_materia/on_mob_life(mob/living/carbon/C, delta_time, times_fired) - C.adjustToxLoss(1 * REAGENTS_EFFECT_MULTIPLIER * delta_time) - C.hal_screwyhud = SCREWYHUD_HEALTHY //fully healed, honest +/datum/reagent/blob/regenerative_materia/on_mob_life(mob/living/carbon/metabolizer, delta_time, times_fired) + metabolizer.adjustToxLoss(1 * REAGENTS_EFFECT_MULTIPLIER * delta_time) ..() + return TRUE -/datum/reagent/blob/regenerative_materia/on_mob_end_metabolize(mob/living/M) - if(iscarbon(M)) - var/mob/living/carbon/N = M - N.hal_screwyhud = 0 - ..() +/datum/reagent/blob/regenerative_materia/on_mob_metabolize(mob/living/metabolizer) + . = ..() + metabolizer.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + +/datum/reagent/blob/regenerative_materia/on_mob_end_metabolize(mob/living/metabolizer) + . = ..() + metabolizer.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) diff --git a/code/modules/antagonists/changeling/powers/tiny_prick.dm b/code/modules/antagonists/changeling/powers/tiny_prick.dm index 052b7043516..a56714caa72 100644 --- a/code/modules/antagonists/changeling/powers/tiny_prick.dm +++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm @@ -223,12 +223,13 @@ /datum/action/changeling/sting/lsd/sting_action(mob/user, mob/living/carbon/target) log_combat(user, target, "stung", "LSD sting") - addtimer(CALLBACK(src, .proc/hallucination_time, target), rand(300,600)) + addtimer(CALLBACK(src, .proc/hallucination_time, target), rand(30 SECONDS, 60 SECONDS)) return TRUE /datum/action/changeling/sting/lsd/proc/hallucination_time(mob/living/carbon/target) - if(target) - target.hallucination = max(90, target.hallucination) + if(QDELETED(src) || QDELETED(target)) + return + target.adjust_hallucinations(180 SECONDS) /datum/action/changeling/sting/cryo name = "Cryogenic Sting" diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm index e4a75bcf646..482c1f3b70e 100644 --- a/code/modules/antagonists/cult/blood_magic.dm +++ b/code/modules/antagonists/cult/blood_magic.dm @@ -239,7 +239,7 @@ /datum/action/innate/cult/blood_spell/horror/do_ability(mob/living/caller, mob/living/carbon/human/clicked_on) - clicked_on.hallucination = max(clicked_on.hallucination, 120) + clicked_on.set_hallucinations_if_lower(240 SECONDS) SEND_SOUND(caller, sound('sound/effects/ghost.ogg', FALSE, TRUE, 50)) var/image/sparkle_image = image('icons/effects/cult/effects.dmi', clicked_on, "bloodsparkles", ABOVE_MOB_LAYER) diff --git a/code/modules/antagonists/heretic/items/madness_mask.dm b/code/modules/antagonists/heretic/items/madness_mask.dm index 82b94b25d07..636aea1891a 100644 --- a/code/modules/antagonists/heretic/items/madness_mask.dm +++ b/code/modules/antagonists/heretic/items/madness_mask.dm @@ -61,7 +61,7 @@ human_in_range.mob_mood.direct_sanity_drain(rand(-2, -20) * delta_time) if(DT_PROB(60, delta_time)) - human_in_range.hallucination = min(human_in_range.hallucination + 5, 120) + human_in_range.adjust_hallucinations_up_to(10 SECONDS, 240 SECONDS) if(DT_PROB(40, delta_time)) human_in_range.set_timed_status_effect(10 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm index fbf9f0c2b1a..811189b378a 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm @@ -47,7 +47,7 @@ if(owner.health > owner.crit_threshold && prob(4)) owner.set_timed_status_effect(20 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) owner.set_timed_status_effect(10 SECONDS, /datum/status_effect/dizziness, only_if_higher = TRUE) - owner.hallucination = min(owner.hallucination + 3, 24) + owner.adjust_hallucinations_up_to(6 SECONDS, 48 SECONDS) if(prob(2)) playsound(owner, pick(GLOB.creepy_ambience), 50, TRUE) diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm index a2c56791e2e..3b4218573b8 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm @@ -225,13 +225,13 @@ sac_target.visible_message(span_danger("[sac_target] begins to shudder violenty as dark tendrils begin to drag them into thin air!")) sac_target.set_handcuffed(new /obj/item/restraints/handcuffs/energy/cult(sac_target)) sac_target.update_handcuffed() - + if(sac_target.legcuffed) sac_target.legcuffed.forceMove(sac_target.drop_location()) sac_target.legcuffed.dropped(sac_target) sac_target.legcuffed = null sac_target.update_worn_legcuffs() - + sac_target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 85, 150) sac_target.do_jitter_animation() log_combat(heretic_mind.current, sac_target, "sacrificed") @@ -320,7 +320,7 @@ sac_target.blur_eyes(15) sac_target.set_timed_status_effect(20 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) sac_target.set_timed_status_effect(20 SECONDS, /datum/status_effect/dizziness, only_if_higher = TRUE) - sac_target.hallucination += 12 + sac_target.adjust_hallucinations(24 SECONDS) sac_target.emote("scream") to_chat(sac_target, span_reallybig(span_hypnophrase("The grasp of the Mansus reveal themselves to you!"))) diff --git a/code/modules/antagonists/heretic/knowledge/void_lore.dm b/code/modules/antagonists/heretic/knowledge/void_lore.dm index c25265c852a..1861d08e5f9 100644 --- a/code/modules/antagonists/heretic/knowledge/void_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/void_lore.dm @@ -195,7 +195,7 @@ /datum/heretic_knowledge/final/void_final/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) . = ..() - priority_announce("[generate_heretic_text()] The nobleman of void [user.real_name] has arrived, step along the Waltz that ends worlds! [generate_heretic_text()]","[generate_heretic_text()]", ANNOUNCER_SPANOMALIES) + priority_announce("[generate_heretic_text()] The nobleman of void [user.real_name] has arrived, stepping along the Waltz that ends worlds! [generate_heretic_text()]","[generate_heretic_text()]", ANNOUNCER_SPANOMALIES) user.client?.give_award(/datum/award/achievement/misc/void_ascension, user) ADD_TRAIT(user, TRAIT_RESISTLOWPRESSURE, MAGIC_TRAIT) diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 5b93ce5a799..2335c8c1d79 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -1142,8 +1142,7 @@ for(var/i in 1 to nuclear_particle_amount) location.fire_nuclear_particle() radiation_pulse(location, max_range = min(sqrt(consumed_amount - nuclear_particle_amount * PN_BZASE_NUCLEAR_PARTICLE_RADIATION_ENERGY_CONVERSION) / PN_BZASE_RAD_RANGE_DIVISOR, GAS_REACTION_MAXIMUM_RADIATION_PULSE_RANGE), threshold = PN_BZASE_RAD_THRESHOLD) - for(var/mob/living/carbon/L in location) - L.hallucination += consumed_amount + visible_hallucination_pulse(location, 1, consumed_amount * 2 SECONDS) var/new_heat_capacity = air.heat_capacity() if(new_heat_capacity > MINIMUM_HEAT_CAPACITY) diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm index 4fde03920d2..026aa26e3c0 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm @@ -325,7 +325,8 @@ internal_output.assert_gases(/datum/gas/healium, /datum/gas/proto_nitrate) internal_output.gases[/datum/gas/proto_nitrate][MOLES] += scaled_production * 1.5 internal_output.gases[/datum/gas/healium][MOLES] += scaled_production * 1.5 - induce_hallucination(50 * power_level, delta_time) + visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * delta_time) + if(5) if(moderator_list[/datum/gas/plasma] > 15) internal_output.assert_gases(/datum/gas/freon) @@ -344,7 +345,7 @@ if(moderator_list[/datum/gas/bz] > 100) internal_output.assert_gases(/datum/gas/healium, /datum/gas/freon) internal_output.gases[/datum/gas/healium][MOLES] += scaled_production - induce_hallucination(500, delta_time) + visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * delta_time) internal_output.gases[/datum/gas/freon][MOLES] += scaled_production * 1.15 if(moderator_list[/datum/gas/healium] > 100) if(critical_threshold_proximity > 400) @@ -367,7 +368,7 @@ radiation *= 2 heat_output *= 2.25 if(moderator_list[/datum/gas/bz]) - induce_hallucination(900, delta_time, force=TRUE) + visible_hallucination_pulse(src, HALLUCINATION_HFR(heat_output), 100 SECONDS * power_level * delta_time) internal_output.gases[/datum/gas/antinoblium][MOLES] += clamp(dirty_production_rate / 0.045, 0, 10) * delta_time if(moderator_list[/datum/gas/healium] > 100) if(critical_threshold_proximity > 400) @@ -406,7 +407,7 @@ check_gravity_pulse(delta_time) - emit_rads() + radiation_pulse(src, max_range = 6, threshold = 0.3) /obj/machinery/atmospherics/components/unary/hypertorus/core/proc/evaporate_moderator(delta_time) // Don't evaporate if the reaction is dead diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm index d86b948459d..dbc09bf1748 100644 --- a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm +++ b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm @@ -556,29 +556,6 @@ qdel(src) -/** - * Induce hallucinations in nearby humans. - * - * force will make hallucinations ignore meson protection. - */ -/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/induce_hallucination(strength, delta_time, force=FALSE) - for(var/mob/living/carbon/human/human in view(src, HALLUCINATION_HFR(heat_output))) - if(!force && istype(human.glasses, /obj/item/clothing/glasses/meson)) - continue - var/distance_root = sqrt(1 / max(1, get_dist(human, src))) - human.hallucination += strength * distance_root * delta_time - human.hallucination = clamp(human.hallucination, 0, 200) - -/** - * Emit radiation - */ -/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/emit_rads() - radiation_pulse( - src, - max_range = 6, - threshold = 0.3, - ) - /* * HFR cracking related procs */ @@ -650,4 +627,3 @@ ) spill_gases(cracked_part, moderator_internal, ratio = HYPERTORUS_STRONG_SPILL_INITIAL) return - diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index d3429be71ed..f813a252d62 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -25,7 +25,17 @@ //Variables declared to change how items in the launch bay are picked and launched. (Almost) all of these are changed in the ui_act proc //Some effect groups are choices, while other are booleans. This is because some effects can stack, while others dont (ex: you can stack explosion and quiet, but you cant stack ordered launch and random launch) /datum/centcom_podlauncher - var/static/list/ignored_atoms = typecacheof(list(null, /mob/dead, /obj/effect/landmark, /obj/docking_port, /obj/effect/particle_effect/sparks, /obj/effect/pod_landingzone, /obj/effect/hallucination/simple/supplypod_selector, /obj/effect/hallucination/simple/dropoff_location)) + /// Static typecache of atoms we won't lift up, or pod or whatever. + var/static/list/ignored_atoms = typecacheof(list( + null, // I don't know why null is the first element of this typepache but it was there when I found it + /mob/dead, + /obj/effect/landmark, + /obj/docking_port, + /obj/effect/particle_effect/sparks, + /obj/effect/pod_landingzone, + /obj/effect/client_image_holder, + )) + var/turf/oldTurf //Keeps track of where the user was at if they use the "teleport to centcom" button, so they can go back var/client/holder //client of whoever is using this datum var/area/centcom/central_command_areas/supplypod/loading/bay //What bay we're using to launch shit from. @@ -46,8 +56,12 @@ var/list/orderedArea = list() //Contains an ordered list of turfs in an area (filled in the createOrderedArea() proc), read top-left to bottom-right. Used for the "ordered" launch mode (launchChoice = 1) var/list/turf/acceptableTurfs = list() //Contians a list of turfs (in the "bay" area on centcom) that have items that can be launched. Taken from orderedArea var/list/launchList = list() //Contains whatever is going to be put in the supplypod and fired. Taken from acceptableTurfs - var/obj/effect/hallucination/simple/supplypod_selector/selector //An effect used for keeping track of what item is going to be launched when in "ordered" mode (launchChoice = 1) - var/obj/effect/hallucination/simple/dropoff_location/indicator + + /// An effect used for showing where a reverse pod will land + var/obj/effect/client_image_holder/dropoff_location/indicator + /// An effect used for keeping track of what item is going to be launched next when in "ordered" mode (launchChoice = 1) + var/obj/effect/client_image_holder/supplypod_selector/selector + var/obj/structure/closet/supplypod/centcompod/temp_pod //The temporary pod that is modified by this datum, then cloned. The buildObject() clone of this pod is what is launched // Stuff needed to render the map var/map_name @@ -794,7 +808,7 @@ QDEL_NULL(temp_pod) //Delete the temp_pod QDEL_NULL(selector) //Delete the selector effect QDEL_NULL(indicator) - . = ..() + return ..() /datum/centcom_podlauncher/proc/supplypod_punish_log(list/whoDyin) var/podString = effectBurst ? "5 pods" : "a pod" @@ -874,7 +888,7 @@ GLOBAL_DATUM_INIT(podlauncher, /datum/centcom_podlauncher, new) temp_pod.reverse_dropoff_coords = list(target_turf.x, target_turf.y, target_turf.z) indicator.forceMove(target_turf) -/obj/effect/hallucination/simple/supplypod_selector +/obj/effect/client_image_holder/supplypod_selector // Shows which item will be taken next name = "Supply Selector (Only you can see this)" image_icon = 'icons/obj/supplypods_32x32.dmi' image_state = "selector" @@ -883,7 +897,7 @@ GLOBAL_DATUM_INIT(podlauncher, /datum/centcom_podlauncher, new) plane = ABOVE_GAME_PLANE alpha = 150 -/obj/effect/hallucination/simple/dropoff_location +/obj/effect/client_image_holder/dropoff_location // Shows where revese pods lands name = "Dropoff Location (Only you can see this)" image_icon = 'icons/obj/supplypods_32x32.dmi' image_state = "dropoff_indicator" diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm index eaed1ec8e48..de4fbde3c26 100644 --- a/code/modules/clothing/suits/reactive_armour.dm +++ b/code/modules/clothing/suits/reactive_armour.dm @@ -334,51 +334,32 @@ desc = "An experimental suit of armor with sensitive detectors hooked up to the mind of the wearer, sending mind pulses that causes hallucinations around you." cooldown_message = span_danger("The connection is currently out of sync... Recalibrating.") emp_message = span_warning("You feel the backsurge of a mind pulse.") - var/range = 3 - -/obj/item/clothing/suit/armor/reactive/hallucinating/dropped(mob/user) - ..() - if(istype(user)) - REMOVE_TRAIT(user, TRAIT_MADNESS_IMMUNE, "reactive_hallucinating_armor") - -/obj/item/clothing/suit/armor/reactive/hallucinating/equipped(mob/user, slot) - ..() - if(slot_flags & slot) //Was equipped to a valid slot for this item? - ADD_TRAIT(user, TRAIT_MADNESS_IMMUNE, "reactive_hallucinating_armor") + clothing_traits = list(TRAIT_MADNESS_IMMUNE) /obj/item/clothing/suit/armor/reactive/hallucinating/cooldown_activation(mob/living/carbon/human/owner) var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread sparks.set_up(1, 1, src) sparks.start() - ..() + return ..() /obj/item/clothing/suit/armor/reactive/hallucinating/reactive_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], sending out mental pulses!")) - hallucination_pulse(owner) + visible_hallucination_pulse( + center = get_turf(owner), + radius = 3, + hallucination_duration = 50 SECONDS, + hallucination_max_duration = 300 SECONDS, + ) reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration return TRUE /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.hallucination += 25 - owner.hallucination = clamp(owner.hallucination, 0, 150) + owner.adjust_hallucinations_up_to(50 SECONDS, 300 SECONDS) reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration return TRUE -/obj/item/clothing/suit/armor/reactive/hallucinating/proc/hallucination_pulse(mob/living/carbon/human/owner) - var/turf/location = get_turf(owner) - for(var/mob/living/carbon/human/near in view(location, range)) - // If they are immune to hallucinations. - if (HAS_TRAIT(near, TRAIT_MADNESS_IMMUNE) || (near.mind && HAS_TRAIT(near.mind, TRAIT_MADNESS_IMMUNE))) - continue - - // Everyone else gets hallucinations. - var/dist = sqrt(1 / max(1, get_dist(near, location))) - near.hallucination += 25 * dist - near.hallucination = clamp(near.hallucination, 0, 150) - //Bioscrambling - /obj/item/clothing/suit/armor/reactive/bioscrambling name = "reactive bioscrambling armor" desc = "An experimental suit of armor with sensitive detectors hooked up to a biohazard release valve. It scrambles the bodies of those around." diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index 2c42079cf9f..a879c277915 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -156,8 +156,9 @@ Runs the event SIGNAL_HANDLER return CANCEL_RANDOM_EVENT -//Special admins setup -/datum/round_event_control/proc/admin_setup() +/// Any special things admins can do while triggering this event to "improve" it. +/// Return [ADMIN_CANCEL_EVENT] to stop the event from actually happening after all +/datum/round_event_control/proc/admin_setup(mob/admin) return /datum/round_event //NOTE: Times are measured in master controller ticks! diff --git a/code/modules/events/false_alarm.dm b/code/modules/events/false_alarm.dm index df421c6faf7..48f16700380 100644 --- a/code/modules/events/false_alarm.dm +++ b/code/modules/events/false_alarm.dm @@ -7,7 +7,7 @@ category = EVENT_CATEGORY_BUREAUCRATIC description = "Fakes an event announcement." -/datum/round_event_control/falsealarm/admin_setup() +/datum/round_event_control/falsealarm/admin_setup(mob/admin) if(!check_rights(R_FUN)) return diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm index e55c6380045..aa16be5506b 100644 --- a/code/modules/events/immovable_rod.dm +++ b/code/modules/events/immovable_rod.dm @@ -17,7 +17,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 category = EVENT_CATEGORY_SPACE description = "The station passes through an immovable rod." -/datum/round_event_control/immovable_rod/admin_setup() +/datum/round_event_control/immovable_rod/admin_setup(mob/admin) if(!check_rights(R_FUN)) return diff --git a/code/modules/events/mass_hallucination.dm b/code/modules/events/mass_hallucination.dm index 0844194c5f6..d2ccc2d49e1 100644 --- a/code/modules/events/mass_hallucination.dm +++ b/code/modules/events/mass_hallucination.dm @@ -1,48 +1,135 @@ /datum/round_event_control/mass_hallucination name = "Mass Hallucination" + description = "All crewmembers start to hallucinate the same thing." typepath = /datum/round_event/mass_hallucination weight = 10 max_occurrences = 2 min_players = 1 category = EVENT_CATEGORY_HEALTH - description = "Multiple crewmembers start to hallucinate the same thing." + + /// For admins, what hallucination did we pick + var/admin_forced_hallucination + /// For admins, what arguments are we passing to said hallucination + var/list/admin_forced_args + +/datum/round_event_control/mass_hallucination/admin_setup(mob/admin) + if(!check_rights(R_FUN)) + return ADMIN_CANCEL_EVENT + + var/force = tgui_alert(usr, "Do you want to force a hallucination?", name, list("Yes", "No", "Cancel")) + if(force == "Cancel") + return ADMIN_CANCEL_EVENT + if(force != "Yes") + return + + var/force_what = tgui_alert(usr, "Generic hallucination or Custom configured delusion? (Delusions are those which make people appear as other mobs)", name, list("Hallucination", "Custom Delusion", "Cancel")) + switch(force_what) + if("Cancel") + return ADMIN_CANCEL_EVENT + + if("Hallucination") + var/chosen = select_hallucination_type(admin, "What hallucination should be forced for [name]?", name) + if(!chosen || !check_rights(R_FUN)) + return ADMIN_CANCEL_EVENT + + admin_forced_hallucination = chosen + + if("Custom Delusion") + var/list/chosen_args = create_delusion(admin) + if(!length(chosen_args) || !check_rights(R_FUN)) + return ADMIN_CANCEL_EVENT + + admin_forced_hallucination = chosen_args[1] + admin_forced_args = chosen_args.Copy(3) /datum/round_event/mass_hallucination fakeable = FALSE /datum/round_event/mass_hallucination/start() - switch(rand(1,4)) - if(1) //same sound for everyone - var/sound = pick("airlock","airlock_pry","console","explosion","far_explosion","mech","glass","alarm","beepsky","mech","wall_decon","door_hack","tesla") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - if(C.z in SSmapping.levels_by_trait(ZTRAIT_CENTCOM))//not for admin/ooc stuff - continue - new /datum/hallucination/sounds(C, TRUE, sound) - if(2) - var/weirdsound = pick("phone","hallelujah","highlander","hyperspace","game_over","creepy","tesla") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - if(C.z in SSmapping.levels_by_trait(ZTRAIT_CENTCOM))//not for admin/ooc stuff - continue - new /datum/hallucination/weird_sounds(C, TRUE, weirdsound) - if(3) - var/stationmessage = pick("ratvar","shuttle_dock","blob_alert","malf_ai","meteors","supermatter") - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - if(C.z in SSmapping.levels_by_trait(ZTRAIT_CENTCOM))//not for admin/ooc stuff - continue - new /datum/hallucination/stationmessage(C, TRUE, stationmessage) - if(4 to 6) - var/picked_hallucination = pick( /datum/hallucination/bolts, - /datum/hallucination/chat, - /datum/hallucination/message, - /datum/hallucination/bolts, - /datum/hallucination/fake_flood, - /datum/hallucination/battle, - /datum/hallucination/fire, - /datum/hallucination/self_delusion, - /datum/hallucination/death, - /datum/hallucination/delusion, - /datum/hallucination/oh_yeah) - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - if(C.z in SSmapping.levels_by_trait(ZTRAIT_CENTCOM))//not for admin/ooc stuff - continue - new picked_hallucination(C, TRUE) + var/datum/round_event_control/mass_hallucination/our_controller = control + + var/picked_hallucination = our_controller?.admin_forced_hallucination + var/list/other_args = our_controller?.admin_forced_args + + if(!picked_hallucination) + var/category_to_pick_from = rand(1, 10) + switch(category_to_pick_from) + if(1) + // Send the same sound to everyone + picked_hallucination = get_random_valid_hallucination_subtype(/datum/hallucination/fake_sound/normal) + + if(2) + // Send the same sound to everyone, but weird + picked_hallucination = get_random_valid_hallucination_subtype(/datum/hallucination/fake_sound/weird) + + if(3) + // Send the same message to everyone + picked_hallucination = get_random_valid_hallucination_subtype(/datum/hallucination/station_message) + + if(4) + // Send the same delusion to everyone, but... + picked_hallucination = get_random_valid_hallucination_subtype(/datum/hallucination/delusion/preset) + // The delusion will affect everyone BUT the hallucinator. + other_args = list( + duration = 30 SECONDS, + skip_nearby = FALSE, + affects_us = FALSE, + affects_others = TRUE, + play_wabbajack = FALSE, + ) + + if(5) + // Send the same delusion to everyone, but... + picked_hallucination = get_random_valid_hallucination_subtype(/datum/hallucination/delusion/preset) + // The delusion will affect only the hallucinator. + other_args = list( + duration = 45 SECONDS, + skip_nearby = FALSE, + affects_us = TRUE, + affects_others = FALSE, + play_wabbajack = TRUE, + ) + + if(6 to 10) + // Send the same generic hallucination type to everyone + var/static/list/generic_hallucinations = list( + /datum/hallucination/bolts, + /datum/hallucination/chat, + /datum/hallucination/death, + /datum/hallucination/fake_flood, + /datum/hallucination/fire, + /datum/hallucination/message, + /datum/hallucination/oh_yeah, + /datum/hallucination/xeno_attack, + ) + + picked_hallucination = pick(generic_hallucinations) + + if(!picked_hallucination) + CRASH("[type] couldn't find a hallucination to play. (Got: [picked_hallucination], Picked category: [category_to_pick_from])") + + var/list/hallucination_args = list(picked_hallucination, "mass hallucination") + if(islist(other_args)) + hallucination_args += other_args + + // We'll only hallucinate for carbons now, even though livings can hallucinate just fine in most cases. + for(var/mob/living/carbon/hallucinating as anything in GLOB.carbon_list) + // If they're on centcom, skip them entirely. + if(is_centcom_level(hallucinating.z)) + continue + // We can skip dead carbons as well + if(hallucinating.stat == DEAD) + continue + // Hallucinations can have side effects on mobs, like being stunned, + // so we'll play the hallucination to clientless mobs as well. + // Unless the mob is off the station z-level. It's unlikely anyone will notice. + if(hallucinating.z != 0 && !is_station_level(hallucinating.z) && !hallucinating.client) + continue + + // Not using the wrapper here because we already have a list / arglist + hallucinating._cause_hallucination(hallucination_args) + +/datum/round_event/mass_hallucination/end() + var/datum/round_event_control/mass_hallucination/our_controller = control + our_controller.admin_forced_hallucination = null + our_controller.admin_forced_args = null diff --git a/code/modules/events/wizard/departmentrevolt.dm b/code/modules/events/wizard/departmentrevolt.dm index 478d2109670..4fedb8e784e 100644 --- a/code/modules/events/wizard/departmentrevolt.dm +++ b/code/modules/events/wizard/departmentrevolt.dm @@ -13,7 +13,7 @@ ///admin choice on whether this nation will have objectives to attack other nations, default true for !fun! var/dangerous_nation = TRUE -/datum/round_event_control/wizard/deprevolt/admin_setup() +/datum/round_event_control/wizard/deprevolt/admin_setup(mob/admin) if(!check_rights(R_FUN)) return diff --git a/code/modules/events/wizard/madness.dm b/code/modules/events/wizard/madness.dm index b960b074678..bcf40be2883 100644 --- a/code/modules/events/wizard/madness.dm +++ b/code/modules/events/wizard/madness.dm @@ -7,7 +7,7 @@ var/forced_secret -/datum/round_event_control/wizard/madness/admin_setup() +/datum/round_event_control/wizard/madness/admin_setup(mob/admin) if(!check_rights(R_FUN)) return diff --git a/code/modules/hallucination/HUD.dm b/code/modules/hallucination/HUD.dm deleted file mode 100644 index 8e7bf445d70..00000000000 --- a/code/modules/hallucination/HUD.dm +++ /dev/null @@ -1,156 +0,0 @@ -/* HUD Hallucinations - * - * Contains: - * Fake Alerts - * Health Alerts - * Health Doll Damage - */ - -/datum/hallucination/hudscrew - -/datum/hallucination/hudscrew/New(mob/living/carbon/C, forced = TRUE, screwyhud_type) - set waitfor = FALSE - ..() - //Screwy HUD - var/chosen_screwyhud = screwyhud_type - if(!chosen_screwyhud) - chosen_screwyhud = pick(SCREWYHUD_CRIT,SCREWYHUD_DEAD,SCREWYHUD_HEALTHY) - target.set_screwyhud(chosen_screwyhud) - feedback_details += "Type: [target.hal_screwyhud]" - QDEL_IN(src, rand(100, 250)) - -/datum/hallucination/hudscrew/Destroy() - target?.set_screwyhud(SCREWYHUD_NONE) - return ..() - -/datum/hallucination/fake_alert - var/alert_type - -/datum/hallucination/fake_alert/New(mob/living/carbon/C, forced = TRUE, specific, duration = 150) - set waitfor = FALSE - ..() - alert_type = pick( - ALERT_NOT_ENOUGH_OXYGEN, - ALERT_NOT_ENOUGH_PLASMA, - ALERT_NOT_ENOUGH_CO2, - ALERT_TOO_MUCH_OXYGEN, - ALERT_TOO_MUCH_CO2, - ALERT_TOO_MUCH_PLASMA, - ALERT_NUTRITION, - ALERT_GRAVITY, - ALERT_FIRE, - ALERT_TEMPERATURE_HOT, - ALERT_TEMPERATURE_COLD, - ALERT_PRESSURE, - ALERT_NEW_LAW, - ALERT_LOCKED, - ALERT_HACKED, - ALERT_CHARGE, - ) - - if(specific) - alert_type = specific - feedback_details += "Type: [alert_type]" - switch(alert_type) - if(ALERT_NOT_ENOUGH_OXYGEN) - target.throw_alert(alert_type, /atom/movable/screen/alert/not_enough_oxy, override = TRUE) - if(ALERT_NOT_ENOUGH_PLASMA) - target.throw_alert(alert_type, /atom/movable/screen/alert/not_enough_plas, override = TRUE) - if(ALERT_NOT_ENOUGH_CO2) - target.throw_alert(alert_type, /atom/movable/screen/alert/not_enough_co2, override = TRUE) - if(ALERT_TOO_MUCH_OXYGEN) - target.throw_alert(alert_type, /atom/movable/screen/alert/too_much_oxy, override = TRUE) - if(ALERT_TOO_MUCH_CO2) - target.throw_alert(alert_type, /atom/movable/screen/alert/too_much_co2, override = TRUE) - if(ALERT_TOO_MUCH_PLASMA) - target.throw_alert(alert_type, /atom/movable/screen/alert/too_much_plas, override = TRUE) - if(ALERT_NUTRITION) - if(prob(50)) - target.throw_alert(alert_type, /atom/movable/screen/alert/fat, override = TRUE) - else - target.throw_alert(alert_type, /atom/movable/screen/alert/starving, override = TRUE) - if(ALERT_GRAVITY) - target.throw_alert(alert_type, /atom/movable/screen/alert/weightless, override = TRUE) - if(ALERT_FIRE) - target.throw_alert(alert_type, /atom/movable/screen/alert/fire, override = TRUE) - if(ALERT_TEMPERATURE_HOT) - alert_type = "temp" - target.throw_alert(alert_type, /atom/movable/screen/alert/hot, 3, override = TRUE) - if(ALERT_TEMPERATURE_COLD) - alert_type = "temp" - target.throw_alert(alert_type, /atom/movable/screen/alert/cold, 3, override = TRUE) - if(ALERT_PRESSURE) - if(prob(50)) - target.throw_alert(alert_type, /atom/movable/screen/alert/highpressure, 2, override = TRUE) - else - target.throw_alert(alert_type, /atom/movable/screen/alert/lowpressure, 2, override = TRUE) - //BEEP BOOP I AM A ROBOT - if(ALERT_NEW_LAW) - target.throw_alert(alert_type, /atom/movable/screen/alert/newlaw, override = TRUE) - if(ALERT_LOCKED) - target.throw_alert(alert_type, /atom/movable/screen/alert/locked, override = TRUE) - if(ALERT_HACKED) - target.throw_alert(alert_type, /atom/movable/screen/alert/hacked, override = TRUE) - if(ALERT_CHARGE) - target.throw_alert(alert_type, /atom/movable/screen/alert/emptycell, override = TRUE) - - addtimer(CALLBACK(src, .proc/cleanup), duration) - -/datum/hallucination/fake_alert/proc/cleanup() - target.clear_alert(alert_type, clear_override = TRUE) - qdel(src) - -///Causes the target to see incorrect health damages on the healthdoll -/datum/hallucination/fake_health_doll - var/timer_id = null - -///Creates a specified doll hallucination, or picks one randomly -/datum/hallucination/fake_health_doll/New(mob/living/carbon/human/human_mob, forced = TRUE, specific_limb, severity, duration = 500) - . = ..() - if(!specific_limb) - specific_limb = pick(list(SCREWYDOLL_HEAD, SCREWYDOLL_CHEST, SCREWYDOLL_L_ARM, SCREWYDOLL_R_ARM, SCREWYDOLL_L_LEG, SCREWYDOLL_R_LEG)) - if(!severity) - severity = rand(1, 5) - LAZYSET(human_mob.hal_screwydoll, specific_limb, severity) - human_mob.update_health_hud() - - timer_id = addtimer(CALLBACK(src, .proc/cleanup), duration, TIMER_STOPPABLE) - -///Increments the severity of the damage seen on the doll -/datum/hallucination/fake_health_doll/proc/increment_fake_damage() - if(!ishuman(target)) - stack_trace("Somehow [target] managed to get a fake health doll hallucination, while not being a human mob.") - var/mob/living/carbon/human/human_mob = target - for(var/entry in human_mob.hal_screwydoll) - human_mob.hal_screwydoll[entry] = clamp(human_mob.hal_screwydoll[entry]+1, 1, 5) - human_mob.update_health_hud() - -///Adds a fake limb to the hallucination datum effect -/datum/hallucination/fake_health_doll/proc/add_fake_limb(specific_limb, severity) - if(!specific_limb) - specific_limb = pick(list(SCREWYDOLL_HEAD, SCREWYDOLL_CHEST, SCREWYDOLL_L_ARM, SCREWYDOLL_R_ARM, SCREWYDOLL_L_LEG, SCREWYDOLL_R_LEG)) - if(!severity) - severity = rand(1, 5) - var/mob/living/carbon/human/human_mob = target - LAZYSET(human_mob.hal_screwydoll, specific_limb, severity) - target.update_health_hud() - -/datum/hallucination/fake_health_doll/target_deleting() - if(isnull(timer_id)) - return - deltimer(timer_id) - timer_id = null - ..() - -///Cleans up the hallucinations - this deletes any overlap, but that shouldn't happen. -/datum/hallucination/fake_health_doll/proc/cleanup() - qdel(src) - -//So that the associated addition proc cleans it up correctly -/datum/hallucination/fake_health_doll/Destroy() - if(!ishuman(target)) - stack_trace("Somehow [target] managed to get a fake health doll hallucination, while not being a human mob.") - var/mob/living/carbon/human/human_mob = target - LAZYNULL(human_mob.hal_screwydoll) - human_mob.update_health_hud() - return ..() diff --git a/code/modules/hallucination/_hallucination.dm b/code/modules/hallucination/_hallucination.dm index 8f54b0b2af8..90622ed430f 100644 --- a/code/modules/hallucination/_hallucination.dm +++ b/code/modules/hallucination/_hallucination.dm @@ -1,162 +1,227 @@ -GLOBAL_LIST_INIT(hallucination_list, list( - /datum/hallucination/chat = 100, - /datum/hallucination/message = 60, - /datum/hallucination/sounds = 50, - /datum/hallucination/battle = 20, - /datum/hallucination/dangerflash = 15, - /datum/hallucination/hudscrew = 12, - /datum/hallucination/fake_health_doll = 12, - /datum/hallucination/fake_alert = 12, - /datum/hallucination/weird_sounds = 8, - /datum/hallucination/stationmessage = 7, - /datum/hallucination/fake_flood = 7, - /datum/hallucination/stray_bullet = 7, - /datum/hallucination/bolts = 7, - /datum/hallucination/items_other = 7, - /datum/hallucination/husks = 7, - /datum/hallucination/items = 4, - /datum/hallucination/fire = 3, - /datum/hallucination/self_delusion = 2, - /datum/hallucination/delusion = 2, - /datum/hallucination/shock = 1, - /datum/hallucination/death = 1, - /datum/hallucination/oh_yeah = 1 - )) - - -/mob/living/carbon/proc/handle_hallucinations(delta_time, times_fired) - if(!hallucination) - return - - hallucination = max(hallucination - (0.5 * delta_time), 0) - if(world.time < next_hallucination) - return - - var/halpick = pick_weight(GLOB.hallucination_list) - new halpick(src, FALSE) - - next_hallucination = world.time + rand(100, 600) - -/mob/living/carbon/proc/set_screwyhud(hud_type) - hal_screwyhud = hud_type - update_health_hud() - +/** + * # Hallucination datum. + * + * Handles effects of a hallucination on a living mob. + * Created and triggered via the [cause hallucination proc][/mob/living/proc/cause_hallucination]. + * + * See also: [the hallucination status effect][/datum/status_effect/hallucination]. + */ /datum/hallucination - var/natural = TRUE - var/mob/living/carbon/target - var/feedback_details //extra info for investigate + /// What is this hallucination's weight in the random hallucination pool? + var/random_hallucination_weight = 0 + /// Who's our next highest abstract parent type? + var/abstract_hallucination_parent = /datum/hallucination + /// Extra info about the hallucination displayed in the log. + var/feedback_details = "" + /// The mob we're targeting with the hallucination. + var/mob/living/hallucinator -/datum/hallucination/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - target = C - natural = !forced +/datum/hallucination/New(mob/living/hallucinator) + if(!isliving(hallucinator)) + stack_trace("[type] was created without a hallucinating mob.") + qdel(src) + return - // Cancel early if the target is deleted - RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/target_deleting) + src.hallucinator = hallucinator + RegisterSignal(hallucinator, COMSIG_PARENT_QDELETING, .proc/target_deleting) + GLOB.all_ongoing_hallucinations += src +/// Signal proc for [COMSIG_PARENT_QDELETING], if the mob hallucinating us is deletes, we should delete too. /datum/hallucination/proc/target_deleting() SIGNAL_HANDLER qdel(src) -/datum/hallucination/proc/wake_and_restore() - target.set_screwyhud(SCREWYHUD_NONE) - target.SetSleeping(0) +/// Starts the hallucination. +/datum/hallucination/proc/start() + SHOULD_CALL_PARENT(FALSE) + stack_trace("[type] didn't implement any hallucination effects in start.") /datum/hallucination/Destroy() - target.investigate_log("was afflicted with a hallucination of type [type] by [natural?"hallucination status":"an external source"]. [feedback_details]", INVESTIGATE_HALLUCINATIONS) + if(hallucinator) + UnregisterSignal(hallucinator, COMSIG_PARENT_QDELETING) + hallucinator = null - if (target) - UnregisterSignal(target, COMSIG_PARENT_QDELETING) - - target = null + GLOB.all_ongoing_hallucinations -= src return ..() -//Returns a random turf in a ring around the target mob, useful for sound hallucinations +/// Returns a random turf in a ring around the hallucinator mob. +/// Useful for sound hallucinations. /datum/hallucination/proc/random_far_turf() - var/x_based = prob(50) - var/first_offset = pick(-8,-7,-6,-5,5,6,7,8) - var/second_offset = rand(-8,8) - var/x_off - var/y_off - if(x_based) - x_off = first_offset - y_off = second_offset + var/first_offset = pick(-8, -7, -6, -5, 5, 6, 7, 8) + var/second_offset = rand(-8, 8) + var/x_offset + var/y_offset + if(prob(50)) + x_offset = first_offset + y_offset = second_offset else - y_off = first_offset - x_off = second_offset - var/turf/T = locate(target.x + x_off, target.y + y_off, target.z) - return T + x_offset = second_offset + y_offset = first_offset -/obj/effect/hallucination + return locate(hallucinator.x + x_offset, hallucinator.y + y_offset, hallucinator.z) + +/// Gets a random non-security member of the crew that is at least 8 tiles away. +/datum/hallucination/proc/random_non_sec_crewmember() + var/list/possible_fakes = list() + for(var/datum/mind/possible_fake as anything in get_crewmember_minds()) + // Sec won't make sense. (Neither will cap but we'll just let it slide) + if(possible_fake.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY) + continue + // Look for minds on the manifest in control of humans + var/mob/living/carbon/human/fake_body = possible_fake.current + if(!istype(fake_body) || fake_body == hallucinator) + continue + // This also won't make sense in most cases + if(get_dist(fake_body, hallucinator) < 8) + continue + possible_fakes += fake_body + + return length(possible_fakes) ? pick(possible_fakes) : null + +/** + * Simple effect that holds an image + * to be shown to one or multiple clients only. + * + * Pass a list of mobs in initialize() that corresponds to all mobs that can see it. + */ +/obj/effect/client_image_holder invisibility = INVISIBILITY_OBSERVER anchored = TRUE - var/mob/living/carbon/target = null -/obj/effect/hallucination/simple - var/image_icon = 'icons/mob/nonhuman-player/alien.dmi' - var/image_state = "alienh_pounce" - var/px = 0 - var/py = 0 - var/col_mod = null - var/image/current_image = null + /// A list of mobs which can see us. + var/list/mob/who_sees_us + /// The created image, what we look like. + var/image/shown_image + /// The icon file the image uses. If null, we have no image + var/image_icon + /// The icon state the image uses + var/image_state + /// The x pixel offset of the image + var/image_pixel_x = 0 + /// The y pixel offset of the image + var/image_pixel_y = 0 + /// Optional, the color of the image + var/image_color + /// The layer of the image var/image_layer = MOB_LAYER + /// The plane of the image var/image_plane = GAME_PLANE - var/active = TRUE //qdelery -/obj/effect/hallucination/singularity_pull() - return - -/obj/effect/hallucination/singularity_act() - return - -/obj/effect/hallucination/simple/Initialize(mapload, mob/living/carbon/T) +/obj/effect/client_image_holder/Initialize(mapload, list/mobs_which_see_us) . = ..() - if(!T) - stack_trace("A hallucination was created with no target") + if(isnull(mobs_which_see_us)) + stack_trace("Client image holder was created with no mobs to see it.") return INITIALIZE_HINT_QDEL - target = T - current_image = GetImage() - if(target.client) - target.client.images |= current_image -/obj/effect/hallucination/simple/proc/GetImage() - var/image/I = image(image_icon,src,image_state,image_layer,dir=src.dir) - I.plane = image_plane - I.pixel_x = px - I.pixel_y = py - if(col_mod) - I.color = col_mod - return I + shown_image = generate_image() -/obj/effect/hallucination/simple/proc/Show(update=1) - if(active) - if(target.client) - target.client.images.Remove(current_image) - if(update) - current_image = GetImage() - if(target.client) - target.client.images |= current_image + if(!islist(mobs_which_see_us)) + mobs_which_see_us = list(mobs_which_see_us) -/obj/effect/hallucination/simple/update_icon(updates=ALL, new_state, new_icon, new_px=0, new_py=0) - image_state = new_state - if(new_icon) - image_icon = new_icon - else - image_icon = initial(image_icon) - px = new_px - py = new_py + who_sees_us = list() + for(var/mob/seer as anything in mobs_which_see_us) + RegisterSignal(seer, COMSIG_MOB_LOGIN, .proc/show_image_to) + RegisterSignal(seer, COMSIG_PARENT_QDELETING, .proc/remove_seer) + who_sees_us += seer + show_image_to(seer) + +/obj/effect/client_image_holder/Destroy(force) + if(shown_image) + for(var/mob/seer as anything in who_sees_us) + remove_seer(seer) + shown_image = null + + who_sees_us.Cut() // probably not needed but who knows + return ..() + +/// Signal proc to clean up references if people who see us are deleted. +/obj/effect/client_image_holder/proc/remove_seer(mob/source) + SIGNAL_HANDLER + + UnregisterSignal(source, list(COMSIG_MOB_LOGIN, COMSIG_PARENT_QDELETING)) + hide_image_from(source) + who_sees_us -= source + + // No reason to exist, anymore + if(!QDELETED(src) && !length(who_sees_us)) + qdel(src) + +/// Generates the image which we take on. +/obj/effect/client_image_holder/proc/generate_image() + var/image/created = image(image_icon, src, image_state, image_layer, dir = src.dir) + created.plane = image_plane + created.pixel_x = image_pixel_x + created.pixel_y = image_pixel_y + if(image_color) + created.color = image_color + return created + +/// Shows the image we generated to the passed mob +/obj/effect/client_image_holder/proc/show_image_to(mob/show_to) + SIGNAL_HANDLER + + show_to.client?.images |= shown_image + +/// Hides the image we generated from the passed mob +/obj/effect/client_image_holder/proc/hide_image_from(mob/hide_from) + SIGNAL_HANDLER + + hide_from.client?.images -= shown_image + +/// Simple helper for refreshing / showing the image to everyone in our list. +/obj/effect/client_image_holder/proc/regenerate_image() + for(var/mob/seer as anything in who_sees_us) + hide_image_from(seer) + + shown_image = generate_image() + + for(var/mob/seer as anything in who_sees_us) + show_image_to(seer) + +// Whenever we perform icon updates, regenerate our image +/obj/effect/client_image_holder/update_icon(updates = ALL) . = ..() - Show() + regenerate_image() -/obj/effect/hallucination/simple/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) +// If we move for some reason, regenerate our image +/obj/effect/client_image_holder/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) . = ..() if(!loc) return - Show() + regenerate_image() -/obj/effect/hallucination/simple/Destroy() - if(target.client) - target.client.images.Remove(current_image) - active = FALSE +/obj/effect/client_image_holder/singularity_pull() + return + +/obj/effect/client_image_holder/singularity_act() + return + +/** + * A client-side image effect tied to the existence of a hallucination. + */ +/obj/effect/client_image_holder/hallucination + invisibility = INVISIBILITY_OBSERVER + anchored = TRUE + /// The hallucination that created us. + var/datum/hallucination/parent + +/obj/effect/client_image_holder/hallucination/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent) + . = ..() + if(!parent) + stack_trace("[type] was created without a parent hallucination.") + return INITIALIZE_HINT_QDEL + + RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/parent_deleting) + src.parent = parent + +/obj/effect/client_image_holder/hallucination/Destroy(force) + UnregisterSignal(parent, COMSIG_PARENT_QDELETING) + parent = null return ..() + +/// Signal proc for [COMSIG_PARENT_QDELETING], if our associated hallucination deletes, we should too +/obj/effect/client_image_holder/hallucination/proc/parent_deleting(datum/source) + SIGNAL_HANDLER + + qdel(src) diff --git a/code/modules/hallucination/airlock.dm b/code/modules/hallucination/airlock.dm deleted file mode 100644 index 950a267f75c..00000000000 --- a/code/modules/hallucination/airlock.dm +++ /dev/null @@ -1,90 +0,0 @@ -/* Airlock Hallucinations - * - * Contains: - * Nearby airlocks being bolted - * Nearby airlocks being unbolted - */ - -/datum/hallucination/bolts - var/list/airlocks_to_hit - var/list/locks - var/next_action = 0 - var/locking = TRUE - -/datum/hallucination/bolts/New(mob/living/carbon/C, forced, door_number) - set waitfor = FALSE - ..() - if(!door_number) - door_number = rand(0,4) //if 0 bolts all visible doors - var/count = 0 - feedback_details += "Door amount: [door_number]" - - for(var/obj/machinery/door/airlock/A in range(7, target)) - if(count>door_number && door_number>0) - break - if(!A.density) - continue - count++ - LAZYADD(airlocks_to_hit, A) - - if(!LAZYLEN(airlocks_to_hit)) //no valid airlocks in sight - qdel(src) - return - - START_PROCESSING(SSfastprocess, src) - -/datum/hallucination/bolts/process(delta_time) - next_action -= (delta_time * 10) - if (next_action > 0) - return - - if (locking) - var/atom/next_airlock = pop(airlocks_to_hit) - if (next_airlock) - var/obj/effect/hallucination/fake_door_lock/lock = new(get_turf(next_airlock)) - lock.target = target - lock.airlock = next_airlock - LAZYADD(locks, lock) - - if (!LAZYLEN(airlocks_to_hit)) - locking = FALSE - next_action = 10 SECONDS - return - else - var/obj/effect/hallucination/fake_door_lock/next_unlock = popleft(locks) - if (next_unlock) - next_unlock.unlock() - else - qdel(src) - return - - next_action = rand(4, 12) - -/datum/hallucination/bolts/Destroy() - . = ..() - QDEL_LIST(locks) - STOP_PROCESSING(SSfastprocess, src) - -/obj/effect/hallucination/fake_door_lock - layer = CLOSED_DOOR_LAYER + 1 //for Bump priority - plane = GAME_PLANE - var/image/bolt_light - var/obj/machinery/door/airlock/airlock - -/obj/effect/hallucination/fake_door_lock/proc/lock() - bolt_light = image(airlock.overlays_file, get_turf(airlock), "lights_bolts",layer=airlock.layer+0.1) - if(target.client) - target.client.images |= bolt_light - target.playsound_local(get_turf(airlock), 'sound/machines/boltsdown.ogg',30,0,3) - -/obj/effect/hallucination/fake_door_lock/proc/unlock() - if(target.client) - target.client.images.Remove(bolt_light) - target.playsound_local(get_turf(airlock), 'sound/machines/boltsup.ogg',30,0,3) - qdel(src) - -/obj/effect/hallucination/fake_door_lock/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(mover == target && airlock.density) - return FALSE - diff --git a/code/modules/hallucination/battle.dm b/code/modules/hallucination/battle.dm new file mode 100644 index 00000000000..f02e4a2019e --- /dev/null +++ b/code/modules/hallucination/battle.dm @@ -0,0 +1,166 @@ +/// Battle hallucination, makes it sound like a melee or gun battle is going on in the background. +/datum/hallucination/battle + abstract_hallucination_parent = /datum/hallucination/battle + random_hallucination_weight = 3 + +/// Subtype of battle hallucination for gun based battles, where it sounds like someone is being shot. +/datum/hallucination/battle/gun + abstract_hallucination_parent = /datum/hallucination/battle/gun + /// The lower end to how many shots we'll fire. + var/shots_to_fire_lower_range = 3 + /// The upper end to how many shots we'll fire. + var/shots_to_fire_upper_range = 6 + /// The sound effect we play when we "fire" a shot. + var/fire_sound = 'sound/weapons/gun/shotgun/shot.ogg' + /// The sound we make when our shot actually "hits" "someone". + var/hit_person_sound = 'sound/weapons/pierce.ogg' + /// The sound we make when our shot misses someone and "hits" a "wall". + var/hit_wall_sound = SFX_RICOCHET + /// The number of successful hits required to "down" the "someone" we're firing at. + var/number_of_hits_to_end = 2 + /// The probability chance we have to make our "hit" person fall down after we pass the number_of_hits_to_end. + var/chance_to_fall = 80 + +/datum/hallucination/battle/gun/start() + fire_loop(random_far_turf(), rand(shots_to_fire_lower_range, shots_to_fire_upper_range)) + return TRUE + +/// The main loop for gun based hallucinations. +/datum/hallucination/battle/gun/proc/fire_loop(turf/source, shots_left = 3, hits = 0) + if(QDELETED(src) || QDELETED(hallucinator) || !source) + return + + // We shoot our shot. + hallucinator.playsound_local(source, fire_sound, 25, TRUE) + + // Shortly after shooting our shot, it plays a hit (or miss) sound. + var/next_hit_sound = rand(0.5 SECONDS, 1 SECONDS) + if(prob(50)) + addtimer(CALLBACK(hallucinator, /mob/.proc/playsound_local, source, hit_person_sound, 25, TRUE), next_hit_sound) + hits++ + else + addtimer(CALLBACK(hallucinator, /mob/.proc/playsound_local, source, hit_wall_sound, 25, TRUE), next_hit_sound) + + // If we scored enough hits, we have a chance to knock them down and stop the hallucination early. + if(hits >= number_of_hits_to_end && prob(chance_to_fall)) + addtimer(CALLBACK(hallucinator, /mob/.proc/playsound_local, source, SFX_BODYFALL, 25, TRUE), next_hit_sound) + qdel(src) + + // Or, if we do have shots left, keep it going. + else if(--shots_left > 0) + addtimer(CALLBACK(src, .proc/fire_loop, source, shots_left, hits), rand(CLICK_CD_RANGE, CLICK_CD_RANGE + 6)) + + // Otherwise, if we have no shots left, stop the hallucination. + else + qdel(src) + + +/// Gun battle hallucination that sounds like disabler fire. +/datum/hallucination/battle/gun/disabler + shots_to_fire_lower_range = 5 + shots_to_fire_upper_range = 10 + fire_sound = 'sound/weapons/taser2.ogg' + hit_person_sound = 'sound/weapons/tap.ogg' + hit_wall_sound = 'sound/weapons/effects/searwall.ogg' + number_of_hits_to_end = 3 + chance_to_fall = 70 + +/// Gun battle hallucination that sounds like laser fire. +/datum/hallucination/battle/gun/laser + shots_to_fire_lower_range = 5 + shots_to_fire_upper_range = 10 + fire_sound = 'sound/weapons/laser.ogg' + hit_person_sound = 'sound/weapons/sear.ogg' + hit_wall_sound = 'sound/weapons/effects/searwall.ogg' + number_of_hits_to_end = 4 + chance_to_fall = 70 + +/// A hallucination of someone being hit with a stun prod, followed by cable cuffing. +/datum/hallucination/battle/stun_prod + +/datum/hallucination/battle/stun_prod/start() + var/turf/source = random_far_turf() + + hallucinator.playsound_local(source, 'sound/weapons/egloves.ogg', 40, TRUE) + hallucinator.playsound_local(source, SFX_BODYFALL, 25, TRUE) + addtimer(CALLBACK(src, .proc/fake_cuff, source), 2 SECONDS) + return TRUE + +/// Plays a fake cable-cuff sound and deletes the hallucination. +/datum/hallucination/battle/stun_prod/proc/fake_cuff(turf/source) + if(QDELETED(src) || QDELETED(hallucinator) || !source) + return + + hallucinator.playsound_local(source, 'sound/weapons/cablecuff.ogg', 15, TRUE) + qdel(src) + +/// A hallucination of someone being stun batonned, and subsequently harmbatonned. +/datum/hallucination/battle/harm_baton + +/datum/hallucination/battle/harm_baton/start() + var/turf/source = random_far_turf() + + hallucinator.playsound_local(source, 'sound/weapons/egloves.ogg', 40, TRUE) + hallucinator.playsound_local(source, SFX_BODYFALL, 25, TRUE) + + addtimer(CALLBACK(src, .proc/harmbaton_loop, source, rand(5, 12)), 2 SECONDS) + return TRUE + +/// The main sound loop for harmbatonning. +/datum/hallucination/battle/harm_baton/proc/harmbaton_loop(turf/source, hits_remaing = 5) + if(QDELETED(src) || QDELETED(hallucinator) || !source) + return + + hallucinator.playsound_local(source, SFX_SWING_HIT, 50, TRUE) + if(--hits_remaing <= 0) + qdel(src) + + else + addtimer(CALLBACK(src, .proc/harmbaton_loop, source, hits_remaing), rand(CLICK_CD_MELEE, CLICK_CD_MELEE + 4)) + +/// A hallucination of someone unsheathing an energy sword, going to town, and sheathing it again. +/datum/hallucination/battle/e_sword + +/datum/hallucination/battle/e_sword/start() + var/turf/source = random_far_turf() + + hallucinator.playsound_local(source, 'sound/weapons/saberon.ogg', 15, 1) + addtimer(CALLBACK(src, .proc/stab_loop, source, rand(4, 8)), CLICK_CD_MELEE) + return TRUE + +/// The main sound loop of someone being esworded. +/datum/hallucination/battle/e_sword/proc/stab_loop(turf/source, stabs_remaining = 4) + if(QDELETED(src) || QDELETED(hallucinator) || !source) + return + + if(stabs_remaining >= 1) + hallucinator.playsound_local(source, 'sound/weapons/blade1.ogg', 50, TRUE) + + else + hallucinator.playsound_local(source, 'sound/weapons/saberoff.ogg', 15, TRUE) + qdel(src) + return + + if(stabs_remaining == 4) + hallucinator.playsound_local(source, SFX_BODYFALL, 25, TRUE) + + addtimer(CALLBACK(src, .proc/stab_loop, source, stabs_remaining - 1), rand(CLICK_CD_MELEE, CLICK_CD_MELEE + 6)) + +/// A hallucination of a syndicate bomb ticking down. +/datum/hallucination/battle/bomb + +/datum/hallucination/battle/bomb/start() + addtimer(CALLBACK(src, .proc/fake_tick, random_far_turf(), rand(3, 11)), 1.5 SECONDS) + return TRUE + +/// The loop of the (fake) bomb ticking down. +/datum/hallucination/battle/bomb/proc/fake_tick(turf/source, ticks_remaining = 3) + if(QDELETED(src) || QDELETED(hallucinator) || !source) + return + + hallucinator.playsound_local(source, 'sound/items/timer.ogg', 25, FALSE) + if(--ticks_remaining <= 0) + qdel(src) + + else + addtimer(CALLBACK(src, .proc/fake_tick, source, ticks_remaining), 1.5 SECONDS) diff --git a/code/modules/hallucination/body.dm b/code/modules/hallucination/body.dm new file mode 100644 index 00000000000..9bb8a02a0e6 --- /dev/null +++ b/code/modules/hallucination/body.dm @@ -0,0 +1,56 @@ +/// Makes a random body appear and disappear quickly. +/datum/hallucination/body + abstract_hallucination_parent = /datum/hallucination/body + /// The file to make the body image from. + var/body_image_file + /// The icon state to make the body image form. + var/body_image_state + /// The actual image we made and showed show. + var/image/shown_body + +/datum/hallucination/body/start() + // This hallucination is purely visual, so we don't need to bother for clientless mobs + if(!hallucinator.client) + return FALSE + + var/list/possible_points = list() + for(var/turf/open/floor/open_floor in view(hallucinator)) + possible_points += open_floor + + if(!length(possible_points)) + return FALSE + + shown_body = make_body_image(pick(possible_points)) + + hallucinator.client?.images |= shown_body + QDEL_IN(src, rand(3 SECONDS, 5 SECONDS)) //Only seen for a brief moment. + return TRUE + +/datum/hallucination/body/Destroy() + hallucinator.client?.images -= shown_body + shown_body = null + return ..() + +/// Makes the image of the body to show at the location passed. +/datum/hallucination/body/proc/make_body_image(turf/location) + return image(body_image_file, location, body_image_state, TURF_LAYER) + +/datum/hallucination/body/husk + random_hallucination_weight = 4 + body_image_file = 'icons/mob/species/human/human.dmi' + body_image_state = "husk" + +/datum/hallucination/body/husk/sideways + random_hallucination_weight = 2 + +/datum/hallucination/body/husk/sideways/make_body_image(turf/location) + var/image/body = ..() + var/matrix/turn_matrix = matrix() + turn_matrix.Turn(90) + body.transform = turn_matrix + return body + +/datum/hallucination/body/alien + random_hallucination_weight = 1 + body_image_file = 'icons/mob/nonhuman-player/alien.dmi' + body_image_state = "alienother" diff --git a/code/modules/hallucination/bolted_airlocks.dm b/code/modules/hallucination/bolted_airlocks.dm new file mode 100644 index 00000000000..1cf5f03b967 --- /dev/null +++ b/code/modules/hallucination/bolted_airlocks.dm @@ -0,0 +1,127 @@ +/datum/hallucination/bolts + random_hallucination_weight = 7 + /// 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 + var/list/datum/weakref/locks + /// A number relating to the number of ssfastprocessing ticks (x 10) until the next action + var/next_action = 0 + /// Whether we're currently locking, or unlocking + var/locking = TRUE + +/datum/hallucination/bolts/start() + var/door_number = rand(0, 4) //if 0, we bolt all visible doors + feedback_details += "Door amount: [door_number]" + + for(var/obj/machinery/door/airlock/nearby_airlock in view(hallucinator)) + if(LAZYLEN(airlocks_to_hit) > door_number && door_number > 0) + break + if(!nearby_airlock.density) + continue + LAZYADD(airlocks_to_hit, WEAKREF(nearby_airlock)) + + if(!LAZYLEN(airlocks_to_hit)) // Not an airlock in sight + return FALSE + + START_PROCESSING(SSfastprocess, src) + return TRUE + +/datum/hallucination/bolts/process(delta_time) + if(QDELETED(src)) + return + + next_action -= (delta_time * 10) + if(next_action > 0) + return + + if(locking) + var/datum/weakref/next_airlock = pop(airlocks_to_hit) + var/obj/machinery/door/airlock/to_lock = next_airlock?.resolve() + if(to_lock) + var/obj/effect/client_image_holder/hallucination/fake_door_lock/lock = new(to_lock.loc, hallucinator, src, to_lock) + LAZYADD(locks, WEAKREF(lock)) + + if(!LAZYLEN(airlocks_to_hit)) + locking = FALSE + next_action = 100 + return + + else + var/datum/weakref/next_unlock = popleft(locks) + var/obj/effect/client_image_holder/hallucination/fake_door_lock/to_unlock = next_unlock?.resolve() + if(to_unlock) + to_unlock.unlock() + + else + // All unlocked, qdel time + qdel(src) + return + + next_action = rand(4, 12) + +/datum/hallucination/bolts/Destroy() + // Clean up any locks we happen to have remaining on qdel. + // Hypothetically this shouldn't delete anything. But just in case. + for(var/datum/weakref/leftover_lock_ref as anything in locks) + var/obj/effect/client_image_holder/hallucination/fake_door_lock/leftover_lock = leftover_lock_ref.resolve() + if(!QDELETED(leftover_lock)) + qdel(leftover_lock) + + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/obj/effect/client_image_holder/hallucination/fake_door_lock + layer = CLOSED_DOOR_LAYER + 1 //for Bump priority + plane = GAME_PLANE + + /// The real airlock we're fake bolting down. + var/obj/machinery/door/airlock/airlock + +/obj/effect/client_image_holder/hallucination/fake_door_lock/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent, obj/machinery/door/airlock/airlock) + if(!airlock) + stack_trace("[type] was created somewhere without an associated airlock.") + return INITIALIZE_HINT_QDEL + + src.airlock = airlock + RegisterSignal(airlock, COMSIG_PARENT_QDELETING, .proc/on_airlock_deleted) + // We need to grab these for our image before we run our parent's parent initialize + src.image_icon = airlock.overlays_file + src.image_state = "lights_[AIRLOCK_LIGHT_BOLTS]" + + return ..() + +/obj/effect/client_image_holder/hallucination/fake_door_lock/Destroy(force) + UnregisterSignal(airlock, COMSIG_PARENT_QDELETING) + airlock = null + return ..() + +/obj/effect/client_image_holder/hallucination/fake_door_lock/generate_image() + var/image/created = ..() + created.layer = airlock.layer + 0.1 + return created + +/obj/effect/client_image_holder/hallucination/fake_door_lock/show_image_to(mob/show_to) + . = ..() + show_to.playsound_local(get_turf(src), 'sound/machines/boltsdown.ogg', 30, FALSE, 3) + +/obj/effect/client_image_holder/hallucination/fake_door_lock/hide_image_from(mob/show_to) + . = ..() + show_to.playsound_local(get_turf(src), 'sound/machines/boltsup.ogg', 30, FALSE, 3) + +/obj/effect/client_image_holder/hallucination/fake_door_lock/proc/on_airlock_deleted(datum/source) + SIGNAL_HANDLER + + qdel(src) + +/obj/effect/client_image_holder/hallucination/fake_door_lock/proc/unlock() + for(var/mob/seer as anything in who_sees_us) + hide_image_from(seer) + + shown_image = null + qdel(src) + +/obj/effect/client_image_holder/hallucination/fake_door_lock/CanAllowThrough(atom/movable/mover, border_dir) + if((mover in who_sees_us) && airlock.density) + return FALSE + + return ..() diff --git a/code/modules/hallucination/bubblegum_attack.dm b/code/modules/hallucination/bubblegum_attack.dm new file mode 100644 index 00000000000..52796c8dea6 --- /dev/null +++ b/code/modules/hallucination/bubblegum_attack.dm @@ -0,0 +1,99 @@ +/// Sends a fake bubblegum charging through a nearby wall to our target. +/datum/hallucination/oh_yeah + random_hallucination_weight = 1 + /// 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. + var/image/fake_rune + /// if TRUE, we will also send one of the hallucination lines when we start. + var/haunt_them = FALSE + /// if haunt_them is TRUE, they will also be shown one of these lines when the hallucination occurs + var/static/list/hallucination_lines = BUBBLEGUM_HALLUCINATION_LINES + +/datum/hallucination/oh_yeah/New(mob/living/hallucinator, source = "an external source", haunt_them = FALSE) + src.haunt_them = haunt_them + return ..() + +/datum/hallucination/oh_yeah/Destroy() + if(fake_broken_wall) + hallucinator.client?.images -= fake_broken_wall + fake_broken_wall = null + if(fake_rune) + hallucinator.client?.images -= fake_rune + fake_rune = null + + return ..() + +/datum/hallucination/oh_yeah/start() + var/turf/closed/wall/wall_source = locate() in range(7, hallucinator) + if(!wall_source) + return FALSE + + feedback_details += "Source: ([wall_source.x], [wall_source.y], [wall_source.z])" + + var/turf/target_landing_turf = get_turf(hallucinator) + var/turf/target_landing_image_turf = get_step(target_landing_turf, SOUTHWEST) // The icon is 3x3, so we shift down+left + + if(hallucinator.client) + + fake_broken_wall = image('icons/turf/floors.dmi', wall_source, "plating", layer = TURF_LAYER) + fake_broken_wall.override = TRUE + fake_rune = image('icons/effects/96x96.dmi', target_landing_image_turf, "landing", layer = ABOVE_OPEN_TURF_LAYER) + + hallucinator.client?.images |= fake_broken_wall + hallucinator.client?.images |= fake_rune + + hallucinator.playsound_local(wall_source, 'sound/effects/meteorimpact.ogg', 150, TRUE) + + if(haunt_them) + to_chat(hallucinator, pick(hallucination_lines)) + + var/obj/effect/client_image_holder/hallucination/bubblegum/fake_bubbles = new(wall_source, hallucinator, src) + addtimer(CALLBACK(src, .proc/charge_loop, fake_bubbles, target_landing_turf), 1 SECONDS) + return TRUE + +/** + * Recursive function that operates as a "fake charge" of our effect towards the target turf. + */ +/datum/hallucination/oh_yeah/proc/charge_loop(obj/effect/client_image_holder/hallucination/bubblegum/fake_bubbles, turf/landing_turf) + if(QDELETED(src)) + return + + if(QDELETED(hallucinator) \ + || QDELETED(fake_bubbles) \ + || !landing_turf \ + || fake_bubbles.z != hallucinator.z \ + || fake_bubbles.z != landing_turf.z \ + ) + qdel(src) + return + + if(get_turf(fake_bubbles) == landing_turf || hallucinator.stat == DEAD) + QDEL_IN(src, 3 SECONDS) + return + + fake_bubbles.forceMove(get_step_towards(fake_bubbles, landing_turf)) + fake_bubbles.setDir(get_dir(fake_bubbles, landing_turf)) + hallucinator.playsound_local(get_turf(fake_bubbles), 'sound/effects/meteorimpact.ogg', 150, TRUE) + shake_camera(hallucinator, 2, 1) + + if(fake_bubbles.Adjacent(hallucinator)) + hallucinator.Paralyze(8 SECONDS) + hallucinator.adjustStaminaLoss(40) + step_away(hallucinator, fake_bubbles) + shake_camera(hallucinator, 4, 3) + hallucinator.visible_message( + span_warning("[hallucinator] jumps backwards, falling on the ground!"), + span_userdanger("[fake_bubbles] slams into you!"), + ) + QDEL_IN(src, 3 SECONDS) + + else + addtimer(CALLBACK(src, .proc/charge_loop, fake_bubbles, landing_turf), 0.2 SECONDS) + +/// Fake bubblegum hallucination effect for the oh yeah hallucination +/obj/effect/client_image_holder/hallucination/bubblegum + name = "Bubblegum" + image_icon = 'icons/mob/simple/lavaland/96x96megafauna.dmi' + image_state = "bubblegum" + image_pixel_x = -32 diff --git a/code/modules/hallucination/chat.dm b/code/modules/hallucination/chat.dm deleted file mode 100644 index 0e0bf0ed457..00000000000 --- a/code/modules/hallucination/chat.dm +++ /dev/null @@ -1,169 +0,0 @@ -/* Chat Hallucinations - * - * Contains: - * Radio messages (";Someone recall the shuttle!", ";Set John Doe to arrest!") - * Speak messages ("Get out!", "Did you hear that?") - * Action messages ("You feel a tiny prick!", "John Doe puts the cryptographic sequencer into their backpack.") - * Station messages ("The Emergency Shuttle has docked with the station.", "Hostile runtimes detected in all station systems") - */ - -#define HAL_LINES_FILE "hallucination.json" - -/datum/hallucination/chat - -/datum/hallucination/chat/New(mob/living/carbon/C, forced = TRUE, force_radio, specific_message) - set waitfor = FALSE - ..() - var/target_name = target.first_name() - var/speak_messages = list("[pick_list_replacements(HAL_LINES_FILE, "suspicion")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "conversation")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "greetings")][target.first_name()]!",\ - "[pick_list_replacements(HAL_LINES_FILE, "getout")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "weird")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "didyouhearthat")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "doubt")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "aggressive")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "help")]!!",\ - "[pick_list_replacements(HAL_LINES_FILE, "escape")]",\ - "I'm infected, [pick_list_replacements(HAL_LINES_FILE, "infection_advice")]!") - - var/radio_messages = list("[pick_list_replacements(HAL_LINES_FILE, "people")] is [pick_list_replacements(HAL_LINES_FILE, "accusations")]!",\ - "Help!",\ - "[pick_list_replacements(HAL_LINES_FILE, "threat")] in [pick_list_replacements(HAL_LINES_FILE, "location")][prob(50)?"!":"!!"]",\ - "[pick("Where's [target.first_name()]?", "Set [target.first_name()] to arrest!")]",\ - "[pick("C","Ai, c","Someone c","Rec")]all the shuttle!",\ - "AI [pick("rogue", "is dead")]!!") - - var/mob/living/carbon/person = null - var/datum/language/understood_language = target.get_random_understood_language() - for(var/mob/living/carbon/H in view(target)) - if(H == target) - continue - if(!person) - person = H - else - if(get_dist(target,H)[other] puts the [pick(\ - "revolver","energy sword","cryptographic sequencer","power sink","energy bow",\ - "hybrid taser","stun baton","flash","syringe gun","circular saw","tank transfer valve",\ - "ritual dagger","spellbook",\ - "Codex Cicatrix", "Living Heart",\ - "pulse rifle","captain's spare ID","hand teleporter","hypospray","antique laser gun","X-01 MultiPhase Energy Gun","station's blueprints"\ - )] into [equipped_backpack].") - - message_pool.Add("[other] [pick("sneezes","coughs")].") - - message_pool.Add(span_notice("You hear something squeezing through the ducts..."), \ - span_notice("Your [pick("arm", "leg", "back", "head")] itches."),\ - span_warning("You feel [pick("hot","cold","dry","wet","woozy","faint")]."), - span_warning("Your stomach rumbles."), - span_warning("Your head hurts."), - span_warning("You hear a faint buzz in your head."), - "[target] sneezes.") - if(prob(10)) - message_pool.Add(span_warning("Behind you."),\ - span_warning("You hear a faint laughter."), - span_warning("You see something move."), - span_warning("You hear skittering on the ceiling."), - span_warning("You see an inhumanly tall silhouette moving in the distance.")) - if(prob(10)) - message_pool.Add("[pick_list_replacements(HAL_LINES_FILE, "advice")]") - var/chosen = pick(message_pool) - feedback_details += "Message: [chosen]" - to_chat(target, chosen) - qdel(src) - -/datum/hallucination/stationmessage - -/datum/hallucination/stationmessage/New(mob/living/carbon/C, forced = TRUE, message) - set waitfor = FALSE - ..() - if(!message) - message = pick("ratvar","shuttle dock","blob alert","malf ai","meteors","supermatter") - feedback_details += "Type: [message]" - switch(message) - if("blob alert") - to_chat(target, "

Biohazard Alert

") - to_chat(target, "

[span_alert("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.")]

") - SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_OUTBREAK5]) - if("ratvar") - target.playsound_local(target, 'sound/machines/clockcult/ark_deathrattle.ogg', 50, FALSE, pressure_affected = FALSE) - target.playsound_local(target, 'sound/effects/clockcult_gateway_disrupted.ogg', 50, FALSE, pressure_affected = FALSE) - addtimer(CALLBACK( - target, - /mob/.proc/playsound_local, - target, - 'sound/effects/explosion_distant.ogg', - 50, - FALSE, - /* frequency = */ null, - /* falloff_exponential = */ null, - /* channel = */ null, - /* pressure_affected = */ FALSE - ), 27) - if("shuttle dock") - to_chat(target, "

Priority Announcement

") - to_chat(target, "

[span_alert("The Emergency Shuttle has docked with the station. You have 3 minutes to board the Emergency Shuttle.")]

") - SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_SHUTTLEDOCK]) - if("malf ai") //AI is doomsdaying! - to_chat(target, "

Anomaly Alert

") - to_chat(target, "

[span_alert("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core.")]

") - SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_AIMALF]) - if("meteors") //Meteors inbound! - to_chat(target, "

Meteor Alert

") - to_chat(target, "

[span_alert("Meteors have been detected on collision course with the station.")]

") - SEND_SOUND(target, SSstation.announcer.event_sounds[ANNOUNCER_METEORS]) - if("supermatter") - SEND_SOUND(target, 'sound/magic/charge.ogg') - to_chat(target, span_boldannounce("You feel reality distort for a moment...")) diff --git a/code/modules/hallucination/death.dm b/code/modules/hallucination/death.dm deleted file mode 100644 index 744ff878a56..00000000000 --- a/code/modules/hallucination/death.dm +++ /dev/null @@ -1,34 +0,0 @@ -/datum/hallucination/death - -/datum/hallucination/death/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - target.set_screwyhud(SCREWYHUD_DEAD) - target.Paralyze(300) - target.silent += 10 - to_chat(target, span_deadsay("[target.real_name] has died at [get_area_name(target)].")) - - var/delay = 0 - - if(prob(50)) - var/mob/fakemob - var/list/dead_people = list() - for(var/mob/dead/observer/G in GLOB.player_list) - dead_people += G - if(LAZYLEN(dead_people)) - fakemob = pick(dead_people) - else - fakemob = target //ever been so lonely you had to haunt yourself? - if(fakemob) - delay = rand(20, 50) - addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, target, "DEAD: [fakemob.name] says, \"[pick("rip","why did i just drop dead?","hey [target.first_name()]","git gud","you too?","is the AI rogue?",\ - "i[prob(50)?" fucking":""] hate [pick("blood cult", "clock cult", "revenants", "this round","this","myself","admins","you")]")]\""), delay) - - addtimer(CALLBACK(src, .proc/cleanup), delay + rand(70, 90)) - -/datum/hallucination/death/proc/cleanup() - if (target) - target.set_screwyhud(SCREWYHUD_NONE) - target.SetParalyzed(0) - target.silent = FALSE - qdel(src) diff --git a/code/modules/hallucination/delusions.dm b/code/modules/hallucination/delusions.dm new file mode 100644 index 00000000000..d09745fdb15 --- /dev/null +++ b/code/modules/hallucination/delusions.dm @@ -0,0 +1,177 @@ +/// A hallucination that makes us and (possibly) other people look like something else. +/datum/hallucination/delusion + abstract_hallucination_parent = /datum/hallucination/delusion + + /// The duration of the delusions + var/duration = 30 SECONDS + + /// If TRUE, this delusion affects us + var/affects_us = TRUE + /// If TRUE, this hallucination affects all humans in existence + var/affects_others = FALSE + /// If TRUE, people in view of our hallcuinator won't be affected (requires affects_others) + var/skip_nearby = FALSE + /// If TRUE, we will play the wabbajack sound effect to the hallucinator + var/play_wabbajack = FALSE + + /// The file the delusion image is made from + var/delusion_icon_file + /// The icon state of the delusion image + var/delusion_icon_state + /// The name of the delusion image + var/delusion_name + + /// A list of all images we've made + var/list/image/delusions + +/datum/hallucination/delusion/New( + mob/living/hallucinator, + duration = 30 SECONDS, + affects_us = TRUE, + affects_others = FALSE, + skip_nearby = TRUE, + play_wabbajack = FALSE, +) + + src.duration = duration + src.affects_us = affects_us + src.affects_others = affects_others + src.skip_nearby = skip_nearby + src.play_wabbajack = play_wabbajack + return ..() + +/datum/hallucination/delusion/Destroy() + if(!QDELETED(hallucinator)) + for(var/image/to_remove as anything in delusions) + hallucinator.client?.images -= to_remove + + return ..() + +/datum/hallucination/delusion/start() + if(!hallucinator.client || !delusion_icon_file) + return FALSE + + feedback_details += "Delusion: [delusion_name]" + + var/list/mob/living/carbon/human/funny_looking_mobs = list() + + // The delusion includes others - all humans + if(affects_others) + funny_looking_mobs |= GLOB.human_list.Copy() + + // The delusion includes us - we might be in it already, we might not + if(affects_us) + funny_looking_mobs |= hallucinator + + // The delusion should not inlude us + else + funny_looking_mobs -= hallucinator + + // The delusion shouldn not include anyone in view of us + if(skip_nearby) + for(var/mob/living/carbon/human/nearby_human in view(hallucinator)) + if(nearby_human == hallucinator) // Already handled by affects_us + continue + funny_looking_mobs -= nearby_human + + for(var/mob/living/carbon/human/found_human in funny_looking_mobs) + var/image/funny_image = make_delusion_image(found_human) + LAZYADD(delusions, funny_image) + hallucinator.client.images |= funny_image + + if(play_wabbajack) + to_chat(hallucinator, span_hear("...wabbajack...wabbajack...")) + hallucinator.playsound_local(get_turf(hallucinator), 'sound/magic/staff_change.ogg', 50, TRUE) + + if(duration > 0) + QDEL_IN(src, duration) + return TRUE + +/datum/hallucination/delusion/proc/make_delusion_image(mob/over_who) + var/image/funny_image = image(delusion_icon_file, over_who, delusion_icon_state) + funny_image.name = delusion_name + funny_image.override = TRUE + return funny_image + +/// Used for making custom delusions. +/datum/hallucination/delusion/custom + random_hallucination_weight = 0 + +/datum/hallucination/delusion/custom/New( + mob/living/hallucinator, + duration = 30 SECONDS, + affects_us = TRUE, + affects_others = FALSE, + skip_nearby = FALSE, + play_wabbajack = FALSE, + custom_icon_file, + custom_icon_state, + custom_name, +) + + if(!custom_icon_file || !custom_icon_state) + stack_trace("Custom delusion hallucination was created without any custom icon information passed.") + + src.delusion_icon_file = custom_icon_file + src.delusion_icon_state = custom_icon_state + src.delusion_name = custom_name + + return ..() + +/datum/hallucination/delusion/preset + abstract_hallucination_parent = /datum/hallucination/delusion/preset + random_hallucination_weight = 2 + +/datum/hallucination/delusion/preset/nothing + delusion_icon_file = 'icons/effects/effects.dmi' + delusion_icon_state = "nothing" + delusion_name = "..." + +/datum/hallucination/delusion/preset/curse + delusion_icon_file = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + delusion_icon_state = "curseblob" + delusion_name = "???" + +/datum/hallucination/delusion/preset/monkey + delusion_icon_file = 'icons/mob/species/human/human.dmi' + delusion_icon_state = "monkey" + delusion_name = "monkey" + +/datum/hallucination/delusion/preset/monkey/start() + delusion_name += " ([rand(1, 999)])" + return ..() + +/datum/hallucination/delusion/preset/corgi + delusion_icon_file = 'icons/mob/simple/pets.dmi' + delusion_icon_state = "corgi" + delusion_name = "corgi" + +/datum/hallucination/delusion/preset/carp + delusion_icon_file = 'icons/mob/simple/carp.dmi' + delusion_icon_state = "carp" + delusion_name = "carp" + +/datum/hallucination/delusion/preset/skeleton + delusion_icon_file = 'icons/mob/species/human/human.dmi' + delusion_icon_state = "skeleton" + delusion_name = "skeleton" + +/datum/hallucination/delusion/preset/zombie + delusion_icon_file = 'icons/mob/species/human/human.dmi' + delusion_icon_state = "zombie" + delusion_name = "zombie" + +/datum/hallucination/delusion/preset/demon + delusion_icon_file = 'icons/mob/simple/mob.dmi' + delusion_icon_state = "daemon" + delusion_name = "demon" + +/datum/hallucination/delusion/preset/cyborg + play_wabbajack = TRUE + delusion_icon_file = 'icons/mob/silicon/robots.dmi' + delusion_icon_state = "robot" + delusion_name = "cyborg" + +/datum/hallucination/delusion/preset/cyborg/make_delusion_image(mob/over_who) + . = ..() + hallucinator.playsound_local(get_turf(over_who), 'sound/voice/liveagain.ogg', 75, TRUE) diff --git a/code/modules/hallucination/fake_alert.dm b/code/modules/hallucination/fake_alert.dm new file mode 100644 index 00000000000..56cecba0476 --- /dev/null +++ b/code/modules/hallucination/fake_alert.dm @@ -0,0 +1,107 @@ +/// Fake alert hallucination. Causes a fake alert to be thrown to the hallucinator. +/datum/hallucination/fake_alert + abstract_hallucination_parent = /datum/hallucination/fake_alert + random_hallucination_weight = 1 + + var/del_timer_id + /// The duration of the alert being thrown. + var/duration + /// The category of the fake alert + var/alert_category + /// The type of the fake alert. Can be a list, if you want it to draw from multiple types (randomly). + var/alert_type + /// Optional, the severity of the alert. + var/optional_severity + +/datum/hallucination/fake_alert/New(mob/living/hallucinator, duration = 15 SECONDS) + src.duration = duration + return ..() + +/datum/hallucination/fake_alert/Destroy() + if(del_timer_id) + deltimer(del_timer_id) + if(!QDELETED(hallucinator)) + hallucinator.clear_alert(alert_category, clear_override = TRUE) + return ..() + +/datum/hallucination/fake_alert/start() + var/picked_type = islist(alert_type) ? pick(alert_type) : alert_type + + feedback_details += "Alert type: [alert_category], Actual type: [alert_type]" + + hallucinator.throw_alert( + category = alert_category, + type = picked_type, + severity = optional_severity, + override = TRUE, + ) + + del_timer_id = QDEL_IN(src, duration) + return TRUE + +/datum/hallucination/fake_alert/need_oxygen + alert_category = ALERT_NOT_ENOUGH_OXYGEN + alert_type = /atom/movable/screen/alert/not_enough_oxy + +/datum/hallucination/fake_alert/need_plasma + alert_category = ALERT_NOT_ENOUGH_PLASMA + alert_type = /atom/movable/screen/alert/not_enough_plas + +/datum/hallucination/fake_alert/need_co2 + alert_category = ALERT_NOT_ENOUGH_CO2 + alert_type = /atom/movable/screen/alert/not_enough_co2 + +/datum/hallucination/fake_alert/bad_oxygen + alert_category = ALERT_TOO_MUCH_OXYGEN + alert_type = /atom/movable/screen/alert/too_much_oxy + +/datum/hallucination/fake_alert/bad_plasma + alert_category = ALERT_TOO_MUCH_PLASMA + alert_type = /atom/movable/screen/alert/too_much_plas + +/datum/hallucination/fake_alert/bad_co2 + alert_category = ALERT_TOO_MUCH_CO2 + alert_type = /atom/movable/screen/alert/too_much_co2 + +/datum/hallucination/fake_alert/nutrition + alert_category = ALERT_NUTRITION + alert_type = list(/atom/movable/screen/alert/fat, /atom/movable/screen/alert/starving) + +/datum/hallucination/fake_alert/gravity + alert_category = ALERT_GRAVITY + alert_type = /atom/movable/screen/alert/weightless + +/datum/hallucination/fake_alert/fire + alert_category = ALERT_FIRE + alert_type = /atom/movable/screen/alert/fire + +/datum/hallucination/fake_alert/hot + alert_category = ALERT_TEMPERATURE + alert_type = /atom/movable/screen/alert/hot + optional_severity = 3 + +/datum/hallucination/fake_alert/cold + alert_category = ALERT_TEMPERATURE + alert_type = /atom/movable/screen/alert/cold + optional_severity = 3 + +/datum/hallucination/fake_alert/pressure + alert_category = ALERT_PRESSURE + alert_type = list(/atom/movable/screen/alert/highpressure, /atom/movable/screen/alert/lowpressure) + optional_severity = 2 + +/datum/hallucination/fake_alert/law + alert_category = ALERT_NEW_LAW + alert_type = /atom/movable/screen/alert/newlaw + +/datum/hallucination/fake_alert/locked + alert_category = ALERT_LOCKED + alert_type = /atom/movable/screen/alert/locked + +/datum/hallucination/fake_alert/hacked + alert_category = ALERT_HACKED + alert_type = /atom/movable/screen/alert/hacked + +/datum/hallucination/fake_alert/need_charge + alert_category = ALERT_CHARGE + alert_type = /atom/movable/screen/alert/emptycell diff --git a/code/modules/hallucination/fake_chat.dm b/code/modules/hallucination/fake_chat.dm new file mode 100644 index 00000000000..07221e4b776 --- /dev/null +++ b/code/modules/hallucination/fake_chat.dm @@ -0,0 +1,89 @@ +/// Sends a fake chat message to the hallucinator. +/datum/hallucination/chat + random_hallucination_weight = 100 + + /// If TRUE, we force the message to be hallucinated from common radio. Only set in New() + var/force_radio + /// If set, a message we force to be picked, rather than an auto-generated message. Only set in New() + var/specific_message + +/datum/hallucination/chat/New(mob/living/hallucinator, force_radio = FALSE, specific_message) + src.force_radio = force_radio + src.specific_message = specific_message + return ..() + +/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 + + if(!speaker) + speaker = nearby_human + else if(get_dist(hallucinator, nearby_human) < get_dist(hallucinator, speaker)) + speaker = nearby_human + + // Get person to affect if radio hallucination + var/is_radio = !speaker || force_radio + if(is_radio) + var/list/humans = list() + for(var/mob/living/carbon/human/existing_human in GLOB.alive_mob_list) + humans += existing_human + speaker = pick(humans) + + // Time to generate a message. + // Spans of our message + var/spans = list(speaker.speech_span) + + // Contents of our message + var/chosen = specific_message + // If we didn't have a preset one, let's make one up. + if(!chosen) + if(is_radio) + chosen = pick(list("Help!", + "[pick_list_replacements(HALLUCINATION_FILE, "people")] is [pick_list_replacements(HALLUCINATION_FILE, "accusations")]!", + "[pick_list_replacements(HALLUCINATION_FILE, "threat")] in [pick_list_replacements(HALLUCINATION_FILE, "location")][prob(50)?"!":"!!"]", + "[pick("Where's [hallucinator.first_name()]?", "Set [hallucinator.first_name()] to arrest!")]", + "[pick("C","Ai, c","Someone c","Rec")]all the shuttle!", + "AI [pick("rogue", "is dead")]!!", + "Borgs rogue!", + )) + else + chosen = pick(list("[pick_list_replacements(HALLUCINATION_FILE, "suspicion")]", + "[pick_list_replacements(HALLUCINATION_FILE, "conversation")]", + "[pick_list_replacements(HALLUCINATION_FILE, "greetings")][hallucinator.first_name()]!", + "[pick_list_replacements(HALLUCINATION_FILE, "getout")]", + "[pick_list_replacements(HALLUCINATION_FILE, "weird")]", + "[pick_list_replacements(HALLUCINATION_FILE, "didyouhearthat")]", + "[pick_list_replacements(HALLUCINATION_FILE, "doubt")]", + "[pick_list_replacements(HALLUCINATION_FILE, "aggressive")]", + "[pick_list_replacements(HALLUCINATION_FILE, "help")]!!", + "[pick_list_replacements(HALLUCINATION_FILE, "escape")]", + "I'm infected, [pick_list_replacements(HALLUCINATION_FILE, "infection_advice")]!", + )) + + chosen = capitalize(chosen) + + chosen = replacetext(chosen, "%TARGETNAME%", hallucinator.first_name()) + + // Log the message + feedback_details += "Type: [is_radio ? "Radio" : "Talk"], Source: [speaker.real_name], Message: [chosen]" + + var/plus_runechat = hallucinator.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat) + + // Display the message + if(!is_radio && !plus_runechat) + var/image/speech_overlay = image('icons/mob/effects/talk.dmi', speaker, "default0", layer = ABOVE_MOB_LAYER) + INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, speech_overlay, list(hallucinator.client), 30) + + if(plus_runechat) + hallucinator.create_chat_message(speaker, understood_language, chosen, spans) + + // And actually show them the message, for real. + var/message = hallucinator.compose_message(speaker, understood_language, chosen, is_radio ? "[FREQ_COMMON]" : null, spans, face_name = TRUE) + to_chat(hallucinator, message) + + // Then clean up. + qdel(src) + return TRUE diff --git a/code/modules/hallucination/fake_death.dm b/code/modules/hallucination/fake_death.dm new file mode 100644 index 00000000000..c6c0bc1e3c7 --- /dev/null +++ b/code/modules/hallucination/fake_death.dm @@ -0,0 +1,82 @@ +/datum/hallucination/death + random_hallucination_weight = 1 + +/datum/hallucination/death/Destroy() + if(!QDELETED(hallucinator)) + hallucinator.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_dead, type) + + return ..() + +/datum/hallucination/death/start() + hallucinator.Paralyze(30 SECONDS) + hallucinator.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_dead, type) + + if(iscarbon(hallucinator)) + var/mob/living/carbon/carbon_hallucinator = hallucinator + carbon_hallucinator.silent += 10 + + to_chat(hallucinator, span_deadsay("[hallucinator.real_name] has died at [get_area_name(hallucinator)].")) + + var/delay = 0 + + if(prob(50)) + var/mob/who_is_salting + if(length(GLOB.dead_player_list)) + who_is_salting = pick(GLOB.dead_mob_list) + + if(who_is_salting) + delay = rand(2 SECONDS, 5 SECONDS) + + var/static/list/things_to_hate = list( + "admins", + "batons", + "blood cult", + "coders", + "heretics", + "myself", + "revenants", + "revs", + "sec", + "ss13", + "this game", + "this round", + "this shift", + "this shit", + "this", + "wizards", + "you", + ) + + var/list/dead_chat_salt = list( + "...", + "FUCK", + "git gud", + "god damn it", + "hey [hallucinator.first_name()]", + "i[prob(50) ? " fucking" : ""] hate [pick(things_to_hate)]", + "is the AI rogue?", + "rip", + "shitsec", + "why did i just drop dead?", + "why was i gibbed", + "wizard?", + "you too?", + ) + + addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, hallucinator, span_deadsay("DEAD: [who_is_salting.name] says, \"[pick(dead_chat_salt)]\"")), delay) + + addtimer(CALLBACK(src, .proc/wake_up), delay + rand(7 SECONDS, 9 SECONDS)) + return TRUE + +/datum/hallucination/death/proc/wake_up() + if(!QDELETED(hallucinator)) + hallucinator.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_dead, type) + + if(iscarbon(hallucinator)) + var/mob/living/carbon/carbon_hallucinator = hallucinator + carbon_hallucinator.silent = 0 + + hallucinator.SetParalyzed(0 SECONDS) + + if(!QDELETED(src)) + qdel(src) diff --git a/code/modules/hallucination/fake_message.dm b/code/modules/hallucination/fake_message.dm new file mode 100644 index 00000000000..43041ef667f --- /dev/null +++ b/code/modules/hallucination/fake_message.dm @@ -0,0 +1,74 @@ +/datum/hallucination/message + random_hallucination_weight = 60 + +/datum/hallucination/message/start() + var/list/nearby_humans = list() + var/adjacent_to_us = FALSE + var/mob/living/carbon/human/suspicious_personnel + for(var/mob/living/carbon/human/nearby_human in oview(hallucinator, 7)) + if(get_dist(nearby_human, hallucinator) <= 1) + suspicious_personnel = nearby_human + adjacent_to_us = TRUE + break + nearby_humans += nearby_human + + if(!suspicious_personnel && length(nearby_humans)) + suspicious_personnel = pick(nearby_humans) + + var/list/message_pool = list() + if(suspicious_personnel) + if(adjacent_to_us) + message_pool[span_warning("You feel a tiny prick!")] = 5 + + var/obj/item/storage/equipped_backpack = suspicious_personnel.get_item_by_slot(ITEM_SLOT_BACK) + if(istype(equipped_backpack)) + // in the future, this could / should be de-harcoded and + // just draw from a pool uplink, theft, and antag item typepaths + var/static/list/stash_items = list( + ".357 revovler", + "energy sword", + "cryptographic sequencer", + "power sink", + "C-4 charge", + "energy bow", + "stun baton", + "flash", + "dart pistol", + "circular saw", + "ritual dagger", + "spellbook", + "Codex Cicatrix", + "captain's spare ID", + "hand teleporter", + "hypospray", + "antique laser gun", + "X-01 MultiPhase Energy Gun", + "station's blueprints", + ) + message_pool[span_notice("[suspicious_personnel] puts the [pick(stash_items)] into [equipped_backpack].")] = 5 + + message_pool["[span_bold("[suspicious_personnel]")] [pick("sneezes", "coughs")]."] = 1 + + message_pool[span_notice("You hear something squeezing through the ducts...")] = 1 + + message_pool[span_warning("Your [pick("arm", "leg", "back", "head")] itches.")] = 1 + message_pool[span_warning("You feel [pick("hot", "cold", "dry", "wet", "woozy", "faint")].")] = 1 + message_pool[span_warning("Your stomach rumbles.")] = 1 + message_pool[span_warning("Your head hurts.")] = 1 + message_pool[span_warning("You hear a faint buzz in your head.")] = 1 + + if(prob(10)) + message_pool[span_warning("Behind you.")] = 1 + message_pool[span_warning("You hear a faint laughter.")] = 1 + message_pool[span_warning("You hear skittering on the ceiling.")] = 1 + message_pool[span_warning("You see an inhumanly tall silhouette moving in the distance.")] = 2 + + if(prob(30)) + var/some_help = pick_list_replacements(HALLUCINATION_FILE, "advice") + message_pool[some_help] = 4 + + var/chosen = pick_weight(message_pool) + feedback_details += "Message: [chosen]" + to_chat(hallucinator, chosen) + qdel(src) + return TRUE diff --git a/code/modules/hallucination/fake_plasmaflood.dm b/code/modules/hallucination/fake_plasmaflood.dm new file mode 100644 index 00000000000..d352b4d7c54 --- /dev/null +++ b/code/modules/hallucination/fake_plasmaflood.dm @@ -0,0 +1,103 @@ + +#define FAKE_FLOOD_EXPAND_TIME 20 +#define FAKE_FLOOD_MAX_RADIUS 10 + +/// Plasma starts flooding from the nearby vent +/datum/hallucination/fake_flood + random_hallucination_weight = 7 + + var/list/image/flood_images = list() + var/list/obj/effect/plasma_image_holder/flood_image_holders = list() + var/list/turf/flood_turfs = list() + var/image_icon = 'icons/effects/atmospherics.dmi' + var/image_state = "plasma" + var/radius = 0 + var/next_expand = 0 + +/datum/hallucination/fake_flood/start() + // This hallucination is purely visual, so we don't need to bother for clientless mobs + if(!hallucinator.client) + return FALSE + + var/turf/center + + for(var/obj/machinery/atmospherics/components/unary/vent_pump/nearby_pump in orange(7, hallucinator)) + if(nearby_pump.welded) + continue + + center = get_turf(nearby_pump) + break + + if(!center) + return FALSE + + feedback_details += "Vent Coords: ([center.x], [center.y], [center.z])" + create_new_plasma_image(center) + hallucinator.client?.images |= flood_images + + next_expand = world.time + FAKE_FLOOD_EXPAND_TIME + START_PROCESSING(SSobj, src) + return TRUE + +/datum/hallucination/fake_flood/process() + if(next_expand > world.time) + return + + radius++ + if(radius > FAKE_FLOOD_MAX_RADIUS) + qdel(src) + return + + expand_flood() + + if(get_turf(hallucinator) in flood_turfs) + var/mob/living/carbon/carbon_hallucinator = hallucinator + if(istype(carbon_hallucinator) && !carbon_hallucinator.internal) + hallucinator.cause_hallucination(/datum/hallucination/fake_alert/bad_plasma, "fake plasmaflood hallucination") + + next_expand = world.time + FAKE_FLOOD_EXPAND_TIME + +/datum/hallucination/fake_flood/proc/expand_flood() + for(var/image/flood_image in flood_images) + flood_image.alpha = min(flood_image.alpha + 50, 255) + + for(var/turf/flooded_turf in flood_turfs) + for(var/dir in GLOB.cardinals) + var/turf/nearby_turf = get_step(flooded_turf, dir) + if((nearby_turf in flood_turfs) || !TURFS_CAN_SHARE(nearby_turf, flooded_turf) || isspaceturf(nearby_turf)) + continue + create_new_plasma_image(nearby_turf) + + hallucinator.client?.images |= flood_images + +/datum/hallucination/fake_flood/proc/create_new_plasma_image(turf/to_flood) + flood_turfs += to_flood + + var/obj/effect/plasma_image_holder/image_holder = new(to_flood) + flood_image_holders += image_holder + + var/image/plasma_image = image(image_icon, image_holder, image_state, FLY_LAYER) + plasma_image.alpha = 50 + plasma_image.plane = ABOVE_GAME_PLANE + flood_images += plasma_image + +/datum/hallucination/fake_flood/Destroy() + STOP_PROCESSING(SSobj, src) + + hallucinator.client?.images -= flood_images + + flood_turfs.Cut() // We don't own these + flood_images.Cut() // We also don't own these, kinda + QDEL_LIST(flood_image_holders) // But we DO own these + + return ..() + +/obj/effect/plasma_image_holder + icon_state = "nothing" + anchored = TRUE + layer = FLY_LAYER + plane = ABOVE_GAME_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +#undef FAKE_FLOOD_EXPAND_TIME +#undef FAKE_FLOOD_MAX_RADIUS diff --git a/code/modules/hallucination/fake_sound.dm b/code/modules/hallucination/fake_sound.dm new file mode 100644 index 00000000000..75788cfb81a --- /dev/null +++ b/code/modules/hallucination/fake_sound.dm @@ -0,0 +1,276 @@ +/// Hallucination that plays a fake sound somewhere nearby. +/datum/hallucination/fake_sound + abstract_hallucination_parent = /datum/hallucination/fake_sound + + /// Volume of the fake sound + var/volume = 50 + /// Whether the fake sound has vary or not + var/sound_vary = TRUE + /// A path to a sound, or a list of sounds, that plays when we trigger + var/sound_type + +/datum/hallucination/fake_sound/start() + 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]" + qdel(src) + return TRUE + +/// Actually plays the fake sound. +/datum/hallucination/fake_sound/proc/play_fake_sound(turf/source, sound_to_play = sound_type) + hallucinator.playsound_local(source, sound_to_play, volume, sound_vary) + +/// Used to queue additional, delayed fake sounds via a callback. +/datum/hallucination/fake_sound/proc/queue_fake_sound(turf/source, sound_to_play, volume_override, vary_override, delay) + if(!delay) + CRASH("[type] queued a fake sound without a timer.") + + // Queue the sound to be played with a timer on the mob, not the datum, because we'll probably get qdel'd + addtimer(CALLBACK(hallucinator, /mob/proc/playsound_local, source, sound_to_play, volume_override || volume, vary_override || sound_vary), delay) + +/datum/hallucination/fake_sound/normal + abstract_hallucination_parent = /datum/hallucination/fake_sound/normal + random_hallucination_weight = 5 + +/datum/hallucination/fake_sound/normal/airlock + volume = 30 + sound_type = 'sound/machines/airlock.ogg' + +/datum/hallucination/fake_sound/normal/airlock_pry + volume = 100 + sound_type = 'sound/machines/airlock_alien_prying.ogg' + +/datum/hallucination/fake_sound/normal/airlock_pry/play_fake_sound(turf/source, sound_to_play) + . = ..() + queue_fake_sound(source, 'sound/machines/airlockforced.ogg', 50, TRUE, delay = 5 SECONDS) + +/datum/hallucination/fake_sound/normal/console + volume = 25 + sound_type = 'sound/machines/terminal_prompt.ogg' + +/datum/hallucination/fake_sound/normal/boom + sound_type = list('sound/effects/explosion1.ogg', 'sound/effects/explosion2.ogg') + +/datum/hallucination/fake_sound/normal/distant_boom + sound_type = 'sound/effects/explosionfar.ogg' + +/datum/hallucination/fake_sound/normal/glass + sound_type = list('sound/effects/glassbr1.ogg', 'sound/effects/glassbr2.ogg', 'sound/effects/glassbr3.ogg') + +/datum/hallucination/fake_sound/normal/alarm + volume = 100 + sound_type = 'sound/machines/alarm.ogg' + +/datum/hallucination/fake_sound/normal/beepsky + volume = 35 + sound_type = 'sound/voice/beepsky/freeze.ogg' + +/datum/hallucination/fake_sound/normal/mech + volume = 40 + sound_type = 'sound/mecha/mechstep.ogg' + /// The turf the mech started walking from. + var/turf/mech_source + /// What dir is the mech walking? + var/mech_dir = NORTH + /// How many steps are left in the walk? + var/steps_left = 0 + +/datum/hallucination/fake_sound/normal/mech/Destroy() + mech_source = null + return ..() + +/datum/hallucination/fake_sound/normal/mech/start() + mech_dir = pick(GLOB.cardinals) + steps_left = rand(4, 9) + mech_source = random_far_turf() + + mech_walk() + return TRUE + +/datum/hallucination/fake_sound/normal/mech/proc/mech_walk() + if(QDELETED(src)) + return + + if(prob(75)) + play_fake_sound(mech_source) + mech_source = get_step(mech_source, mech_dir) + else + play_fake_sound(mech_source) + mech_dir = pick(GLOB.cardinals) + + if(--steps_left <= 0) + qdel(src) + + else + addtimer(CALLBACK(src, .proc/mech_walk), 1 SECONDS) + +/datum/hallucination/fake_sound/normal/wall_deconstruction + sound_type = 'sound/items/welder.ogg' + +/datum/hallucination/fake_sound/normal/wall_deconstruction/play_fake_sound(turf/source, sound_to_play) + . = ..() + queue_fake_sound(source, 'sound/items/welder2.ogg', delay = 10.5 SECONDS) + queue_fake_sound(source, 'sound/items/ratchet.ogg', delay = 12 SECONDS) + +/datum/hallucination/fake_sound/normal/door_hacking + sound_type = 'sound/items/screwdriver.ogg' + volume = 30 + +/datum/hallucination/fake_sound/normal/door_hacking/play_fake_sound(turf/source, sound_to_play) + // Make it sound like someone's pulsing a multitool one or multiple times. + // Screwdriver happens immediately... + . = ..() + + var/hacking_time = rand(4 SECONDS, 8 SECONDS) + // Multitool sound. + queue_fake_sound(source, 'sound/weapons/empty.ogg', delay = 0.8 SECONDS) + if(hacking_time > 4.5 SECONDS) + // Another multitool sound if the hacking time is long. + queue_fake_sound(source, 'sound/weapons/empty.ogg', delay = 3 SECONDS) + if(prob(50)) + // Bonus multitool sound, rapidly after the last. + queue_fake_sound(source, 'sound/weapons/empty.ogg', delay = 3.5 SECONDS) + + if(hacking_time > 5.5 SECONDS) + // A final multitool sound if the hacking time is very long. + queue_fake_sound(source, 'sound/weapons/empty.ogg', delay = 5 SECONDS) + + // Crowbarring it open. + queue_fake_sound(source, 'sound/machines/airlockforced.ogg', delay = hacking_time) + +/datum/hallucination/fake_sound/normal/steam + volume = 75 + sound_type = 'sound/machines/steam_hiss.ogg' + +/datum/hallucination/fake_sound/normal/flash + random_hallucination_weight = 2 // "it's revs" + volume = 90 + sound_type = 'sound/weapons/flash.ogg' + +/datum/hallucination/fake_sound/weird + abstract_hallucination_parent = /datum/hallucination/fake_sound/weird + random_hallucination_weight = 1 + + /// if FALSE, we will pass "null" in as the turf source, meaning the sound will just play without direction / etc. + var/no_source = FALSE + +/datum/hallucination/fake_sound/weird/play_fake_sound(turf/source, sound_to_play) + if(no_source) + return ..(null, sound_to_play) + + return ..() + +/datum/hallucination/fake_sound/weird/antag + random_hallucination_weight = 0 // This one's a bit gamey, so I'll leave it disabled by default. Have fun badmins + volume = 90 + sound_vary = FALSE + no_source = TRUE + sound_type = list( + 'sound/ambience/antag/bloodcult.ogg', + 'sound/ambience/antag/clockcultalr.ogg', + 'sound/ambience/antag/ecult_op.ogg', + 'sound/ambience/antag/ling_aler.ogg', + 'sound/ambience/antag/malf.ogg', + //'sound/ambience/antag/new_clock.ogg', // This one's much louder than the others, otherwise I would + 'sound/ambience/antag/ops.ogg', + 'sound/ambience/antag/tatoralert.ogg', + ) + +/datum/hallucination/fake_sound/weird/chimp_event + volume = 90 + sound_vary = FALSE + no_source = TRUE + sound_type = 'sound/ambience/antag/monkey.ogg' + +/datum/hallucination/fake_sound/weird/colossus + sound_type = 'sound/magic/clockwork/invoke_general.ogg' + +/datum/hallucination/fake_sound/weird/creepy + +/datum/hallucination/fake_sound/weird/creepy/New(mob/living/hallucinator) + . = ..() + //These sounds are (mostly) taken from Hidden: Source + sound_type = GLOB.creepy_ambience + +/datum/hallucination/fake_sound/weird/curse_sound + volume = 40 + sound_vary = FALSE + no_source = TRUE + sound_type = 'sound/magic/curse.ogg' + +/datum/hallucination/fake_sound/weird/game_over + sound_vary = FALSE + sound_type = 'sound/misc/compiler-failure.ogg' + +/datum/hallucination/fake_sound/weird/hallelujah + sound_vary = FALSE + sound_type = 'sound/effects/pray_chaplain.ogg' + +/datum/hallucination/fake_sound/weird/highlander + sound_vary = FALSE + no_source = TRUE + sound_type = 'sound/misc/highlander.ogg' + +/datum/hallucination/fake_sound/weird/hyperspace + sound_vary = FALSE + no_source = TRUE + sound_type = 'sound/runtime/hyperspace/hyperspace_begin.ogg' + +/datum/hallucination/fake_sound/weird/laugher + sound_type = list( + 'sound/voice/human/womanlaugh.ogg', + 'sound/voice/human/manlaugh1.ogg', + 'sound/voice/human/manlaugh2.ogg', + ) + +/datum/hallucination/fake_sound/weird/phone + volume = 15 + sound_vary = FALSE + sound_type = 'sound/weapons/ring.ogg' + +/datum/hallucination/fake_sound/weird/phone/play_fake_sound(turf/source, sound_to_play) + for(var/next_ring in 1 to 3) + queue_fake_sound(source, sound_to_play, delay = 2.5 SECONDS * next_ring) + + return ..() + +/datum/hallucination/fake_sound/weird/spell + sound_type = list( + 'sound/magic/disintegrate.ogg', + 'sound/magic/ethereal_enter.ogg', + 'sound/magic/ethereal_exit.ogg', + 'sound/magic/fireball.ogg', + 'sound/magic/forcewall.ogg', + 'sound/magic/teleport_app.ogg', + 'sound/magic/teleport_diss.ogg', + ) + +/datum/hallucination/fake_sound/weird/spell/just_jaunt // A few antags use jaunts, so this sound specifically is fun to isolate + sound_type = 'sound/magic/ethereal_enter.ogg' + +/datum/hallucination/fake_sound/weird/summon_sound // Heretic circle sound, notably + volume = 75 + sound_type = 'sound/magic/castsummon.ogg' + +/datum/hallucination/fake_sound/weird/tesloose + volume = 35 + sound_type = 'sound/magic/lightningbolt.ogg' + +/datum/hallucination/fake_sound/weird/tesloose/play_fake_sound(turf/source, sound_to_play) + . = ..() + for(var/next_shock in 1 to rand(2, 4)) + queue_fake_sound(source, sound_to_play, volume_override = volume + (15 * next_shock), delay = 3 SECONDS * next_shock) + +/datum/hallucination/fake_sound/weird/xeno + random_hallucination_weight = 2 // Some of these are ambience sounds too + volume = 25 + sound_type = list( + 'sound/voice/lowHiss1.ogg', + 'sound/voice/lowHiss2.ogg', + 'sound/voice/lowHiss3.ogg', + 'sound/voice/lowHiss4.ogg', + 'sound/voice/hiss1.ogg', + 'sound/voice/hiss2.ogg', + 'sound/voice/hiss3.ogg', + 'sound/voice/hiss4.ogg', + ) diff --git a/code/modules/hallucination/fire.dm b/code/modules/hallucination/fire.dm deleted file mode 100644 index 4566fd884a6..00000000000 --- a/code/modules/hallucination/fire.dm +++ /dev/null @@ -1,89 +0,0 @@ -#define RAISE_FIRE_COUNT 3 -#define RAISE_FIRE_TIME 3 - -/datum/hallucination/fire - var/active = TRUE - var/stage = 0 - var/image/fire_overlay - - var/next_action = 0 - var/times_to_lower_stamina - var/fire_clearing = FALSE - var/increasing_stages = TRUE - var/time_spent = 0 - -/datum/hallucination/fire/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - target.set_fire_stacks(max(target.fire_stacks, 0.1)) //Placebo flammability - fire_overlay = image('icons/mob/effects/onfire.dmi', target, "human_burning", ABOVE_MOB_LAYER) - if(target.client) - target.client.images += fire_overlay - to_chat(target, span_userdanger("You're set on fire!")) - target.throw_alert(ALERT_FIRE, /atom/movable/screen/alert/fire, override = TRUE) - times_to_lower_stamina = rand(5, 10) - addtimer(CALLBACK(src, .proc/start_expanding), 20) - -/datum/hallucination/fire/Destroy() - . = ..() - STOP_PROCESSING(SSfastprocess, src) - -/datum/hallucination/fire/proc/start_expanding() - if (isnull(target)) - qdel(src) - return - START_PROCESSING(SSfastprocess, src) - -/datum/hallucination/fire/process(delta_time) - if (isnull(target)) - qdel(src) - return - - if(target.fire_stacks <= 0) - clear_fire() - - time_spent += delta_time - - if (fire_clearing) - next_action -= delta_time - if (next_action < 0) - stage -= 1 - update_temp() - next_action += 3 - else if (increasing_stages) - var/new_stage = min(round(time_spent / RAISE_FIRE_TIME), RAISE_FIRE_COUNT) - if (stage != new_stage) - stage = new_stage - update_temp() - - if (stage == RAISE_FIRE_COUNT) - increasing_stages = FALSE - else if (times_to_lower_stamina) - next_action -= delta_time - if (next_action < 0) - target.adjustStaminaLoss(15) - next_action += 2 - times_to_lower_stamina -= 1 - else - clear_fire() - -/datum/hallucination/fire/proc/update_temp() - if(stage <= 0) - target.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE) - else - target.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE) - target.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, stage, override = TRUE) - -/datum/hallucination/fire/proc/clear_fire() - if(!active) - return - active = FALSE - target.clear_alert(ALERT_FIRE, clear_override = TRUE) - if(target.client) - target.client.images -= fire_overlay - QDEL_NULL(fire_overlay) - fire_clearing = TRUE - next_action = 0 - -#undef RAISE_FIRE_COUNT -#undef RAISE_FIRE_TIME diff --git a/code/modules/hallucination/hazard.dm b/code/modules/hallucination/hazard.dm index 5278c9d7e8a..dc8f6df865f 100644 --- a/code/modules/hallucination/hazard.dm +++ b/code/modules/hallucination/hazard.dm @@ -1,128 +1,102 @@ -/* Hazard Hallucinations - * - * Contains: - * Lava - * Chasm - * Anomaly - */ +/// Hallucinations that create a hazard somewhere nearby that actually has a danger associated. +/datum/hallucination/hazard + abstract_hallucination_parent = /datum/hallucination/hazard + random_hallucination_weight = 5 -/datum/hallucination/dangerflash - -/datum/hallucination/dangerflash/New(mob/living/carbon/C, forced = TRUE, danger_type) - set waitfor = FALSE - ..() - //Flashes of danger + /// The type of effect we create + var/hazard_type = /obj/effect/client_image_holder/hallucination/danger +/datum/hallucination/hazard/start() var/list/possible_points = list() - for(var/turf/open/floor/F in view(target,world.view)) - possible_points += F - if(possible_points.len) - var/turf/open/floor/danger_point = pick(possible_points) - if(!danger_type) - danger_type = pick("lava","chasm","anomaly") - switch(danger_type) - if("lava") - new /obj/effect/hallucination/danger/lava(danger_point, target) - if("chasm") - new /obj/effect/hallucination/danger/chasm(danger_point, target) - if("anomaly") - new /obj/effect/hallucination/danger/anomaly(danger_point, target) + for(var/turf/open/floor/floor_in_view in view(hallucinator)) + possible_points += floor_in_view - qdel(src) + if(!length(possible_points)) + return FALSE -/obj/effect/hallucination/danger - var/image/image + new hazard_type(pick(possible_points), hallucinator, src) + QDEL_IN(src, rand(20 SECONDS, 45 SECONDS)) + return TRUE -/obj/effect/hallucination/danger/proc/show_icon() +/datum/hallucination/hazard/lava + hazard_type = /obj/effect/client_image_holder/hallucination/danger/lava + +/datum/hallucination/hazard/chasm + hazard_type = /obj/effect/client_image_holder/hallucination/danger/chasm + +/datum/hallucination/hazard/anomaly + hazard_type = /obj/effect/client_image_holder/hallucination/danger/anomaly + +/// These hallucination effects cause side effects when the hallucinator walks into them. +/obj/effect/client_image_holder/hallucination/danger + image_layer = TURF_LAYER + +/obj/effect/client_image_holder/hallucination/danger/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent) + . = ..() + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = .proc/atom_touched_holder, + COMSIG_ATOM_EXITED = .proc/atom_touched_holder, + ) + AddElement(/datum/element/connect_loc, loc_connections) + +/obj/effect/client_image_holder/hallucination/danger/proc/atom_touched_holder(datum/source, atom/movable/entered) + SIGNAL_HANDLER + + if(!(entered in who_sees_us)) + return + + on_hallucinator_entered(entered) + +/// Applies effects whenever the hallucinator enters the turf with our hallucination present. +/obj/effect/client_image_holder/hallucination/danger/proc/on_hallucinator_entered(mob/living/afflicted) return -/obj/effect/hallucination/danger/proc/clear_icon() - if(image && target.client) - target.client.images -= image - -/obj/effect/hallucination/danger/Initialize(mapload, _target) - . = ..() - target = _target - show_icon() - QDEL_IN(src, rand(200, 450)) - -/obj/effect/hallucination/danger/Destroy() - clear_icon() - . = ..() - -/obj/effect/hallucination/danger/lava +/obj/effect/client_image_holder/hallucination/danger/lava name = "lava" + image_icon = 'icons/turf/floors/lava.dmi' -/obj/effect/hallucination/danger/lava/Initialize(mapload, _target) - . = ..() - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = .proc/on_entered, - ) - AddElement(/datum/element/connect_loc, loc_connections) - -/obj/effect/hallucination/danger/lava/show_icon() +/obj/effect/client_image_holder/hallucination/danger/lava/generate_image() var/turf/danger_turf = get_turf(src) - image = image('icons/turf/floors/lava.dmi', src, "lava-[danger_turf.smoothing_junction || 0]", TURF_LAYER) - if(target.client) - target.client.images += image + image_state = "lava-[danger_turf.smoothing_junction || 0]" + return ..() -/obj/effect/hallucination/danger/lava/proc/on_entered(datum/source, atom/movable/AM) - SIGNAL_HANDLER - if(AM == target) - target.adjustStaminaLoss(20) - new /datum/hallucination/fire(target) +/obj/effect/client_image_holder/hallucination/danger/lava/on_hallucinator_entered(mob/living/afflicted) + afflicted.adjustStaminaLoss(20) + afflicted.cause_hallucination(/datum/hallucination/fire, "fake lava hallucination") -/obj/effect/hallucination/danger/chasm +/obj/effect/client_image_holder/hallucination/danger/chasm name = "chasm" + image_icon = 'icons/turf/floors/chasms.dmi' -/obj/effect/hallucination/danger/chasm/Initialize(mapload, _target) - . = ..() - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = .proc/on_entered, - ) - AddElement(/datum/element/connect_loc, loc_connections) - -/obj/effect/hallucination/danger/chasm/show_icon() +/obj/effect/client_image_holder/hallucination/danger/chasm/generate_image() var/turf/danger_turf = get_turf(src) - image = image('icons/turf/floors/chasms.dmi', src, "chasms-[danger_turf.smoothing_junction || 0]", TURF_LAYER) - if(target.client) - target.client.images += image + image_state = "chasms-[danger_turf.smoothing_junction || 0]" + return ..() -/obj/effect/hallucination/danger/chasm/proc/on_entered(datum/source, atom/movable/AM) - SIGNAL_HANDLER - if(AM == target) - if(istype(target, /obj/effect/dummy/phased_mob)) - return - to_chat(target, span_userdanger("You fall into the chasm!")) - target.Paralyze(40) - addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, target, span_notice("It's surprisingly shallow.")), 15) - QDEL_IN(src, 30) +/obj/effect/client_image_holder/hallucination/danger/chasm/on_hallucinator_entered(mob/living/afflicted) + to_chat(afflicted, span_userdanger("You fall into the chasm!")) + afflicted.visible_message(span_warning("[afflicted] falls to the ground suddenly!"), ignored_mobs = afflicted) + afflicted.Paralyze(4 SECONDS) + addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, afflicted, span_notice("...It's surprisingly shallow.")), 1.5 SECONDS) + QDEL_IN(src, 3 SECONDS) -/obj/effect/hallucination/danger/anomaly +/obj/effect/client_image_holder/hallucination/danger/anomaly name = "flux wave anomaly" + image_icon = 'icons/effects/anomalies.dmi' + image_state = "flux" + image_layer = OBJ_LAYER + 0.01 -/obj/effect/hallucination/danger/anomaly/Initialize(mapload) +/obj/effect/client_image_holder/hallucination/danger/anomaly/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent) . = ..() START_PROCESSING(SSobj, src) - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = .proc/on_entered, - ) - AddElement(/datum/element/connect_loc, loc_connections) -/obj/effect/hallucination/danger/anomaly/process(delta_time) - if(DT_PROB(45, delta_time)) - step(src,pick(GLOB.alldirs)) - -/obj/effect/hallucination/danger/anomaly/Destroy() +/obj/effect/client_image_holder/hallucination/danger/anomaly/Destroy() STOP_PROCESSING(SSobj, src) return ..() -/obj/effect/hallucination/danger/anomaly/show_icon() - image = image('icons/effects/effects.dmi',src,"electricity2",OBJ_LAYER+0.01) - if(target.client) - target.client.images += image +/obj/effect/client_image_holder/hallucination/danger/anomaly/process(delta_time) + if(DT_PROB(ANOMALY_MOVECHANCE, delta_time)) + step(src, pick(GLOB.alldirs)) -/obj/effect/hallucination/danger/anomaly/proc/on_entered(datum/source, atom/movable/AM) - SIGNAL_HANDLER - if(AM == target) - new /datum/hallucination/shock(target) +/obj/effect/client_image_holder/hallucination/danger/anomaly/on_hallucinator_entered(mob/living/afflicted) + afflicted.cause_hallucination(/datum/hallucination/shock, "fake anomaly hallucination") diff --git a/code/modules/hallucination/hostile_mob.dm b/code/modules/hallucination/hostile_mob.dm deleted file mode 100644 index 3f4674d0ffa..00000000000 --- a/code/modules/hallucination/hostile_mob.dm +++ /dev/null @@ -1,171 +0,0 @@ -/* Hostile Mob Hallucinations - * - * Contains: - * Xeno - * Clown - * Bubblegum - */ - -/obj/effect/hallucination/simple/xeno - image_icon = 'icons/mob/nonhuman-player/alien.dmi' - image_state = "alienh_pounce" - -/obj/effect/hallucination/simple/xeno/Initialize(mapload, mob/living/carbon/T) - . = ..() - name = "alien hunter ([rand(1, 1000)])" - -/obj/effect/hallucination/simple/xeno/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - update_icon(ALL, "alienh_pounce") - if(hit_atom == target && target.stat!=DEAD) - target.Paralyze(100) - target.visible_message(span_danger("[target] flails around wildly."),span_userdanger("[name] pounces on you!")) - -// The numbers of seconds it takes to get to each stage of the xeno attack choreography -#define XENO_ATTACK_STAGE_LEAP_AT_TARGET 1 -#define XENO_ATTACK_STAGE_LEAP_AT_PUMP 2 -#define XENO_ATTACK_STAGE_CLIMB 3 -#define XENO_ATTACK_STAGE_FINISH 6 - -/// Xeno crawls from nearby vent,jumps at you, and goes back in -/datum/hallucination/xeno_attack - var/turf/pump_location = null - var/obj/effect/hallucination/simple/xeno/xeno = null - var/time_processing = 0 - var/stage = XENO_ATTACK_STAGE_LEAP_AT_TARGET - -/datum/hallucination/xeno_attack/New(mob/living/carbon/C, forced = TRUE) - ..() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in orange(7,target)) - if(!U.welded) - pump_location = get_turf(U) - break - - if(pump_location) - feedback_details += "Vent Coords: [pump_location.x],[pump_location.y],[pump_location.z]" - xeno = new(pump_location, target) - START_PROCESSING(SSfastprocess, src) - else - qdel(src) - -/datum/hallucination/xeno_attack/process(delta_time) - time_processing += delta_time - - if (time_processing >= stage) - switch (time_processing) - if (XENO_ATTACK_STAGE_FINISH to INFINITY) - to_chat(target, span_notice("[xeno.name] scrambles into the ventilation ducts!")) - qdel(src) - if (XENO_ATTACK_STAGE_CLIMB to XENO_ATTACK_STAGE_FINISH) - to_chat(target, span_notice("[xeno.name] begins climbing into the ventilation system...")) - stage = XENO_ATTACK_STAGE_FINISH - if (XENO_ATTACK_STAGE_LEAP_AT_PUMP to XENO_ATTACK_STAGE_CLIMB) - xeno.update_icon(ALL, "alienh_leap", 'icons/mob/nonhuman-player/alienleap.dmi', -32, -32) - xeno.throw_at(pump_location, 7, 1, spin = FALSE, diagonals_first = TRUE) - stage = XENO_ATTACK_STAGE_CLIMB - if (XENO_ATTACK_STAGE_LEAP_AT_TARGET to XENO_ATTACK_STAGE_LEAP_AT_PUMP) - xeno.update_icon(ALL, "alienh_leap", 'icons/mob/nonhuman-player/alienleap.dmi', -32, -32) - xeno.throw_at(target, 7, 1, spin = FALSE, diagonals_first = TRUE) - stage = XENO_ATTACK_STAGE_LEAP_AT_PUMP - -/datum/hallucination/xeno_attack/Destroy() - . = ..() - - STOP_PROCESSING(SSfastprocess, src) - QDEL_NULL(xeno) - pump_location = null - -#undef XENO_ATTACK_STAGE_LEAP_AT_TARGET -#undef XENO_ATTACK_STAGE_LEAP_AT_PUMP -#undef XENO_ATTACK_STAGE_CLIMB -#undef XENO_ATTACK_STAGE_FINISH - -/obj/effect/hallucination/simple/clown - image_icon = 'icons/mob/simple/animal.dmi' - image_state = "clown" - -/obj/effect/hallucination/simple/clown/Initialize(mapload, mob/living/carbon/T, duration) - ..(loc, T) - name = pick(GLOB.clown_names) - QDEL_IN(src,duration) - -/obj/effect/hallucination/simple/clown/scary - image_state = "scary_clown" - -/obj/effect/hallucination/simple/bubblegum - name = "Bubblegum" - image_icon = 'icons/mob/simple/lavaland/96x96megafauna.dmi' - image_state = "bubblegum" - px = -32 - -/datum/hallucination/oh_yeah - var/obj/effect/hallucination/simple/bubblegum/bubblegum - var/image/fakebroken - var/image/fakerune - var/turf/landing - var/charged - var/next_action = 0 - -/datum/hallucination/oh_yeah/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - . = ..() - var/turf/closed/wall/wall - for(var/turf/closed/wall/W in range(7,target)) - wall = W - break - if(!wall) - return INITIALIZE_HINT_QDEL - feedback_details += "Source: [wall.x],[wall.y],[wall.z]" - - fakebroken = image('icons/turf/floors.dmi', wall, "plating", layer = TURF_LAYER) - landing = get_turf(target) - var/turf/landing_image_turf = get_step(landing, SOUTHWEST) //the icon is 3x3 - fakerune = image('icons/effects/96x96.dmi', landing_image_turf, "landing", layer = ABOVE_OPEN_TURF_LAYER) - fakebroken.override = TRUE - if(target.client) - target.client.images |= fakebroken - target.client.images |= fakerune - target.playsound_local(wall,'sound/effects/meteorimpact.ogg', 150, 1) - bubblegum = new(wall, target) - addtimer(CALLBACK(src, .proc/start_processing), 10) - -/datum/hallucination/oh_yeah/proc/start_processing() - if (isnull(target)) - qdel(src) - return - START_PROCESSING(SSfastprocess, src) - -/datum/hallucination/oh_yeah/process(delta_time) - next_action -= delta_time - - if (next_action > 0) - return - - if (get_turf(bubblegum) != landing && target?.stat != DEAD) - if(!landing || (get_turf(bubblegum)).loc.z != landing.loc.z) - qdel(src) - return - bubblegum.forceMove(get_step_towards(bubblegum, landing)) - bubblegum.setDir(get_dir(bubblegum, landing)) - target.playsound_local(get_turf(bubblegum), 'sound/effects/meteorimpact.ogg', 150, 1) - shake_camera(target, 2, 1) - if(bubblegum.Adjacent(target) && !charged) - charged = TRUE - target.Paralyze(80) - target.adjustStaminaLoss(40) - step_away(target, bubblegum) - shake_camera(target, 4, 3) - target.visible_message(span_warning("[target] jumps backwards, falling on the ground!"),span_userdanger("[bubblegum] slams into you!")) - next_action = 0.2 - else - STOP_PROCESSING(SSfastprocess, src) - QDEL_IN(src, 3 SECONDS) - -/datum/hallucination/oh_yeah/Destroy() - if(target.client) - target.client.images.Remove(fakebroken) - target.client.images.Remove(fakerune) - QDEL_NULL(fakebroken) - QDEL_NULL(fakerune) - QDEL_NULL(bubblegum) - STOP_PROCESSING(SSfastprocess, src) - return ..() diff --git a/code/modules/hallucination/hud_screw.dm b/code/modules/hallucination/hud_screw.dm new file mode 100644 index 00000000000..88a9fccc078 --- /dev/null +++ b/code/modules/hallucination/hud_screw.dm @@ -0,0 +1,26 @@ +/// Screwyhud, makes the user's health bar hud wonky +/datum/hallucination/screwy_hud + abstract_hallucination_parent = /datum/hallucination/screwy_hud + random_hallucination_weight = 4 + + /// The type of hud we give to the hallucinator + var/screwy_hud_type = SCREWYHUD_NONE + +/datum/hallucination/screwy_hud/start() + hallucinator.apply_status_effect(screwy_hud_type, type) + QDEL_IN(src, rand(10 SECONDS, 25 SECONDS)) + return TRUE + +/datum/hallucination/screwy_hud/Destroy() + if(!QDELETED(hallucinator)) + hallucinator.remove_status_effect(screwy_hud_type, type) + return ..() + +/datum/hallucination/screwy_hud/crit + screwy_hud_type = /datum/status_effect/grouped/screwy_hud/fake_crit + +/datum/hallucination/screwy_hud/dead + screwy_hud_type = /datum/status_effect/grouped/screwy_hud/fake_dead + +/datum/hallucination/screwy_hud/healthy + screwy_hud_type = /datum/status_effect/grouped/screwy_hud/fake_healthy diff --git a/code/modules/hallucination/husk.dm b/code/modules/hallucination/husk.dm deleted file mode 100644 index abe70b4dcec..00000000000 --- a/code/modules/hallucination/husk.dm +++ /dev/null @@ -1,31 +0,0 @@ -/datum/hallucination/husks - var/image/halbody - -/datum/hallucination/husks/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - var/list/possible_points = list() - for(var/turf/open/floor/F in view(target,world.view)) - possible_points += F - if(possible_points.len) - var/turf/open/floor/husk_point = pick(possible_points) - switch(rand(1,4)) - if(1) - var/image/body = image('icons/mob/species/human/human.dmi',husk_point,"husk",TURF_LAYER) - var/matrix/M = matrix() - M.Turn(90) - body.transform = M - halbody = body - if(2,3) - halbody = image('icons/mob/species/human/human.dmi',husk_point,"husk",TURF_LAYER) - if(4) - halbody = image('icons/mob/nonhuman-player/alien.dmi',husk_point,"alienother",TURF_LAYER) - - if(target.client) - target.client.images += halbody - QDEL_IN(src, rand(30,50)) //Only seen for a brief moment. - -/datum/hallucination/husks/Destroy() - target?.client?.images -= halbody - QDEL_NULL(halbody) - return ..() diff --git a/code/modules/hallucination/inhand_fake_item.dm b/code/modules/hallucination/inhand_fake_item.dm new file mode 100644 index 00000000000..199fb138c72 --- /dev/null +++ b/code/modules/hallucination/inhand_fake_item.dm @@ -0,0 +1,146 @@ +/// Hallucinates a fake item in our hands, pockets, or belt or whatever. +/datum/hallucination/fake_item + abstract_hallucination_parent = /datum/hallucination/fake_item + random_hallucination_weight = 1 + + /// 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 + /// What item should we use as a template, grabbing its icon and icon state and name? + var/obj/item/template_item_type + +/datum/hallucination/fake_item/start() + var/list/slots_free = list() + if(valid_slots & ITEM_SLOT_HANDS) + for(var/hand in hallucinator.get_empty_held_indexes()) + slots_free[ui_hand_position(hand)] = ITEM_SLOT_HANDS + + // These slots are human only, + they have to have a uniform + var/mob/living/carbon/human/human_hallucinator = hallucinator + if(istype(hallucinator) && human_hallucinator.w_uniform) + if((valid_slots & ITEM_SLOT_BELT) && !human_hallucinator.belt) + slots_free[ui_belt] = ITEM_SLOT_BELT + if((valid_slots & ITEM_SLOT_LPOCKET) && !human_hallucinator.l_store) + slots_free[ui_storage1] = ITEM_SLOT_LPOCKET + if((valid_slots & ITEM_SLOT_RPOCKET) && !human_hallucinator.r_store) + slots_free[ui_storage2] = ITEM_SLOT_RPOCKET + + if(!length(slots_free)) + return FALSE + + var/picked_space = pick(slots_free) + var/obj/item/hallucinated/hallucinated_item = make_fake_item(picked_space, slots_free[picked_space]) + feedback_details += "Item Type: [hallucinated_item.name]" + + hallucinator.client?.screen += hallucinated_item + QDEL_IN(src, rand(15 SECONDS, 35 SECONDS)) + return TRUE + +/datum/hallucination/fake_item/proc/make_fake_item(where_to_put_it, equip_flags) + var/obj/item/hallucinated/hallucinated_item = new(hallucinator, src) + hallucinated_item.screen_loc = where_to_put_it + + hallucinated_item.name = initial(template_item_type.name) + hallucinated_item.desc = initial(template_item_type.desc) + hallucinated_item.icon = initial(template_item_type.icon) + hallucinated_item.icon_state = initial(template_item_type.icon_state) + hallucinated_item.w_class = initial(template_item_type.w_class) // Not strictly necessary, but keen eyed people will notice + + return hallucinated_item + +/datum/hallucination/fake_item/c4 + template_item_type = /obj/item/grenade/c4 + +/datum/hallucination/fake_item/c4/make_fake_item(where_to_put_it, equip_flags) + if(prob(50)) + template_item_type = /obj/item/grenade/c4/x4 + return ..() + +/datum/hallucination/fake_item/revolver + template_item_type = /obj/item/gun/ballistic/revolver + valid_slots = ITEM_SLOT_HANDS|ITEM_SLOT_BELT + +/datum/hallucination/fake_item/esword + template_item_type = /obj/item/melee/energy/sword/saber + valid_slots = ITEM_SLOT_HANDS|ITEM_SLOT_LPOCKET|ITEM_SLOT_RPOCKET + +/datum/hallucination/fake_item/esword/make_fake_item(where_to_put_it, equip_flags) + // Make the item via parent call + var/obj/item/hallucinated/hallucinated_item = ..() + + // If we were placed in our mob's hands there's a 40% chance to make it appear active + if((equip_flags & ITEM_SLOT_HANDS) && prob(40)) + var/obj/item/melee/energy/sword/saber/sabre_color = pick(subtypesof(/obj/item/melee/energy/sword/saber)) + // Yes this can break if someone changes esword icon stuff + hallucinated_item.icon_state = "[hallucinated_item.icon_state]_on_[initial(sabre_color.sword_color_icon)]" + hallucinator.playsound_local(get_turf(hallucinator), 'sound/weapons/saberon.ogg', 35, TRUE) + + return hallucinated_item + +/datum/hallucination/fake_item/baton + template_item_type = /obj/item/melee/baton/security/loaded + valid_slots = ITEM_SLOT_HANDS|ITEM_SLOT_BELT + +/datum/hallucination/fake_item/baton/make_fake_item(where_to_put_it, equip_flags) + var/obj/item/hallucinated/hallucinated_item = ..() + + // If we were placed in our mob's hands there's a 30% chance to make it appear active + if((equip_flags & ITEM_SLOT_HANDS) && prob(30)) + // Yes this can break if someone changes baton icon stuff + hallucinated_item.icon_state = "[hallucinated_item.icon_state]_active" + hallucinator.playsound_local(get_turf(hallucinator), SFX_SPARKS, 75, TRUE, -1) + + return hallucinated_item + +/datum/hallucination/fake_item/emag + template_item_type = /obj/item/card/emag + valid_slots = ITEM_SLOT_HANDS|ITEM_SLOT_LPOCKET|ITEM_SLOT_RPOCKET + +/datum/hallucination/fake_item/emag/make_fake_item(where_to_put_it, equip_flags) + if(prob(50)) + template_item_type = /obj/item/card/emag/doorjack + return ..() + +/datum/hallucination/fake_item/flashbang + template_item_type = /obj/item/grenade/flashbang + valid_slots = ITEM_SLOT_HANDS + +/datum/hallucination/fake_item/flashbang/make_fake_item(where_to_put_it, equip_flags) + var/obj/item/hallucinated/hallucinated_item = ..() + if(prob(15)) + // Yes this can break if someone changse grenade icon stuff + hallucinated_item.icon_state = "[hallucinated_item.icon_state]_active" + hallucinator.playsound_local(get_turf(hallucinator), 'sound/weapons/armbomb.ogg', 60, TRUE) + to_chat(hallucinator, span_warning("You prime [hallucinated_item]! 5 seconds!")) + + return hallucinated_item + +/obj/item/hallucinated + name = "mirage" + plane = ABOVE_HUD_PLANE + interaction_flags_item = NONE + item_flags = ABSTRACT | DROPDEL | EXAMINE_SKIP | HAND_ITEM | NOBLUDGEON // Most of these flags don't matter, but better safe than sorry + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + /// The hallucination that created us. + var/datum/hallucination/parent + +/obj/item/hallucinated/Initialize(mapload, datum/hallucination/parent) + . = ..() + if(!parent) + stack_trace("[type] was created without a parent hallucination.") + return INITIALIZE_HINT_QDEL + + RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/parent_deleting) + src.parent = parent + + ADD_TRAIT(src, TRAIT_NODROP, INNATE_TRAIT) + +/obj/item/hallucinated/Destroy(force) + UnregisterSignal(parent, COMSIG_PARENT_QDELETING) + parent = null + return ..() + +/// Signal proc for [COMSIG_PARENT_QDELETING], if our associated hallucination deletes, we should too +/obj/item/hallucinated/proc/parent_deleting(datum/source) + SIGNAL_HANDLER + + qdel(src) diff --git a/code/modules/hallucination/item.dm b/code/modules/hallucination/item.dm deleted file mode 100644 index bc659dd3811..00000000000 --- a/code/modules/hallucination/item.dm +++ /dev/null @@ -1,172 +0,0 @@ -/* Item Hallucinations - * - * Contains: - * Putting items in other nearby peoples hands - * Putting items in your hands - */ - -/datum/hallucination/items_other - -/datum/hallucination/items_other/New(mob/living/carbon/C, forced = TRUE, item_type) - set waitfor = FALSE - ..() - var/item - if(!item_type) - item = pick(list("esword","taser","ebow","baton","dual_esword","ttv","flash","armblade")) - else - item = item_type - feedback_details += "Item: [item]" - var/side - var/image_file - var/image/A = null - var/list/mob_pool = list() - - for(var/mob/living/carbon/human/M in view(7,target)) - if(M != target) - mob_pool += M - if(!mob_pool.len) - return - - var/mob/living/carbon/human/H = pick(mob_pool) - feedback_details += " Mob: [H.real_name]" - - var/free_hand = H.get_empty_held_index_for_side(LEFT_HANDS) - if(free_hand) - side = "left" - else - free_hand = H.get_empty_held_index_for_side(RIGHT_HANDS) - if(free_hand) - side = "right" - - if(side) - switch(item) - if("esword") - if(side == "right") - image_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - else - image_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - target.playsound_local(H, 'sound/weapons/saberon.ogg',35,1) - A = image(image_file,H,"e_sword_on_red", layer=ABOVE_MOB_LAYER) - if("dual_esword") - if(side == "right") - image_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - else - image_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - target.playsound_local(H, 'sound/weapons/saberon.ogg',35,1) - A = image(image_file,H,"dualsaberred1", layer=ABOVE_MOB_LAYER) - if("taser") - if(side == "right") - image_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' - else - image_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' - A = image(image_file,H,"advtaserstun4", layer=ABOVE_MOB_LAYER) - if("ebow") - if(side == "right") - image_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' - else - image_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' - A = image(image_file,H,"crossbow", layer=ABOVE_MOB_LAYER) - if("baton") - if(side == "right") - image_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - else - image_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - target.playsound_local(H, SFX_SPARKS,75,1,-1) - A = image(image_file,H,"baton", layer=ABOVE_MOB_LAYER) - if("ttv") - if(side == "right") - image_file = 'icons/mob/inhands/weapons/bombs_righthand.dmi' - else - image_file = 'icons/mob/inhands/weapons/bombs_lefthand.dmi' - A = image(image_file,H,"ttv", layer=ABOVE_MOB_LAYER) - if("flash") - if(side == "right") - image_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - else - image_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - A = image(image_file,H,"flashtool", layer=ABOVE_MOB_LAYER) - if("armblade") - if(side == "right") - image_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' - else - image_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' - target.playsound_local(H, 'sound/effects/blobattack.ogg',30,1) - A = image(image_file,H,"arm_blade", layer=ABOVE_MOB_LAYER) - if(target.client) - target.client.images |= A - addtimer(CALLBACK(src, .proc/cleanup, item, A, H), rand(15 SECONDS, 25 SECONDS)) - return - qdel(src) - -/datum/hallucination/items_other/proc/cleanup(item, atom/image_used, has_the_item) - if (isnull(target)) - qdel(src) - return - if(item == "esword" || item == "dual_esword") - target.playsound_local(has_the_item, 'sound/weapons/saberoff.ogg',35,1) - if(item == "armblade") - target.playsound_local(has_the_item, 'sound/effects/blobattack.ogg',30,1) - target.client.images.Remove(image_used) - qdel(src) - -/datum/hallucination/items/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - //Strange items - - var/obj/halitem = new - - halitem = new - var/obj/item/l_hand = target.get_item_for_held_index(1) - var/obj/item/r_hand = target.get_item_for_held_index(2) - var/l = ui_hand_position(target.get_held_index_of_item(l_hand)) - var/r = ui_hand_position(target.get_held_index_of_item(r_hand)) - var/list/slots_free = list(l,r) - if(l_hand) - slots_free -= l - if(r_hand) - slots_free -= r - if(ishuman(target)) - var/mob/living/carbon/human/H = target - if(!H.belt) - slots_free += ui_belt - if(!H.l_store) - slots_free += ui_storage1 - if(!H.r_store) - slots_free += ui_storage2 - if(slots_free.len) - halitem.screen_loc = pick(slots_free) - halitem.plane = ABOVE_HUD_PLANE - switch(rand(1,6)) - if(1) //revolver - halitem.icon = 'icons/obj/weapons/guns/ballistic.dmi' - halitem.icon_state = "revolver" - halitem.name = "Revolver" - if(2) //c4 - halitem.icon = 'icons/obj/weapons/grenade.dmi' - halitem.icon_state = "plastic-explosive0" - halitem.name = "C4" - if(prob(25)) - halitem.icon_state = "plasticx40" - if(3) //sword - halitem.icon = 'icons/obj/weapons/transforming_energy.dmi' - halitem.icon_state = "e_sword" - halitem.name = "energy sword" - if(4) //stun baton - halitem.icon = 'icons/obj/weapons/items_and_weapons.dmi' - halitem.icon_state = "stunbaton" - halitem.name = "Stun Baton" - if(5) //emag - halitem.icon = 'icons/obj/card.dmi' - halitem.icon_state = "emag" - halitem.name = "Cryptographic Sequencer" - if(6) //flashbang - halitem.icon = 'icons/obj/weapons/grenade.dmi' - halitem.icon_state = "flashbang1" - halitem.name = "Flashbang" - feedback_details += "Type: [halitem.name]" - if(target.client) - target.client.screen += halitem - QDEL_IN(halitem, rand(150, 350)) - - qdel(src) diff --git a/code/modules/hallucination/nearby_fake_item.dm b/code/modules/hallucination/nearby_fake_item.dm new file mode 100644 index 00000000000..c69556cfffa --- /dev/null +++ b/code/modules/hallucination/nearby_fake_item.dm @@ -0,0 +1,129 @@ +/// A hallucination that delivers the illusion that someone nearby has pulled out a weapon or item. +/datum/hallucination/nearby_fake_item + abstract_hallucination_parent = /datum/hallucination/nearby_fake_item + random_hallucination_weight = 1 + + /// The icon file to draw from for left hand icons + var/left_hand_file + /// The icon file to draw from for right hand icons + var/right_hand_file + /// The icon state of the files to make the image from + var/image_icon_state + /// The image we actually generate + var/image/generated_image + +/datum/hallucination/nearby_fake_item/Destroy() + if(generated_image) + hallucinator.client?.images -= generated_image + generated_image = null + return ..() + +/datum/hallucination/nearby_fake_item/start() + // This hallucination is purely visual, so we don't need to bother for clientless mobs + if(!hallucinator.client) + return FALSE + + var/list/mob_pool = list() + for(var/mob/living/carbon/human/nearby_mob in view(7, hallucinator)) + if(nearby_mob == hallucinator) + continue + mob_pool += nearby_mob + + if(!length(mob_pool)) + return FALSE + + var/mob/living/carbon/human/who_has_the_item = pick(mob_pool) + feedback_details += "Mob: [who_has_the_item.real_name]" + + if(who_has_the_item.get_empty_held_index_for_side(LEFT_HANDS)) + generated_image = generate_fake_image(who_has_the_item, file = left_hand_file) + + else if(who_has_the_item.get_empty_held_index_for_side(RIGHT_HANDS)) + generated_image = generate_fake_image(who_has_the_item, file = right_hand_file) + + if(generated_image) + hallucinator.client?.images += generated_image + addtimer(CALLBACK(src, .proc/remove_image, who_has_the_item), rand(15 SECONDS, 25 SECONDS)) + return TRUE + + return FALSE + +/// Generates the image with the given file on the passed mob. +/datum/hallucination/nearby_fake_item/proc/generate_fake_image(mob/living/carbon/human/holder, file) + return image(file, holder, image_icon_state, layer = ABOVE_MOB_LAYER) + +/// Remove the image when all's said and done. +/datum/hallucination/nearby_fake_item/proc/remove_image(mob/living/carbon/human/holder) + if(QDELETED(src) || QDELETED(hallucinator) || !generated_image) + return + + hallucinator.client?.images -= generated_image + generated_image = null + qdel(src) + +/datum/hallucination/nearby_fake_item/e_sword + left_hand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + image_icon_state = "e_sword_on_red" + +/datum/hallucination/nearby_fake_item/e_sword/generate_fake_image(mob/living/carbon/human/holder, file) + hallucinator.playsound_local(get_turf(holder), 'sound/weapons/saberon.ogg', 35, TRUE) + return ..() + +/datum/hallucination/nearby_fake_item/e_sword/remove_image(mob/living/carbon/human/holder) + if(!QDELETED(holder)) + hallucinator.playsound_local(get_turf(holder), 'sound/weapons/saberoff.ogg', 35, TRUE) + return ..() + +/datum/hallucination/nearby_fake_item/e_sword/double_bladed + image_icon_state = "dualsaberred1" + +/datum/hallucination/nearby_fake_item/taser + left_hand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' + image_icon_state = "advtaserstun4" + +/datum/hallucination/nearby_fake_item/taser/ebow // OOP be like. + image_icon_state = "crossbow" + +/datum/hallucination/nearby_fake_item/baton + left_hand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + image_icon_state = "baton" + +/datum/hallucination/nearby_fake_item/baton/generate_fake_image(mob/living/carbon/human/holder, file) + hallucinator.playsound_local(get_turf(holder), SFX_SPARKS, 75, TRUE, -1) + return ..() + +/datum/hallucination/nearby_fake_item/flash + left_hand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + image_icon_state = "flashtool" + +/datum/hallucination/nearby_fake_item/flash/generate_fake_image(mob/living/carbon/human/holder, file) + hallucinator.playsound_local(get_turf(holder), 'sound/items/handling/component_pickup.ogg', 35, vary = FALSE) + return ..() + +/datum/hallucination/nearby_fake_item/flash/remove_image(mob/living/carbon/human/holder) + if(!QDELETED(holder)) + hallucinator.playsound_local(get_turf(holder), 'sound/items/handling/component_drop.ogg', 35, vary = FALSE) + return ..() + +/datum/hallucination/nearby_fake_item/armblade + left_hand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' + image_icon_state = "arm_blade" + +/datum/hallucination/nearby_fake_item/armblade/generate_fake_image(mob/living/carbon/human/holder, file) + hallucinator.playsound_local(get_turf(holder), 'sound/effects/blobattack.ogg', 35, TRUE) + return ..() + +/datum/hallucination/nearby_fake_item/armblade/remove_image(mob/living/carbon/human/holder) + if(!QDELETED(holder)) + hallucinator.playsound_local(get_turf(holder), 'sound/effects/blobattack.ogg', 35, TRUE) + return ..() + +/datum/hallucination/nearby_fake_item/ttv + left_hand_file = 'icons/mob/inhands/weapons/bombs_lefthand.dmi' + right_hand_file = 'icons/mob/inhands/weapons/bombs_righthand.dmi' + image_icon_state = "ttv" diff --git a/code/modules/hallucination/on_fire.dm b/code/modules/hallucination/on_fire.dm new file mode 100644 index 00000000000..bc3696f8c0e --- /dev/null +++ b/code/modules/hallucination/on_fire.dm @@ -0,0 +1,120 @@ +#define RAISE_FIRE_COUNT 3 +#define RAISE_FIRE_TIME 3 + +/datum/hallucination/fire + random_hallucination_weight = 3 + + /// Are we currently burning our mob? + var/active = TRUE + /// What stare of fire are we in? + var/stage = 0 + + /// What icon file to use for our hallucinator + var/fire_icon = 'icons/mob/effects/onfire.dmi' + /// What icon state to use for our hallucinator + var/fire_icon_state = "human_big_fire" + /// Our fire overlay we generate + var/image/fire_overlay + + /// When should we do our next action of the hallucination? + var/next_action = 0 + /// How may times do we apply stamina damage to our mob? + var/times_to_lower_stamina + /// Are we currently fake-clearing our hallucinated fire? + var/fire_clearing = FALSE + /// Are the stages going up or down? + var/increasing_stages = TRUE + /// 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" + + else if(!ishuman(hallucinator)) + fire_icon_state = "generic_fire" + + return ..() + +/datum/hallucination/fire/start() + hallucinator.set_fire_stacks(max(hallucinator.fire_stacks, 0.1)) //Placebo flammability + fire_overlay = image(fire_icon, hallucinator, fire_icon_state, ABOVE_MOB_LAYER) + 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) + times_to_lower_stamina = rand(5, 10) + addtimer(CALLBACK(src, .proc/start_expanding), 2 SECONDS) + return TRUE + +/datum/hallucination/fire/Destroy() + hallucinator.adjust_fire_stacks(-0.1) + hallucinator.clear_alert(ALERT_FIRE, clear_override = TRUE) + hallucinator.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE) + if(fire_overlay) + hallucinator.client?.images -= fire_overlay + fire_overlay = null + + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/datum/hallucination/fire/proc/start_expanding() + if(QDELETED(src)) + return + + START_PROCESSING(SSfastprocess, src) + +/datum/hallucination/fire/process(delta_time) + if(QDELETED(src)) + return + + if(hallucinator.fire_stacks <= 0) + clear_fire() + + time_spent += delta_time + + if(fire_clearing) + next_action -= delta_time + if(next_action < 0) + stage -= 1 + update_temp() + next_action += 3 + + else if(increasing_stages) + var/new_stage = min(round(time_spent / RAISE_FIRE_TIME), RAISE_FIRE_COUNT) + if(stage != new_stage) + stage = new_stage + update_temp() + + if(stage == RAISE_FIRE_COUNT) + increasing_stages = FALSE + + else if(times_to_lower_stamina) + next_action -= delta_time + if(next_action < 0) + hallucinator.adjustStaminaLoss(15) + next_action += 2 + times_to_lower_stamina -= 1 + + else + clear_fire() + +/datum/hallucination/fire/proc/update_temp() + if(stage <= 0) + hallucinator.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE) + else + hallucinator.clear_alert(ALERT_TEMPERATURE, clear_override = TRUE) + hallucinator.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/hot, stage, override = TRUE) + +/datum/hallucination/fire/proc/clear_fire() + if(!active) + return + + active = FALSE + hallucinator.clear_alert(ALERT_FIRE, clear_override = TRUE) + hallucinator.client?.images -= fire_overlay + fire_overlay = null + fire_clearing = TRUE + next_action = 0 + +#undef RAISE_FIRE_COUNT +#undef RAISE_FIRE_TIME diff --git a/code/modules/hallucination/plasma_flood.dm b/code/modules/hallucination/plasma_flood.dm deleted file mode 100644 index ea76106231c..00000000000 --- a/code/modules/hallucination/plasma_flood.dm +++ /dev/null @@ -1,83 +0,0 @@ -#define FAKE_FLOOD_EXPAND_TIME 20 -#define FAKE_FLOOD_MAX_RADIUS 10 - -/obj/effect/plasma_image_holder - icon_state = "nothing" - anchored = TRUE - layer = FLY_LAYER - plane = ABOVE_GAME_PLANE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/datum/hallucination/fake_flood - //Plasma starts flooding from the nearby vent - var/turf/center - var/list/flood_images = list() - var/list/flood_image_holders = list() - var/list/turf/flood_turfs = list() - var/image_icon = 'icons/effects/atmospherics.dmi' - var/image_state = "plasma" - var/radius = 0 - var/next_expand = 0 - -/datum/hallucination/fake_flood/New(mob/living/carbon/C, forced = TRUE) - ..() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in orange(7,target)) - if(!U.welded) - center = get_turf(U) - break - if(!center) - qdel(src) - return - feedback_details += "Vent Coords: [center.x],[center.y],[center.z]" - var/obj/effect/plasma_image_holder/pih = new(center) - var/image/plasma_image = image(image_icon, pih, image_state, FLY_LAYER) - plasma_image.alpha = 50 - plasma_image.plane = ABOVE_GAME_PLANE - flood_images += plasma_image - flood_image_holders += pih - flood_turfs += center - if(target.client) - target.client.images |= flood_images - next_expand = world.time + FAKE_FLOOD_EXPAND_TIME - START_PROCESSING(SSobj, src) - -/datum/hallucination/fake_flood/process() - if(next_expand <= world.time) - radius++ - if(radius > FAKE_FLOOD_MAX_RADIUS) - qdel(src) - return - Expand() - if((get_turf(target) in flood_turfs) && !target.internal) - new /datum/hallucination/fake_alert(target, TRUE, ALERT_TOO_MUCH_PLASMA) - next_expand = world.time + FAKE_FLOOD_EXPAND_TIME - -/datum/hallucination/fake_flood/proc/Expand() - for(var/image/I in flood_images) - I.alpha = min(I.alpha + 50, 255) - for(var/turf/FT in flood_turfs) - for(var/dir in GLOB.cardinals) - var/turf/T = get_step(FT, dir) - if((T in flood_turfs) || !TURFS_CAN_SHARE(T, FT) || isspaceturf(T)) //If we've gottem already, or if they're not alright to spread with. - continue - var/obj/effect/plasma_image_holder/pih = new(T) - var/image/new_plasma = image(image_icon, pih, image_state, FLY_LAYER) - new_plasma.alpha = 50 - new_plasma.plane = ABOVE_GAME_PLANE - flood_images += new_plasma - flood_image_holders += pih - flood_turfs += T - if(target.client) - target.client.images |= flood_images - -/datum/hallucination/fake_flood/Destroy() - STOP_PROCESSING(SSobj, src) - qdel(flood_turfs) - flood_turfs = list() - if(target.client) - target.client.images.Remove(flood_images) - qdel(flood_images) - flood_images = list() - qdel(flood_image_holders) - flood_image_holders = list() - return ..() diff --git a/code/modules/hallucination/polymorph.dm b/code/modules/hallucination/polymorph.dm deleted file mode 100644 index 4cd6ae1e85d..00000000000 --- a/code/modules/hallucination/polymorph.dm +++ /dev/null @@ -1,101 +0,0 @@ -/* Polymorph Hallucinations - * - * Contains: - * Nearby mobs are polymorphed into other creatures - * Polymorphing yourself into other creatures - */ -/datum/hallucination/delusion - var/list/image/delusions = list() - -/datum/hallucination/delusion/New(mob/living/carbon/C, forced, force_kind = null , duration = 300,skip_nearby = TRUE, custom_icon = null, custom_icon_file = null, custom_name = null) - set waitfor = FALSE - . = ..() - var/image/A = null - var/kind = force_kind ? force_kind : pick("nothing","monkey","corgi","carp","skeleton","demon","zombie") - feedback_details += "Type: [kind]" - var/list/nearby - if(skip_nearby) - nearby = get_hearers_in_view(7, target) - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - if(H == target) - continue - if(skip_nearby && (H in nearby)) - continue - switch(kind) - if("nothing") - A = image('icons/effects/effects.dmi',H,"nothing") - A.name = "..." - if("monkey")//Monkey - A = image('icons/mob/species/human/human.dmi',H,"monkey") - A.name = "Monkey ([rand(1,999)])" - if("carp")//Carp - A = image('icons/mob/simple/carp.dmi',H,"carp") - A.name = "Space Carp" - if("corgi")//Corgi - A = image('icons/mob/simple/pets.dmi',H,"corgi") - A.name = "Corgi" - if("skeleton")//Skeletons - A = image('icons/mob/species/human/human.dmi',H,"skeleton") - A.name = "Skeleton" - if("zombie")//Zombies - A = image('icons/mob/species/human/human.dmi',H,"zombie") - A.name = "Zombie" - if("demon")//Demon - A = image('icons/mob/simple/mob.dmi',H,"daemon") - A.name = "Demon" - if("custom") - A = image(custom_icon_file, H, custom_icon) - A.name = custom_name - A.override = 1 - if(target.client) - delusions |= A - target.client.images |= A - if(duration) - QDEL_IN(src, duration) - -/datum/hallucination/delusion/Destroy() - for(var/image/I in delusions) - if(target.client) - target.client.images.Remove(I) - return ..() - -/datum/hallucination/self_delusion - var/image/delusion - -/datum/hallucination/self_delusion/New(mob/living/carbon/C, forced, force_kind = null , duration = 300, custom_icon = null, custom_icon_file = null, wabbajack = TRUE) //set wabbajack to false if you want to use another fake source - set waitfor = FALSE - ..() - var/image/A = null - var/kind = force_kind ? force_kind : pick("monkey","corgi","carp","skeleton","demon","zombie","robot") - feedback_details += "Type: [kind]" - switch(kind) - if("monkey")//Monkey - A = image('icons/mob/species/human/human.dmi',target,"monkey") - if("carp")//Carp - A = image('icons/mob/simple/animal.dmi',target,"carp") - if("corgi")//Corgi - A = image('icons/mob/simple/pets.dmi',target,"corgi") - if("skeleton")//Skeletons - A = image('icons/mob/species/human/human.dmi',target,"skeleton") - if("zombie")//Zombies - A = image('icons/mob/species/human/human.dmi',target,"zombie") - if("demon")//Demon - A = image('icons/mob/simple/mob.dmi',target,"daemon") - if("robot")//Cyborg - A = image('icons/mob/silicon/robots.dmi',target,"robot") - target.playsound_local(target,'sound/voice/liveagain.ogg', 75, 1) - if("custom") - A = image(custom_icon_file, target, custom_icon) - A.override = 1 - if(target.client) - if(wabbajack) - to_chat(target, span_hear("...wabbajack...wabbajack...")) - target.playsound_local(target,'sound/magic/staff_change.ogg', 50, 1) - delusion = A - target.client.images |= A - QDEL_IN(src, duration) - -/datum/hallucination/self_delusion/Destroy() - if(target.client) - target.client.images.Remove(delusion) - return ..() diff --git a/code/modules/hallucination/screwy_health_doll.dm b/code/modules/hallucination/screwy_health_doll.dm new file mode 100644 index 00000000000..94863d3590e --- /dev/null +++ b/code/modules/hallucination/screwy_health_doll.dm @@ -0,0 +1,80 @@ +///Causes the target to see incorrect health damages on the healthdoll +/datum/hallucination/fake_health_doll + random_hallucination_weight = 12 + + /// The duration of the hallucination + var/duration + /// Assoc list of [ref to bodyparts] to [severity] + var/list/bodyparts = list() + /// Timer ID for when we're deleted + var/del_timer_id + +/datum/hallucination/fake_health_doll/New(mob/living/hallucinator, duration = 50 SECONDS) + src.duration = duration + return ..() + +// So that the associated addition proc cleans it up correctly +/datum/hallucination/fake_health_doll/Destroy() + if(del_timer_id) + deltimer(del_timer_id) + + for(var/obj/item/bodypart/limb as anything in bodyparts) + remove_bodypart(limb) + + hallucinator.update_health_hud() + return ..() + +/datum/hallucination/fake_health_doll/start() + if(!ishuman(hallucinator)) + return FALSE + + add_fake_limb() + del_timer_id = QDEL_IN(src, duration) + return TRUE + +/// Increments the severity of the damage seen on all the limbs we are already tracking. +/datum/hallucination/fake_health_doll/proc/increment_fake_damage() + + for(var/obj/item/bodypart/limb as anything in bodyparts) + bodyparts[limb] = clamp(bodyparts[limb] + 1, 1, 5) + + hallucinator.update_health_hud() + +/** + * Adds a fake limb to the effect. + * + * specific_limb - optional, the specific limb to apply the effect to. If not passed, picks a random limb + * seveirty - optional, the specific severity level to apply the effect. Clamped from 1 to 5. If not passed, picks a random number. + */ +/datum/hallucination/fake_health_doll/proc/add_fake_limb(obj/item/bodypart/specific_limb, severity) + var/mob/living/carbon/human/human_mob = hallucinator + + var/obj/item/bodypart/picked = specific_limb || pick(human_mob.bodyparts) + if(!(picked in bodyparts)) + RegisterSignal(picked, list(COMSIG_PARENT_QDELETING, COMSIG_BODYPART_REMOVED), .proc/remove_bodypart) + RegisterSignal(picked, COMSIG_BODYPART_UPDATING_HEALTH_HUD, .proc/on_bodypart_hud_update) + RegisterSignal(picked, COMSIG_BODYPART_CHECKED_FOR_INJURY, .proc/on_bodypart_checked) + + bodyparts[picked] = clamp(severity || rand(1, 5), 1, 5) + hallucinator.update_health_hud() + +/// Remove a bodypart from our list, unregistering all associated signals and handling the reference +/datum/hallucination/fake_health_doll/proc/remove_bodypart(obj/item/bodypart/source) + SIGNAL_HANDLER + + UnregisterSignal(source, list(COMSIG_PARENT_QDELETING, COMSIG_BODYPART_REMOVED, COMSIG_BODYPART_UPDATING_HEALTH_HUD, COMSIG_BODYPART_CHECKED_FOR_INJURY)) + bodyparts -= source + +/// Whenever a bodypart we're tracking has their health hud updated, override it with our fake overlay +/datum/hallucination/fake_health_doll/proc/on_bodypart_hud_update(obj/item/bodypart/source, mob/living/carbon/human/owner) + SIGNAL_HANDLER + + var/mutable_appearance/fake_overlay = mutable_appearance('icons/hud/screen_gen.dmi', "[source.body_zone][bodyparts[source]]") + owner.hud_used.healthdoll.add_overlay(fake_overlay) + return COMPONENT_OVERRIDE_BODYPART_HEALTH_HUD + +/// Signal proc for [COMSIG_BODYPART_CHECKED_FOR_INJURY]. Our bodyparts look a lot more wounded than they actually are. +/datum/hallucination/fake_health_doll/proc/on_bodypart_checked(obj/item/bodypart/source, mob/living/carbon/examiner, list/check_list, list/limb_damage) + SIGNAL_HANDLER + + limb_damage[BRUTE] = bodyparts[source] * 0.2 * source.max_damage diff --git a/code/modules/hallucination/shock.dm b/code/modules/hallucination/shock.dm index e38057036a8..8176a6c2159 100644 --- a/code/modules/hallucination/shock.dm +++ b/code/modules/hallucination/shock.dm @@ -1,31 +1,63 @@ +/// Causes a fake "zap" to the hallucinator. /datum/hallucination/shock + random_hallucination_weight = 1 + + var/electrocution_icon = 'icons/mob/species/human/human.dmi' + var/electrocution_icon_state = "electrocuted_base" var/image/shock_image var/image/electrocution_skeleton_anim -/datum/hallucination/shock/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - shock_image = image(target, target, dir = target.dir) +/datum/hallucination/shock/New(mob/living/hallucinator) + electrocution_icon_state = ishuman(hallucinator) ? "electrocuted_base" : "electrocuted_generic" + return ..() + +/datum/hallucination/shock/Destroy() + if(shock_image) + hallucinator.client?.images -= shock_image + shock_image = null + if(electrocution_skeleton_anim) + hallucinator.client?.images -= electrocution_skeleton_anim + electrocution_skeleton_anim = null + + return ..() + +/datum/hallucination/shock/start() + shock_image = image(hallucinator, hallucinator, dir = hallucinator.dir) shock_image.appearance_flags |= KEEP_APART - shock_image.color = rgb(0,0,0) + shock_image.color = rgb(0, 0, 0) shock_image.override = TRUE - electrocution_skeleton_anim = image('icons/mob/species/human/human.dmi', target, icon_state = "electrocuted_base", layer=ABOVE_MOB_LAYER) + + electrocution_skeleton_anim = image(electrocution_icon, hallucinator, icon_state = electrocution_icon_state, layer = ABOVE_MOB_LAYER) electrocution_skeleton_anim.appearance_flags |= RESET_COLOR|KEEP_APART - to_chat(target, span_userdanger("You feel a powerful shock course through your body!")) - if(target.client) - target.client.images |= shock_image - target.client.images |= electrocution_skeleton_anim - addtimer(CALLBACK(src, .proc/reset_shock_animation), 40) - target.playsound_local(get_turf(src), SFX_SPARKS, 100, 1) - target.staminaloss += 50 - target.Stun(4 SECONDS) - target.do_jitter_animation(300) // Maximum jitter - target.adjust_timed_status_effect(20 SECONDS, /datum/status_effect/jitter) + + to_chat(hallucinator, span_userdanger("You feel a powerful shock course through your body!")) + hallucinator.visible_message(span_warning("[hallucinator] falls to the ground, shaking!"), ignored_mobs = hallucinator) + hallucinator.client?.images |= shock_image + hallucinator.client?.images |= electrocution_skeleton_anim + + hallucinator.playsound_local(get_turf(src), SFX_SPARKS, 100, TRUE) + hallucinator.adjustStaminaLoss(50) + hallucinator.Stun(4 SECONDS) + hallucinator.do_jitter_animation(300) // Maximum jitter + hallucinator.adjust_timed_status_effect(20 SECONDS, /datum/status_effect/jitter) + + addtimer(CALLBACK(src, .proc/reset_shock_animation), 4 SECONDS) addtimer(CALLBACK(src, .proc/shock_drop), 2 SECONDS) + QDEL_IN(src, 4 SECONDS) + return TRUE /datum/hallucination/shock/proc/reset_shock_animation() - target.client?.images.Remove(shock_image) - target.client?.images.Remove(electrocution_skeleton_anim) + if(QDELETED(hallucinator)) + return + + hallucinator.client?.images -= shock_image + shock_image = null + + hallucinator.client?.images -= electrocution_skeleton_anim + electrocution_skeleton_anim = null /datum/hallucination/shock/proc/shock_drop() - target.Paralyze(6 SECONDS) + if(QDELETED(hallucinator)) + return + + hallucinator.Paralyze(6 SECONDS) diff --git a/code/modules/hallucination/sound.dm b/code/modules/hallucination/sound.dm deleted file mode 100644 index 00dc752a191..00000000000 --- a/code/modules/hallucination/sound.dm +++ /dev/null @@ -1,243 +0,0 @@ -/* Sound Hallucinations - * - * Contains: - * Fighting sounds - * Machinery sounds - * Special effects sounds - */ - -/datum/hallucination/battle - var/battle_type - var/iterations_left - var/hits = 0 - var/next_action = 0 - var/turf/source - -/datum/hallucination/battle/New(mob/living/carbon/C, forced = TRUE, new_battle_type) - ..() - - source = random_far_turf() - - battle_type = new_battle_type - if (isnull(battle_type)) - battle_type = pick("laser", "disabler", "esword", "gun", "stunprod", "harmbaton", "bomb") - feedback_details += "Type: [battle_type]" - var/process = TRUE - - switch(battle_type) - if("disabler", "laser") - iterations_left = rand(5, 10) - if("esword") - iterations_left = rand(4, 8) - target.playsound_local(source, 'sound/weapons/saberon.ogg',15, 1) - if("gun") - iterations_left = rand(3, 6) - if("stunprod") //Stunprod + cablecuff - process = FALSE - target.playsound_local(source, 'sound/weapons/egloves.ogg', 40, 1) - target.playsound_local(source, get_sfx(SFX_BODYFALL), 25, 1) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/weapons/cablecuff.ogg', 15, 1), 20) - if("harmbaton") //zap n slap - iterations_left = rand(5, 12) - target.playsound_local(source, 'sound/weapons/egloves.ogg', 40, 1) - target.playsound_local(source, get_sfx(SFX_BODYFALL), 25, 1) - next_action = 2 SECONDS - if("bomb") // Tick Tock - iterations_left = rand(3, 11) - - if (process) - START_PROCESSING(SSfastprocess, src) - else - qdel(src) - -/datum/hallucination/battle/process(delta_time) - next_action -= (delta_time * 10) - - if (next_action > 0) - return - - switch (battle_type) - if ("disabler", "laser", "gun") - var/fire_sound - var/hit_person_sound - var/hit_wall_sound - var/number_of_hits - var/chance_to_fall - - switch (battle_type) - if ("disabler") - fire_sound = 'sound/weapons/taser2.ogg' - hit_person_sound = 'sound/weapons/tap.ogg' - hit_wall_sound = 'sound/weapons/effects/searwall.ogg' - number_of_hits = 3 - chance_to_fall = 70 - if ("laser") - fire_sound = 'sound/weapons/laser.ogg' - hit_person_sound = 'sound/weapons/sear.ogg' - hit_wall_sound = 'sound/weapons/effects/searwall.ogg' - number_of_hits = 4 - chance_to_fall = 70 - if ("gun") - fire_sound = 'sound/weapons/gun/shotgun/shot.ogg' - hit_person_sound = 'sound/weapons/pierce.ogg' - hit_wall_sound = SFX_RICOCHET - number_of_hits = 2 - chance_to_fall = 80 - - target.playsound_local(source, fire_sound, 25, 1) - - if(prob(50)) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, hit_person_sound, 25, 1), rand(5,10)) - hits += 1 - else - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, hit_wall_sound, 25, 1), rand(5,10)) - - next_action = rand(CLICK_CD_RANGE, CLICK_CD_RANGE + 6) - - if(hits >= number_of_hits && prob(chance_to_fall)) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, get_sfx(SFX_BODYFALL), 25, 1), next_action) - qdel(src) - return - if ("esword") - target.playsound_local(source, 'sound/weapons/blade1.ogg', 50, 1) - - if (hits == 4) - target.playsound_local(source, get_sfx(SFX_BODYFALL), 25, 1) - - next_action = rand(CLICK_CD_MELEE, CLICK_CD_MELEE + 6) - hits += 1 - - if (iterations_left == 1) - target.playsound_local(source, 'sound/weapons/saberoff.ogg', 15, 1) - if ("harmbaton") - target.playsound_local(source, SFX_SWING_HIT, 50, 1) - next_action = rand(CLICK_CD_MELEE, CLICK_CD_MELEE + 4) - if ("bomb") - target.playsound_local(source, 'sound/items/timer.ogg', 25, 0) - next_action = 15 - - iterations_left -= 1 - if (iterations_left == 0) - qdel(src) - -/datum/hallucination/battle/Destroy() - . = ..() - source = null - STOP_PROCESSING(SSfastprocess, src) - -/datum/hallucination/sounds - -/datum/hallucination/sounds/New(mob/living/carbon/C, forced = TRUE, sound_type) - set waitfor = FALSE - ..() - var/turf/source = random_far_turf() - if(!sound_type) - sound_type = pick("airlock","airlock pry","console","explosion","far explosion","mech","glass","alarm","beepsky","mech","wall decon","door hack") - feedback_details += "Type: [sound_type]" - //Strange audio - switch(sound_type) - if("airlock") - target.playsound_local(source,'sound/machines/airlock.ogg', 30, 1) - if("airlock pry") - target.playsound_local(source,'sound/machines/airlock_alien_prying.ogg', 100, 1) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/machines/airlockforced.ogg', 30, 1), 50) - if("console") - target.playsound_local(source,'sound/machines/terminal_prompt.ogg', 25, 1) - if("explosion") - if(prob(50)) - target.playsound_local(source,'sound/effects/explosion1.ogg', 50, 1) - else - target.playsound_local(source, 'sound/effects/explosion2.ogg', 50, 1) - if("far explosion") - target.playsound_local(source, 'sound/effects/explosionfar.ogg', 50, 1) - if("glass") - target.playsound_local(source, pick('sound/effects/glassbr1.ogg','sound/effects/glassbr2.ogg','sound/effects/glassbr3.ogg'), 50, 1) - if("alarm") - target.playsound_local(source, 'sound/machines/alarm.ogg', 100, 0) - if("beepsky") - target.playsound_local(source, 'sound/voice/beepsky/freeze.ogg', 35, 0) - if("mech") - new /datum/hallucination/mech_sounds(C, forced, sound_type) - //Deconstructing a wall - if("wall decon") - target.playsound_local(source, 'sound/items/welder.ogg', 50, 1) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/items/welder2.ogg', 50, 1), 105) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/items/ratchet.ogg', 50, 1), 120) - //Hacking a door - if("door hack") - target.playsound_local(source, 'sound/items/screwdriver.ogg', 50, 1) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/machines/airlockforced.ogg', 30, 1), rand(40, 80)) - qdel(src) - -/datum/hallucination/mech_sounds - var/mech_dir - var/steps_left - var/next_action = 0 - var/turf/source - -/datum/hallucination/mech_sounds/New() - . = ..() - mech_dir = pick(GLOB.cardinals) - steps_left = rand(4, 9) - source = random_far_turf() - START_PROCESSING(SSfastprocess, src) - -/datum/hallucination/mech_sounds/process(delta_time) - next_action -= delta_time - if (next_action > 0) - return - - if(prob(75)) - target.playsound_local(source, 'sound/mecha/mechstep.ogg', 40, 1) - source = get_step(source, mech_dir) - else - target.playsound_local(source, 'sound/mecha/mechturn.ogg', 40, 1) - mech_dir = pick(GLOB.cardinals) - - steps_left -= 1 - if (!steps_left) - qdel(src) - return - next_action = 1 - -/datum/hallucination/mech_sounds/Destroy() - . = ..() - STOP_PROCESSING(SSfastprocess, src) - -/datum/hallucination/weird_sounds - -/datum/hallucination/weird_sounds/New(mob/living/carbon/C, forced = TRUE, sound_type) - set waitfor = FALSE - ..() - var/turf/source = random_far_turf() - if(!sound_type) - sound_type = pick("phone","hallelujah","highlander","laughter","hyperspace","game over","creepy","tesla") - feedback_details += "Type: [sound_type]" - //Strange audio - switch(sound_type) - if("phone") - target.playsound_local(source, 'sound/weapons/ring.ogg', 15) - for (var/next_rings in 1 to 3) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/weapons/ring.ogg', 15), 25 * next_rings) - if("hyperspace") - target.playsound_local(null, 'sound/runtime/hyperspace/hyperspace_begin.ogg', 50) - if("hallelujah") - target.playsound_local(source, 'sound/effects/pray_chaplain.ogg', 50) - if("highlander") - target.playsound_local(null, 'sound/misc/highlander.ogg', 50) - if("game over") - target.playsound_local(source, 'sound/misc/compiler-failure.ogg', 50) - if("laughter") - if(prob(50)) - target.playsound_local(source, 'sound/voice/human/womanlaugh.ogg', 50, 1) - else - target.playsound_local(source, pick('sound/voice/human/manlaugh1.ogg', 'sound/voice/human/manlaugh2.ogg'), 50, 1) - if("creepy") - //These sounds are (mostly) taken from Hidden: Source - target.playsound_local(source, pick(GLOB.creepy_ambience), 50, 1) - if("tesla") //Tesla loose! - target.playsound_local(source, 'sound/magic/lightningbolt.ogg', 35, 1) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/magic/lightningbolt.ogg', 65, 1), 30) - addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, 'sound/magic/lightningbolt.ogg', 100, 1), 60) - - qdel(src) diff --git a/code/modules/hallucination/station_message.dm b/code/modules/hallucination/station_message.dm new file mode 100644 index 00000000000..8ce29944506 --- /dev/null +++ b/code/modules/hallucination/station_message.dm @@ -0,0 +1,118 @@ +#define ALERT_TITLE(text) ("

" + text + "

") +#define ALERT_BODY(text) ("

" + span_alert(text) + "

") + +/datum/hallucination/station_message + abstract_hallucination_parent = /datum/hallucination/station_message + random_hallucination_weight = 1 + +/datum/hallucination/station_message/start() + qdel(src) // To be implemented by subtypes, call parent for easy cleanup + return TRUE + +/datum/hallucination/station_message/blob_alert + +/datum/hallucination/station_message/blob_alert/start() + to_chat(hallucinator, ALERT_TITLE("Biohazard Alert")) + to_chat(hallucinator, ALERT_BODY("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.")) + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_OUTBREAK5])) + return ..() + +/datum/hallucination/station_message/shuttle_dock + +/datum/hallucination/station_message/shuttle_dock/start() + to_chat(hallucinator, ALERT_TITLE("Priority Announcement")) + to_chat(hallucinator, ALERT_BODY("[SSshuttle.emergency || "The Emergency Shuttle"] has docked with the station. You have 3 minutes to board the Emergency Shuttle.")) + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_SHUTTLEDOCK])) + return ..() + +/datum/hallucination/station_message/malf_ai + +/datum/hallucination/station_message/malf_ai/start() + if(!(locate(/mob/living/silicon/ai) in GLOB.silicon_mobs)) + return FALSE + + to_chat(hallucinator, ALERT_TITLE("Anomaly Alert")) + to_chat(hallucinator, ALERT_BODY("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core.")) + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_AIMALF])) + return ..() + +/datum/hallucination/station_message/heretic + /// This is gross and will probably easily be outdated in some time but c'est la vie. + /// Maybe if someone datumizes heretic paths or something this can be improved + var/static/list/ascension_bodies = list( + "Fear the blaze, for the Ashlord, %FAKENAME% has ascended! The flames shall consume all!", + "Master of blades, the Torn Champion's disciple, %FAKENAME% has ascended! Their steel is that which will cut reality in a maelstom of silver!", + "Ever coiling vortex. Reality unfolded. ARMS OUTREACHED, THE LORD OF THE NIGHT, %FAKENAME% has ascended! Fear the ever twisting hand!", + "Fear the decay, for the Rustbringer, %FAKENAME% has ascended! None shall escape the corrosion!", + "The nobleman of void %FAKENAME% has arrived, stepping along the Waltz that ends worlds!", + ) + +/datum/hallucination/station_message/heretic/start() + // Unfortunately, this will not be synced if mass hallucinated + var/mob/living/carbon/human/totally_real_heretic = random_non_sec_crewmember() + if(!totally_real_heretic) + return FALSE + + var/message_with_name = pick(ascension_bodies) + message_with_name = replacetext(message_with_name, "%FAKENAME%", totally_real_heretic.real_name) + + to_chat(hallucinator, ALERT_TITLE(generate_heretic_text())) + to_chat(hallucinator, ALERT_BODY("[generate_heretic_text()] [message_with_name] [generate_heretic_text()]")) + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_SPANOMALIES])) + return ..() + +/datum/hallucination/station_message/cult_summon + +/datum/hallucination/station_message/cult_summon/start() + // Same, will not be synced if mass hallucinated + var/mob/living/carbon/human/totally_real_cult_leader = random_non_sec_crewmember() + if(!totally_real_cult_leader) + return FALSE + + // Get a fake area that the summoning is happening in + var/area/hallucinator_area = get_area(hallucinator) + var/area/fake_summon_area_type = pick(GLOB.the_station_areas - hallucinator_area.type) + var/area/fake_summon_area = GLOB.areas_by_type[fake_summon_area_type] + + to_chat(hallucinator, ALERT_TITLE("Central Command Higher Dimensional Affairs")) + to_chat(hallucinator, ALERT_BODY("Figments from an eldritch god are being summoned by [totally_real_cult_leader.real_name] \ + into [fake_summon_area] from an unknown dimension. Disrupt the ritual at all costs!")) + + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_SPANOMALIES])) + return ..() + +/datum/hallucination/station_message/meteors + random_hallucination_weight = 2 + +/datum/hallucination/station_message/meteors/start() + to_chat(hallucinator, ALERT_TITLE("Meteor Alert")) + to_chat(hallucinator, ALERT_BODY("Meteors have been detected on collision course with the station.")) + SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_METEORS])) + return ..() + +/datum/hallucination/station_message/supermatter_delam + +/datum/hallucination/station_message/supermatter_delam/start() + SEND_SOUND(hallucinator, 'sound/magic/charge.ogg') + to_chat(hallucinator, span_boldannounce("You feel reality distort for a moment...")) + return ..() + +/datum/hallucination/station_message/clock_cult_ark + // Clock cult's long gone, but this stays for posterity. + random_hallucination_weight = 0 + +/datum/hallucination/station_message/clock_cult_ark/start() + hallucinator.playsound_local(hallucinator, 'sound/machines/clockcult/ark_deathrattle.ogg', 50, FALSE, pressure_affected = FALSE) + hallucinator.playsound_local(hallucinator, 'sound/effects/clockcult_gateway_disrupted.ogg', 50, FALSE, pressure_affected = FALSE) + addtimer(CALLBACK(src, .proc/play_distant_explosion_sound), 2.7 SECONDS) + return TRUE // does not call parent to finish up the sound in a few seconds + +/datum/hallucination/station_message/clock_cult_ark/proc/play_distant_explosion_sound() + if(QDELETED(src)) + return + + hallucinator.playsound_local(get_turf(hallucinator), 'sound/effects/explosion_distant.ogg', 50, FALSE, pressure_affected = FALSE) + qdel(src) + +#undef ALERT_TITLE +#undef ALERT_BODY diff --git a/code/modules/hallucination/stray_bullet.dm b/code/modules/hallucination/stray_bullet.dm index b0fba6d724a..c45e9301b89 100644 --- a/code/modules/hallucination/stray_bullet.dm +++ b/code/modules/hallucination/stray_bullet.dm @@ -1,21 +1,317 @@ -//hallucination projectile code in code/modules/projectiles/projectile/special.dm +/// Shoots a random, fake projectile to the hallucinator /datum/hallucination/stray_bullet + random_hallucination_weight = 7 + +/datum/hallucination/stray_bullet/start() + var/list/turf/starting_locations = list() + for(var/turf/open/open_out_of_view in view(world.view + 1, hallucinator) - view(world.view, hallucinator)) + starting_locations += open_out_of_view + if(!length(starting_locations)) + return FALSE + + var/turf/start = pick(starting_locations) + var/fake_type = pick(subtypesof(/obj/projectile/hallucination)) + + feedback_details += "Type: [fake_type], Source: ([start.x], [start.y], [start.z])" + + var/obj/projectile/hallucination/fake_projectile = new fake_type(start, src) + + fake_projectile.preparePixelProjectile(hallucinator, start) + fake_projectile.fire() + + QDEL_IN(src, 10 SECONDS) // Should clean up the projectile if it somehow gets stuck. + return TRUE + +/// Hallucinated projectiles. +/obj/projectile/hallucination + name = "bullet" + icon = null + icon_state = null + hitsound = "" + suppressed = SUPPRESSED_VERY + ricochets_max = 0 + ricochet_chance = 0 + damage = 0 + nodamage = TRUE + projectile_type = /obj/projectile/hallucination + log_override = TRUE + /// Our parent hallucination that's created us + var/datum/hallucination/parent + /// The image that represents our projectile itself + var/image/fake_bullet + + var/hal_icon = 'icons/obj/weapons/guns/projectiles.dmi' + var/hal_icon_state + var/hal_fire_sound + var/hal_hitsound + var/hal_hitsound_wall + var/hal_impact_effect + var/hal_impact_effect_wall + + var/hit_duration + var/hit_duration_wall + +/obj/projectile/hallucination/Initialize(mapload, datum/hallucination/parent) + . = ..() + if(!parent) + stack_trace("[type] was created without a parent hallucination.") + return INITIALIZE_HINT_QDEL + + src.parent = parent + RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/parent_deleting) + + +/obj/projectile/hallucination/Destroy() + if(!QDELETED(parent.hallucinator)) + parent.hallucinator.client?.images -= fake_bullet + fake_bullet = null + + UnregisterSignal(parent, COMSIG_PARENT_QDELETING) + parent = null + return ..() + +/// Signal proc for [COMSIG_PARENT_QDELETING], if our associated hallucination deletes, we need to clean up +/obj/projectile/hallucination/proc/parent_deleting(datum/source) + SIGNAL_HANDLER -/datum/hallucination/stray_bullet/New(mob/living/carbon/C, forced = TRUE) - set waitfor = FALSE - ..() - var/list/turf/startlocs = list() - for(var/turf/open/T in view(world.view+1,target)-view(world.view,target)) - startlocs += T - if(!startlocs.len) - qdel(src) - return - var/turf/start = pick(startlocs) - var/proj_type = pick(subtypesof(/obj/projectile/hallucination)) - feedback_details += "Type: [proj_type]" - var/obj/projectile/hallucination/H = new proj_type(start) - target.playsound_local(start, H.hal_fire_sound, 60, 1) - H.hal_target = target - H.preparePixelProjectile(target, start) - H.fire() qdel(src) + +/obj/projectile/hallucination/fire() + if(hal_fire_sound) + parent.hallucinator.playsound_local(get_turf(src), hal_fire_sound, 60, TRUE) + + fake_bullet = image(hal_icon, src, hal_icon_state, ABOVE_MOB_LAYER) + parent.hallucinator.client?.images += fake_bullet + return ..() + +/obj/projectile/hallucination/on_hit(atom/target, blocked, pierce_hit) + . = ..() + if(. != BULLET_ACT_HIT) + return + + if(ismob(target)) + if(hal_hitsound) + parent.hallucinator.playsound_local(target, hal_hitsound, 100, 1) + on_mob_hit(target) + + else + if(hal_hitsound_wall) + parent.hallucinator.playsound_local(loc, hal_hitsound_wall, 40, 1) + if(hal_impact_effect_wall) + spawn_hit(target, TRUE) + + qdel(src) + return TRUE + +/// Called when a mob is hit by the fake projectile +/obj/projectile/hallucination/proc/on_mob_hit(mob/living/hit_mob) + if(hit_mob == parent.hallucinator) + to_chat(parent.hallucinator, span_userdanger("[hit_mob] is hit by \a [src] in the chest!")) + apply_effect_to_hallucinator(parent.hallucinator) + + else if(hit_mob in view(parent.hallucinator)) + to_chat(parent.hallucinator, span_danger("[hit_mob] is hit by \a [src] in the chest!")) + + if(damage_type == BRUTE) + var/splatter_dir = dir + if(starting) + splatter_dir = get_dir(starting, get_turf(hit_mob)) + spawn_blood(hit_mob, splatter_dir) + + else if(hal_impact_effect) + spawn_hit(hit_mob, FALSE) + +/// Called when the hallucinator themselves are hit by the fake projectile +/obj/projectile/hallucination/proc/apply_effect_to_hallucinator(mob/living/afflicted) + return + +/// Called after a mob is hit by the fake projectile, and our fake projectile is of brute type, to create fake blood +/obj/projectile/hallucination/proc/spawn_blood(mob/living/bleeding, set_dir) + if(!parent.hallucinator.client) // Purely visual, don't need to do this for clientless mobs + return + + var/splatter_icon_state + if(ISDIAGONALDIR(set_dir)) + splatter_icon_state = "splatter[pick(1, 2, 6)]" + else + splatter_icon_state = "splatter[pick(3, 4, 5)]" + + var/image/blood = image('icons/effects/blood.dmi', bleeding, splatter_icon_state, ABOVE_MOB_LAYER) + var/target_pixel_x = 0 + var/target_pixel_y = 0 + switch(set_dir) + if(NORTH) + target_pixel_y = 16 + if(SOUTH) + target_pixel_y = -16 + layer = ABOVE_MOB_LAYER + if(EAST) + target_pixel_x = 16 + if(WEST) + target_pixel_x = -16 + if(NORTHEAST) + target_pixel_x = 16 + target_pixel_y = 16 + if(NORTHWEST) + target_pixel_x = -16 + target_pixel_y = 16 + if(SOUTHEAST) + target_pixel_x = 16 + target_pixel_y = -16 + layer = ABOVE_MOB_LAYER + if(SOUTHWEST) + target_pixel_x = -16 + target_pixel_y = -16 + layer = ABOVE_MOB_LAYER + + parent.hallucinator.client?.images |= blood + animate(blood, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = 5) + addtimer(CALLBACK(src, .proc/clean_up_blood, blood), 0.5 SECONDS) + +/obj/projectile/hallucination/proc/clean_up_blood(image/blood) + parent.hallucinator.client?.images -= blood + +/// Called with a non-mob atom was hit by our fake projectile, or a mob was hit and our damge type is not brute +/obj/projectile/hallucination/proc/spawn_hit(atom/hit_atom, is_wall) + if(!parent.hallucinator.client) // Purely visual, don't need to do this for clientless mobs + return + + var/image/hit_effect = image('icons/effects/blood.dmi', hit_atom, is_wall ? hal_impact_effect_wall : hal_impact_effect, ABOVE_MOB_LAYER) + hit_effect.pixel_x = hit_atom.pixel_x + rand(-4,4) + hit_effect.pixel_y = hit_atom.pixel_y + rand(-4,4) + parent.hallucinator.client.images |= hit_effect + addtimer(CALLBACK(src, .proc/clean_up_hit, hit_effect), is_wall ? hit_duration_wall : hit_duration) + +/obj/projectile/hallucination/proc/clean_up_hit(image/hit_effect) + parent.hallucinator.client?.images -= hit_effect + +/obj/projectile/hallucination/bullet + name = "bullet" + hal_icon_state = "bullet" + hal_fire_sound = "gunshot" + hal_hitsound = 'sound/weapons/pierce.ogg' + hal_hitsound_wall = SFX_RICOCHET + hal_impact_effect = "impact_bullet" + hal_impact_effect_wall = "impact_bullet" + hit_duration = 5 + hit_duration_wall = 5 + +/obj/projectile/hallucination/bullet/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.adjustStaminaLoss(60) + +/obj/projectile/hallucination/laser + name = "laser" + damage_type = BURN + hal_icon_state = "laser" + hal_fire_sound = 'sound/weapons/laser.ogg' + hal_hitsound = 'sound/weapons/sear.ogg' + hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' + hal_impact_effect = "impact_laser" + hal_impact_effect_wall = "impact_laser_wall" + hit_duration = 4 + hit_duration_wall = 10 + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + + ricochets_max = 50 + ricochet_chance = 80 + reflectable = REFLECT_NORMAL // No idea if this works + +/obj/projectile/hallucination/laser/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.adjustStaminaLoss(20) + afflicted.blur_eyes(2) + +/obj/projectile/hallucination/taser + name = "electrode" + damage_type = BURN + hal_icon_state = "spark" + color = "#FFFF00" + hal_fire_sound = 'sound/weapons/taser.ogg' + hal_hitsound = 'sound/weapons/taserhit.ogg' + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/projectile/hallucination/taser/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.Paralyze(10 SECONDS) + afflicted.adjust_timed_status_effect(40 SECONDS, /datum/status_effect/speech/stutter) + if(HAS_TRAIT(afflicted, TRAIT_HULK)) + afflicted.say(pick( + ";RAAAAAAAARGH!", + ";HNNNNNNNNNGGGGGGH!", + ";GWAAAAAAAARRRHHH!", + "NNNNNNNNGGGGGGGGHH!", + ";AAAAAAARRRGH!"), + forced = "hulk (hallucinating)", + ) + else if((afflicted.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(afflicted, TRAIT_STUNIMMUNE)) + addtimer(CALLBACK(afflicted, /mob/living/carbon.proc/do_jitter_animation, 20), 0.5 SECONDS) + +/obj/projectile/hallucination/disabler + name = "disabler beam" + damage_type = STAMINA + hal_icon_state = "omnilaser" + hal_fire_sound = 'sound/weapons/taser2.ogg' + hal_hitsound = 'sound/weapons/tap.ogg' + hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' + hal_impact_effect = "impact_laser_blue" + hal_impact_effect_wall = null + hit_duration = 4 + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + + ricochets_max = 50 + ricochet_chance = 80 + reflectable = REFLECT_NORMAL // No idea if this works + +/obj/projectile/hallucination/disabler/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.adjustStaminaLoss(30) + +/obj/projectile/hallucination/ebow + name = "bolt" + damage_type = TOX + hal_icon_state = "cbbolt" + hal_fire_sound = 'sound/weapons/genhit.ogg' + hal_hitsound = null + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/projectile/hallucination/ebow/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.adjust_timed_status_effect(10 SECONDS, /datum/status_effect/speech/slurring) + afflicted.Knockdown(1 SECONDS) + afflicted.adjustStaminaLoss(75) // 60 stam + 15 tox + afflicted.blur_eyes(10) + +/obj/projectile/hallucination/change + name = "bolt of change" + damage_type = BURN + hal_icon_state = "ice_1" + hal_fire_sound = 'sound/magic/staff_change.ogg' + hal_hitsound = null + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/projectile/hallucination/change/apply_effect_to_hallucinator(mob/living/afflicted) + // Future idea: Make it so any other mob hit appear to be polymorphed to the hallucinator + afflicted.cause_hallucination( \ + get_random_valid_hallucination_subtype(/datum/hallucination/delusion/preset), \ + "fake [name]", \ + duration = 30 SECONDS, \ + affects_us = TRUE, \ + affects_others = FALSE, \ + skip_nearby = FALSE, \ + play_wabbajack = TRUE, \ + ) + +/obj/projectile/hallucination/death + name = "bolt of death" + damage_type = BURN + hal_icon_state = "pulse1_bl" + hal_fire_sound = 'sound/magic/wandodeath.ogg' + hal_hitsound = null + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/projectile/hallucination/death/apply_effect_to_hallucinator(mob/living/afflicted) + afflicted.cause_hallucination(/datum/hallucination/death, "fake [name]") diff --git a/code/modules/hallucination/xeno_attack.dm b/code/modules/hallucination/xeno_attack.dm new file mode 100644 index 00000000000..9cae5550467 --- /dev/null +++ b/code/modules/hallucination/xeno_attack.dm @@ -0,0 +1,103 @@ +/// Xeno crawls from nearby vent, jumps at you, and goes back in. +/datum/hallucination/xeno_attack + random_hallucination_weight = 2 + +/datum/hallucination/xeno_attack/start() + var/turf/xeno_attack_source + for(var/obj/machinery/atmospherics/components/unary/vent_pump/nearby_pump in orange(7, hallucinator)) + if(nearby_pump.welded) + continue + xeno_attack_source = get_turf(nearby_pump) + break + + if(!xeno_attack_source) + return FALSE + + feedback_details += "Vent Coords: ([xeno_attack_source.x], [xeno_attack_source.y], [xeno_attack_source.z])" + + var/obj/effect/client_image_holder/hallucination/xeno/fake_xeno = new(xeno_attack_source, hallucinator, src) + addtimer(CALLBACK(src, .proc/leap_at_target, fake_xeno, xeno_attack_source), 1 SECONDS) + return TRUE + +/// Leaps from the vent to the hallucinator. +/datum/hallucination/xeno_attack/proc/leap_at_target(obj/effect/client_image_holder/hallucination/xeno/fake_xeno, turf/attack_source) + if(QDELETED(src)) + return + if(QDELETED(fake_xeno)) + qdel(src) + return + + fake_xeno.set_leaping() + fake_xeno.throw_at(hallucinator, 7, 1, spin = FALSE, diagonals_first = TRUE) + addtimer(CALLBACK(src, .proc/leap_back_to_pump, fake_xeno), 1 SECONDS) + +/// Leaps from the hallucinator back to the vent. +/datum/hallucination/xeno_attack/proc/leap_back_to_pump(obj/effect/client_image_holder/hallucination/xeno/fake_xeno, turf/attack_source) + if(QDELETED(src)) + return + if(QDELETED(fake_xeno) || !attack_source) + qdel(src) + return + + fake_xeno.set_leaping() + fake_xeno.throw_at(attack_source, 7, 1, spin = FALSE, diagonals_first = TRUE) + addtimer(CALLBACK(src, .proc/begin_crawling, fake_xeno), 1 SECONDS) + +/// Mimics ventcrawling into the vent. +/datum/hallucination/xeno_attack/proc/begin_crawling(obj/effect/client_image_holder/hallucination/xeno/fake_xeno) + if(QDELETED(src)) + return + if(QDELETED(fake_xeno)) + qdel(src) + return + + to_chat(hallucinator, span_notice("[fake_xeno.name] begins climbing into the ventilation system...")) + addtimer(CALLBACK(src, .proc/disappear, fake_xeno), 3 SECONDS) + +/// Disappears into the vent, ending the hallucination. +/datum/hallucination/xeno_attack/proc/disappear(obj/effect/client_image_holder/hallucination/xeno/fake_xeno) + if(QDELETED(src)) + return + if(!QDELETED(fake_xeno)) + to_chat(hallucinator, span_notice("[fake_xeno.name] scrambles into the ventilation ducts!")) + + qdel(src) + +/// The xeno hallucination that goes with the xeno attack hallucination. +/obj/effect/client_image_holder/hallucination/xeno + image_icon = 'icons/mob/nonhuman-player/alien.dmi' + image_state = "alienh_pounce" + +/obj/effect/client_image_holder/hallucination/xeno/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent) + . = ..() + name = "alien hunter ([rand(1, 1000)])" + +// The hallucination "throws" us at the hallucinator, so whenever we impact, we're actually landing a "leap". +/obj/effect/client_image_holder/hallucination/xeno/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + set_unleaping() + if(!isliving(hit_atom)) + return + var/mob/living/hit_living = hit_atom + if(hit_living != parent.hallucinator || hit_living.stat == DEAD) + return + hit_living.Paralyze(10 SECONDS) + hit_living.visible_message( + span_warning("[hit_living] flails around wildly."), + span_userdanger("[name] pounces on you!"), + ) + +/// Sets our icon to look like we're leaping. +/obj/effect/client_image_holder/hallucination/xeno/proc/set_leaping() + image_icon = 'icons/mob/nonhuman-player/alienleap.dmi' + image_state = "alienh_leap" + image_pixel_x = -32 + image_pixel_y = -32 + update_appearance(UPDATE_ICON) + +/// Resets our icon to our initial state. +/obj/effect/client_image_holder/hallucination/xeno/proc/set_unleaping() + image_icon = initial(image_icon) + image_state = initial(image_state) + image_pixel_x = initial(image_pixel_x) + image_pixel_y = initial(image_pixel_y) + update_appearance(UPDATE_ICON) diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm index 20b9afa98aa..77de88aa9b6 100644 --- a/code/modules/mining/lavaland/megafauna_loot.dm +++ b/code/modules/mining/lavaland/megafauna_loot.dm @@ -278,8 +278,7 @@ var/mob/living/carbon/wearer = loc if(istype(wearer) && DT_PROB(1, delta_time)) //cursed by bubblegum if(prob(7.5)) - new /datum/hallucination/oh_yeah(wearer) - to_chat(wearer, span_colossus("[pick("I AM IMMORTAL.","I SHALL TAKE BACK WHAT'S MINE.","I SEE YOU.","YOU CANNOT ESCAPE ME FOREVER.","DEATH CANNOT HOLD ME.")]")) + wearer.cause_hallucination(/datum/hallucination/oh_yeah, "H.E.C.K suit", haunt_them = TRUE) else to_chat(wearer, span_warning("[pick("You hear faint whispers.","You smell ash.","You feel hot.","You hear a roar in the distance.")]")) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 316a420f0f7..092bde8d7fa 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -239,12 +239,6 @@ return TRUE return FALSE -/mob/living/carbon/hallucinating() - if(hallucination) - return TRUE - else - return FALSE - /mob/living/carbon/resist_buckle() if(HAS_TRAIT(src, TRAIT_RESTRAINED)) changeNext_move(CLICK_CD_BREAKOUT) @@ -749,29 +743,39 @@ clear_fullscreen("brute") /mob/living/carbon/update_health_hud(shown_health_amount) - if(!client || !hud_used) + if(!client || !hud_used?.healths) return - if(hud_used.healths) - if(stat != DEAD) - . = 1 - if(shown_health_amount == null) - shown_health_amount = health - if(shown_health_amount >= maxHealth) - hud_used.healths.icon_state = "health0" - else if(shown_health_amount > maxHealth*0.8) - hud_used.healths.icon_state = "health1" - else if(shown_health_amount > maxHealth*0.6) - hud_used.healths.icon_state = "health2" - else if(shown_health_amount > maxHealth*0.4) - hud_used.healths.icon_state = "health3" - else if(shown_health_amount > maxHealth*0.2) - hud_used.healths.icon_state = "health4" - else if(shown_health_amount > 0) - hud_used.healths.icon_state = "health5" - else - hud_used.healths.icon_state = "health6" - else - hud_used.healths.icon_state = "health7" + + if(stat == DEAD) + hud_used.healths.icon_state = "health7" + return + + if(SEND_SIGNAL(src, COMSIG_CARBON_UPDATING_HEALTH_HUD, shown_health_amount) & COMPONENT_OVERRIDE_HEALTH_HUD) + return + + if(shown_health_amount == null) + shown_health_amount = health + + if(shown_health_amount >= maxHealth) + hud_used.healths.icon_state = "health0" + + else if(shown_health_amount > maxHealth * 0.8) + hud_used.healths.icon_state = "health1" + + else if(shown_health_amount > maxHealth * 0.6) + hud_used.healths.icon_state = "health2" + + else if(shown_health_amount > maxHealth * 0.4) + hud_used.healths.icon_state = "health3" + + else if(shown_health_amount > maxHealth*0.2) + hud_used.healths.icon_state = "health4" + + else if(shown_health_amount > 0) + hud_used.healths.icon_state = "health5" + + else + hud_used.healths.icon_state = "health6" /mob/living/carbon/update_stamina_hud(shown_stamina_amount) if(!client || !hud_used?.stamina) @@ -1020,7 +1024,6 @@ VV_DROPDOWN_OPTION(VV_HK_MAKE_AI, "Make AI") VV_DROPDOWN_OPTION(VV_HK_MODIFY_BODYPART, "Modify bodypart") VV_DROPDOWN_OPTION(VV_HK_MODIFY_ORGANS, "Modify organs") - VV_DROPDOWN_OPTION(VV_HK_HALLUCINATION, "Hallucinate") VV_DROPDOWN_OPTION(VV_HK_MARTIAL_ART, "Give Martial Arts") VV_DROPDOWN_OPTION(VV_HK_GIVE_TRAUMA, "Give Brain Trauma") VV_DROPDOWN_OPTION(VV_HK_CURE_TRAUMA, "Cure Brain Traumas") @@ -1128,18 +1131,6 @@ cure_all_traumas(TRAUMA_RESILIENCE_ABSOLUTE) log_admin("[key_name(usr)] has cured all traumas from [key_name(src)].") message_admins(span_notice("[key_name_admin(usr)] has cured all traumas from [key_name_admin(src)].")) - if(href_list[VV_HK_HALLUCINATION]) - if(!check_rights(NONE)) - return - var/list/hallucinations = subtypesof(/datum/hallucination) - var/result = input(usr, "Choose the hallucination to apply","Send Hallucination") as null|anything in sort_list(hallucinations, /proc/cmp_typepaths_asc) - if(!usr) - return - if(QDELETED(src)) - to_chat(usr, "Mob doesn't exist anymore") - return - if(result) - new result(src, TRUE) /mob/living/carbon/can_resist() return bodyparts.len > 2 && ..() @@ -1147,7 +1138,7 @@ /mob/living/carbon/proc/hypnosis_vulnerable() if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) return FALSE - if(hallucinating()) + if(has_status_effect(/datum/status_effect/hallucination)) return TRUE if(IsSleeping()) return TRUE @@ -1325,9 +1316,6 @@ if(NAMEOF(src, disgust)) set_disgust(var_value) . = TRUE - if(NAMEOF(src, hal_screwyhud)) - set_screwyhud(var_value) - . = TRUE if(NAMEOF(src, handcuffed)) set_handcuffed(var_value) . = TRUE diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index fc55e3df6bd..409c4f633cc 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -85,9 +85,10 @@ var/list/icon_render_keys = list() var/static/list/limb_icon_cache = list() - //halucination vars - var/hal_screwyhud = SCREWYHUD_NONE - var/next_hallucination = 0 + /// Used to temporarily increase severity of / apply a new damage overlay (the red ring around the ui / screen). + /// This number will translate to equivalent brute or burn damage taken. Handled in [mob/living/proc/update_damage_hud]. + /// (For example, setting damageoverlaytemp = 20 will add 20 "damage" to the overlay the next time it updates.) + /// This number is also reset to 0 every tick of carbon Life(). Pain. var/damageoverlaytemp = 0 ///used to halt stamina regen temporarily diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index 1ed2a02a03b..a4791f313d2 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -69,7 +69,7 @@ var/temp = getBruteLoss() - if(!(user == src && src.hal_screwyhud == SCREWYHUD_HEALTHY)) //fake healthy + if(!(user == src && has_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy))) //fake healthy if(temp) if (temp < 25) msg += "[t_He] [t_has] minor bruising.\n" diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index c58615d3bbe..1800fd258ad 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -209,9 +209,9 @@ else if(l_limbs_missing >= 2 && r_limbs_missing >= 2) msg += "[t_He] [p_do()]n't seem all there.\n" - if(!(user == src && src.hal_screwyhud == SCREWYHUD_HEALTHY)) //fake healthy + if(!(user == src && has_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy))) //fake healthy var/temp - if(user == src && src.hal_screwyhud == SCREWYHUD_CRIT)//fake damage + if(user == src && has_status_effect(/datum/status_effect/grouped/screwy_hud/fake_crit))//fake damage temp = 50 else temp = getBruteLoss() diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index d8cb972a157..af2b76e0059 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -771,52 +771,46 @@ /mob/living/carbon/human/update_health_hud() if(!client || !hud_used) return - if(dna.species.update_health_hud()) + + // Updates the health bar, also sends signal + . = ..() + + // Updates the health doll + if(!hud_used.healthdoll) return - else - if(hud_used.healths) - if(..()) //not dead - switch(hal_screwyhud) - if(SCREWYHUD_CRIT) - hud_used.healths.icon_state = "health6" - if(SCREWYHUD_DEAD) - hud_used.healths.icon_state = "health7" - if(SCREWYHUD_HEALTHY) - hud_used.healths.icon_state = "health0" - if(hud_used.healthdoll) - hud_used.healthdoll.cut_overlays() - if(stat != DEAD) - hud_used.healthdoll.icon_state = "healthdoll_OVERLAY" - for(var/obj/item/bodypart/body_part as anything in bodyparts) - var/icon_num = 0 - //Hallucinations - if(body_part.type in hal_screwydoll) - icon_num = hal_screwydoll[body_part.type] - hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[body_part.body_zone][icon_num]")) - continue - //Not hallucinating - var/damage = body_part.burn_dam + body_part.brute_dam - var/comparison = (body_part.max_damage/5) - if(damage) - icon_num = 1 - if(damage > (comparison)) - icon_num = 2 - if(damage > (comparison*2)) - icon_num = 3 - if(damage > (comparison*3)) - icon_num = 4 - if(damage > (comparison*4)) - icon_num = 5 - if(hal_screwyhud == SCREWYHUD_HEALTHY) - icon_num = 0 - if(icon_num) - hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[body_part.body_zone][icon_num]")) - for(var/t in get_missing_limbs()) //Missing limbs - hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[t]6")) - for(var/t in get_disabled_limbs()) //Disabled limbs - hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[t]7")) - else - hud_used.healthdoll.icon_state = "healthdoll_DEAD" + + hud_used.healthdoll.cut_overlays() + if(stat == DEAD) + hud_used.healthdoll.icon_state = "healthdoll_DEAD" + return + + hud_used.healthdoll.icon_state = "healthdoll_OVERLAY" + for(var/obj/item/bodypart/body_part as anything in bodyparts) + var/icon_num = 0 + + if(SEND_SIGNAL(body_part, COMSIG_BODYPART_UPDATING_HEALTH_HUD, src) & COMPONENT_OVERRIDE_BODYPART_HEALTH_HUD) + continue + + var/damage = body_part.burn_dam + body_part.brute_dam + var/comparison = (body_part.max_damage/5) + if(damage) + icon_num = 1 + if(damage > (comparison)) + icon_num = 2 + if(damage > (comparison*2)) + icon_num = 3 + if(damage > (comparison*3)) + icon_num = 4 + if(damage > (comparison*4)) + icon_num = 5 + if(has_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy)) + icon_num = 0 + if(icon_num) + hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[body_part.body_zone][icon_num]")) + for(var/t in get_missing_limbs()) //Missing limbs + hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[t]6")) + for(var/t in get_disabled_limbs()) //Disabled limbs + hud_used.healthdoll.add_overlay(mutable_appearance('icons/hud/screen_gen.dmi', "[t]7")) /mob/living/carbon/human/fully_heal(admin_revive = FALSE) dna?.species.spec_fully_heal(src) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 4dc96ccfd8b..b23d11569cd 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -719,89 +719,16 @@ missing -= body_part.body_zone if(body_part.is_pseudopart) //don't show injury text for fake bodyparts; ie chainsaw arms or synthetic armblades continue - var/self_aware = FALSE - if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) - self_aware = TRUE - var/limb_max_damage = body_part.max_damage - var/status = "" - var/brutedamage = body_part.brute_dam - var/burndamage = body_part.burn_dam - if(hallucination) - if(prob(30)) - brutedamage += rand(30,40) - if(prob(30)) - burndamage += rand(30,40) - if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) - status = "[brutedamage] brute damage and [burndamage] burn damage" - if(!brutedamage && !burndamage) - status = "no damage" - - else - if(body_part.type in hal_screwydoll)//Are we halucinating? - brutedamage = (hal_screwydoll[body_part.type] * 0.2)*limb_max_damage - - if(brutedamage > 0) - status = body_part.light_brute_msg - if(brutedamage > (limb_max_damage*0.4)) - status = body_part.medium_brute_msg - if(brutedamage > (limb_max_damage*0.8)) - status = body_part.heavy_brute_msg - if(brutedamage > 0 && burndamage > 0) - status += " and " - - if(burndamage > (limb_max_damage*0.8)) - status += body_part.heavy_burn_msg - else if(burndamage > (limb_max_damage*0.2)) - status += body_part.medium_burn_msg - else if(burndamage > 0) - status += body_part.light_burn_msg - - if(status == "") - status = "OK" - var/no_damage - if(status == "OK" || status == "no damage") - no_damage = TRUE - var/isdisabled = "" - if(body_part.bodypart_disabled) - isdisabled = " is disabled" - if(no_damage) - isdisabled += " but otherwise" - else - isdisabled += " and" - combined_msg += "\t Your [body_part.name][isdisabled][self_aware ? " has " : " is "][status]." - - for(var/thing in body_part.wounds) - var/datum/wound/W = thing - var/msg - switch(W.severity) - if(WOUND_SEVERITY_TRIVIAL) - //msg = "\t Your [LB.name] is suffering [W.a_or_from] [lowertext(W.name)]." //ORIGINAL - msg = "\t Your [body_part.name] is suffering [W.a_or_from] [W.get_topic_name(src)]." //SKYRAT EDIT CHANGE - MEDICAL - if(WOUND_SEVERITY_MODERATE) - //msg = "\t Your [LB.name] is suffering [W.a_or_from] [lowertext(W.name)]!" //ORIGINAL - msg = "\t Your [body_part.name] is suffering [W.a_or_from] [W.get_topic_name(src)]!" //SKYRAT EDIT CHANGE - MEDICAL - if(WOUND_SEVERITY_SEVERE) - //msg = "\t Your [LB.name] is suffering [W.a_or_from] [lowertext(W.name)]!" //ORIGINAL - msg = "\t Your [body_part.name] is suffering [W.a_or_from] [W.get_topic_name(src)]!" //SKYRAT EDIT CHANGE - MEDICAL - if(WOUND_SEVERITY_CRITICAL) - //msg = "\t Your [LB.name] is suffering [W.a_or_from] [lowertext(W.name)]!!" //ORIGINAL - msg = "\t Your [body_part.name] is suffering [W.a_or_from] [W.get_topic_name(src)]!!" //SKYRAT EDIT CHANGE - MEDICAL - combined_msg += msg - - for(var/obj/item/I in body_part.embedded_objects) - if(I.isEmbedHarmless()) - combined_msg += "\t There is \a [I] stuck to your [body_part.name]!" - else - combined_msg += "\t There is \a [I] embedded in your [body_part.name]!" + body_part.check_for_injuries(src, combined_msg) //SKYRAT EDIT ADDITION BEGIN - MEDICAL if(body_part.current_gauze) var/datum/bodypart_aid/current_gauze = body_part.current_gauze - combined_msg += "\t Your [body_part.name] is [current_gauze.desc_prefix] with [current_gauze.get_description()]." + combined_msg += "\t [span_notice("Your [body_part.name] is [current_gauze.desc_prefix] with [current_gauze.get_description()].")]" if(body_part.current_splint) var/datum/bodypart_aid/current_splint = body_part.current_splint - combined_msg += "\t Your [body_part.name] is [current_splint.desc_prefix] with [current_splint.get_description()]." + combined_msg += "\t [span_notice("Your [body_part.name] is [current_splint.desc_prefix] with [current_splint.get_description()].")]" //SKYRAT EDIT END for(var/t in missing) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 0e6ffb5be01..0fc321a0715 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -88,7 +88,5 @@ ///Exposure to damaging heat levels increases stacks, stacks clean over time when temperatures are lower. Stack is consumed to add a wound. var/heat_exposure_stacks = 0 - ///human specific screwyhuds from hallucinations (define key (bodypart) to int value (severity)) - see /datum/hallucination/fake_health_doll - var/hal_screwydoll /// When an braindead player has their equipment fiddled with, we log that info here for when they come back so they know who took their ID while they were DC'd for 30 seconds var/list/afk_thefts diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 5a9fad65c90..8d00016bd76 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -1031,7 +1031,13 @@ GLOBAL_LIST_EMPTY(features_by_species) /datum/species/proc/pre_equip_species_outfit(datum/job/job, mob/living/carbon/human/equipping, visuals_only = FALSE) return - +/** + * Handling special reagent types. + * + * Return False to run the normal on_mob_life() for that reagent. + * Return True to not run the normal metabolism effects. + * NOTE: If you return TRUE, that reagent will not be removed liike normal! You must handle it manually. + */ /datum/species/proc/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H, delta_time, times_fired) if(chem.type == exotic_blood) H.blood_volume = min(H.blood_volume + round(chem.volume, 0.1), BLOOD_VOLUME_MAXIMUM) @@ -1055,9 +1061,6 @@ GLOBAL_LIST_EMPTY(features_by_species) outfit_important_for_life= new() outfit_important_for_life.equip(human_to_equip) -/datum/species/proc/update_health_hud(mob/living/carbon/human/H) - return FALSE - /** * Species based handling for irradiation * diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index 1a98954eb41..ae4014f3976 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -138,6 +138,7 @@ var/datum/wound/iter_wound = i iter_wound.on_xadone(4 * REAGENTS_EFFECT_MULTIPLIER * delta_time) // plasmamen use plasma to reform their bones or whatever return TRUE + if(istype(chem, /datum/reagent/toxin/bonehurtingjuice)) H.adjustStaminaLoss(7.5 * REAGENTS_EFFECT_MULTIPLIER * delta_time, 0) H.adjustBruteLoss(0.5 * REAGENTS_EFFECT_MULTIPLIER * delta_time, 0) @@ -164,6 +165,13 @@ H.reagents.remove_reagent(chem.type, chem.metabolization_rate * delta_time) return TRUE + if(istype(chem, /datum/reagent/gunpowder)) + H.set_timed_status_effect(15 SECONDS * delta_time, /datum/status_effect/drugginess) + if(H.get_timed_status_effect_duration(/datum/status_effect/hallucination) / 10 < chem.volume) + H.adjust_hallucinations(2.5 SECONDS * delta_time) + // Do normal metabolism + return FALSE + /datum/species/plasmaman/get_species_description() return "Found on the Icemoon of Freyja, plasmamen consist of colonial \ fungal organisms which together form a sentient being. In human space, \ diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 3b9f99d1b84..663c7eab70a 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -275,9 +275,9 @@ if(breath_gases[/datum/gas/bz]) var/bz_partialpressure = (breath_gases[/datum/gas/bz][MOLES]/breath.total_moles())*breath_pressure if(bz_partialpressure > 1) - hallucination += 10 + adjust_hallucinations(20 SECONDS) else if(bz_partialpressure > 0.01) - hallucination += 5 + adjust_hallucinations(10 SECONDS) //NITRIUM if(breath_gases[/datum/gas/nitrium]) @@ -449,9 +449,6 @@ if(silent) silent = max(silent - (0.5 * delta_time), 0) - if(hallucination) - handle_hallucinations(delta_time, times_fired) - /// Base carbon environment handler, adds natural stabilization /mob/living/carbon/handle_environment(datum/gas_mixture/environment, delta_time, times_fired) var/areatemp = get_temperature(environment) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 377d517811c..5dee26a66c3 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -879,7 +879,6 @@ cure_nearsighted() cure_blind() cure_husk() - hallucination = 0 heal_overall_damage(INFINITY, INFINITY, INFINITY, null, TRUE) //heal brute and burn dmg on both organic and robotic limbs, and update health right away. extinguish_mob() set_drowsyness(0) @@ -1802,6 +1801,8 @@ GLOBAL_LIST_EMPTY(fire_appearances) VV_DROPDOWN_OPTION(VV_HK_GIVE_SPEECH_IMPEDIMENT, "Impede Speech (Slurring, stuttering, etc)") VV_DROPDOWN_OPTION(VV_HK_ADD_MOOD, "Add Mood Event") VV_DROPDOWN_OPTION(VV_HK_REMOVE_MOOD, "Remove Mood Event") + VV_DROPDOWN_OPTION(VV_HK_GIVE_HALLUCINATION, "Give Hallucination") + VV_DROPDOWN_OPTION(VV_HK_GIVE_DELUSION_HALLUCINATION, "Give Delusion Hallucination") /mob/living/vv_do_topic(list/href_list) . = ..() @@ -1815,6 +1816,16 @@ GLOBAL_LIST_EMPTY(fire_appearances) if (href_list[VV_HK_REMOVE_MOOD]) admin_remove_mood_event(usr) + if(href_list[VV_HK_GIVE_HALLUCINATION]) + if(!check_rights(NONE)) + return + admin_give_hallucination(usr) + + if(href_list[VV_HK_GIVE_DELUSION_HALLUCINATION]) + if(!check_rights(NONE)) + return + admin_give_delusion(usr) + /mob/living/proc/move_to_error_room() var/obj/effect/landmark/error/error_landmark = locate(/obj/effect/landmark/error) in GLOB.landmarks_list if(error_landmark) @@ -2404,3 +2415,34 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/played_game() . = ..() add_mood_event("gaming", /datum/mood_event/gaming) + +/// Admin only proc for making the mob hallucinate a certain thing +/mob/living/proc/admin_give_hallucination(mob/admin) + if(!admin || !check_rights(NONE)) + return + + var/chosen = select_hallucination_type(admin, "What hallucination do you want to give to [src]?", "Give Hallucination") + if(!chosen || QDELETED(src) || !check_rights(NONE)) + return + + if(!cause_hallucination(chosen, "admin forced by [key_name_admin(admin)]")) + to_chat(admin, "That hallucination ([chosen]) could not be run - it may be invalid with this type of mob or has no effects.") + return + + message_admins("[key_name_admin(admin)] gave [ADMIN_LOOKUPFLW(src)] a hallucination. (Type: [chosen])") + log_admin("[key_name(admin)] gave [src] a hallucination. (Type: [chosen])") + +/// Admin only proc for giving the mob a delusion hallucination with specific arguments +/mob/living/proc/admin_give_delusion(mob/admin) + if(!admin || !check_rights(NONE)) + return + + var/list/delusion_args = create_delusion(admin) + if(QDELETED(src) || !check_rights(NONE) || !length(delusion_args)) + return + + delusion_args[2] = "admin forced" + message_admins("[key_name_admin(admin)] gave [ADMIN_LOOKUPFLW(src)] a delusion hallucination. (Type: [delusion_args[1]])") + log_admin("[key_name(admin)] gave [src] a delusion hallucination. (Type: [delusion_args[1]])") + // Not using the wrapper here because we already have a list / arglist + _cause_hallucination(delusion_args) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 43e92675367..6a2ba37dedd 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -63,10 +63,12 @@ diag_hud_set_health() add_sensors() ADD_TRAIT(src, TRAIT_ADVANCEDTOOLUSER, ROUNDSTART_TRAIT) - ADD_TRAIT(src, TRAIT_MARTIAL_ARTS_IMMUNE, ROUNDSTART_TRAIT) - ADD_TRAIT(src, TRAIT_NOFIRE_SPREAD, ROUNDSTART_TRAIT) - ADD_TRAIT(src, TRAIT_ASHSTORM_IMMUNE, ROUNDSTART_TRAIT) ADD_TRAIT(src, TRAIT_LITERATE, ROUNDSTART_TRAIT) + ADD_TRAIT(src, TRAIT_NOFIRE_SPREAD, ROUNDSTART_TRAIT) + + ADD_TRAIT(src, TRAIT_ASHSTORM_IMMUNE, ROUNDSTART_TRAIT) + ADD_TRAIT(src, TRAIT_MADNESS_IMMUNE, ROUNDSTART_TRAIT) + ADD_TRAIT(src, TRAIT_MARTIAL_ARTS_IMMUNE, ROUNDSTART_TRAIT) /mob/living/silicon/Destroy() QDEL_NULL(radio) diff --git a/code/modules/mob/living/simple_animal/guardian/types/fire.dm b/code/modules/mob/living/simple_animal/guardian/types/fire.dm index fa53d7208ea..44a357e467b 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/fire.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/fire.dm @@ -29,8 +29,24 @@ /mob/living/simple_animal/hostile/guardian/fire/AttackingTarget() . = ..() - if(. && ishuman(target) && target != summoner) - new /datum/hallucination/delusion(target,TRUE,"custom",200,0, icon_state,icon) + if(!.) + return + if(!isliving(target)) + return + if(target == summoner) + return + var/mob/living/living_target = target + living_target.cause_hallucination( \ + /datum/hallucination/delusion/custom, \ + "fire holoparasite ([key_name(src)], owned by [key_name(summoner)])", \ + duration = 20 SECONDS, \ + affects_us = TRUE, \ + affects_others = TRUE, \ + skip_nearby = FALSE, \ + play_wabbajack = FALSE, \ + custom_icon_file = icon, \ + custom_icon_state = icon_state, \ + ) /mob/living/simple_animal/hostile/guardian/fire/proc/on_entered(datum/source, AM as mob|obj) SIGNAL_HANDLER diff --git a/code/modules/mob/living/taste.dm b/code/modules/mob/living/taste.dm index 95702e6ae69..a4597b2ff67 100644 --- a/code/modules/mob/living/taste.dm +++ b/code/modules/mob/living/taste.dm @@ -32,7 +32,7 @@ var/text_output = from.generate_taste_message(src, taste_sensitivity) // We dont want to spam the same message over and over again at the // person. Give it a bit of a buffer. - if(hallucination > 50 && prob(25)) + if(get_timed_status_effect_duration(/datum/status_effect/hallucination) > 100 SECONDS && prob(25)) text_output = pick("spiders","dreams","nightmares","the future","the past","victory",\ "defeat","pain","bliss","revenge","poison","time","space","death","life","truth","lies","justice","memory",\ "regrets","your soul","suffering","music","noise","blood","hunger","the american way") diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 135293a4083..6809c5ec875 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -211,11 +211,6 @@ SHOULD_BE_PURE(TRUE) return eye_blind ? TRUE : HAS_TRAIT(src, TRAIT_BLIND) -///Is the mob hallucinating? -/mob/proc/hallucinating() - return FALSE - - // moved out of admins.dm because things other than admin procs were calling this. /// Returns TRUE if the game has started and we're either an AI with a 0th law, or we're someone with a special role/antag datum /proc/is_special_character(mob/M) diff --git a/code/modules/plumbing/plumbers/synthesizer.dm b/code/modules/plumbing/plumbers/synthesizer.dm index 433535d47aa..d3625e10148 100644 --- a/code/modules/plumbing/plumbers/synthesizer.dm +++ b/code/modules/plumbing/plumbers/synthesizer.dm @@ -62,7 +62,10 @@ /obj/machinery/plumbing/synthesizer/ui_data(mob/user) var/list/data = list() - var/is_hallucinating = user.hallucinating() + var/is_hallucinating = FALSE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) var/list/chemicals = list() for(var/A in dispensable_reagents) diff --git a/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm b/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm index 2345d809baf..07b5142b93b 100644 --- a/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm +++ b/code/modules/power/supermatter/supermatter_delamination/delamination_effects.dm @@ -23,10 +23,10 @@ continue if(victim.z == 0) continue - if(ishuman(victim)) - //Hilariously enough, running into a closet should make you get hit the hardest. - var/mob/living/carbon/human/human = victim - human.hallucination += max(50, min(300, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(victim, sm) + 1)) ) ) + + //Hilariously enough, running into a closet should make you get hit the hardest. + var/hallucination_amount = max(100 SECONDS, min(600 SECONDS, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(victim, src) + 1)))) + victim.adjust_hallucinations(hallucination_amount) for(var/mob/victim as anything in GLOB.player_list) var/turf/victim_turf = get_turf(victim) diff --git a/code/modules/power/supermatter/supermatter_process.dm b/code/modules/power/supermatter/supermatter_process.dm index 504cefed5b0..4a360557b7a 100644 --- a/code/modules/power/supermatter/supermatter_process.dm +++ b/code/modules/power/supermatter/supermatter_process.dm @@ -257,24 +257,17 @@ // no supermatter soothers are nearby. var/psy_coeff_diff = -0.05 for(var/mob/living/carbon/human/seen_by_sm in view(src, HALLUCINATION_RANGE(power))) - // Someone (generally a Psychologist), when looking at the SM - // within hallucination range makes it easier to manage. + // Someone (generally a Psychologist), when looking at the SM within hallucination range makes it easier to manage. if(HAS_TRAIT(seen_by_sm, TRAIT_SUPERMATTER_SOOTHER) || (seen_by_sm.mind && HAS_TRAIT(seen_by_sm.mind, TRAIT_SUPERMATTER_SOOTHER))) psy_coeff_diff = 0.05 psy_overlay = TRUE - // If they are immune to supermatter hallucinations. - if (HAS_TRAIT(seen_by_sm, TRAIT_MADNESS_IMMUNE) || (seen_by_sm.mind && HAS_TRAIT(seen_by_sm.mind, TRAIT_MADNESS_IMMUNE))) - continue - - // Blind people don't get supermatter hallucinations. - if (seen_by_sm.is_blind()) - continue - - // Everyone else gets hallucinations. - var/dist = sqrt(1 / max(1, get_dist(seen_by_sm, src))) - seen_by_sm.hallucination += power * hallucination_power * dist - seen_by_sm.hallucination = clamp(seen_by_sm.hallucination, 0, 200) + visible_hallucination_pulse( + center = src, + radius = HALLUCINATION_RANGE(power), + hallucination_duration = power * hallucination_power, + hallucination_max_duration = 400 SECONDS, + ) psyCoeff = clamp(psyCoeff + psy_coeff_diff, 0, 1) /obj/machinery/power/supermatter_crystal/proc/handle_high_power(datum/gas_mixture/removed) diff --git a/code/modules/projectiles/projectile/special/hallucination.dm b/code/modules/projectiles/projectile/special/hallucination.dm deleted file mode 100644 index d2978ead3dd..00000000000 --- a/code/modules/projectiles/projectile/special/hallucination.dm +++ /dev/null @@ -1,230 +0,0 @@ -/obj/projectile/hallucination - name = "bullet" - icon = null - icon_state = null - hitsound = "" - suppressed = TRUE - ricochets_max = 0 - ricochet_chance = 0 - damage = 0 - nodamage = TRUE - projectile_type = /obj/projectile/hallucination - log_override = TRUE - var/hal_icon_state - var/image/fake_icon - var/mob/living/carbon/hal_target - var/hal_fire_sound - var/hal_hitsound - var/hal_hitsound_wall - var/hal_impact_effect - var/hal_impact_effect_wall - var/hit_duration - var/hit_duration_wall - -/obj/projectile/hallucination/fire() - ..() - fake_icon = image('icons/obj/weapons/guns/projectiles.dmi', src, hal_icon_state, ABOVE_MOB_LAYER) - if(hal_target.client) - hal_target.client.images += fake_icon - -/obj/projectile/hallucination/Destroy() - if(hal_target?.client) - hal_target.client.images -= fake_icon - QDEL_NULL(fake_icon) - return ..() - -/obj/projectile/hallucination/Bump(atom/A) - if(!ismob(A)) - if(hal_hitsound_wall) - hal_target.playsound_local(loc, hal_hitsound_wall, 40, 1) - if(hal_impact_effect_wall) - spawn_hit(A, TRUE) - else if(A == hal_target) - if(hal_hitsound) - hal_target.playsound_local(A, hal_hitsound, 100, 1) - target_on_hit(A) - qdel(src) - return TRUE - -/obj/projectile/hallucination/proc/target_on_hit(mob/M) - if(M == hal_target) - to_chat(hal_target, span_userdanger("[M] is hit by \a [src] in the chest!")) - hal_apply_effect() - else if(M in view(hal_target)) - to_chat(hal_target, span_danger("[M] is hit by \a [src] in the chest!!")) - if(damage_type == BRUTE) - var/splatter_dir = dir - if(starting) - splatter_dir = get_dir(starting, get_turf(M)) - spawn_blood(M, splatter_dir) - else if(hal_impact_effect) - spawn_hit(M, FALSE) - -/obj/projectile/hallucination/proc/spawn_blood(mob/M, set_dir) - set waitfor = 0 - if(!hal_target.client) - return - - var/splatter_icon_state - if(ISDIAGONALDIR(set_dir)) - splatter_icon_state = "splatter[pick(1, 2, 6)]" - else - splatter_icon_state = "splatter[pick(3, 4, 5)]" - - var/image/blood = image('icons/effects/blood.dmi', M, splatter_icon_state, ABOVE_MOB_LAYER) - var/target_pixel_x = 0 - var/target_pixel_y = 0 - switch(set_dir) - if(NORTH) - target_pixel_y = 16 - if(SOUTH) - target_pixel_y = -16 - layer = ABOVE_MOB_LAYER - if(EAST) - target_pixel_x = 16 - if(WEST) - target_pixel_x = -16 - if(NORTHEAST) - target_pixel_x = 16 - target_pixel_y = 16 - if(NORTHWEST) - target_pixel_x = -16 - target_pixel_y = 16 - if(SOUTHEAST) - target_pixel_x = 16 - target_pixel_y = -16 - layer = ABOVE_MOB_LAYER - if(SOUTHWEST) - target_pixel_x = -16 - target_pixel_y = -16 - layer = ABOVE_MOB_LAYER - hal_target.client.images += blood - animate(blood, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = 5) - addtimer(CALLBACK(src, .proc/cleanup_blood), 5) - -/obj/projectile/hallucination/proc/cleanup_blood(image/blood) - hal_target.client.images -= blood - qdel(blood) - -/obj/projectile/hallucination/proc/spawn_hit(atom/A, is_wall) - set waitfor = 0 - if(!hal_target.client) - return - - var/image/hit_effect = image('icons/effects/blood.dmi', A, is_wall ? hal_impact_effect_wall : hal_impact_effect, ABOVE_MOB_LAYER) - hit_effect.pixel_x = A.pixel_x + rand(-4,4) - hit_effect.pixel_y = A.pixel_y + rand(-4,4) - hal_target.client.images += hit_effect - sleep(is_wall ? hit_duration_wall : hit_duration) - hal_target.client.images -= hit_effect - qdel(hit_effect) - - -/obj/projectile/hallucination/proc/hal_apply_effect() - return - -/obj/projectile/hallucination/bullet - name = "bullet" - hal_icon_state = "bullet" - hal_fire_sound = "gunshot" - hal_hitsound = 'sound/weapons/pierce.ogg' - hal_hitsound_wall = SFX_RICOCHET - hal_impact_effect = "impact_bullet" - hal_impact_effect_wall = "impact_bullet" - hit_duration = 5 - hit_duration_wall = 5 - -/obj/projectile/hallucination/bullet/hal_apply_effect() - hal_target.adjustStaminaLoss(60) - -/obj/projectile/hallucination/laser - name = "laser" - damage_type = BURN - hal_icon_state = "laser" - hal_fire_sound = 'sound/weapons/laser.ogg' - hal_hitsound = 'sound/weapons/sear.ogg' - hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' - hal_impact_effect = "impact_laser" - hal_impact_effect_wall = "impact_laser_wall" - hit_duration = 4 - hit_duration_wall = 10 - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - -/obj/projectile/hallucination/laser/hal_apply_effect() - hal_target.adjustStaminaLoss(20) - hal_target.blur_eyes(2) - -/obj/projectile/hallucination/taser - name = "electrode" - damage_type = BURN - hal_icon_state = "spark" - color = "#FFFF00" - hal_fire_sound = 'sound/weapons/taser.ogg' - hal_hitsound = 'sound/weapons/taserhit.ogg' - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/projectile/hallucination/taser/hal_apply_effect() - hal_target.Paralyze(100) - hal_target.adjust_timed_status_effect(40 SECONDS, /datum/status_effect/speech/stutter) - if(hal_target.dna && hal_target.dna.check_mutation(/datum/mutation/human/hulk)) - hal_target.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") - else if((hal_target.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(hal_target, TRAIT_STUNIMMUNE)) - addtimer(CALLBACK(hal_target, /mob/living/carbon.proc/do_jitter_animation, 20), 5) - -/obj/projectile/hallucination/disabler - name = "disabler beam" - damage_type = STAMINA - hal_icon_state = "omnilaser" - hal_fire_sound = 'sound/weapons/taser2.ogg' - hal_hitsound = 'sound/weapons/tap.ogg' - hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' - hal_impact_effect = "impact_laser_blue" - hal_impact_effect_wall = null - hit_duration = 4 - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - -/obj/projectile/hallucination/disabler/hal_apply_effect() - hal_target.adjustStaminaLoss(25) - -/obj/projectile/hallucination/ebow - name = "bolt" - damage_type = TOX - hal_icon_state = "cbbolt" - hal_fire_sound = 'sound/weapons/genhit.ogg' - hal_hitsound = null - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/projectile/hallucination/ebow/hal_apply_effect() - hal_target.Paralyze(100) - hal_target.adjust_timed_status_effect(10 SECONDS, /datum/status_effect/speech/stutter) - hal_target.adjustStaminaLoss(8) - -/obj/projectile/hallucination/change - name = "bolt of change" - damage_type = BURN - hal_icon_state = "ice_1" - hal_fire_sound = 'sound/magic/staff_change.ogg' - hal_hitsound = null - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/projectile/hallucination/change/hal_apply_effect() - new /datum/hallucination/self_delusion(hal_target, TRUE, wabbajack = FALSE) - -/obj/projectile/hallucination/death - name = "bolt of death" - damage_type = BURN - hal_icon_state = "pulse1_bl" - hal_fire_sound = 'sound/magic/wandodeath.ogg' - hal_hitsound = null - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/projectile/hallucination/death/hal_apply_effect() - new /datum/hallucination/death(hal_target, TRUE) diff --git a/code/modules/projectiles/projectile/special/mindflayer.dm b/code/modules/projectiles/projectile/special/mindflayer.dm index d91735cb199..54889bbced1 100644 --- a/code/modules/projectiles/projectile/special/mindflayer.dm +++ b/code/modules/projectiles/projectile/special/mindflayer.dm @@ -4,6 +4,6 @@ /obj/projectile/beam/mindflayer/on_hit(atom/target, blocked = FALSE) . = ..() if(ishuman(target)) - var/mob/living/carbon/human/M = target - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20) - M.hallucination += 30 + var/mob/living/carbon/human/human_hit = target + human_hit.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20) + human_hit.adjust_hallucinations(60 SECONDS) diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index 651805e661c..ff2f4b6a214 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -756,7 +756,7 @@ //SKYRAT EDIT ADDITION END if(owner && reagent) - if(!owner.reagent_check(reagent, delta_time, times_fired) != TRUE) + if(owner.reagent_check(reagent, delta_time, times_fired)) return if(liverless && !reagent.self_consuming) //need to be metabolized return diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index adad03be13b..f2a4ff5fb00 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -231,8 +231,15 @@ ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "ChemDispenser", name) - if(user.hallucinating()) + + var/is_hallucinating = FALSE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) + + if(is_hallucinating) ui.set_autoupdate(FALSE) //to not ruin the immersion by constantly changing the fake chemicals + ui.open() /obj/machinery/chem_dispenser/ui_data(mob/user) @@ -264,8 +271,10 @@ var/chemicals[0] var/is_hallucinating = FALSE - if(user.hallucinating()) - is_hallucinating = TRUE + if(isliving(user)) + var/mob/living/living_user = user + is_hallucinating = !!living_user.has_status_effect(/datum/status_effect/hallucination) + for(var/re in dispensable_reagents) var/datum/reagent/temp = GLOB.chemical_reagents_list[re] if(temp) diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index 4fe8285cf66..6d9aeead883 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -204,12 +204,11 @@ All effects don't start immediately, but rather get worse over time; the rate is taste_description = "pancake syrup" glass_name = "glass of candy corn liquor" glass_desc = "Good for your Imagination." - var/hal_amt = 4 chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/whiskey/candycorn/on_mob_life(mob/living/carbon/drinker, delta_time, times_fired) if(DT_PROB(5, delta_time)) - drinker.hallucination += hal_amt //conscious dreamers can be treasurers to their own currency + drinker.adjust_hallucinations(4 SECONDS * REM * delta_time) ..() /datum/reagent/consumable/ethanol/thirteenloko @@ -468,7 +467,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/absinthe/on_mob_life(mob/living/carbon/drinker, delta_time, times_fired) if(DT_PROB(5, delta_time) && !HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) - drinker.hallucination += 4 //Reference to the urban myth + drinker.adjust_hallucinations(8 SECONDS) ..() /datum/reagent/consumable/ethanol/hooch @@ -802,13 +801,14 @@ All effects don't start immediately, but rather get worse over time; the rate is var/obj/item/organ/internal/liver/liver = drinker.getorganslot(ORGAN_SLOT_LIVER) // if you have a liver and that liver is an officer's liver if(liver && HAS_TRAIT(liver, TRAIT_LAW_ENFORCEMENT_METABOLISM)) + . = TRUE drinker.adjustStaminaLoss(-10 * REM * delta_time, 0) if(DT_PROB(10, delta_time)) - new /datum/hallucination/items_other(drinker) + drinker.cause_hallucination(get_random_valid_hallucination_subtype(/datum/hallucination/nearby_fake_item), name) if(DT_PROB(5, delta_time)) - new /datum/hallucination/stray_bullet(drinker) + drinker.cause_hallucination(/datum/hallucination/stray_bullet, name) + ..() - . = TRUE /datum/reagent/consumable/ethanol/beepsky_smash/on_mob_end_metabolize(mob/living/carbon/drinker) if(beepsky_hallucination) @@ -2648,10 +2648,17 @@ All effects don't start immediately, but rather get worse over time; the rate is chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/drunken_espatier/on_mob_life(mob/living/carbon/drinker, delta_time, times_fired) - drinker.hal_screwyhud = SCREWYHUD_HEALTHY //almost makes you forget how much it hurts drinker.add_mood_event("numb", /datum/mood_event/narcotic_medium, name) //comfortably numb ..() +/datum/reagent/consumable/ethanol/drunken_espatier/on_mob_metabolize(mob/living/drinker) + . = ..() + drinker.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + +/datum/reagent/consumable/ethanol/drunken_espatier/on_mob_end_metabolize(mob/living/drinker) + . = ..() + drinker.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + /datum/reagent/consumable/ethanol/protein_blend name = "Protein Blend" description = "A vile blend of protein, pure grain alcohol, korta flour, and blood. Useful for bulking up, if you can keep it down." @@ -2896,8 +2903,9 @@ All effects don't start immediately, but rather get worse over time; the rate is chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/consumable/ethanol/helianthus/on_mob_life(mob/living/carbon/drinker, delta_time, times_fired) - if(drinker.hallucination < hal_cap && DT_PROB(5, delta_time)) - drinker.hallucination += hal_amt + if(DT_PROB(5, delta_time)) + drinker.adjust_hallucinations_up_to(4 SECONDS * REM * delta_time, 48 SECONDS) + ..() /datum/reagent/consumable/ethanol/plumwine @@ -2929,7 +2937,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/gin_garden name = "Gin Garden" description = "Excellent cooling alcoholic drink with not so ordinary taste." - color = "#6cd87a" + color = "#6cd87a" taste_description = "light gin with sweet ginger and cucumber" glass_icon_state = "gin_garden" glass_name = "gin garden" diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 263fa75918b..eaf564dd4cf 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -30,8 +30,9 @@ M.add_mood_event("[type]_overdose", /datum/mood_event/overdose, name) /datum/reagent/drug/space_drugs/overdose_process(mob/living/M, delta_time, times_fired) - if(M.hallucination < volume && DT_PROB(10, delta_time)) - M.hallucination += 5 + var/hallucination_duration_in_seconds = (M.get_timed_status_effect_duration(/datum/status_effect/hallucination) / 10) + if(hallucination_duration_in_seconds < volume && DT_PROB(10, delta_time)) + M.adjust_hallucinations(10 SECONDS) ..() /datum/reagent/drug/cannabis @@ -220,7 +221,7 @@ M.add_mood_event("salted", /datum/mood_event/stimulant_heavy, name) M.adjustStaminaLoss(-5 * REM * delta_time, 0) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 4 * REM * delta_time) - M.hallucination += 5 * REM * delta_time + M.adjust_hallucinations(10 SECONDS * REM * delta_time) if(!HAS_TRAIT(M, TRAIT_IMMOBILIZED) && !ismovable(M.loc)) step(M, pick(GLOB.cardinals)) step(M, pick(GLOB.cardinals)) @@ -228,7 +229,7 @@ . = TRUE /datum/reagent/drug/bath_salts/overdose_process(mob/living/M, delta_time, times_fired) - M.hallucination += 5 * REM * delta_time + M.adjust_hallucinations(10 SECONDS * REM * delta_time) if(!HAS_TRAIT(M, TRAIT_IMMOBILIZED) && !ismovable(M.loc)) for(var/i in 1 to round(8 * REM * delta_time, 1)) step(M, pick(GLOB.cardinals)) diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index f4427298ed1..3064328249c 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -87,7 +87,7 @@ for(var/effect in typesof(/datum/status_effect/speech)) M.remove_status_effect(effect) M.remove_status_effect(/datum/status_effect/jitter) - M.hallucination = 0 + M.remove_status_effect(/datum/status_effect/hallucination) REMOVE_TRAITS_NOT_IN(M, list(SPECIES_TRAIT, ROUNDSTART_TRAIT, ORGAN_TRAIT)) M.reagents.remove_all_type(/datum/reagent/toxin, 5 * REM * delta_time, FALSE, TRUE) if(M.blood_volume < BLOOD_VOLUME_NORMAL) @@ -125,7 +125,7 @@ M.AdjustParalyzed(-20 * REM * delta_time) if(holder.has_reagent(/datum/reagent/toxin/mindbreaker)) holder.remove_reagent(/datum/reagent/toxin/mindbreaker, 5 * REM * delta_time) - M.hallucination = max(M.hallucination - (10 * REM * delta_time), 0) + M.adjust_hallucinations(-20 SECONDS * REM * delta_time) if(DT_PROB(16, delta_time)) M.adjustToxLoss(1, 0) . = TRUE @@ -144,7 +144,7 @@ holder.remove_reagent(/datum/reagent/toxin/mindbreaker, 5 * REM * delta_time) if(holder.has_reagent(/datum/reagent/toxin/histamine)) holder.remove_reagent(/datum/reagent/toxin/histamine, 5 * REM * delta_time) - M.hallucination = max(M.hallucination - (10 * REM * delta_time), 0) + M.adjust_hallucinations(-20 SECONDS * REM * delta_time) if(DT_PROB(16, delta_time)) M.adjustToxLoss(1, 0) . = TRUE @@ -360,7 +360,6 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED /datum/reagent/medicine/mine_salve/on_mob_life(mob/living/carbon/C, delta_time, times_fired) - C.hal_screwyhud = SCREWYHUD_HEALTHY C.adjustBruteLoss(-0.25 * REM * delta_time, 0) C.adjustFireLoss(-0.25 * REM * delta_time, 0) ..() @@ -385,13 +384,15 @@ if(show_message) to_chat(exposed_carbon, span_danger("You feel your injuries fade away to nothing!") ) -/datum/reagent/medicine/mine_salve/on_mob_end_metabolize(mob/living/M) - REMOVE_TRAIT(M, TRAIT_NUMBED, src) // SKYRAT EDIT ADD -- ANAESTHETIC FOR SURGERY PAIN - M.clear_alert("numbed") // SKYRAT EDIT ADD END - if(iscarbon(M)) - var/mob/living/carbon/N = M - N.hal_screwyhud = SCREWYHUD_NONE - ..() +/datum/reagent/medicine/mine_salve/on_mob_metabolize(mob/living/metabolizer) + . = ..() + metabolizer.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + +/datum/reagent/medicine/mine_salve/on_mob_end_metabolize(mob/living/metabolizer) + . = ..() + REMOVE_TRAIT(metabolizer, TRAIT_NUMBED, src) // SKYRAT EDIT ADD -- ANAESTHETIC FOR SURGERY PAIN + metabolizer.clear_alert("numbed") // SKYRAT EDIT ADD END + metabolizer.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) /datum/reagent/medicine/omnizine name = "Omnizine" @@ -1174,7 +1175,7 @@ ..() /datum/reagent/medicine/earthsblood/overdose_process(mob/living/M, delta_time, times_fired) - M.hallucination = clamp(M.hallucination + (5 * REM * delta_time), 0, 60) + M.adjust_hallucinations_up_to(10 SECONDS * REM * delta_time, 120 SECONDS) if(current_cycle > 25) M.adjustToxLoss(4 * REM * delta_time, 0) if(current_cycle > 100) //podpeople get out reeeeeeeeeeeeeeeeeeeee @@ -1203,8 +1204,9 @@ if(M.get_timed_status_effect_duration(/datum/status_effect/jitter) >= 6 SECONDS) M.adjust_timed_status_effect(-6 SECONDS * REM * delta_time, /datum/status_effect/jitter) - if (M.hallucination >= 5) - M.hallucination -= 5 * REM * delta_time + if (M.get_timed_status_effect_duration(/datum/status_effect/hallucination) >= 10 SECONDS) + M.adjust_hallucinations(-10 SECONDS * REM * delta_time) + if(DT_PROB(10, delta_time)) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 50) M.adjustStaminaLoss(2.5 * REM * delta_time, 0) @@ -1407,7 +1409,7 @@ . = TRUE /datum/reagent/medicine/psicodine/overdose_process(mob/living/M, delta_time, times_fired) - M.hallucination = clamp(M.hallucination + (5 * REM * delta_time), 0, 60) + M.adjust_hallucinations_up_to(10 SECONDS * REM * delta_time, 120 SECONDS) M.adjustToxLoss(1 * REM * delta_time, 0) ..() . = TRUE diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index 3ed55f3d988..41eb088e823 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -112,15 +112,6 @@ UnregisterSignal(holder.my_atom, COMSIG_ATOM_EX_ACT) return ..() -/datum/reagent/gunpowder/on_mob_life(mob/living/carbon/M, delta_time, times_fired) - . = TRUE - ..() - if(!isplasmaman(M)) - return - M.set_timed_status_effect(30 SECONDS * REM * delta_time, /datum/status_effect/drugginess) - if(M.hallucination < volume) - M.hallucination += 5 * REM * delta_time - /datum/reagent/gunpowder/proc/on_ex_act(atom/source, severity, target) SIGNAL_HANDLER if(source.flags_1 & PREVENT_CONTENTS_EXPLOSION_1) diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index a9bb78ee430..ecb0f88f97d 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -306,11 +306,23 @@ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED addiction_types = list(/datum/addiction/hallucinogens = 18) //7.2 per 2 seconds -/datum/reagent/toxin/mindbreaker/on_mob_life(mob/living/carbon/M, delta_time, times_fired) - if(HAS_TRAIT(M, TRAIT_INSANITY)) - M.hallucination = 0 + +/datum/reagent/toxin/mindbreaker/on_mob_metabolize(mob/living/metabolizer) + . = ..() + ADD_TRAIT(metabolizer, TRAIT_HALLUCINATION_SUPPRESSED, type) + +/datum/reagent/toxin/mindbreaker/on_mob_end_metabolize(mob/living/metabolizer) + . = ..() + REMOVE_TRAIT(metabolizer, TRAIT_HALLUCINATION_SUPPRESSED, type) + +/datum/reagent/toxin/mindbreaker/on_mob_life(mob/living/carbon/metabolizer, delta_time, times_fired) + // mindbreaker toxin assuages hallucinations in those plagued with it, mentally + if(metabolizer.has_trauma_type(/datum/brain_trauma/mild/hallucinations)) + metabolizer.remove_status_effect(/datum/status_effect/hallucination) + + // otherwise it creates hallucinations. truly a miracle medicine. else - M.hallucination += 5 * REM * delta_time + metabolizer.adjust_hallucinations(10 SECONDS * REM * delta_time) return ..() diff --git a/code/modules/reagents/withdrawal/generic_addictions.dm b/code/modules/reagents/withdrawal/generic_addictions.dm index c2f57a0e6de..1399facdd7c 100644 --- a/code/modules/reagents/withdrawal/generic_addictions.dm +++ b/code/modules/reagents/withdrawal/generic_addictions.dm @@ -58,12 +58,12 @@ /datum/addiction/alcohol/withdrawal_stage_2_process(mob/living/carbon/affected_carbon, delta_time) . = ..() affected_carbon.set_timed_status_effect(20 SECONDS * delta_time, /datum/status_effect/jitter, only_if_higher = TRUE) - affected_carbon.hallucination = max(5 SECONDS, affected_carbon.hallucination) + affected_carbon.set_hallucinations_if_lower(10 SECONDS) /datum/addiction/alcohol/withdrawal_stage_3_process(mob/living/carbon/affected_carbon, delta_time) . = ..() affected_carbon.set_timed_status_effect(30 SECONDS * delta_time, /datum/status_effect/jitter, only_if_higher = TRUE) - affected_carbon.hallucination = max(5 SECONDS, affected_carbon.hallucination) + affected_carbon.set_hallucinations_if_lower(10 SECONDS) if(DT_PROB(4, delta_time)) if(!HAS_TRAIT(affected_carbon, TRAIT_ANTICONVULSANT)) affected_carbon.apply_status_effect(/datum/status_effect/seizure) @@ -96,7 +96,7 @@ /datum/addiction/maintenance_drugs/withdrawal_enters_stage_1(mob/living/carbon/affected_carbon) . = ..() - affected_carbon.hal_screwyhud = SCREWYHUD_HEALTHY + affected_carbon.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) /datum/addiction/maintenance_drugs/withdrawal_stage_1_process(mob/living/carbon/affected_carbon, delta_time) . = ..() @@ -143,7 +143,7 @@ /datum/addiction/maintenance_drugs/end_withdrawal(mob/living/carbon/affected_carbon) . = ..() - affected_carbon.hal_screwyhud = SCREWYHUD_NONE + affected_carbon.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) if(!ishuman(affected_carbon)) return var/mob/living/carbon/human/affected_human = affected_carbon @@ -158,15 +158,24 @@ /datum/addiction/medicine name = "medicine" withdrawal_stage_messages = list("", "", "") - var/datum/hallucination/fake_alert/hallucination - var/datum/hallucination/fake_health_doll/hallucination2 + /// Weakref to the "fake alert" hallucination we're giving to the addicted + var/datum/weakref/fake_alert_ref + /// Weakref to the "health doll screwup" hallucination we're giving to the addicted + var/datum/weakref/health_doll_ref /datum/addiction/medicine/withdrawal_enters_stage_1(mob/living/carbon/affected_carbon) . = ..() if(!ishuman(affected_carbon)) return - var/mob/living/carbon/human/human_mob = affected_carbon - hallucination2 = new(human_mob, TRUE, severity = 1, duration = 120 MINUTES) + var/datum/hallucination/health_doll = affected_carbon.cause_hallucination( \ + /datum/hallucination/fake_health_doll, \ + "medicine addiction", \ + severity = 1, \ + duration = 120 MINUTES, \ + ) + if(!health_doll) + return + health_doll_ref = WEAKREF(health_doll) /datum/addiction/medicine/withdrawal_stage_1_process(mob/living/carbon/affected_carbon, delta_time) . = ..() @@ -176,41 +185,64 @@ /datum/addiction/medicine/withdrawal_enters_stage_2(mob/living/carbon/affected_carbon) . = ..() var/list/possibilities = list() + if(!HAS_TRAIT(affected_carbon, TRAIT_RESISTHEAT)) - possibilities += ALERT_TEMPERATURE_HOT + possibilities += /datum/hallucination/fake_alert/hot if(!HAS_TRAIT(affected_carbon, TRAIT_RESISTCOLD)) - possibilities += ALERT_TEMPERATURE_COLD + possibilities += /datum/hallucination/fake_alert/cold + var/obj/item/organ/internal/lungs/lungs = affected_carbon.getorganslot(ORGAN_SLOT_LUNGS) if(lungs) if(lungs.safe_oxygen_min) - possibilities += ALERT_NOT_ENOUGH_OXYGEN + possibilities += /datum/hallucination/fake_alert/need_oxygen if(lungs.safe_oxygen_max) - possibilities += ALERT_TOO_MUCH_OXYGEN - var/type = pick(possibilities) - hallucination = new(affected_carbon, TRUE, type, 120 MINUTES)//last for a while basically + possibilities += /datum/hallucination/fake_alert/bad_oxygen + + if(!length(possibilities)) + return + + var/datum/hallucination/fake_alert = affected_carbon.cause_hallucination( \ + pick(possibilities), \ + "medicine addiction", \ + duration = 120 MINUTES, \ + ) + if(!fake_alert) + return + fake_alert_ref = WEAKREF(fake_alert) /datum/addiction/medicine/withdrawal_stage_2_process(mob/living/carbon/affected_carbon, delta_time) . = ..() - if(DT_PROB(10, delta_time)) - hallucination2.add_fake_limb(severity = 1) + var/datum/hallucination/fake_health_doll/hallucination = health_doll_ref?.resolve() + if(QDELETED(hallucination)) + health_doll_ref = null return + + if(DT_PROB(10, delta_time)) + hallucination.add_fake_limb(severity = 1) + return + if(DT_PROB(5, delta_time)) - hallucination2.increment_fake_damage() + hallucination.increment_fake_damage() + return /datum/addiction/medicine/withdrawal_enters_stage_3(mob/living/carbon/affected_carbon) . = ..() - affected_carbon.hal_screwyhud = SCREWYHUD_CRIT + affected_carbon.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_crit, type) /datum/addiction/medicine/withdrawal_stage_3_process(mob/living/carbon/affected_carbon, delta_time) . = ..() - if(DT_PROB(5, delta_time)) - hallucination2.increment_fake_damage() + var/datum/hallucination/fake_health_doll/hallucination = health_doll_ref?.resolve() + if(!QDELETED(hallucination) && DT_PROB(5, delta_time)) + hallucination.increment_fake_damage() return + if(DT_PROB(15, delta_time)) affected_carbon.emote("cough") return + if(DT_PROB(65, delta_time)) return + if(affected_carbon.stat >= SOFT_CRIT) return @@ -218,16 +250,18 @@ if(organ.low_threshold) to_chat(affected_carbon, organ.low_threshold_passed) return + else if (organ.high_threshold_passed) to_chat(affected_carbon, organ.high_threshold_passed) return + to_chat(affected_carbon, span_warning("You feel a dull pain in your [organ.name].")) /datum/addiction/medicine/end_withdrawal(mob/living/carbon/affected_carbon) . = ..() - affected_carbon.hal_screwyhud = SCREWYHUD_NONE - hallucination.cleanup() - QDEL_NULL(hallucination2) + affected_carbon.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_crit, type) + QDEL_NULL(fake_alert_ref) + QDEL_NULL(health_doll_ref) /* SKYRAT CHANGE, MOVED TO MODULAR. ///Nicotine diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 45e85a53b9c..e340e440a7f 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -213,6 +213,84 @@ if(locate(/datum/wound/burn) in wounds) . += span_warning("The flesh on this limb appears badly cooked.") +/** + * Called when a bodypart is checked for injuries. + * + * Modifies the check_list list with the resulting report of the limb's status. + */ +/obj/item/bodypart/proc/check_for_injuries(mob/living/carbon/human/examiner, list/check_list) + + var/list/limb_damage = list(BRUTE = brute_dam, BURN = burn_dam) + + SEND_SIGNAL(src, COMSIG_BODYPART_CHECKED_FOR_INJURY, examiner, check_list, limb_damage) + SEND_SIGNAL(examiner, COMSIG_CARBON_CHECKING_BODYPART, src, check_list, limb_damage) + + var/shown_brute = limb_damage[BRUTE] + var/shown_burn = limb_damage[BURN] + var/status = "" + var/self_aware = HAS_TRAIT(examiner, TRAIT_SELF_AWARE) + + if(self_aware) + if(!shown_brute && !shown_burn) + status = "no damage" + else + status = "[shown_brute] brute damage and [shown_burn] burn damage" + + else + if(shown_brute > (max_damage * 0.8)) + status += heavy_brute_msg + else if(shown_brute > (max_damage * 0.4)) + status += medium_brute_msg + else if(shown_brute > DAMAGE_PRECISION) + status += light_brute_msg + + if(shown_brute > DAMAGE_PRECISION && shown_burn > DAMAGE_PRECISION) + status += " and " + + if(shown_burn > (max_damage * 0.8)) + status += heavy_burn_msg + else if(shown_burn > (max_damage * 0.2)) + status += medium_burn_msg + else if(shown_burn > DAMAGE_PRECISION) + status += light_burn_msg + + if(status == "") + status = "OK" + + var/no_damage + if(status == "OK" || status == "no damage") + no_damage = TRUE + + var/is_disabled = "" + if(bodypart_disabled) + is_disabled = " is disabled" + if(no_damage) + is_disabled += " but otherwise" + else + is_disabled += " and" + + check_list += "\t Your [name][is_disabled][self_aware ? " has " : " is "][status]." + + for(var/datum/wound/wound as anything in wounds) + switch(wound.severity) + if(WOUND_SEVERITY_TRIVIAL) + // check_list += "\t [span_danger("Your [name] is suffering [wound.a_or_from] [lowertext(wound.name)].")]" // SKYRAT EDIT - Medical overhaul-ish - ORIGINAL + check_list += "\t [span_danger("Your [name] is suffering [wound.a_or_from] [wound.get_topic_name(owner)].")]" // SKYRAT EDIT - Medical overhaul-ish + if(WOUND_SEVERITY_MODERATE) + // check_list += "\t [span_warning("Your [name] is suffering [wound.a_or_from] [lowertext(wound.name)]!")]" // SKYRAT EDIT - Medical overhaul-ish - ORIGINAL + check_list += "\t [span_warning("Your [name] is suffering [wound.a_or_from] [wound.get_topic_name(owner)]!")]" // SKYRAT EDIT - Medical overhaul-ish + if(WOUND_SEVERITY_SEVERE) + // check_list += "\t [span_boldwarning("Your [name] is suffering [wound.a_or_from] [lowertext(wound.name)]!")]" // SKYRAT EDIT - Medical overhaul-ish - ORIGINAL + check_list += "\t [span_boldwarning("Your [name] is suffering [wound.a_or_from] [wound.get_topic_name(owner)]!")]" // SKYRAT EDIT - Medical overhaul-ish + if(WOUND_SEVERITY_CRITICAL) + // check_list += "\t [span_boldwarning("Your [name] is suffering [wound.a_or_from] [lowertext(wound.name)]!!")]" // SKYRAT EDIT - Medical overhaul-ish - ORIGINAL + check_list += "\t [span_boldwarning("Your [name] is suffering [wound.a_or_from] [wound.get_topic_name(owner)]!!")]" // SKYRAT EDIT - Medical overhaul-ish + + for(var/obj/item/embedded_thing in embedded_objects) + var/stuck_word = embedded_thing.isEmbedHarmless() ? "stuck" : "embedded" + check_list += "\t There is \a [embedded_thing] [stuck_word] in your [name]!" + + /obj/item/bodypart/blob_act() receive_damage(max_damage, wound_bonus = CANT_WOUND) diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 2975325fd62..fa7adac4a92 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -89,6 +89,7 @@ var/atom/drop_loc = owner.drop_location() SEND_SIGNAL(owner, COMSIG_CARBON_REMOVE_LIMB, src, dismembered) + SEND_SIGNAL(src, COMSIG_BODYPART_REMOVED, owner, dismembered) update_limb(1) owner.remove_bodypart(src) diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index ea25d7f8502..963605f82c5 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -295,7 +295,7 @@ var/bz_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/bz][MOLES]) if(bz_pp > BZ_trip_balls_min) - breather.hallucination += 10 + breather.adjust_hallucinations(20 SECONDS) breather.reagents.add_reagent(/datum/reagent/bz_metabolites,5) if(bz_pp > BZ_brain_damage_min && prob(33)) breather.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 3ba428b31b8..4f0298c36cc 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -100,6 +100,7 @@ #include "gas_transfer.dm" #include "get_turf_pixel.dm" #include "greyscale_config.dm" +#include "hallucination_icons.dm" #include "heretic_knowledge.dm" #include "heretic_rituals.dm" #include "holidays.dm" diff --git a/code/modules/unit_tests/create_and_destroy.dm b/code/modules/unit_tests/create_and_destroy.dm index ae93e3bdb35..1f9a48fe487 100644 --- a/code/modules/unit_tests/create_and_destroy.dm +++ b/code/modules/unit_tests/create_and_destroy.dm @@ -43,8 +43,11 @@ ignore += typesof(/obj/item/poster/wanted) //This expects a seed, we can't pass it ignore += typesof(/obj/item/food/grown) - //Nothing to hallucinate if there's nothing to hallicinate - ignore += typesof(/obj/effect/hallucination) + //Needs clients / mobs to observe it to exist. Also includes hallucinations. + ignore += typesof(/obj/effect/client_image_holder) + //Same to above. Needs a client / mob / hallucination to observe it to exist. + ignore += typesof(/obj/projectile/hallucination) + ignore += typesof(/obj/item/hallucinated) //These want fried food to take on the shape of, we can't pass that in ignore += typesof(/obj/item/food/deepfryholder) //Can't pass in a thing to glow diff --git a/code/modules/unit_tests/hallucination_icons.dm b/code/modules/unit_tests/hallucination_icons.dm new file mode 100644 index 00000000000..337af74edae --- /dev/null +++ b/code/modules/unit_tests/hallucination_icons.dm @@ -0,0 +1,70 @@ +/** + * Unit tests various image related hallucinations + * that their icon_states and icons still exist, + * as often hallucinations are copy and pasted + * implementations of existing image setups + * that may be changed and not updated. + */ +/datum/unit_test/hallucination_icons + +/datum/unit_test/hallucination_icons/Run() + + // Test nearby_fake_item hallucinations for invalid image setups + for(var/datum/hallucination/nearby_fake_item/hallucination as anything in subtypesof(/datum/hallucination/nearby_fake_item)) + var/left_icon = initial(hallucination.left_hand_file) + var/right_icon = initial(hallucination.right_hand_file) + var/icon_state = initial(hallucination.image_icon_state) + check_hallucination_icon(hallucination, left_icon, icon_state) + check_hallucination_icon(hallucination, right_icon, icon_state) + + // Test preset delusion hallucinations for invalid image setups + for(var/datum/hallucination/delusion/preset/hallucination as anything in subtypesof(/datum/hallucination/delusion/preset)) + var/icon = initial(hallucination.delusion_icon_file) + var/icon_state = initial(hallucination.delusion_icon_state) + check_hallucination_icon(hallucination, icon, icon_state) + + // Test fake body hallucinations + for(var/datum/hallucination/body/husk/hallucination as anything in subtypesof(/datum/hallucination/body/husk)) + var/icon = initial(hallucination.body_image_file) + var/icon_state = initial(hallucination.body_image_state) + check_hallucination_icon(hallucination, icon, icon_state) + + // Test on_fire hallucination for if the fire icon state exists + var/datum/hallucination/fire/fire_hallucination = /datum/hallucination/fire + var/fire_hallucination_icon = initial(fire_hallucination.fire_icon) + var/fire_hallucination_icon_state = initial(fire_hallucination.fire_icon_state) + check_hallucination_icon(fire_hallucination, fire_hallucination_icon, fire_hallucination_icon_state) + + // Test shock hallucination for if the shock icon state exists + var/datum/hallucination/shock/shock_hallucination = /datum/hallucination/shock + var/shock_hallucination_icon = initial(shock_hallucination.electrocution_icon) + var/shock_hallucination_icon_state = initial(shock_hallucination.electrocution_icon_state) + check_hallucination_icon(shock_hallucination, shock_hallucination_icon, shock_hallucination_icon_state) + + // Test fake_flood hallucination for if its fake plasmaflood icon exists + var/datum/hallucination/fake_flood/flood_hallucination = /datum/hallucination/fake_flood + var/flood_hallucination_icon = initial(flood_hallucination.image_icon) + var/flood_hallucination_icon_state = initial(flood_hallucination.image_state) + check_hallucination_icon(flood_hallucination, flood_hallucination_icon, flood_hallucination_icon_state) + + // Test hallucination client_image_holders that are used for various hallucinations (bubblegum, xeno attack) + for(var/obj/effect/client_image_holder/hallucination/image_holder as anything in subtypesof(/obj/effect/client_image_holder/hallucination)) + var/icon = initial(image_holder.image_icon) + var/icon_state = initial(image_holder.image_state) + if(!icon_state || !icon) + // Not having an icon_state or icon set by default is okay, for these. + continue + + if(icon_exists(icon, icon_state)) + continue + + Fail("Hallucination image holder [image_holder] had an invalid / missing icon state for the icon [icon].") + +/datum/unit_test/hallucination_icons/proc/check_hallucination_icon(hallucination, icon, icon_state) + if(!icon) + Fail("Hallucination [hallucination] forgot to set its icon file.") + if(!icon_state) + Fail("Hallucination [hallucination] forgot to set an icon state.") + if(!icon || !icon_state || icon_exists(icon, icon_state)) + return + Fail("Hallucination [hallucination] has an invalid icon_state ([icon_state]) for its icon ([icon]).") diff --git a/modular_skyrat/modules/cortical_borer/code/cortical_borer.dm b/modular_skyrat/modules/cortical_borer/code/cortical_borer.dm index e0a52dcaa42..bf41832e6c1 100644 --- a/modular_skyrat/modules/cortical_borer/code/cortical_borer.dm +++ b/modular_skyrat/modules/cortical_borer/code/cortical_borer.dm @@ -48,7 +48,7 @@ GLOBAL_LIST_EMPTY(cortical_borers) . = ..() for(var/datum/borer_focus/body_focus as anything in borer.body_focuses) body_focus.on_add() - carbon_target.hal_screwyhud = SCREWYHUD_HEALTHY + carbon_target.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) //on removal, force the borer out /obj/item/organ/internal/borer_body/Remove(mob/living/carbon/carbon_target, special) @@ -58,7 +58,7 @@ GLOBAL_LIST_EMPTY(cortical_borers) body_focus.on_remove() if(cb_inside) cb_inside.leave_host() - carbon_target.hal_screwyhud = SCREWYHUD_NONE + carbon_target.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) qdel(src) /obj/item/reagent_containers/borer @@ -254,8 +254,7 @@ GLOBAL_LIST_EMPTY(cortical_borers) if(prob(5) && human_host.getToxLoss() <= 80) human_host.adjustToxLoss(5, TRUE, TRUE) - if(human_host.hal_screwyhud != SCREWYHUD_HEALTHY) - human_host.hal_screwyhud = SCREWYHUD_HEALTHY + human_host.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) //cant do anything if the host has sugar if(host_sugar()) diff --git a/modular_skyrat/modules/cortical_borer/code/cortical_borer_abilities.dm b/modular_skyrat/modules/cortical_borer/code/cortical_borer_abilities.dm index 574b8df127f..3d647aacedb 100644 --- a/modular_skyrat/modules/cortical_borer/code/cortical_borer_abilities.dm +++ b/modular_skyrat/modules/cortical_borer/code/cortical_borer_abilities.dm @@ -409,7 +409,7 @@ if (HAS_TRAIT(cortical_owner.human_host, TRAIT_IRRADIATED)) render_list += "Subject is irradiated. Supply toxin healing.\n" - if(cortical_owner.human_host.hallucinating()) + if(cortical_owner.human_host.has_status_effect(/datum/status_effect/hallucination)) render_list += "Subject is hallucinating.\n" //Eyes and ears diff --git a/modular_skyrat/modules/morenarcotics/code/opium.dm b/modular_skyrat/modules/morenarcotics/code/opium.dm index cccd1ddb3eb..eb9831e6cbd 100644 --- a/modular_skyrat/modules/morenarcotics/code/opium.dm +++ b/modular_skyrat/modules/morenarcotics/code/opium.dm @@ -113,23 +113,23 @@ M.add_mood_event("smacked out", /datum/mood_event/narcotic_heavy, name) M.adjustBruteLoss(-0.1 * REM * delta_time, 0) //can be used as a (shitty) painkiller M.adjustFireLoss(-0.1 * REM * delta_time, 0) - M.hal_screwyhud = SCREWYHUD_HEALTHY M.overlay_fullscreen("heroin_euphoria", /atom/movable/screen/fullscreen/color_vision/heroin_color) - ..() + return ..() /datum/reagent/drug/opium/overdose_process(mob/living/M, delta_time, times_fired) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5 * REM * delta_time) M.adjustToxLoss(1 * REM * delta_time, 0) M.adjust_drowsyness(0.5 * REM * normalise_creation_purity() * delta_time) - ..() - . = TRUE + return TRUE -/datum/reagent/drug/opium/on_mob_end_metabolize(mob/living/M) - if(iscarbon(M)) - var/mob/living/carbon/N = M - N.hal_screwyhud = SCREWYHUD_NONE - N.clear_fullscreen("heroin_euphoria") - ..() +/datum/reagent/drug/opium/on_mob_metabolize(mob/living/metabolizer) + . = ..() + metabolizer.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + +/datum/reagent/drug/opium/on_mob_end_metabolize(mob/living/metabolizer) + . = ..() + metabolizer.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + metabolizer.clear_fullscreen("heroin_euphoria") /datum/reagent/drug/opium/heroin name = "heroin" @@ -164,9 +164,10 @@ var/high_message = pick("You feel like tar.", "The blood in your veins feel like syrup.") if(DT_PROB(2.5, delta_time)) to_chat(M, span_notice("[high_message]")) + M.set_timed_status_effect(20 SECONDS * REM * delta_time, /datum/status_effect/drugginess) M.adjustToxLoss(0.5 * REM * delta_time, 0) //toxin damage - ..() + return ..() /datum/reagent/drug/opium/blacktar/liquid //prevents self-duplication by going one step down when mixed name = "liquid black tar heroin" diff --git a/strings/hallucination.json b/strings/hallucination.json index 0ef2eee8c2d..5cfdae87794 100644 --- a/strings/hallucination.json +++ b/strings/hallucination.json @@ -81,6 +81,7 @@ "help": [ "HELP HER", "HELP HIM", + "HELP MAINT", "HELP ME", "HELP THEM", "HELP US", @@ -88,8 +89,8 @@ "HELP", "HELP", "HELP", - "HELP", - "HELP" + "Help", + "Help" ], "escape": [ @@ -111,41 +112,50 @@ "people": [ "%TARGETNAME%", "AI", + "Borg", "Captain", "Ce", "Cmo", + "Geneticist", "Hop", "Hos", "Janitor", "Qm", "Rd", + "Robo", "Viro" ], "accusations": [ "a changeling", - "a clock cultist", "a cultist", "a gang leader", - "a gangster", + "a heretic", "a ling", "a rev", "a revhead", "a tator", "a traitor", - "clockcult", + "a wizard", + "bad", "cult", "dead", - "rogue" + "mindswapped", + "rogue", + "the traitor", + "the wizard" ], "threat": [ "%TARGETNAME%", "Blob", "Blue APC", + "Changeling", "Cult", "Harm", "Help", + "Heretics", + "Heretic", "I hear flashing", "Ling", "Ops", diff --git a/strings/tips.txt b/strings/tips.txt index 7c166102ef7..94890f283ff 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -164,40 +164,6 @@ As the Chaplain, your null rod has anti magic functions: it can destroy cultist As the Chief Engineer, the power flow control console in your office will show you APC infos and lets you control them remotely. As the Chief Engineer, you can rename areas or create entirely new ones using your station blueprints. As the Chief Engineer, you can spy on and even forge PDA communications with the message monitor console! The key is in your office. -<<<<<<< HEAD -As the Chief Engineer, the power flow control console in your office will show you APC infos and lets you control them remotely. -As an Engineer, the supermatter shard is an extremely dangerous piece of equipment: touching it will disintegrate you. So will touching it with telepathy. -As an Engineer, you can electrify grilles by placing powered cables beneath them. -As an Engineer, return to Engineering once in a while to check on the engine and SMES cells. It's always a good idea to make sure the supermatter isn't delaminating. -As an Engineer, you can temporarily power the station solely with the solar arrays. They will provide just enough electricity to power the station for a while, however their output is just slightly below what is truly needed to consistently power the station. -As an Engineer, you can cool the supermatter crystal by spraying it with a fire extinguisher. Only for the brave! -As an Engineer, you can repair windows by using a welding tool on them while not in combat mode. -As an Engineer, you can lock APCs, fire alarms, emitters and radiation collectors using your ID card to prevent others from disabling them. -As an Engineer, your departmental protolathe and circuit printer can manufacture the necessary circuit boards and components to build just about anything. Make extra medical machinery everywhere! Build a gibber for security! Set up an array of emitters pointing down the hall! The possibilities are endless! -As an Engineer, you can pry open secure storage blast doors by disabling the engine room APC's main breaker. This is obviously a bad idea if the engine is running. -As an Engineer, your RCD can be reloaded with iron, glass or plasteel sheets instead of just compressed matter cartridges. -As an Engineer, the Supermatter Monitoring Program on modular computers give you a detailed report on the Supermatter's condition, and the contents of the air inside of the chamber, allowing you to both diagnose and monitor programs from afar! -As an Atmospheric Technician, you can unwrench a pipe regardless of the pressures of the gases inside, but if they're too high they can burst out and injure you! -As an Atmospheric Technician, look into replacing your gas pumps with volumetric gas pumps, as those move air in flat numerical amounts, rather than percentages which leave trace gases. -As an Atmospheric Technician, you are better suited to fighting fires than anyone else. As such, you have access to better firesuits or backpack firefighter tanks. -As an Atmospheric Technician, your backpack firefighter tank can launch resin. This resin will extinguish fires and replace any gases with a safe, room-temperature airmix. -As an Atmospheric Technician, your ATMOS holofan projector blocks gases while allowing objects to pass through. With it, you can quickly contain gas spills, fires and hull breaches. -As an Atmospheric Technician, burning a plasma/oxygen mix inside the incinerator will not only produce power, but also gases such as tritium, water vapor and carbon dioxide. -As an Atmospheric Technician, you can change the layer of a pipe by clicking with it on a wrenched pipe or other atmos component of the desired layer. -As an Atmospheric Technician, pay attention to gas pipe meters. The meter will change color in response to temperature and the bar will increase and get thicker in response to higher pressures. -As the Head of Security, you are expected to coordinate your security force to handle any threat that comes to the station. Sometimes it means making use of the armory to handle a blob, sometimes it means being ruthless during a revolution or cult. -As the Head of Security, you can call for executions or forced cyborgization, which require the Captain's or the Prisoner's consent, respectively. -As the Head of Security, don't let the power go to your head. You may have high access, great equipment, and a miniature army at your side, but being a terrible person without a good reason is grounds for banning. -As the Warden, your duty is to be the watchdog of the brig and handler of prisoners when little is happening, and to hand out equipment and weapons to the security officers when a crisis strikes. -As the Warden, keep a close eye on the armory at all times, as it is a favored strike point of nuclear operatives and cocky traitors. -As the Warden, if a prisoner's crimes are heinous enough you can put them in permabrig or the gulag. Make sure to check on them once in a while! -As the Warden, you can implant criminals you suspect might re-offend with devices that will track their location and allow you to remotely inject them with disabling chemicals. -As the Warden, you can use handcuffs on orange prisoner shoes to turn them into cuffed shoes, forcing prisoners to walk and potentially thwarting an escape. -As a Security Officer, communicate and coordinate with your fellow officers using the security channel (:s) to avoid confusion. -As a Security Officer, your sechuds or HUDsunglasses can not only see crewmates' job assignments and criminal status, but also if they are mindshield implanted. You can tell by the flashing blue outline around their job icon. Use this to your advantage in a revolution to definitively tell who is on your side! -As a Security Officer, mindshield implants can only prevent someone from being turned into a cultist: unlike revolutionaries, it will not de-cult them if they have already been converted. -As a Security Officer, examining someone while wearing sechuds or HUDsunglasses will let you set their arrest level, which will cause Beepsky and other security bots to chase after them. -======= As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and paramedics during a nuclear emergency, blob infestation, or some other crisis to keep people alive and fighting. As the Chief Medical Officer, your hypospray is like a refillable instant injection syringe that can hold 30 units as opposed to the standard 15. As the Clown, eating bananas heals you slightly. Honk! @@ -208,14 +174,13 @@ As the Clown, you can use your stamp on a sheet of cardboard as the first step o As the Clown, your Grail is the mineral bananium, which can be given to the Roboticist to build you a fun and robust mech beloved by everyone. As the Curator, be sure to keep the shelves stocked and the library clean for crew. As the Curator, you are not completely defenseless. Your whip easily disarms people, your laser pointer can blind humans and cyborgs, and you can hide items in wirecut books. ->>>>>>> fcaa0b24db0 (Alphabetized, fixed spelling error, clarifying event descriptions. Polish (#69707)) As the Detective, people leave fingerprints everywhere and on everything. With the exception of white latex, gloves will hide them. All is not lost, however, as gloves leave fibers specific to their kind such as black or nitrile, pointing to a general department. As the Detective, you can use your forensics scanner from a distance. Use this to scan boxes or other storage containers. As the Detective, your revolver can be loaded with .357 ammunition obtained from a hacked autolathe. Firing it has a decent chance to blow up your revolver. As the Head of Personnel, you are not higher ranking than other heads of staff, even though you are expected to take the Captain's place first should he go missing. If the situation seems too rough for you, consider allowing another head to become temporary Captain. As the Head of Security, don't let the power go to your head. You may have high access, great equipment, and a miniature army at your side, but being a terrible person without a good reason is grounds for banning. As the Head of Security, you are expected to coordinate your security force to handle any threat that comes to the station. Sometimes it means making use of the armory to handle a blob, sometimes it means being ruthless during a revolution or cult. -As the Head of Security, you can call for executions or forced cyborgization, but may require the Captain's approval. +As the Head of Security, you can call for executions or forced cyborgization, which require the Captain's or the Prisoner's consent, respectively. As the Lawyer, try to negotiate with the Warden if sentences seem too high for the crime. As the Lawyer, you can try to convince the Captain and Head of Security to hold trials for prisoners in the courtroom. As the Malfunctioning AI, look into flooding the station with plasma fires to kill off large portions of the crew, letting you pick off the remaining few with space suits who escaped. @@ -237,7 +202,7 @@ As the Warden, you can use handcuffs on orange prisoner shoes to turn them into As the Warden, your duty is to be the watchdog of the brig and handler of prisoners when little is happening, and to hand out equipment and weapons to the security officers when a crisis strikes. Clicking on a windoor rather then bumping into it will keep it open, you can click it again to close it. Different weapons have different strengths. Some weapons, such as spears, floor tiles, and throwing stars, deal more damage when thrown compared to when attacked normally. -Don't be afraid to ask for help, whether from your peers or from admins. +Don't be afraid to ask for help, whether from your peers or from mentors. Experiment with different setups of the Supermatter engine to maximize output, but don't risk the crew's safety to do so! Felinids get temporarily distracted by laser pointers. Use this to your advantage when being pursued by one. Firesuits and winter coats offer mild protection from the cold, allowing you to spend longer periods of time near breaches and space than if wearing nothing at all. @@ -249,14 +214,6 @@ If you knock into somebody while doing a wicked grind on a skateboard, they will If you need to drag multiple people either to safety or to space, bring a locker or crate over and stuff them all in before hauling them off. If you're bleeding, you can apply pressure to the limb by grabbing yourself while targeting the bleeding limb. This will slow you down and take up a hand, but it'll slow down how fast you lose blood. Note this won't help the bleeding clot any faster. If you're using hotkey mode, you can stop pulling things using H. -<<<<<<< HEAD -The station's self-destruct terminal is invincible. Go find the disk instead of trying to destroy it. -If there's something you need from another department, try asking! This game isn't singleplayer and you'd be surprised what you can get accomplished together! -You'll quickly lose your interest in the game if you play to win and kill. If you find yourself doing this, take a step back and talk to people - it's a much better experience! -Felinids get temporarily distracted by laser pointers. Use this to your advantage when being pursued by one. -Don't be afraid to ask for help, whether from your peers or from mentors. -You can pin your MODsuit module buttons to your action bar in the suit UI. -======= In a pinch, stripping yourself naked will give you a sizeable resistance to being tackled. What do you value more, your freedom or your dignity? Laying down will help slow down bloodloss. Death will halt it entirely. Maintenance is full of equipment that is randomized every round. Look around and see if anything is worth using. @@ -266,7 +223,6 @@ On most clothing items that go in the exosuit slot, you can put certain small it Remote devices will work when used through cameras. For example: Bluespace RPEDs and door remotes. Sleeping can be used to recover from minor injuries. Sanity, darkness, blindfolds, earmuffs, tables, beds, and bedsheets affect the healing rate. Some roles cannot be antagonists by default, but antag selection is decided first. For instance, you can set Security Officer to High without affecting your chances of becoming an antag -- the game will just select a different role. ->>>>>>> fcaa0b24db0 (Alphabetized, fixed spelling error, clarifying event descriptions. Polish (#69707)) Some weapons are better at taking down robots and structures than others. Don't try to break a window with a scalpel, try a toolbox. Standard epipens contain a potent coagulant that not only slow bloodloss, but also help clot whichever of your wounds is bleeding the most! If you're suffering multiple bad bleeding wounds, make sure to seek out additional treatment ASAP! Suit storage units not only remove blood and dirt from clothing, but also radiation! diff --git a/tgstation.dme b/tgstation.dme index 178dbc74713..05ce6c26564 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -378,6 +378,7 @@ #include "code\__HELPERS\game.dm" #include "code\__HELPERS\global_lists.dm" #include "code\__HELPERS\guid.dm" +#include "code\__HELPERS\hallucinations.dm" #include "code\__HELPERS\heap.dm" #include "code\__HELPERS\hearted.dm" #include "code\__HELPERS\honkerblast.dm" @@ -1287,7 +1288,9 @@ #include "code\datums\status_effects\debuffs\drugginess.dm" #include "code\datums\status_effects\debuffs\drunk.dm" #include "code\datums\status_effects\debuffs\fire_stacks.dm" +#include "code\datums\status_effects\debuffs\hallucination.dm" #include "code\datums\status_effects\debuffs\jitteriness.dm" +#include "code\datums\status_effects\debuffs\screwy_hud.dm" #include "code\datums\status_effects\debuffs\speech_debuffs.dm" #include "code\datums\status_effects\debuffs\strandling.dm" #include "code\datums\storage\storage.dm" @@ -3121,20 +3124,27 @@ #include "code\modules\forensics\_forensics.dm" #include "code\modules\forensics\forensics_helpers.dm" #include "code\modules\hallucination\_hallucination.dm" -#include "code\modules\hallucination\airlock.dm" -#include "code\modules\hallucination\chat.dm" -#include "code\modules\hallucination\death.dm" -#include "code\modules\hallucination\fire.dm" +#include "code\modules\hallucination\battle.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\fake_alert.dm" +#include "code\modules\hallucination\fake_chat.dm" +#include "code\modules\hallucination\fake_death.dm" +#include "code\modules\hallucination\fake_message.dm" +#include "code\modules\hallucination\fake_plasmaflood.dm" +#include "code\modules\hallucination\fake_sound.dm" #include "code\modules\hallucination\hazard.dm" -#include "code\modules\hallucination\hostile_mob.dm" -#include "code\modules\hallucination\HUD.dm" -#include "code\modules\hallucination\husk.dm" -#include "code\modules\hallucination\item.dm" -#include "code\modules\hallucination\plasma_flood.dm" -#include "code\modules\hallucination\polymorph.dm" +#include "code\modules\hallucination\hud_screw.dm" +#include "code\modules\hallucination\inhand_fake_item.dm" +#include "code\modules\hallucination\nearby_fake_item.dm" +#include "code\modules\hallucination\on_fire.dm" +#include "code\modules\hallucination\screwy_health_doll.dm" #include "code\modules\hallucination\shock.dm" -#include "code\modules\hallucination\sound.dm" +#include "code\modules\hallucination\station_message.dm" #include "code\modules\hallucination\stray_bullet.dm" +#include "code\modules\hallucination\xeno_attack.dm" #include "code\modules\holiday\easter.dm" #include "code\modules\holiday\foreign_calendar.dm" #include "code\modules\holiday\holidays.dm" @@ -4184,7 +4194,6 @@ #include "code\modules\projectiles\projectile\special\curse.dm" #include "code\modules\projectiles\projectile\special\floral.dm" #include "code\modules\projectiles\projectile\special\gravity.dm" -#include "code\modules\projectiles\projectile\special\hallucination.dm" #include "code\modules\projectiles\projectile\special\ion.dm" #include "code\modules\projectiles\projectile\special\meteor.dm" #include "code\modules\projectiles\projectile\special\mindflayer.dm"