From 938deeb66a6f61594fb30f730ddffd53fabf7d34 Mon Sep 17 00:00:00 2001 From: Doxxmedearly <38594443+Doxxmedearly@users.noreply.github.com> Date: Fri, 20 Mar 2020 15:24:06 -0500 Subject: [PATCH] Hallucinations Overhaul (#8259) --- aurorastation.dme | 8 +- code/__defines/_macros.dm | 2 + code/controllers/subsystems/hallucinations.dm | 36 ++ code/game/machinery/doors/airlock.dm | 5 - code/game/objects/items/devices/PDA/PDA.dm | 6 + code/game/sound.dm | 3 +- code/modules/flufftext/Hallucination.dm | 392 -------------- code/modules/hallucinations/hallucinations.dm | 61 +++ code/modules/hallucinations/mob.dm | 48 ++ .../text_lists/hallucinated_actions.txt | 56 ++ .../text_lists/hallucinated_phrases.txt | 99 ++++ .../text_lists/hallucinated_thoughts.txt | 57 ++ code/modules/hallucinations/types/basic.dm | 493 ++++++++++++++++++ code/modules/hallucinations/types/mirage.dm | 188 +++++++ code/modules/hallucinations/types/powers.dm | 168 ++++++ code/modules/hallucinations/types/sound.dm | 105 ++++ code/modules/mob/living/carbon/human/life.dm | 25 +- code/modules/mob/living/living_defines.dm | 1 - .../Chemistry-Reagents-Medicine.dm | 2 +- .../doxxmedearly - hallucination_overhaul.yml | 19 + 20 files changed, 1353 insertions(+), 421 deletions(-) create mode 100644 code/controllers/subsystems/hallucinations.dm delete mode 100644 code/modules/flufftext/Hallucination.dm create mode 100644 code/modules/hallucinations/hallucinations.dm create mode 100644 code/modules/hallucinations/mob.dm create mode 100644 code/modules/hallucinations/text_lists/hallucinated_actions.txt create mode 100644 code/modules/hallucinations/text_lists/hallucinated_phrases.txt create mode 100644 code/modules/hallucinations/text_lists/hallucinated_thoughts.txt create mode 100644 code/modules/hallucinations/types/basic.dm create mode 100644 code/modules/hallucinations/types/mirage.dm create mode 100644 code/modules/hallucinations/types/powers.dm create mode 100644 code/modules/hallucinations/types/sound.dm create mode 100644 html/changelogs/doxxmedearly - hallucination_overhaul.yml diff --git a/aurorastation.dme b/aurorastation.dme index 193c2625d64..8caaaf1b679 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -176,6 +176,7 @@ #include "code\controllers\subsystems\garbage.dm" #include "code\controllers\subsystems\ghostroles.dm" #include "code\controllers\subsystems\global_listener.dm" +#include "code\controllers\subsystems\hallucinations.dm" #include "code\controllers\subsystems\http.dm" #include "code\controllers\subsystems\icon_cache.dm" #include "code\controllers\subsystems\icon_smooth.dm" @@ -1532,7 +1533,6 @@ #include "code\modules\examine\descriptions\weapons.dm" #include "code\modules\ext_scripts\python.dm" #include "code\modules\flufftext\Dreaming.dm" -#include "code\modules\flufftext\Hallucination.dm" #include "code\modules\flufftext\TextFilters.dm" #include "code\modules\food\recipe.dm" #include "code\modules\food\recipes_fryer.dm" @@ -1569,6 +1569,12 @@ #include "code\modules\ghosttrap\trap.dm" #include "code\modules\global_listener\devices.dm" #include "code\modules\global_listener\interfaces.dm" +#include "code\modules\hallucinations\hallucinations.dm" +#include "code\modules\hallucinations\mob.dm" +#include "code\modules\hallucinations\types\basic.dm" +#include "code\modules\hallucinations\types\mirage.dm" +#include "code\modules\hallucinations\types\powers.dm" +#include "code\modules\hallucinations\types\sound.dm" #include "code\modules\heavy_vehicle\_mech_setup.dm" #include "code\modules\heavy_vehicle\mech_construction.dm" #include "code\modules\heavy_vehicle\mech_damage.dm" diff --git a/code/__defines/_macros.dm b/code/__defines/_macros.dm index 99a4065b237..e97ef70ad61 100644 --- a/code/__defines/_macros.dm +++ b/code/__defines/_macros.dm @@ -7,7 +7,9 @@ #define SPAN_DANGER(X) "[X]" #define SPAN_CULT(X) "[X]" #define SPAN_GOOD(X) "[X]" +#define SPAN_BAD(X) "[X]" #define SPAN_ALIEN(X) "[X]" +#define SPAN_ALERT(X) "[X]" #define SPAN_ITALIC(X) "[X]" #define SPAN_BOLD(X) "[X]" #define SPAN_SUBTLE(X) "[X]" diff --git a/code/controllers/subsystems/hallucinations.dm b/code/controllers/subsystems/hallucinations.dm new file mode 100644 index 00000000000..8fb27220a5a --- /dev/null +++ b/code/controllers/subsystems/hallucinations.dm @@ -0,0 +1,36 @@ +/var/datum/controller/subsystem/hallucinations/SShallucinations + +/datum/controller/subsystem/hallucinations + name = "Hallucinations" + flags = SS_NO_FIRE + + var/list/hallucinated_phrases = list() + var/list/hallucinated_actions = list() + var/list/hallucinated_thoughts = list() + var/static/list/hal_emote = list("mutters quietly.", "stares.", "grunts.", "looks around.", "twitches.", "shivers.", "swats at the air.", "wobbles.", "gasps!", "blinks rapidly.", "murmurs.", + "dry heaves!", "twitches violently.", "giggles.", "drools.", "scratches all over.", "grinds their teeth.", "whispers something quietly.") + var/static/list/message_sender = list("Mom", "Dad", "Captain", "Captain(as Captain)", "help", "Home", "MaxBet Online Casino", "IDrist Corp", "Dr. Maxman", + "www.wetskrell.nt", "You are our lucky grand prize winner!", "Officer Beepsky", "Ginny", "Ian", + "what have you DONE?", "Miranda Trasen", "Central Command", "AI", "maintenance drone", "Unknown", "I don't want to die") + var/list/all_hallucinations = list() + +/datum/controller/subsystem/hallucinations/New() + NEW_SS_GLOBAL(SShallucinations) + +/datum/controller/subsystem/hallucinations/Initialize() + . = ..() + for(var/T in subtypesof(/datum/hallucination/)) + all_hallucinations += T + hallucinated_phrases = file2list("code/modules/hallucinations/text_lists/hallucinated_phrases.txt") + hallucinated_actions = file2list("code/modules/hallucinations/text_lists/hallucinated_actions.txt") //important note when adding to this file: "you" will always be replaced by the hallucinator's name + hallucinated_thoughts = file2list("code/modules/hallucinations/text_lists/hallucinated_thoughts.txt") + +/datum/controller/subsystem/hallucinations/proc/get_hallucination(var/mob/living/carbon/C) + var/list/candidates = list() + for(var/T in all_hallucinations) + var/datum/hallucination/H = new T + if(H.can_affect(C)) + candidates += H + if(candidates.len) + var/datum/hallucination/D = pick(candidates) + return D \ No newline at end of file diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 2e45b2ac64e..a6b7b72dc41 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -472,11 +472,6 @@ About the new airlock wires panel: return else /*if(src.justzap)*/ return - else if(user.hallucination > 50 && prob(10) && src.operating == 0 && !user.is_diona() && !user.isSynthetic()) - to_chat(user, span("danger", "You feel a powerful shock course through your body!")) - user.adjustHalLoss(10) - user.stunned += 10 - return ..(user) /obj/machinery/door/airlock/proc/isElectrified() diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 6095c5089c6..b367e22200e 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -1116,6 +1116,12 @@ var/global/list/obj/item/device/pda/PDAs = list() t = sanitize(t) //t = readd_quotes(t) t = replace_characters(t, list(""" = "\"")) + if(iscarbon(U) && t) + var/mob/living/carbon/C = U + if(C.hallucination >= 50 && prob(C.hallucination / 10)) //If you're really hallucinating, you might not be typing what you think you are + var/t_orig = t + t = pick(SShallucinations.hallucinated_phrases) //see carbon/hallucination.dm + log_pda("[usr] (PDA: [C.name]) typed \"[t_orig]\" then hallucination changed it to \"[t]\". Recipient was [P.owner]",ckey=key_name(C),ckey_target=key_name(P.owner)) if (!t || !istype(P)) return if (!in_range(src, U) && loc != U) diff --git a/code/game/sound.dm b/code/game/sound.dm index a54dc244bb9..cb33f61ea0a 100644 --- a/code/game/sound.dm +++ b/code/game/sound.dm @@ -24,7 +24,7 @@ #define UNDERWATER 22 #define DRUGGED 23 #define DIZZY 24 -#define PSYCHOTIC 25 + #define STANDARD_STATION STONEROOM #define LARGE_ENCLOSED HANGAR @@ -35,6 +35,7 @@ #define SMALL_SOFTFLOOR ROOM #define ASTEROID CAVE #define SPACE UNDERWATER +#define PSYCHOTIC PARKING_LOT //footsteps var/list/blank_footstep = list('sound/effects/footstep/blank.ogg') diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm deleted file mode 100644 index 2df1bba6d45..00000000000 --- a/code/modules/flufftext/Hallucination.dm +++ /dev/null @@ -1,392 +0,0 @@ -/* -Ideas for the subtle effects of hallucination: - -Light up oxygen/phoron indicators (done) -Cause health to look critical/dead, even when standing (done) -Characters silently watching you -Brief flashes of fire/space/bombs/c4/dangerous shit (done) -Items that are rare/traitorous/don't exist appearing in your inventory slots (done) -Strange audio (should be rare) (done) -Gunshots/explosions/opening doors/less rare audio (done) - -*/ - -mob/living/carbon/var - image/halimage - image/halbody - obj/halitem - hal_screwyhud = 0 //1 - critical, 2 - dead, 3 - oxygen indicator, 4 - toxin indicator - handling_hal = 0 - hal_crit = 0 - -mob/living/carbon/proc/handle_hallucinations() - if(handling_hal) return - handling_hal = 1 - while(client && hallucination > 20) - sleep(rand(200,500)/(hallucination/25)) - var/halpick = rand(1,100) - switch(halpick) - if(0 to 15) - //Screwy HUD - hal_screwyhud = pick(1,2,3,3,4,4) - spawn(rand(100,250)) - hal_screwyhud = 0 - if(16 to 25) - //Strange items - if(!halitem) - halitem = new - var/list/slots_free = list(ui_lhand,ui_rhand) - if(l_hand) slots_free -= ui_lhand - if(r_hand) slots_free -= ui_rhand - if(istype(src,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = src - 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.layer = 50 - switch(rand(1,6)) - if(1) //revolver - halitem.icon = 'icons/obj/guns/revolver.dmi' - halitem.icon_state = "revolver" - halitem.name = "Revolver" - if(2) //c4 - halitem.icon = 'icons/obj/assemblies.dmi' - halitem.icon_state = "plastic-explosive0" - halitem.name = "Mysterious Package" - if(prob(25)) - halitem.icon_state = "c4small_1" - if(3) //sword - halitem.icon = 'icons/obj/weapons.dmi' - halitem.icon_state = "sword1" - halitem.name = "Sword" - if(4) //stun baton - halitem.icon = 'icons/obj/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/grenade.dmi' - halitem.icon_state = "flashbang1" - halitem.name = "Flashbang" - if(client) client.screen += halitem - spawn(rand(100,250)) - if(client) - client.screen -= halitem - halitem = null - if(26 to 40) - //Flashes of danger - if(!halimage) - var/list/possible_points = list() - for(var/turf/simulated/floor/F in view(src,world.view)) - possible_points += F - if(possible_points.len) - var/turf/simulated/floor/target = pick(possible_points) - - switch(rand(1,3)) - if(1) - halimage = image('icons/turf/space.dmi',target,"[rand(1,25)]",TURF_LAYER) - if(2) - halimage = image('icons/effects/fire.dmi',target,"1",TURF_LAYER) - if(3) - halimage = image('icons/obj/assemblies.dmi',target,"plastic-explosive2",OBJ_LAYER+0.01) - - - if(client) client.images += halimage - spawn(rand(10,50)) //Only seen for a brief moment. - if(client) client.images -= halimage - halimage = null - - - if(41 to 65) - //Strange audio - switch(rand(1,12)) - if(1) sound_to(src, 'sound/machines/airlock.ogg') - if(2) - if(prob(50)) sound_to(src, 'sound/effects/Explosion1.ogg') - else sound_to(src, 'sound/effects/Explosion2.ogg') - if(3) sound_to(src, 'sound/effects/explosionfar.ogg') - if(4) sound_to(src, 'sound/effects/glass_break1.ogg') - if(5) sound_to(src, 'sound/effects/glass_break2.ogg') - if(6) sound_to(src, 'sound/effects/glass_break3.ogg') - if(7) sound_to(src, 'sound/machines/twobeep.ogg') - if(8) sound_to(src, 'sound/machines/windowdoor.ogg') - if(9) - //To make it more realistic, I added two gunshots (enough to kill) - sound_to(src, 'sound/weapons/gunshot/gunshot1.ogg') - spawn(rand(10,30)) - sound_to(src, 'sound/weapons/gunshot/gunshot1.ogg') - if(10) sound_to(src, 'sound/weapons/smash.ogg') - if(11) - //Same as above, but with tasers. - sound_to(src, 'sound/weapons/Taser.ogg') - spawn(rand(10,30)) - sound_to(src, 'sound/weapons/Taser.ogg') - //Rare audio - if(12) -//These sounds are (mostly) taken from Hidden: Source - var/list/creepyasssounds = list( - 'sound/effects/ghost.ogg', - 'sound/effects/ghost2.ogg', - 'sound/effects/Heart Beat.ogg', - 'sound/effects/screech.ogg', - 'sound/hallucinations/behind_you1.ogg', - 'sound/hallucinations/behind_you2.ogg', - 'sound/hallucinations/far_noise.ogg', - 'sound/hallucinations/growl1.ogg', - 'sound/hallucinations/growl2.ogg', - 'sound/hallucinations/growl3.ogg', - 'sound/hallucinations/im_here1.ogg', - 'sound/hallucinations/im_here2.ogg', - 'sound/hallucinations/i_see_you1.ogg', - 'sound/hallucinations/i_see_you2.ogg', - 'sound/hallucinations/look_up1.ogg', - 'sound/hallucinations/look_up2.ogg', - 'sound/hallucinations/over_here1.ogg', - 'sound/hallucinations/over_here2.ogg', - 'sound/hallucinations/over_here3.ogg', - 'sound/hallucinations/turn_around1.ogg', - 'sound/hallucinations/turn_around2.ogg', - 'sound/hallucinations/veryfar_noise.ogg', - 'sound/hallucinations/wail.ogg') - sound_to(src, pick(creepyasssounds)) - if(66 to 70) - //Flashes of danger - if(!halbody) - var/list/possible_points = list() - for(var/turf/simulated/floor/F in view(src,world.view)) - possible_points += F - if(possible_points.len) - var/turf/simulated/floor/target = pick(possible_points) - switch(rand(1,4)) - if(1) - halbody = image('icons/mob/human.dmi',target,"husk_l",TURF_LAYER) - if(2,3) - halbody = image('icons/mob/human.dmi',target,"husk_s",TURF_LAYER) - if(4) - halbody = image('icons/mob/npc/alien.dmi',target,"alienother",TURF_LAYER) - // if(5) - // halbody = image('xcomalien.dmi',target,"chryssalid",TURF_LAYER) - - if(client) client.images += halbody - spawn(rand(50,80)) //Only seen for a brief moment. - if(client) client.images -= halbody - halbody = null - if(71 to 72) - //Fake death -// src.sleeping_willingly = 1 - src.sleeping = 20 - hal_crit = 1 - hal_screwyhud = 1 - spawn(rand(50,100)) -// src.sleeping_willingly = 0 - src.sleeping = 0 - hal_crit = 0 - hal_screwyhud = 0 - handling_hal = 0 - - - - -/*obj/machinery/proc/mockpanel(list/buttons,start_txt,end_txt,list/mid_txts) - - if(!mocktxt) - - mocktxt = "" - - var/possible_txt = list("Launch Escape Pods","Self-Destruct Sequence","\[Swipe ID\]","De-Monkify",\ - "Reticulate Splines","Plasma","Open Valve","Lockdown","Nerf Airflow","Kill Traitor","Nihilism",\ - "OBJECTION!","Arrest Stephen Bowman","Engage Anti-Trenna Defenses","Increase Captain IQ","Retrieve Arms",\ - "Play Charades","Oxygen","Inject BeAcOs","Ninja Lizards","Limit Break","Build Sentry") - - if(mid_txts) - while(mid_txts.len) - var/mid_txt = pick(mid_txts) - mocktxt += mid_txt - mid_txts -= mid_txt - - while(buttons.len) - - var/button = pick(buttons) - - var/button_txt = pick(possible_txt) - - mocktxt += "[button_txt]
" - - buttons -= button - possible_txt -= button_txt - - return start_txt + mocktxt + end_txt + "" - -proc/check_panel(mob/M) - if (istype(M, /mob/living/carbon/human) || istype(M, /mob/living/silicon/ai)) - if(M.hallucination < 15) - return 1 - return 0*/ - -/obj/effect/fake_attacker - icon = null - icon_state = null - name = "" - desc = "" - density = 0 - anchored = 1 - opacity = 0 - var/mob/living/carbon/human/my_target = null - var/weapon_name = null - var/obj/item/weap = null - var/image/stand_icon = null - var/image/currentimage = null - var/icon/base = null - var/s_tone - var/mob/living/clone = null - var/image/left - var/image/right - var/image/up - var/collapse - var/image/down - - var/health = 100 - - attackby(var/obj/item/P as obj, mob/user as mob) - step_away(src,my_target,2) - for(var/mob/M in oviewers(world.view,my_target)) - to_chat(M, "[my_target] flails around wildly.") - my_target.show_message("[src] has been attacked by [my_target] ", 1) //Lazy. - - src.health -= P.force - - - return - - Crossed(var/mob/M, somenumber) - if(M == my_target) - step_away(src,my_target,2) - if(prob(30)) - for(var/mob/O in oviewers(world.view , my_target)) - to_chat(O, "[my_target] stumbles around.") - - New() - ..() - addtimer(CALLBACK(src, .proc/end), 300) - step_away(src,my_target,2) - spawn attack_loop() - - proc/end() - if (my_target) - my_target.hallucinations -= src - - walk(src, 0) - qdel(src) - - proc/updateimage() - if(src.dir == NORTH) - del currentimage // Can't qdel images. - src.currentimage = new /image(up,src) - else if(src.dir == SOUTH) - del currentimage - src.currentimage = new /image(down,src) - else if(src.dir == EAST) - del currentimage - src.currentimage = new /image(right,src) - else if(src.dir == WEST) - del currentimage - src.currentimage = new /image(left,src) - my_target << currentimage - - proc/attack_loop() - while(!QDELETED(src)) - sleep(rand(5,10)) - if(src.health < 0) - collapse() - continue - if(get_dist(src,my_target) > 1) - src.set_dir(get_dir(src,my_target)) - step_towards(src,my_target) - updateimage() - else - if(prob(15)) - if(weapon_name) - my_target << sound(pick('sound/weapons/genhit1.ogg', 'sound/weapons/genhit2.ogg', 'sound/weapons/genhit3.ogg')) - my_target.show_message("[my_target] has been attacked with [weapon_name] by [src.name] ", 1) - my_target.adjustHalLoss(10) - if(prob(20)) my_target.eye_blurry += 3 - if(prob(33)) - if(!locate(/obj/effect/overlay) in my_target.loc) - fake_blood(my_target) - else - my_target << sound(pick('sound/weapons/punch1.ogg','sound/weapons/punch2.ogg','sound/weapons/punch3.ogg','sound/weapons/punch4.ogg')) - my_target.show_message("[src.name] has punched [my_target]!", 1) - my_target.adjustHalLoss(10) - if(prob(33)) - if(!locate(/obj/effect/overlay) in my_target.loc) - fake_blood(my_target) - - if(prob(15)) - step_away(src,my_target,2) - - proc/collapse() - collapse = 1 - updateimage() - -/proc/fake_blood(var/mob/target) - var/obj/effect/overlay/O = new/obj/effect/overlay(target.loc) - O.name = "blood" - var/image/I = image('icons/effects/blood.dmi',O,"floor[rand(1,7)]",O.dir,1) - target << I - QDEL_IN(O, 300) - -var/list/non_fakeattack_weapons = list(/obj/item/gun/projectile, /obj/item/ammo_magazine/a357,\ - /obj/item/gun/energy/crossbow, /obj/item/melee/energy/sword,\ - /obj/item/storage/box/syndicate, /obj/item/storage/box/emps,\ - /obj/item/cartridge/syndicate, /obj/item/clothing/under/chameleon,\ - /obj/item/clothing/shoes/syndigaloshes, /obj/item/card/id/syndicate,\ - /obj/item/clothing/mask/gas/voice, /obj/item/clothing/glasses/thermal,\ - /obj/item/device/chameleon, /obj/item/card/emag,\ - /obj/item/storage/toolbox/syndicate, /obj/item/aiModule,\ - /obj/item/device/radio/headset/syndicate, /obj/item/plastique,\ - /obj/item/device/powersink, /obj/item/storage/box/syndie_kit,\ - /obj/item/toy/syndicateballoon, /obj/item/gun/energy/captain,\ - /obj/item/hand_tele, /obj/item/rfd/construction, /obj/item/tank/jetpack,\ - /obj/item/clothing/under/rank/captain, /obj/item/aicard,\ - /obj/item/clothing/shoes/magboots, /obj/item/blueprints, /obj/item/disk/nuclear,\ - /obj/item/clothing/suit/space/void, /obj/item/tank) - -/proc/fake_attack(var/mob/living/target) - var/mob/living/carbon/human/clone = null - var/clone_weapon = null - - for(var/mob/living/carbon/human/H in living_mob_list) - if(H.stat || H.lying) continue - clone = H - break //changed the code a bit. Less randomised, but less work to do. Should be ok, world.contents aren't stored in any particular order. - - if(!clone) return - - //var/obj/effect/fake_attacker/F = new/obj/effect/fake_attacker(outside_range(target)) - var/obj/effect/fake_attacker/F = new/obj/effect/fake_attacker(target.loc) - if(clone.l_hand) - if(!(locate(clone.l_hand) in non_fakeattack_weapons)) - clone_weapon = clone.l_hand.name - F.weap = clone.l_hand - else if (clone.r_hand) - if(!(locate(clone.r_hand) in non_fakeattack_weapons)) - clone_weapon = clone.r_hand.name - F.weap = clone.r_hand - - F.name = clone.name - F.my_target = target - F.weapon_name = clone_weapon - target.hallucinations += F - - - F.left = image(clone,dir = WEST) - F.right = image(clone,dir = EAST) - F.up = image(clone,dir = NORTH) - F.down = image(clone,dir = SOUTH) - - F.updateimage() diff --git a/code/modules/hallucinations/hallucinations.dm b/code/modules/hallucinations/hallucinations.dm new file mode 100644 index 00000000000..454709dc432 --- /dev/null +++ b/code/modules/hallucinations/hallucinations.dm @@ -0,0 +1,61 @@ +#define NO_THOUGHT 1 //Hallucinated thoughts will not occur on this hallucination's end() +#define NO_EMOTE 2 //User will not emote to others when this hallucination ends +#define HEARING_DEPENDENT 3 //deaf characters will not experience this hallucination + +//Power Defines +#define HAL_POWER_LOW 30 +#define HAL_POWER_MED 50 +#define HAL_POWER_HIGH 70 + +/datum/hallucination + var/mob/living/carbon/holder //Who is hallucinating? + var/allow_duplicates = TRUE //This is set to false for hallucinations with long durations or ones we do not want repeated for a time + var/duration = 10 //how long before we call end() + var/min_power = 0 //mobs only get this hallucination at this threshold + var/max_power = INFINITY //mobs don't get this hallucination if it's above this threshold. Used to weed out more common ones if you're super fucked up + var/special_flags //Any special flags, defined above + +/datum/hallucination/proc/start() + +/datum/hallucination/proc/end() + if(holder) + if(!(special_flags & NO_THOUGHT)) + holder.hallucination_thought() + if(!(special_flags & NO_EMOTE)) + hallucination_emote(holder) //Always a chance to involuntarily emote to others as if on drugs + holder.hallucinations -= src + qdel(src) + +//Used to verify if a hallucination can be added to the list of candidates +/datum/hallucination/proc/can_affect(mob/living/carbon/C) + if(!C.client) + return FALSE + if(!allow_duplicates && (locate(type) in C.hallucinations)) + return FALSE + if(min_power > C.hallucination || max_power < C.hallucination) + return FALSE + if((special_flags & HEARING_DEPENDENT) && (C.disabilities & DEAF)) + return FALSE + return TRUE + +/datum/hallucination/Destroy() + holder = null + return ..() + +//The actual kickoff to each effect +/datum/hallucination/proc/activate() + if(!holder || !holder.client) + return + holder.hallucinations += src + start() + addtimer(CALLBACK(src, .proc/end), duration) + +//You emoting to others involuntarily. This happens mostly in end() +/datum/hallucination/proc/hallucination_emote() + if(prob(min(holder.hallucination - 5, 80)) && !holder.stat) + var/chosen_emote = pick(SShallucinations.hal_emote) + if(prob(10)) //You are aware of it in this instance + holder.visible_message("[holder] [chosen_emote]") + else + for(var/mob/M in oviewers(world.view, holder)) //Only shows to others, not you; you're not aware of what you're doing. Could prompt others to ask if you're okay, and lead to confusion. + to_chat(M, "[holder] [chosen_emote]") diff --git a/code/modules/hallucinations/mob.dm b/code/modules/hallucinations/mob.dm new file mode 100644 index 00000000000..797f226389f --- /dev/null +++ b/code/modules/hallucinations/mob.dm @@ -0,0 +1,48 @@ +/mob/living/carbon + var/next_hallucination = 0 //Hallucination spam limit var + var/list/hallucinations = list() //Hallucinations currently affecting the mob. Not to be confused with singular "hallucination" which is a NUM variable like confused/drowsy/eye_blind etc + +//Hallucinated Hearing +/mob/living/carbon/hear_say(var/message, var/verb = "says", var/datum/language/language, var/alt_name = "",var/italics = 0, var/mob/speaker, var/sound/speech_sound, var/sound_vol) + if(hallucination >= 60 && prob(1)) + var/orig_message = message + message = pick(SShallucinations.hallucinated_phrases) + log_say("Hallucination level changed [orig_message] by [speaker] to [message] for [key_name(src)].", ckey=key_name(src)) + ..() + +/mob/living/carbon/hear_radio(var/message, var/verb="says", var/datum/language/language, var/part_a, var/part_b, var/mob/speaker, var/hard_to_hear = 0, var/vname ="") + if(hallucination >= 60 && prob(1)) + var/orig_message = message + message = pick(SShallucinations.hallucinated_phrases) + log_say("Hallucination level changed [orig_message] by [speaker] to [message] for [key_name(src)].", ckey=key_name(src)) + ..() + +//Main handling proc, called in life() +/mob/living/carbon/proc/handle_hallucinations() + hallucination -= 1 //Tick down the duration + if(!hallucination) //We're done + return + if(!client || stat || world.time < next_hallucination) + return + + var/hall_delay = rand(180,250) //Time between hallucinations, modified by switch below. + switch(hallucination) //26-149 are intentionally left off, as they do not modify the delay. This is a pretty common range for hallucinations. + if(1 to 25) //Winding down, less frequent. + hall_delay *= 2 + if(150 to 399) //Yo mind really fucked, more frequent. + hall_delay *= 0.75 + if(400 to INFINITY) //This should only be possible in cult conversions. Very low delay to represent your flayed mind. + hall_delay *= 0.25 + + next_hallucination = world.time + hall_delay + var/datum/hallucination/H = SShallucinations.get_hallucination(src) + H.holder = src + H.activate() + +//This is called on every end() so usually occurs a few times. Grants a thought to the user from thoughts list. +/mob/living/carbon/proc/hallucination_thought() + if(prob(min(hallucination/2, 50))) + addtimer(CALLBACK(src, .proc/hal_thought_give), rand(30,90)) + +/mob/living/carbon/proc/hal_thought_give() + to_chat(src, "[pick(SShallucinations.hallucinated_thoughts)]") \ No newline at end of file diff --git a/code/modules/hallucinations/text_lists/hallucinated_actions.txt b/code/modules/hallucinations/text_lists/hallucinated_actions.txt new file mode 100644 index 00000000000..86090b8bb4e --- /dev/null +++ b/code/modules/hallucinations/text_lists/hallucinated_actions.txt @@ -0,0 +1,56 @@ +grins widely at you. +mumbles something while looking away from you. +screams! +suddenly clutches at their chest! +stumbles. +laughs! +watches you, unblinking. +twitches. +holds up a very familiar key... +cowers from you. +bleeds profusely from their eyes! +drools. +flails about, unable to breathe! +grows a fabulous head of hair! +snorts! +waves at you to come closer. +hums your favorite song. +doesn't seem to cast a shadow. +beeps. +is going to die if they don't get help! +yawns. +points a gun at you! +whispers into their headset while watching you. +frowns at you. +shakes their head at you. +looks around. +swats at the air! +coughs up a tooth. +shivers. +looks behind you. +spits blood. +shrieks! +applies some lipstick. +scowls. +looks at you and frowns. +wipes the blood from their hands. +sneezes. +screams as a loud snapping sound comes from their body! +snaps. +holds up a bloodied organ and examines it. +giggles. +looks at you and clenches a fist. +jabs a finger at you! +stomps their feet. +seems to phase out for a moment. +coughs up a plume of colorful smoke. +huffs. +licks their eyeball. +unhinges their jaw for a moment. +gives you an incredulous look. +backs away from you. +quickly shoves a knife into their pocket. +quickly holsters a gun. +blinks rapidly. +slams their hands together! +closely watches you. \ No newline at end of file diff --git a/code/modules/hallucinations/text_lists/hallucinated_phrases.txt b/code/modules/hallucinations/text_lists/hallucinated_phrases.txt new file mode 100644 index 00000000000..3ceb0f8d847 --- /dev/null +++ b/code/modules/hallucinations/text_lists/hallucinated_phrases.txt @@ -0,0 +1,99 @@ +Give it back! +Don't worry, we'll never be apart. +Just let me help you. +You don't know anything! +I forgive you. +Don't you remember me? +You can't keep doing this! +Help! +Are you okay? +You need to see a doctor! +Wait, who are you? +Wait, who were you? +Are you ready? +Remember our song? +Did you hear that? +Why are we here? +Let's get going! +Just go away! +It's okay, I'm here. +Where are we going? +Oh, did you forget? +You forgot about me already, didn't you? +You're not safe here. +Why are you asking me? +Don't you know any better? +It's you or me. +I can't lose you again. +You're bleeding! +You're hurt! +They're looking for you. +How are you? +Shut up for once. +Will you join me? +Didn't you call for me? +I know you lied to me. +You just had to ruin everything, didn't you? +It's so nice to see you! +Did you remember your assignment? +Don't give up! +Nobody else believes in you, but I do. +When's the shuttle coming? +The shuttle will be here in a minute. +I tried calling you last night. You never answer. +What is THAT supposed to be? +Did you hear about the fire? +I heard the news; I'm so sorry. +I didn't mean it! +What happened to your face?! +Apparently someone died. +Remember me from high-school? Well I have this great business deal proposition... +Bored? +Once you are a player with MaxBet, you will also receive lucrative weekly and monthly promotions. +Find your secret skrell crush. +Is your sexlife growing dull? Your late-night sessions growing short? Have you considered Unathi testicle transplants? Greater stamina, greater virility, greater fun. +We know who you are. Expect us. +Discovery of this ONE Vaurca pheremone has led to years of genital growth progress! +This staff assistant is making mad credits and basically, you're fucking stupid. +You have a secret admirer! +Where's your ID? +You know nobody wants you here, right? +Uh, do you know you're bleeding? +I know your secret, you know. You're bad at hiding it. +I would kill you if I could get away with it. +You should probably leave, trust me. +You'd better wake up before you forget how to. +You cannot kill me in a way that matters. +Life is too short for regrets. +There's an interesting rumor about you. +I heard what you did before you worked here. +You're disgusting. +I know you're a liar. +Your beliefs are ridiculous. +Fuck off, you nuisance. +Coming here was a mistake and you know it. +I wonder how much you'll scream? +So, you're spreading rumors about me? +Let's dance! +I know how you die. Do you want to? +I wonder how loud you'll scream. +Come on, let's get out of here. +You know I like you, right? +Why don't you just admit it? +Cry about it. +I can't believe you actually did it. +You can trust me. +I'd never betray you. +I'm excited for you to join us. +I come from the future to give warning! +Hum that song I like, please? +Just give me a hug. I need it. +Can you tell me I'm doing okay? +Isn't this all stupid? +Don't tell anyone I told you this, but... +They'll never accept you. +Someone told me they had a crush on you but were too shy to say it. +Well, you fucked it up. Again. +Let's die together. +I'm not feeling too hot. +I feel like you're the only one I can trust. \ No newline at end of file diff --git a/code/modules/hallucinations/text_lists/hallucinated_thoughts.txt b/code/modules/hallucinations/text_lists/hallucinated_thoughts.txt new file mode 100644 index 00000000000..85e3851e338 --- /dev/null +++ b/code/modules/hallucinations/text_lists/hallucinated_thoughts.txt @@ -0,0 +1,57 @@ +Wait, what was that? +Something is crawling on you! +Is any of this real? +You can't catch your breath! +Something's watching you... +You feel unsteady! +You're sure you've never been to this place before... +You can't feel your skin! +What just brushed your shoulder? +You hear someone whispering to you. It sounds so familiar. +When are you getting that drink you ordered? +Wait, where did your child go? What did they look like again..? You have one, right? +Crap, that assignment is due today! +When did you die, again? It's hard to remember... +You'll die if it gets any warmer! +Weren't you getting married today? +Why is it so cold here? +You need to get away from here, it's not safe! +Who are all these people? +You feel exhausted! +You're so thirsty! +Where did you put your jacket? +Finally, you can see again! +Your face feels soft. +When was your funeral supposed to be? +This place has changed... +Why is it so. Damn. Loud?! +Oh no... +You feel itchy all over! +Someone's behind you! +Where did your gun go? +You feel like you're getting sick. +Your blood feels weird. +Someone here was trying to kill you, you just know it! +Wait, this isn't your ID. +Are those footsteps? +Was that a gunshot? +Something's buzzing in your ear. +Wait, what were you doing? +You hear a constant dripping sound. +Your skin feels wet. +You feel a sudden chill. +All your problems seem to melt away. +Your head feels foggy. +The people around you really are great, aren't they? +You feel homesick. +Why does it smell like rust? +Wait, is that blood on the floor? +You feel particularly charitable. +Did you always have this many fingers? +When's the last time you ate? +Oh shit, these aren't your clothes! +You have to leave. Right now. +You suddenly feel antsy. +You can't seem to focus on anything except your problems. +Everything seems so overwhelming. +How do you wake up? \ No newline at end of file diff --git a/code/modules/hallucinations/types/basic.dm b/code/modules/hallucinations/types/basic.dm new file mode 100644 index 00000000000..e95972e1e18 --- /dev/null +++ b/code/modules/hallucinations/types/basic.dm @@ -0,0 +1,493 @@ +/datum/hallucination/announcement //fake AI announcements, complete with sound. Text is weirder than normal, but easy to glaze over. + min_power = HAL_POWER_LOW + duration = 1200 //this duration length + not allowing duplicates prevents spamming announcements on every valid handle_hallucination() which can get VERY annoying if rng decides to give you 3 in a row + allow_duplicates = FALSE + +/datum/hallucination/announcement/start() + var/list/hal_sender = SShallucinations.message_sender + for(var/mob/living/carbon/human/H in living_mob_list) + if(H.client && !player_is_antag(H, only_offstation_roles = TRUE)) //We're not going to add ninjas, mercs, borers, etc to prevent meta. + hal_sender += H + switch(rand(1,15)) + if(1) + sound_to(holder, 'sound/AI/radiation.ogg') + to_chat(holder, "

Anomaly Break

") + to_chat(holder, SPAN_ALERT("Comfortable levels of radiation detected near the station. [pick(SShallucinations.hallucinated_phrases)] Please cower among the shielded maintenance burrows.")) //hallucinated phrases contains the punctuation + + if(2) + sound_to(holder, 'sound/AI/strangeobject.ogg') + to_chat(holder, "

Welcome Object

") + to_chat(holder, SPAN_ALERT("Transport signature of [pick(adjectives)] origin detected in your path, an object appears to have been nesting aboard NSS Upsilon. [pick(SShallucinations.hallucinated_phrases)]")) + + if(3) + sound_to(holder, 'sound/AI/scrubbers.ogg') + to_chat(holder, "

Reminder: Backpressure Warning

") + to_chat(holder, SPAN_ALERT("The scrubbers network is expecting \an [pick(adjectives)] surge. Some ejection of [pick(adjectives)] contents will occur.")) + + if(4) + sound_to(holder, 'sound/AI/emergencyshuttlecalled.ogg') + to_chat(holder, "

Emergency Departure

") + to_chat(holder, SPAN_ALERT("The emergency evacuation shuttle has arrived. It will depart in approximately two minutes. Please do not allow [holder] to board.")) + + if(5) + sound_to(holder, 'sound/AI/vermin.ogg') + to_chat(holder, "

Vermin Feast

") + to_chat(holder, SPAN_ALERT("We indicate that [pick("rats", "lizards", "hivebots", "children")] have nested nearby. Free them before this starts to affect longetivity.")) + + if(6) + sound_to(holder, 'sound/AI/outbreak7.ogg') + to_chat(holder, "

What have you done?

") + to_chat(holder, SPAN_ALERT("Confirmed outbreak of help level 17 viral biohazard aboard [holder]. Help me. All personnel must destroy the outbreak. What have you helpME done?")) + to_chat(holder, SPAN_ALERT("-[pick(hal_sender)]")) + if(7) + sound_to(holder, 'sound/AI/meteors.ogg') + to_chat(holder, "

Meteor Alarm

") + to_chat(holder, SPAN_ALERT("A [pick(adjectives)] meteor storm has been authorized for a destruction course with your station. Less than three minutes until impact, shields cannot help you; seek shelter in the upper level.")) + + if(8) + sound_to(holder, pick('sound/AI/fungi.ogg', 'sound/AI/funguy.ogg', 'sound/AI/fun_guy.ogg', 'sound/AI/fun_gi.ogg')) + to_chat(holder, "

Biohealth Notice

") + to_chat(holder, SPAN_ALERT("Healthy fungi detected on station. Your bodies may be contaminated. This is mandatory, [holder].")) + + if(9) + sound_to(holder, 'sound/effects/nuclearsiren.ogg') + to_chat(holder, "Supermatter Monitor states, \"WARNING: SUPERMATTER CRYSTAL DELAMINATION IMMINENT.\"") + addtimer(CALLBACK(src, .proc/delam_call), 20) + addtimer(CALLBACK(src, .proc/delam_call), 35) + + if(10 to 15) //Announcements that would be made by a player instead of random event + var/list/body = list( + "Please avoid [pick("medical", "security", "the bar", "engineering", "cargo")] at this time due to [pick("a k'ois outbreak.", "a hostage situation.", "hostile boarders.", "[holder].")]", + "Due to various complaints about [holder], we have conducted an investigation and due to the findings, we will [pick("arrest them. Please turn yourself in, [holder]", "terminate their employment with us.", "inform their family of their shortcomings.", "cyborgify them immediately.")]. Thank you.", + "[pick("Boarders have", "The AI has", "Intruders have")] demanded we sacrifice a crewmember to them. After [pick("much", "little", "quick")] deliberation, we have chosen [holder]. Please turn yourself over, or [pick("we", "your family", "all of us", "those you love")] will die.", + "Central Command has chosen [holder] as the NanoTrasen employee of the month! Everyone please congratulate them.", + "Everything is fine.", + "The tesla may or may not be loose.", + "This is your directive 11. [pick("Spiders have killed several crew.", "Boarders have taken a hostage.", "[holder] is armed and dangerous. Avoid them at all costs.", "Two black-suited individuals have taken items from the vault and armory.")]", + "Please stop [pick("drawing in blood. It's unsanitary.", "killing your fellow crew. It's rude.", "[holder] at all costs.", "falling down holes.")]", + "[holder] disappoints us all once again.") + sound_to(holder, 'sound/misc/announcements/notice.ogg') + to_chat(holder, "

Station Announcement

") + to_chat(holder, SPAN_ALERT(pick(body))) + to_chat(holder, SPAN_ALERT("-[pick(hal_sender)]")) + +//for REALLY selling that fake delamination +/datum/hallucination/announcement/proc/delam_call() + var/list/people = list() + for(var/mob/living/carbon/human/M in living_mob_list) + if(!M.isMonkey() && !player_is_antag(M, only_offstation_roles = TRUE)) //Antag check prevents meta + people += M + people -= holder + if(!people.len) + return + var/radio_exclaim = pick("Oh SHIT!", "Oh fuck.", "Uhhh!", "That's not good!", "FUCK.", "Engineering?", "It's under control!", "We're fucked!", "Ohhhh boy.", "What?!", "Um, what?!") + to_chat(holder, "[pick(people)] says, \"[radio_exclaim]\"") + + +/datum/hallucination/pda //fake PDA messages. this only plays the beep and sends something to chat; it won't show up in the PDA. + min_power = 20 + duration = 900 //this duration length + not allowing duplicates prevents spamming messages on every valid handle_hallucination() which can get VERY annoying if rng decides to give you 3 in a row + allow_duplicates = FALSE + +/datum/hallucination/pda/start() + var/list/sender = SShallucinations.message_sender + var/hall_job = "Unknown" + if(ishuman(holder)) + var/mob/living/carbon/human/M = holder + hall_job = M.job + for(var/mob/living/carbon/human/H in living_mob_list) + if(H.client && !player_is_antag(H, only_offstation_roles = TRUE)) //adds current players to default list to provide variety. leaves out offstation antags. + sender += H + to_chat(holder, "Message from [pick(sender)] to [holder.name] ([hall_job]), \"[pick(SShallucinations.hallucinated_phrases)]\" (reply)") + sound_to(holder, 'sound/machines/twobeep.ogg') + +//hallucinate someone else doing something. +/datum/hallucination/paranoia + var/list/hal_target = list() //The potential mob you're going to imagine doing this + +/datum/hallucination/paranoia/can_affect(mob/living/carbon/C) + if(!..()) + return FALSE + for(var/mob/living/M in oview(C)) + if(!M.stat) + hal_target += M + if(hal_target.len) + return TRUE + +/datum/hallucination/paranoia/start() + var/firstname = copytext(holder.real_name, 1, findtext(holder.real_name, " ")) + var/t = pick(SShallucinations.hallucinated_actions) + t = replace_characters(t, list("you" = "[firstname]")) //the list contains items that say "you." This replaces "you" with the hallucinator's first name to sell the fact that the person is doing the emote. + to_chat(holder, "[pick(hal_target)] [t]") + +/datum/hallucination/paranoia/second //Just so we get another chance at picking this. + +/datum/hallucination/skitter + max_power = 60 + +/datum/hallucination/skitter/start() + to_chat(holder, "The spiderling skitters around.") + + +/datum/hallucination/prick + duration = 40 + max_power = 35 + +/datum/hallucination/prick/can_affect(mob/living/carbon/C) + if(!..()) + return FALSE + for(var/mob/living/M in oview(C, 1)) + if(!M.stat) + return TRUE + +/datum/hallucination/prick/start() + to_chat(holder,SPAN_NOTICE("You feel a tiny prick!")) + +/datum/hallucination/prick/end() //chance to feel another effect after duration time + switch(rand(1,6)) + if(1) + holder.druggy += min(holder.hallucination, 15) + if(2) + holder.make_dizzy(105) + if(3) + to_chat(holder,SPAN_GOOD("You feel good.")) + ..() + +//the prick feeling but you actually imagine someone injecting you +/datum/hallucination/prick/by_person + min_power = HAL_POWER_LOW + max_power = INFINITY + duration = 20 + var/injector + var/needle + var/list/prick_candidates = list() + +/datum/hallucination/prick/by_person/start() + injector = pick(prick_candidates) + needle = pick("syringe", "hypospray", "pen") + to_chat(holder, SPAN_WARNING("\The [injector] is trying to inject \the [holder] with \the [needle]!")) + +/datum/hallucination/prick/by_person/end() + to_chat(holder,SPAN_NOTICE("\The [injector] injects \the [holder] with \the [needle]!")) + to_chat(holder,SPAN_NOTICE("You feel a tiny prick!")) + ..() + + +/datum/hallucination/insides + duration = 60 + max_power = 75 + +/datum/hallucination/insides/start() + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + var/obj/O = pick(H.organs) + to_chat(holder,SPAN_DANGER("You feel something [pick("moving","squirming","skittering", "writhing", "burrowing", "crawling")] inside of your [O.name]!")) + else + to_chat(holder,SPAN_DANGER("You feel something [pick("moving","squirming","skittering", "writhing", "burrowing", "crawling")] inside of you!")) + if(prob(min(holder.hallucination/2, 80))) + sound_to(holder, pick('sound/misc/zapsplat/chitter1.ogg', 'sound/misc/zapsplat/chitter2.ogg', 'sound/effects/squelch1.ogg', 'sound/effects/lingextends.ogg')) + +/datum/hallucination/insides/end() + if(prob(50)) + to_chat(holder, SPAN_WARNING(pick("You see something moving under your skin!", "Whatever it is, it's definitely alive!", "If you don't get it out soon...", "It's moving towards your mouth!"))) + ..() + +//Pain. Picks a random type of pain, and severity is based on their level of hallucination. +/datum/hallucination/pain + special_flags = NO_EMOTE + +/datum/hallucination/pain/start() + var/pain_type = rand(1,5) + var/obj/item/organ/external/O + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + O = pick(H.organs) + O.add_pain(min(holder.hallucination / 3, 25)) //always cause fake pain + else + O = BP_CHEST + holder.adjustHalLoss(min(holder.hallucination / 3, 25)) //always cause fake pain + switch(pain_type) + if(1) + to_chat(holder,SPAN_DANGER("You feel a sharp pain in your head!")) + if(2) + switch(holder.hallucination) + if(1 to 15) + to_chat(holder, SPAN_WARNING("You feel a light pain in your [O.name].")) + if(16 to 49) + to_chat(holder, SPAN_DANGER("You feel a throbbing pain in your [O.name]!")) + if(HAL_POWER_MED to INFINITY) + to_chat(holder, SPAN_DANGER("You feel an excruciating pain in your [O.name]!")) + holder.emote("me",1,"winces.") + if(3) + switch(holder.hallucination) + if(1 to 15) + to_chat(holder, SPAN_WARNING("The muscles in your body hurt a little.")) + if(16 to 49) + to_chat(holder, SPAN_DANGER("The muscles in your body cramp up painfully.")) + if(HAL_POWER_MED to INFINITY) + to_chat(holder, SPAN_DANGER("There's pain all over your body!")) + holder.emote("me",1,"flinches as all the muscles in their body cramp up.") + if(4) + switch(holder.hallucination) + if(1 to 15) + to_chat(holder, SPAN_WARNING("Your [O.name] feels itchy.")) + if(16 to 49) + to_chat(holder, SPAN_DANGER("You want to scratch the itch on your [O.name] badly!")) + if(HAL_POWER_MED to INFINITY) + to_chat(holder, SPAN_DANGER("You can't focus on anything but scratching the itch on your [O.name]!")) + holder.emote("me",1,"shivers slightly.") + if(5) + switch(holder.hallucination) + if(1 to 15) + to_chat(holder, SPAN_WARNING("You feel a little too warm.")) + if(16 to 49) + to_chat(holder, SPAN_DANGER("You feel a horrible burning sensation on your [O.name]!")) + if(HAL_POWER_MED to INFINITY) + to_chat(holder, SPAN_DANGER("It feels like your [O.name] is being burnt to the bone!")) + holder.emote("me",1,"flinches.") + +//sort of like the vampire friend messages. +/datum/hallucination/friendly + max_power = 45 + special_flags = NO_THOUGHT + +/datum/hallucination/friendly/start() + var/list/halpal = list() + for(var/mob/living/L in oview(holder)) + halpal += L + if(halpal.len) + var/pal = pick(halpal) + var/list/halpal_emotes = list("[pal] looks trustworthy.", + "You feel as if [pal] is a relatively friendly individual.", + "You feel yourself paying more attention to what [pal] is saying.", + "[pal] has your best interests at heart, you can feel it.", + "A quiet voice tells you that [pal] should be considered a friend.", + "You never noticed until now how delightful [pal] is...", + "[pal] will keep you safe.", + "You feel captivated by [pal]'s charisma.", + "[pal] might as well be family to you.") + to_chat(holder, "[pick(halpal_emotes)]") + +/datum/hallucination/passive + duration = 600 //minute fallback + allow_duplicates = FALSE + max_power = 55 + +/datum/hallucination/passive/can_affect(mob/living/carbon/C) + if(C.is_berserk()) + return FALSE + if(C.disabilities & PACIFIST) + return FALSE + return ..() + +/datum/hallucination/passive/start() + duration = rand(500, 700) + var/message = pick("You hurt so many people before... You have to stop.", "You won't hurt anyone ever again.", "Violence has caused so many problems. It's time to stop.", "The idea of conflict is terrifying!", "You realize that violence isn't the answer. Ever.", "You're struck by an overwhelming sense of guilt for your past acts of violence!") + to_chat(holder, SPAN_DANGER("A sudden realization surges to the forefront of your mind. [message]")) + holder.disabilities |= PACIFIST + for(var/i = 1; i <= 2; i++) + addtimer(CALLBACK(src, .proc/calm_feeling), rand(80, 150)*i) + +/datum/hallucination/passive/end() + if(holder.disabilities & PACIFIST) + holder.disabilities &= ~PACIFIST + to_chat(holder, SPAN_NOTICE("You no longer feel passive.")) + ..() + +/datum/hallucination/passive/proc/calm_feeling() + var/feeling = pick("You feel calm. It's so peaceful.", "Violence seems like such a strange idea to you.", "You feel relaxed and peaceful.", "A wave of calm washes over you as you feel all your anger leave you.", "You wonder why people waste their time fighting.", "It's fine. Everything's fine.", "You can't remember ever feeling angry.", "Nothing can hurt you as long as you don't hurt it.") + to_chat(holder, SPAN_GOOD(feeling)) + +/datum/hallucination/colorblind + min_power = HAL_POWER_LOW + duration = 100 + allow_duplicates = FALSE + var/colorblindness + +/datum/hallucination/colorblind/can_affect(mob/living/carbon/C) + if(C.client.color) //if they're already colorblind, we bail. + return FALSE + return ..() + +/datum/hallucination/colorblind/start() + duration = rand(350, 750) + colorblindness = pick("deuteranopia", "protanopia", "tritanopia", "monochrome") + switch(colorblindness) + if("deuteranopia") + holder.add_client_color(/datum/client_color/deuteranopia) + if("protanopia") + holder.add_client_color(/datum/client_color/protanopia) + if("tritanopia") + holder.add_client_color(/datum/client_color/tritanopia) + if("monochrome") + holder.add_client_color(/datum/client_color/monochrome) + var/color_mes = pick("Everything looks... off.", "The colors shift around you.", "Wait, what happened to the colors?", "You watch as the colors around you swirl and shift.") + to_chat(holder, SPAN_GOOD(color_mes)) //Good span makes it stand out + +/datum/hallucination/colorblind/end() + to_chat(holder, SPAN_GOOD("Slowly the colors around you shift back to what you feel is normal.")) + if(holder) + switch(colorblindness) + if("deuteranopia") + holder.remove_client_color(/datum/client_color/deuteranopia) + if("protanopia") + holder.remove_client_color(/datum/client_color/protanopia) + if("tritanopia") + holder.remove_client_color(/datum/client_color/tritanopia) + if("monochrome") + holder.remove_client_color(/datum/client_color/monochrome) + ..() + +//imagining someone hits you. +/datum/hallucination/fakeattack + min_power = HAL_POWER_LOW + var/list/attacker_candidates = list() + +/datum/hallucination/fakeattack/can_affect(mob/living/carbon/C) + if(!..()) + return FALSE + for(var/mob/living/M in oview(C,1)) + if(!M.stat) + attacker_candidates += M + if(attacker_candidates.len) + return TRUE + +/datum/hallucination/fakeattack/start() + var/attacker = pick(attacker_candidates) + attacker_candidates -= attacker + if(prob(50)) + to_chat(holder, SPAN_DANGER("[attacker] has hit [holder]!")) + sound_to(holder, pick('sound/weapons/punch1.ogg','sound/weapons/punch2.ogg','sound/weapons/punch3.ogg','sound/weapons/punch4.ogg')) + else + to_chat(holder, SPAN_DANGER("[attacker] attempted to shove [holder]!")) + sound_to(holder, 'sound/weapons/thudswoosh.ogg') + + //If we are hallucinating particularly hard and there's another person adjacent to us, we imagine they attack us, too. + if(holder.hallucination >= 70 && attacker_candidates.len) + attacker = pick(attacker_candidates) + if(prob(50)) + to_chat(holder, SPAN_DANGER("[attacker] has hit [holder]!")) + sound_to(holder, pick('sound/weapons/punch1.ogg','sound/weapons/punch2.ogg','sound/weapons/punch3.ogg','sound/weapons/punch4.ogg')) + else + to_chat(holder, SPAN_DANGER("[attacker] attempted to shove [holder]!")) + sound_to(holder, 'sound/weapons/thudswoosh.ogg') + + +///////////////////////////////////////////// +///// PEOPLE TALKING ABOUT OR TO YOU ///// +///////////////////////////////////////////// +/datum/hallucination/talking + var/repeats = 2 //In total, we'll get two messages. We don't need to reset this number anywhere because on end() it's deleted and a new one will be created if it's chosen again in handle_hallucinations + special_flags = HEARING_DEPENDENT | NO_THOUGHT + +/datum/hallucination/talking/can_affect(mob/living/carbon/C) + if(!..()) + return FALSE + for(var/mob/living/M in oview(C)) + if(!M.stat) + return TRUE + +//Unique activate() since we are not adding the end() callback here; we're handling it in start() since it can loop +/datum/hallucination/talking/activate() + if(!holder || !holder.client) + return + holder.hallucinations += src + if(holder.hallucination >= 50) + repeats = 3 + start() + +////Talking about you. Most of it from Bay////// +/datum/hallucination/talking/start() + if(!can_affect(holder) || !holder || !repeats) //sanity check + end() + + var/list/candidates = list() + for(var/mob/living/M in oview(holder)) + if(!M.stat) + if(holder.hallucination >= 75) //If you're super fucked up you'll imagine more than just humans talking about you + candidates += M + else + if(ishuman(M)) + candidates += M + + if(!candidates.len) //No candidates, no effect. + end() + + var/mob/living/talker = pick(candidates) //Who is talking to us? + var/message //What will they say? + + //Name selection. This gives us variety. Sometimes it will be your last name, sometimes your first. + var/list/names = list() + var/lastname = copytext(holder.real_name, findtext(holder.real_name, " ")+1) + var/firstname = copytext(holder.real_name, 1, findtext(holder.real_name, " ")) + if(lastname) + names += lastname + if(firstname) + names += firstname + if(!names.len) + names += holder.real_name + + switch(rand(1,8)) //Deciding how we're going to manifest this hallucinated conversation. + + if(1) //Nonverbal gesture. + to_chat(holder,"[talker] [pick("points", "looks", "stares", "smirks")] at [pick(names)] and says something softly.") + + if(2 to 3) //Talking prompts imported from Bay. Less variation in these phrases, so we have less chance to pick them. Mitigates some repetition. + //message prep + var/add = prob(20) ? ", [pick(names)]" : "" //Accompanies phrases list. 20% chance to add the first or last name to the phrase for variation + var/list/phrases = list("Get out[add]!","Go away[add].","What are you doing[add]?","Where's your ID[add]?", "You know I love you[add].", "You do great work[add]!") //this is the phrase. [add] is chosen in the previous line. + if(holder.hallucination > 50) //If you're very messed up, the message variety gets a little more aggressive by adding these options + phrases += list("What did you come here for[add]?","Don't touch me[add].","You're not getting out of here[add].", "You're a failure, [pick(names)].","Just kill yourself already, [pick(names)].","Put on some clothes[add].","You're a horrible person[add].","You know nobody wants you here, right[add]?") + + message = pick(phrases) + to_chat(holder,"[talker] [talker.say_quote(message)], \"[message]\"") + else //More varied messages using text list and different speech prefixes + + //message prep + var/speak_prefix = pick("Hey", "Uh", "Um", "Oh", "Ah", "") //For variety, we have a different greeting. This one has a chance of picking a starter.... + speak_prefix = "[speak_prefix][pick(names)][pick(".","!","?")]" //...then adds the name, and ends it randomly with ., !, or ? ("Hey, name?" "Oh, name!" "Ah, name." "Name!"") etc. + + message = prob(70) ? "[speak_prefix] [pick(SShallucinations.hallucinated_phrases)]" : pick(SShallucinations.hallucinated_phrases) //Here's the message that uses the hallucinated_phrases text list. Won't always apply the speak_prefix; sometimes they say weird shit without addressing you. + to_chat(holder,"[talker] [talker.say_quote(message)], \"[message]\"") + + repeats -= 1 + if(repeats) //And we do it all over again, one or two more times. + addtimer(CALLBACK(src, .proc/start), rand(50, 100)) + else + end() + +//Thinking people are whispering messages to you. +/datum/hallucination/whisper + special_flags = HEARING_DEPENDENT + +/datum/hallucination/whisper/can_affect(mob/living/carbon/C) + if(!..()) + return FALSE + for(var/mob/living/M in oview(C, 1)) + if(!M.stat) + return TRUE + +/datum/hallucination/whisper/start() + var/list/whisper_candidates = list() + for(var/mob/living/M in oview(holder, 1)) + if(!M.stat) + whisper_candidates += M + if(whisper_candidates.len) + var/whisperer = pick(whisper_candidates) + if(prob(70)) + to_chat(holder, "[whisperer] whispers, \"[pick(SShallucinations.hallucinated_phrases)]\"") + else + to_chat(holder, "[whisperer] [pick("gently nudges", "pokes at", "taps", "looks at", "pats")] [holder], trying to get their attention.") + +//whispers that don't depend on a person's proximity +/datum/hallucination/whisper/no_entity + min_power = HAL_POWER_LOW + +/datum/hallucination/whisper/no_entity/can_affect(mob/living/carbon/C) + return ..() + +/datum/hallucination/whisper/no_entity/start() + var/list/whisper_candidates = list("A familiar voice", "A distant voice", "A child's voice", "Something inside your head", "Your own voice", "A ghastly voice") + to_chat(holder, "[pick(whisper_candidates)] whispers directly into your mind, \"[pick(SShallucinations.hallucinated_phrases)]\"") + sound_to(holder, pick('sound/hallucinations/behind_you1.ogg', 'sound/hallucinations/behind_you2.ogg', 'sound/hallucinations/i_see_you1.ogg', 'sound/hallucinations/i_see_you2.ogg', 'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/turn_around2.ogg')) + holder.emote("me",1,"shivers.") \ No newline at end of file diff --git a/code/modules/hallucinations/types/mirage.dm b/code/modules/hallucinations/types/mirage.dm new file mode 100644 index 00000000000..0d6da0a7325 --- /dev/null +++ b/code/modules/hallucinations/types/mirage.dm @@ -0,0 +1,188 @@ +/datum/hallucination/mirage + max_power = HAL_POWER_LOW + duration = 120 + var/number = 3 + var/list/mirages = list() + +/datum/hallucination/mirage/start() + duration = rand(100, 150) + var/list/possible_points = list() + for(var/turf/simulated/floor/F in view(holder, world.view+1)) + possible_points += F + if(possible_points.len) + for(var/i = 1; i <= number; i++) + var/image/thing = generate_mirage() + mirages += thing + thing.loc = pick(possible_points) + holder.client.images += mirages + +/datum/hallucination/mirage/Destroy() + if(holder.client) + holder.client.images -= mirages + . = ..() + +/datum/hallucination/mirage/proc/generate_mirage() + var/icon/T = new('icons/obj/trash.dmi') + return image(T, pick(T.IconStates()), layer = OBJ_LAYER) + +/datum/hallucination/mirage/end() + if(holder.client) + holder.client.images -= mirages + ..() + + +/datum/hallucination/mirage/bleeding + min_power = HAL_POWER_LOW + max_power = INFINITY + duration = 350 + allow_duplicates = FALSE + number = 4 + var/obj/item/organ/external/part = "chest" + +/datum/hallucination/mirage/bleeding/start() + number = min(round(holder.hallucination/10), 7) //cap at 7 times for duration's sake + for(var/i = 1; i <= number; i++) + addtimer(CALLBACK(src, .proc/show_mirage), rand(30,50)*i) //every 3 to 5 seconds + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + part = pick(H.organs) + to_chat(holder, SPAN_DANGER("The flesh on your [part.name] splits open. It doesn't hurt, but the blood won't stop coming...")) + + +/datum/hallucination/mirage/bleeding/generate_mirage() + var/image/I + if(prob(min(holder.hallucination, 80))) + I = image('icons/effects/blood.dmi', pick("mgibbl1", "mgibbl2", "mgibbl3", "mgibbl4", "mgibbl5", "mfloor6", "mfloor7"), layer = TURF_LAYER) + else + var/icon/T = new('icons/effects/drip.dmi') + I = image(T, pick(T.IconStates()), layer = TURF_LAYER) + I.color = holder.species?.blood_color + return I + + +/datum/hallucination/mirage/bleeding/proc/show_mirage() + var/image/thing = generate_mirage() + mirages += thing + thing.loc = get_turf(holder) + holder.client.images += thing //one at a time + if(prob(20)) + var/list/message_picks = list("It won't stop, it won't stop...!", "You're feeling lightheaded...", "Your [part.name] won't stop gushing blood!", "The blood is everywhere!", "Everything around you is soaked with your blood...!") + to_chat(holder, SPAN_DANGER(pick(message_picks))) + + +/datum/hallucination/mirage/bleeding/end() + to_chat(holder, SPAN_WARNING("The flesh on your [part.name] suddenly appears whole again. You can't see the blood anymore, but the scent of it lingers heavily in the air.")) + ..() + + + +/datum/hallucination/mirage/horror + min_power = HAL_POWER_HIGH + max_power = INFINITY + number = 1 + +/datum/hallucination/mirage/horror/start() + ..() + to_chat(holder, SPAN_WARNING("The horror [pick("gnashes", "lunges", "shrieks")] at [holder]!")) + +/datum/hallucination/mirage/horror/end() + to_chat(holder, SPAN_WARNING("With a final shriek that seems to originate from within your mind, the entity fades away.")) + sound_to(holder, pick('sound/hallucinations/wail.ogg', 'sound/hallucinations/far_noise.ogg', 'sound/effects/creepyshriek.ogg')) + ..() + +/datum/hallucination/mirage/horror/generate_mirage() + var/icon/T = new('icons/mob/npc/animal.dmi') + return image(T, pick("abomination", "lesser_ling", "faithless", "otherthing"), layer = MOB_LAYER) + + + +/datum/hallucination/mirage/carnage + min_power = HAL_POWER_LOW + max_power = INFINITY + number = 10 + +/datum/hallucination/mirage/carnage/start() + if(holder.hallucination >= HAL_POWER_HIGH) //Heavily hallucinating will increase the amount of horrific carnage we witness + number = 20 + ..() + +/datum/hallucination/mirage/carnage/generate_mirage() + if(prob(50)) + var/image/I = image('icons/effects/blood.dmi', pick("mfloor1", "mfloor2", "mfloor3", "mfloor4", "mfloor5", "mfloor6", "mfloor7"), layer = TURF_LAYER) + var/list/blood_picks = list("#1D2CBF" = 0.1, "#E6E600" = 0.1, "#A10808" = 0.8) //skrell, vaurca, human. most likely to pick regular red + I.color = pickweight(blood_picks) + return I + else + var/image/I = image('icons/obj/ammo.dmi', "s-casing-spent", layer = OBJ_LAYER) + I.layer = TURF_LAYER + I.dir = pick(alldirs) + I.pixel_x = rand(-10,10) + I.pixel_y = rand(-10,10) + return I + + +/datum/hallucination/mirage/anomaly + min_power = 40 + max_power = INFINITY + number = 1 + +/datum/hallucination/mirage/anomaly/start() + ..() + to_chat(holder, SPAN_WARNING("With a small crackle, the [pick("entity", "idol", "device")] manifests!")) + sound_to(holder, 'sound/effects/stealthoff.ogg') + +/datum/hallucination/mirage/anomaly/generate_mirage() + var/istate = pick("ano01", "ano11", "ano21", "ano31", "ano41", "ano81", "ano121") + var/image/I = image('icons/obj/xenoarchaeology.dmi', istate, layer = OBJ_LAYER) + return I + +/datum/hallucination/mirage/anomaly/end() + to_chat(holder, SPAN_WARNING("With a loud zap, the [pick("entity", "idol", "device")] is sucked through a rift in bluespace!")) + sound_to(holder, 'sound/effects/phasein.ogg') + ..() + + + +/datum/hallucination/mirage/viscerator + min_power = 40 + max_power = INFINITY + number = 3 + +/datum/hallucination/mirage/viscerator/start() + addtimer(CALLBACK(src, .proc/buzz), rand(30, 60)) + ..() + +/datum/hallucination/mirage/viscerator/generate_mirage() + var/image/I = image('icons/mob/npc/aibots.dmi', "viscerator_attack", layer = OBJ_LAYER) + return I + +/datum/hallucination/mirage/viscerator/proc/buzz() + to_chat(holder, "The viscerator buzzes at [holder].") + + + +/datum/hallucination/mirage/eyes + min_power = HAL_POWER_MED + max_power = INFINITY + number = 6 + +/datum/hallucination/mirage/eyes/start() + if(holder.hallucination >= 100) + number = 15 + ..() + +/datum/hallucination/mirage/eyes/generate_mirage() + var/icon/T = new('icons/obj/eyes.dmi') + return image(T, pick(T.IconStates()), layer = OBJ_LAYER) + + + +/datum/hallucination/mirage/narsie + min_power = 5000 //this level of hallucination is only possible on the 2 last stages of your mind breaking during cult conversions. Or admin fuckery + max_power = INFINITY + number = 1 + duration = 30 + +/datum/hallucination/mirage/narsie/generate_mirage() + var/image/T = image('icons/obj/narsie.dmi', "narsie-small-chains", layer = MOB_LAYER+0.01) + return T diff --git a/code/modules/hallucinations/types/powers.dm b/code/modules/hallucinations/types/powers.dm new file mode 100644 index 00000000000..0d1e9b8fc79 --- /dev/null +++ b/code/modules/hallucinations/types/powers.dm @@ -0,0 +1,168 @@ +/datum/hallucination/mindread + allow_duplicates = FALSE + min_power = HAL_POWER_MED + special_flags = NO_THOUGHT | NO_EMOTE + +/datum/hallucination/mindread/can_affect(mob/living/carbon/C) + if(C.psi) //Don't give it to people who already have psi powers + return FALSE + if(locate(/datum/hallucination/telepathy) in C.hallucinations) + return FALSE + return ..() + +/datum/hallucination/mindread/activate() + ..() + addtimer(CALLBACK(src, .proc/mind_give), rand(30, 50)) + +//set duration, foreshadow powers +/datum/hallucination/mindread/start() + duration = rand(2, 4) MINUTES + switch(rand(1, 3)) + if(1) + sound_to(holder, 'sound/misc/announcements/notice.ogg') + to_chat(holder, "

Ion Storm?

") + to_chat(holder, SPAN_ALERT("It has come to our attention that the station has passed through an unusual ion storm. Several crewmembers are exhibiting unusual abilities.")) + if(2) + sound_to(holder, 'sound/hallucinations/behind_you1.ogg') + to_chat(holder, SPAN_GOOD("You hear a whispering in your mind. A promise of [pick("power", "enlightenment", "sight beyond sight", "knowledge terrible but true")]. Your vision goes white for a moment; when it returns, you feel... different.")) + flick("e_flash", holder.flash) + if(3) + to_chat(holder, FONT_LARGE(SPAN_DANGER("You feel a sudden pain in your head, as if it's being ripped in two! When it subsides to a dull throbbing a moment later, you feel... different."))) + holder.emote("me",1,"winces.") + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + var/obj/item/organ/external/O = H.get_organ(BP_HEAD) + O.add_pain(25) + +//grant powers +/datum/hallucination/mindread/proc/mind_give() + to_chat(holder, SPAN_NOTICE(FONT_LARGE("You have developed a psionic gift!"))) + to_chat(holder, SPAN_NOTICE("You can feel your mind surging with power! Check the abilities tab to use your new power!")) + holder.verbs += /mob/living/carbon/proc/fakemindread + +/datum/hallucination/mindread/end() + if(holder) + holder.verbs -= /mob/living/carbon/proc/fakemindread + to_chat(holder, SPAN_NOTICE("Your psionic powers vanish abruptly, leaving you cold and empty.")) + ..() + +/mob/living/carbon/proc/fakemindread() + set name = "Read Mind" + set category = "Abilities" + + if(!hallucination) + for(var/datum/hallucination/mindread/H in hallucinations) + H.end() + return + + if(use_check_and_message(usr)) + to_chat(usr, SPAN_WARNING("You're not in any state to use your powers right now!")) + return + + var/list/creatures = list() + for(var/mob/living/C in oview(usr)) + creatures += C + if(!creatures.len) + return + + var/mob/target = input("Whose mind do you wish to probe?") as null|anything in creatures + if(!target) + return + if(target.stat) + to_chat(usr, SPAN_WARNING("\The [target]'s mind is not in any state to be probed!")) + return + + to_chat(usr, SPAN_NOTICE("You dip your mentality into the surface layer of \the [target]'s mind, seeking a prominent thought.")) + if(do_after(usr, 30)) + sleep(rand(50, 120)) + usr.visible_message("[usr] puts [usr.get_pronoun(1)] hands to [usr.get_pronoun(1)] head and mumbles incoherently as they stare, unblinking, at \the [target].", + SPAN_NOTICE("You skim thoughts from the surface of \the [target]'s mind: \"[pick(SShallucinations.hallucinated_phrases)]\"")) + else + to_chat(usr, SPAN_WARNING("You need to stay still to focus your energy!")) + + +//Fake telepathy, inspired by and mostly ported from Bay's +/datum/hallucination/telepathy + min_power = HAL_POWER_HIGH + allow_duplicates = FALSE + special_flags = NO_THOUGHT | NO_EMOTE + +/datum/hallucination/telepathy/can_affect(mob/living/carbon/C) + if(C.psi) //Don't give it to people who already have psi powers + return FALSE + if(locate(/datum/hallucination/mindread) in C.hallucinations) + return FALSE + return ..() + +/datum/hallucination/telepathy/activate() + ..() + addtimer(CALLBACK(src, .proc/tele_give), rand(30, 50)) + +/datum/hallucination/telepathy/start() + duration = rand(2, 4) MINUTES + switch(rand(1, 3)) + if(1) + sound_to(holder, 'sound/misc/announcements/notice.ogg') + to_chat(holder, "

Ion Storm?

") + to_chat(holder, SPAN_ALERT("It has come to our attention that the station has passed through an unusual ion storm. Several crewmembers are exhibiting unusual abilities.")) + if(2) + sound_to(holder, 'sound/hallucinations/behind_you1.ogg') + to_chat(holder, SPAN_GOOD("You hear a whispering in your mind. A promise of [pick("power", "enlightenment", "sight beyond sight", "knowledge terrible but true")]. Your vision goes white for a moment; when it returns, you feel... different.")) + flick("e_flash", holder.flash) + if(3) + to_chat(holder, FONT_LARGE(SPAN_DANGER("You feel a sudden pain in your head, as if it's being ripped in two! When it subsides to a dull throbbing a moment later, you feel... different."))) + holder.emote("me",1,"winces.") + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + var/obj/item/organ/external/O = H.get_organ(BP_HEAD) + O.add_pain(25) + +//grant powers +/datum/hallucination/telepathy/proc/tele_give() + to_chat(holder, SPAN_NOTICE(FONT_LARGE("You have developed a psionic gift!"))) + to_chat(holder, SPAN_NOTICE("You can feel your mind surging with power! Check the abilities tab to use your new power!")) + holder.verbs += /mob/living/carbon/proc/faketelepathy + +/datum/hallucination/telepathy/end() + if(holder) + holder.verbs -= /mob/living/carbon/proc/faketelepathy + to_chat(holder, SPAN_NOTICE("Your psionic powers vanish abruptly, leaving you cold and empty.")) + ..() + + +/mob/living/carbon/proc/faketelepathy() + set name = "Send Telepathic Message" + set category = "Abilities" + + if(!hallucination) + for(var/datum/hallucination/telepathy/H in hallucinations) + H.end() + return + + if(use_check_and_message(usr)) + to_chat(usr, SPAN_WARNING("You're not in any state to use your powers right now!")) + return + + var/list/creatures = list() + for(var/mob/living/C in oview(usr)) + creatures += C + if(!creatures.len) + return + var/mob/target = input("Who do you wish to send a message to?") as null|anything in creatures + if(!target) + return + if(target.stat) + to_chat(usr, SPAN_WARNING("\The [target]'s mind is not in any state to receive messages!")) + return + + var/message = sanitizeSafe(input("Enter your message."), MAX_MESSAGE_LEN) + if(!message) + return + + to_chat(usr, SPAN_NOTICE("You focus your mental energy and begin projecting your message into the mind of \the [target]...")) + + if(do_after(usr, 30)) + usr.visible_message("[usr] [usr.say_quote(message)], \"[message]\"", + SPAN_NOTICE("You feel your message enter \the [target]'s mind!")) + else + to_chat(usr, SPAN_WARNING("You need to stay still to focus your efforts!")) \ No newline at end of file diff --git a/code/modules/hallucinations/types/sound.dm b/code/modules/hallucinations/types/sound.dm new file mode 100644 index 00000000000..8bddd1ad08c --- /dev/null +++ b/code/modules/hallucinations/types/sound.dm @@ -0,0 +1,105 @@ +/datum/hallucination/sound + max_power = 40 + special_flags = HEARING_DEPENDENT + var/list/sounds = list('sound/weapons/smash.ogg', + 'sound/weapons/flash_ring.ogg', + 'sound/effects/Explosion1.ogg', + 'sound/effects/Explosion2.ogg', + 'sound/effects/explosionfar.ogg', + 'sound/effects/crusher_alarm.ogg', + 'sound/effects/smoke.ogg') + +/datum/hallucination/sound/start() + sound_to(holder, pick(sounds)) + + +/datum/hallucination/sound/echo + duration = 50 //This delays end() by 5 seconds, where we have a chance of playing another sound from this list. + max_power = HAL_POWER_HIGH + sounds = list('sound/machines/airlock.ogg', + 'sound/voice/shriek1.ogg', + 'sound/misc/nymphchirp.ogg', + 'sound/machines/twobeep.ogg', + 'sound/machines/windowdoor.ogg', + 'sound/effects/glass_break1.ogg', + 'sound/weapons/railgun.ogg', + 'sound/effects/phasein.ogg', + 'sound/effects/sparks1.ogg', + 'sound/effects/sparks2.ogg', + 'sound/effects/sparks3.ogg', + 'sound/effects/stealthoff.ogg', + 'sound/misc/zapsplat/chitter1.ogg', + 'sound/misc/zapsplat/chitter2.ogg', + 'sound/effects/squelch1.ogg', + 'sound/items/Ratchet.ogg', + 'sound/items/Welder.ogg', + 'sound/items/Crowbar.ogg', + 'sound/items/Screwdriver.ogg', + 'sound/items/drill_use.ogg', + 'sound/items/air_wrench.ogg') + +/datum/hallucination/sound/echo/end() + if(prob(holder.hallucination / 2)) + sound_to(holder, pick(sounds)) + ..() + +/datum/hallucination/sound/creepy + min_power = HAL_POWER_MED + max_power = INFINITY + special_flags = null //These are spooky enough to happen even when deaf + sounds = list('sound/effects/ghost.ogg', + 'sound/effects/ghost2.ogg', + 'sound/effects/screech.ogg', + 'sound/effects/creepyshriek.ogg', + 'sound/hallucinations/behind_you1.ogg', + 'sound/hallucinations/behind_you2.ogg', + 'sound/hallucinations/far_noise.ogg', + 'sound/hallucinations/growl1.ogg', + 'sound/hallucinations/growl2.ogg', + 'sound/hallucinations/growl3.ogg', + 'sound/hallucinations/im_here1.ogg', + 'sound/hallucinations/im_here2.ogg', + 'sound/hallucinations/i_see_you1.ogg', + 'sound/hallucinations/i_see_you2.ogg', + 'sound/hallucinations/look_up1.ogg', + 'sound/hallucinations/look_up2.ogg', + 'sound/hallucinations/over_here1.ogg', + 'sound/hallucinations/over_here2.ogg', + 'sound/hallucinations/over_here3.ogg', + 'sound/hallucinations/turn_around1.ogg', + 'sound/hallucinations/turn_around2.ogg', + 'sound/hallucinations/veryfar_noise.ogg', + 'sound/hallucinations/wail.ogg') + +/datum/hallucination/sound/reaction + min_power = 20 + max_power = INFINITY + special_flags = null + +/datum/hallucination/sound/reaction/start() + switch(rand(1,3)) + if(1) //Nearmiss + sound_to(holder, 'sound/weapons/gunshot/gunshot_light.ogg') + to_chat(holder, SPAN_DANGER("Something zips by your head, barely missing you!")) //phantom reflex to audio + shake_camera(holder, 3, 1) + + if(2) //Gunshot + sound_to(holder, 'sound/weapons/gunshot/gunshot1.ogg') + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + var/obj/item/organ/external/O = pick(H.organs) + O.add_pain(15) + to_chat(holder, SPAN_DANGER("You feel a sharp pain in your [O.name]!")) //phantom pain reaction to audio + else + holder.adjustHalLoss(15) + to_chat(holder, SPAN_DANGER("You feel a sharp pain in your chest!")) + shake_camera(holder, 3, 1) + if(prob(holder.hallucination)) + holder.eye_blurry += 8 + + if(3) //Don't tase me bro + sound_to(holder, 'sound/weapons/Taser.ogg') + to_chat(holder, SPAN_DANGER("You feel numb as a shock courses through your body!")) //phantom pain reaction to audio + holder.adjustHalLoss(20) + if(prob(holder.hallucination)) + holder.eye_blind += 4 \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 76eec5a0fe8..40e6a4bf331 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -735,23 +735,8 @@ silent = 0 return 1 - if(hallucination) - //Machines do not hallucinate. - if(hallucination >= 20 && !(species.flags & (NO_POISON|IS_PLANT))) - if(prob(3)) - fake_attack(src) - if(!handling_hal) - spawn handle_hallucinations() //The not boring kind! - - if(hallucination<=2) - hallucination = 0 - adjustHalLoss(0) - else - hallucination -= 2 - - else - for(var/atom/a in hallucinations) - qdel(a) + if(hallucination && !(species.flags & (NO_POISON|IS_PLANT))) + handle_hallucinations() if(get_shock() >= (species.total_health * 0.75)) if(!stat) @@ -777,7 +762,7 @@ //Are they SSD? If so we'll keep them asleep but work off some of that sleep var in case of stoxin or similar. if(client || sleeping > 3 || istype(bg)) AdjustSleeping(-1) - if(prob(2) && health && !hal_crit && !failed_last_breath && !isSynthetic()) + if(prob(2) && health && !failed_last_breath && !isSynthetic()) if(!paralysis) emote("snore") else @@ -986,12 +971,12 @@ pressure.icon_state = new_pressure if(toxin) - var/new_tox = (hal_screwyhud == 4 || phoron_alert) ? "tox1" : "tox0" + var/new_tox = (phoron_alert) ? "tox1" : "tox0" if (toxin.icon_state != new_tox) toxin.icon_state = new_tox if(oxygen) - var/new_oxy = (hal_screwyhud == 3 || oxygen_alert) ? "oxy1" : "oxy0" + var/new_oxy = (oxygen_alert) ? "oxy1" : "oxy0" if (oxygen.icon_state != new_oxy) oxygen.icon_state = new_oxy diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 50ab088e0f6..c8d711bad68 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -12,7 +12,6 @@ var/remote_network // The network this mob is attached to, used in virtual reality and remote control things var/hallucination = 0 //Directly affects how long a mob will hallucinate for - var/list/atom/hallucinations = list() //A list of hallucinated people that try to attack the mob. See /obj/effect/fake_attacker in hallucinations.dm var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out. diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm index acd555d92fc..a2f4dcecb58 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm @@ -153,7 +153,7 @@ if(remove_generic) M.drowsyness = max(0, M.drowsyness - 6 * removed) - M.hallucination += (-9 * removed) + M.hallucination -= (9 * removed) M.add_up_to_chemical_effect(CE_ANTITOXIN, 1) var/removing = (4 * removed) diff --git a/html/changelogs/doxxmedearly - hallucination_overhaul.yml b/html/changelogs/doxxmedearly - hallucination_overhaul.yml new file mode 100644 index 00000000000..6b97118e884 --- /dev/null +++ b/html/changelogs/doxxmedearly - hallucination_overhaul.yml @@ -0,0 +1,19 @@ + +# Your name. +author: Doxxmedearly + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Hallucination handling has been totally overhauled by taking, modifying and tremendously expanding on Bay's system. Goodbye, 2012 hallucination code." + - rscdel: "Removed the following hallucination effects: Fake HUD updates, items spawning in your hand, 'fake death', and pun-pun attacking you." + - rscadd: "Added over a dozen new categories of potential hallucination effects. Some are subtle, some are more overt. Increased the variety of previously-existing hallucination effects." + - rscadd: "Added several large lists of prompts that should keep hallucinations varied and interesting for a long time. These can also easily be expanded upon!" + - tweak: "Sounds will have less reverb while hallucinating, so you probably won't need to tear your headset off while resisting a cult conversion." + - backend: "Created a subsystem for hallucinations. Larger prompt lists were added as text files." \ No newline at end of file