Hallucination Refactor (#17661)

* begone accursed beast

* cleanup and notes

* screen object fix

* varset clear

* don't raw send sounds, spark on door sound, pain hallucination

* lengthen this a bit

* more pain messages, become jittery

* note

* lets clean that up to

* Revert "lets clean that up to"

This reverts commit 2409e41870.

* cleanup and define

* use accessors

* death isn't working so have toxins instead, it's scarier

* don't actually clean these up, because of how xenochi works

* additional protection

* hunger joke

* Use get_component instead of a cache

* empty scope removed

* Updated xenochi code to use hallu component

---------

Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
Will
2025-05-15 03:17:39 -04:00
committed by GitHub
parent 8eb403bf73
commit 709e9d130c
12 changed files with 689 additions and 610 deletions
+111 -386
View File
@@ -8,407 +8,132 @@ 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
/datum/component/hallucinations
dupe_mode = COMPONENT_DUPE_UNIQUE // First come first serve
/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
//to_chat(src, "Screwy HUD")
hal_screwyhud = pick(1,2,3,3,4,4)
spawn(rand(100,250))
hal_screwyhud = 0
if(16 to 25)
//Strange items
//to_chat(src, "Traitor 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(ishuman(src))
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.hud_layerise()
switch(rand(1,6))
if(1) //revolver
halitem.icon = 'icons/obj/gun.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
//to_chat(src, "Danger Flash")
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)
VAR_PRIVATE/mob/living/carbon/human/our_human = null
VAR_PRIVATE/datum/weakref/halimage
VAR_PRIVATE/datum/weakref/halbody
VAR_PRIVATE/list/halitem = list() // weakref pair of obj-key, client-value
switch(rand(1,3))
if(1)
//to_chat(src, "Space")
halimage = image('icons/turf/space.dmi',target,"[rand(1,25)]",TURF_LAYER)
if(2)
//to_chat(src, "Fire")
halimage = image('icons/effects/fire.dmi',target,"1",TURF_LAYER)
if(3)
//to_chat(src, "C4")
halimage = image('icons/obj/assemblies.dmi',target,"plastic-explosive2",OBJ_LAYER+0.01)
VAR_PRIVATE/hal_crit = FALSE
VAR_PRIVATE/hal_screwyhud = HUD_HALLUCINATION_NONE
/datum/component/hallucinations/Initialize()
if(!ishuman(parent))
return COMPONENT_INCOMPATIBLE
our_human = parent
make_timer()
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
//to_chat(src, "Strange Audio")
switch(rand(1,12))
if(1) src << 'sound/machines/door/old_airlock.ogg'
if(2)
if(prob(50))src << 'sound/effects/Explosion1.ogg'
else src << 'sound/effects/Explosion2.ogg'
if(3) src << 'sound/effects/explosionfar.ogg'
if(4) src << 'sound/effects/Glassbr1.ogg'
if(5) src << 'sound/effects/Glassbr2.ogg'
if(6) src << 'sound/effects/Glassbr3.ogg'
if(7) src << 'sound/machines/twobeep.ogg'
if(8) src << 'sound/machines/door/windowdoor.ogg'
if(9)
//To make it more realistic, I added two gunshots (enough to kill)
src << 'sound/weapons/Gunshot1.ogg'
spawn(rand(10,30))
src << 'sound/weapons/Gunshot2.ogg'
if(10) src << 'sound/weapons/smash.ogg'
if(11)
//Same as above, but with tasers.
src << 'sound/weapons/Taser.ogg'
spawn(rand(10,30))
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')
src << pick(creepyasssounds)
if(66 to 70)
//Flashes of danger
//to_chat(src, "Danger Flash")
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/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
SetSleeping(20)
hal_crit = 1
hal_screwyhud = 1
spawn(rand(50,100))
// src.sleeping_willingly = 0
SetSleeping(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 Site Manager 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 += "<a href='byond://?src=\ref[src];[button]'>[button_txt]</a><br>"
buttons -= button
possible_txt -= button_txt
return start_txt + mocktxt + end_txt + "</TT></BODY></HTML>"
/proc/check_panel(mob/M)
if (ishuman(M) || isAI(M))
if(M.hallucination < 15)
return 1
return 0*/
/obj/effect/fake_attacker
icon = null
icon_state = null
name = ""
desc = ""
density = FALSE
anchored = TRUE
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
/obj/effect/fake_attacker/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, span_bolddanger("[my_target] flails around wildly."))
my_target.show_message(span_bolddanger("[src] has been attacked by [my_target] "), 1) //Lazy.
src.health -= P.force
return
/obj/effect/fake_attacker/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, span_bolddanger("[my_target] stumbles around."))
/obj/effect/fake_attacker/Initialize(mapload)
/datum/component/hallucinations/Destroy(force)
if(halitem.len)
remove_hallucination_item()
our_human = null
. = ..()
QDEL_IN(src, 30 SECONDS)
step_away(src,my_target,2)
addtimer(CALLBACK(src, PROC_REF(attack_loop)), rand(5, 10), TIMER_DELETE_ME)
/obj/effect/fake_attacker/Destroy()
if(my_target)
my_target.hallucinations -= src
return ..()
/datum/component/hallucinations/proc/make_timer()
PROTECTED_PROC(TRUE)
addtimer(CALLBACK(src, PROC_REF(trigger)), ((rand(20,50) SECONDS) / (min(our_human.hallucination,100)/25)), TIMER_DELETE_ME)
/datum/component/hallucinations/proc/get_fakecrit()
SHOULD_NOT_OVERRIDE(TRUE)
return hal_crit
/obj/effect/fake_attacker/proc/updateimage()
// qdel(src.currentimage)
/datum/component/hallucinations/proc/get_hud_state()
SHOULD_NOT_OVERRIDE(TRUE)
return hal_screwyhud
if(src.dir == NORTH)
qdel(src.currentimage)
src.currentimage = new /image(up,src)
else if(src.dir == SOUTH)
qdel(src.currentimage)
src.currentimage = new /image(down,src)
else if(src.dir == EAST)
qdel(src.currentimage)
src.currentimage = new /image(right,src)
else if(src.dir == WEST)
qdel(src.currentimage)
src.currentimage = new /image(left,src)
my_target << currentimage
/obj/effect/fake_attacker/proc/attack_loop()
if(src.health < 0)
collapse()
addtimer(CALLBACK(src, PROC_REF(attack_loop)), rand(5, 10), TIMER_DELETE_ME)
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Traditional hallucinations
/////////////////////////////////////////////////////////////////////////////////////////////////////
/mob/living/carbon/proc/handle_hallucinations()
if(get_hallucination_component() || !client)
return
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(span_bolddanger("[my_target] has been attacked with [weapon_name] by [src.name]!"), 1)
my_target.halloss += 8
if(prob(20)) my_target.eye_blurry += 3
if(prob(33))
if(!locate(/obj/effect/overlay) in my_target.loc)
fake_blood(my_target)
LoadComponent(/datum/component/hallucinations)
/mob/living/carbon/proc/get_hallucination_component()
RETURN_TYPE(/datum/component/hallucinations)
return GetComponent(/datum/component/hallucinations)
/datum/component/hallucinations/proc/trigger()
PROTECTED_PROC(TRUE)
if(QDELETED(our_human))
qdel(src)
return
if(!our_human.client)
qdel(src)
return
if(our_human.hallucination < HALLUCINATION_THRESHOLD)
qdel(src)
return
handle_hallucinating()
make_timer()
/datum/component/hallucinations/proc/handle_hallucinating()
PROTECTED_PROC(TRUE)
var/halpick = rand(1,100)
switch(halpick)
if(0 to 15)
event_hudscrew()
if(16 to 25)
event_fake_item()
if(26 to 40)
event_flash_environmental_threats()
if(41 to 65)
event_strange_sound()
if(66 to 70)
if(prob(20) && our_human.nutrition < 100)
event_hunger() // Hungi, meant for xenochi but this is too funny to pass up
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(span_bolddanger("[src.name] has punched [my_target]!"), 1)
my_target.halloss += 4
if(prob(33))
if(!locate(/obj/effect/overlay) in my_target.loc)
fake_blood(my_target)
event_flash_monsters()
if(71 to 72)
event_sleeping()
if(73 to 75) // If you don't want hallucination beatdowns, comment this out
event_attacker()
if(76 to 80)
event_painmessage()
if(prob(15))
step_away(src,my_target,2)
addtimer(CALLBACK(src, PROC_REF(attack_loop)), rand(5, 10), TIMER_DELETE_ME)
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Xenochimera hallucinations
// Unlike normal hallucinations this one is triggered from handle_feral randomly.
// So it destroys itself after it triggers, freeing up space for the next run of it.
/////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/component/hallucinations/xenochimera/make_timer()
var/datum/component/xenochimera/XC = our_human.GetComponent(/datum/component/xenochimera)
var/F = XC ? (XC.feral / 10) : 1
addtimer(CALLBACK(src, PROC_REF(trigger)), ((rand(20,50) SECONDS) / F), TIMER_DELETE_ME)
/obj/effect/fake_attacker/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, 30 SECONDS)
var/list/non_fakeattack_weapons = list(/obj/item/gun/projectile, /obj/item/ammo_magazine/s357,\
/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/chameleon, /obj/item/card/emag,\
/obj/item/storage/toolbox/syndicate, /obj/item/aiModule,\
/obj/item/radio/headset/syndicate, /obj/item/plastique,\
/obj/item/powersink, /obj/item/storage/box/syndie_kit,\
/obj/item/toy/syndicateballoon, /obj/item/gun/energy/captain,\
/obj/item/hand_tele, /obj/item/rcd, /obj/item/tank/jetpack,\
/obj/item/clothing/under/rank/captain, /obj/item/aicard,\
/obj/item/clothing/shoes/magboots, /obj/item/areaeditor/blueprints, /obj/item/disk/nuclear,\
/obj/item/clothing/suit/space/void, /obj/item/tank)
/proc/fake_attack(var/mob/living/target)
var/list/possible_clones = new/list()
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
possible_clones += H
// 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(!possible_clones.len)
/datum/component/hallucinations/xenochimera/trigger()
if(QDELETED(our_human))
qdel(src)
return
clone = pick(possible_clones)
if(!clone)
if(!our_human.client)
qdel(src)
return
var/datum/component/xenochimera/XC = our_human.GetComponent(/datum/component/xenochimera)
if(!XC || XC.feral < XENOCHIFERAL_THRESHOLD)
qdel(src)
return
handle_hallucinating()
QDEL_IN(src,rand(3,9)SECONDS)
//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.base = new /icon(clone.stand_icon)
// F.currentimage = new /image(clone)
/*
F.left = new /icon(clone.stand_icon,dir=WEST)
for(var/icon/i in clone.overlays)
F.left.Blend(i)
F.up = new /icon(clone.stand_icon,dir=NORTH)
for(var/icon/i in clone.overlays)
F.up.Blend(i)
F.down = new /icon(clone.stand_icon,dir=SOUTH)
for(var/icon/i in clone.overlays)
F.down.Blend(i)
F.right = new /icon(clone.stand_icon,dir=EAST)
for(var/icon/i in clone.overlays)
F.right.Blend(i)
target << F.up
*/
F.updateimage()
/datum/component/hallucinations/xenochimera/handle_hallucinating()
var/halpick = rand(1,100)
switch(halpick)
if(0 to 15) //15% chance
//Screwy HUD
event_hudscrew()
if(16 to 25) //10% chance
event_fake_item()
if(26 to 35) //10% chance
event_flash_environmental_threats()
if(36 to 55) //20% chance
event_strange_sound()
if(56 to 60) //5% chance
event_flash_monsters()
if(61 to 85) //25% chance
event_hunger()
if(86 to 100) //15% chance
event_hear_voices()
+218
View File
@@ -0,0 +1,218 @@
/obj/effect/fake_attacker // I did want to use /image/client_only but this needs to be somewhat more complex
name = ""
desc = ""
density = FALSE
anchored = TRUE
opacity = 0
VAR_PRIVATE/list/clients = list()
VAR_PRIVATE/list/image/dir_images = list()
/obj/effect/fake_attacker/process()
. = ..()
// Passive cleanup
for(var/datum/weakref/C in clients)
var/client/CW = C?.resolve()
if(isnull(CW))
clients.Remove(C)
if(!clients.len)
qdel(src)
/obj/effect/fake_attacker/set_dir(newdir)
if(!(newdir in GLOB.cardinal))
newdir &= ~(EAST|WEST) // Don't allow diagonals, prefer north/south
if(!newdir)
newdir = SOUTH
var/updatesprite = (dir != newdir)
. = ..()
if(updatesprite)
for(var/datum/weakref/C in clients)
var/client/CW = C?.resolve()
clear_images_from_client(CW)
assign_image_to_client(CW)
/obj/effect/fake_attacker/Moved(atom/old_loc, direction, forced, movetime)
. = ..()
var/turf_move = isturf(loc) && isturf(old_loc)
for(var/img in dir_images)
var/image/G = dir_images[img]
G.loc = loc
if(turf_move)
var/MT = 0.7 SECONDS
G.pixel_x = (old_loc.x - loc.x) * WORLD_ICON_SIZE
G.pixel_y = (old_loc.y - loc.y) * WORLD_ICON_SIZE
animate(G, pixel_x = 0, time = MT, flags = ANIMATION_PARALLEL)
animate(G, pixel_y = 0, time = MT, flags = ANIMATION_PARALLEL)
/obj/effect/fake_attacker/Destroy(force)
. = ..()
clear_every_clients_images()
qdel_all_images()
/obj/effect/fake_attacker/proc/create_images_from(var/atom/clone)
SHOULD_NOT_OVERRIDE(TRUE)
dir_images["[NORTH]"] = image(clone,dir = NORTH)
dir_images["[SOUTH]"] = image(clone,dir = SOUTH)
dir_images["[EAST]"] = image(clone,dir = EAST)
dir_images["[WEST]"] = image(clone,dir = WEST)
for(var/img in dir_images)
var/image/G = dir_images[img]
G.layer = clone.layer
G.plane = clone.plane
G.appearance_flags = clone.appearance_flags
G.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
G.loc = loc
/obj/effect/fake_attacker/proc/append_client(var/client/C)
SHOULD_NOT_OVERRIDE(TRUE)
clients.Add(WEAKREF(C))
assign_image_to_client(C)
/obj/effect/fake_attacker/proc/assign_image_to_client(var/client/C)
PRIVATE_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
if(!C)
return
if(!dir_images.len)
return
var/image/I = dir_images["[dir]"]
if(I)
C.images += I
/obj/effect/fake_attacker/proc/clear_every_clients_images()
for(var/datum/weakref/C in clients)
clear_images_from_client(C?.resolve())
/obj/effect/fake_attacker/proc/clear_images_from_client(var/client/C)
PRIVATE_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
if(!C)
return
if(!dir_images.len)
return
for(var/img in dir_images)
C.images -= dir_images[img]
/obj/effect/fake_attacker/proc/qdel_all_images()
PRIVATE_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
for(var/img in dir_images)
qdel_null(dir_images[img])
dir_images.Cut()
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Hallucination attackers with AI behaviors
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/mob/proc/create_hallucination_attacker(var/turf/T = null,var/mob/living/carbon/human/clone = null, var/forced_type = null)
SHOULD_NOT_OVERRIDE(TRUE)
if(!client)
return null
if(!clone)
// Get a randomized clone from the living mob's list, must be standing
var/list/possible_clones = new/list()
for(var/mob/living/carbon/human/H in living_mob_list)
if(H.stat || H.lying)
continue
possible_clones += H
if(!possible_clones.len)
return null
clone = pick(possible_clones)
if(!clone)
return null
if(!T)
// Get the target's turf, then make some random steps to get away from them, respecting walls
T = get_turf(clone)
var/turf/CT = T
for(var/i = 0 to 8)
var/Cdir = pick(GLOB.cardinal)
if(prob(30))
Cdir = clone.dir // Results in hallucinations somewhat being in front of you
var/turf/NT = get_step(CT,Cdir)
if(!NT.density)
CT = NT
T = CT
if(!forced_type)
// Picking from all available options
var/list/get_types = subtypesof(/obj/effect/fake_attacker/human)
forced_type = pick(get_types)
// Finally! After a thousand years I'm finally free to conquer EARTH!
return new forced_type(T,src,clone)
/obj/effect/fake_attacker/human
VAR_PROTECTED/datum/weakref/target = null
var/requires_hallucinating = TRUE // Mob will qdel if the target is not hallucinating if this is true
/obj/effect/fake_attacker/human/Initialize(mapload,var/mob/targeting_mob,var/atom/clone_appearance_from)
. = ..()
START_PROCESSING(SSobj, src)
set_target(targeting_mob)
create_images_from(clone_appearance_from)
append_client(targeting_mob.client)
name = clone_appearance_from.name
// Usually we want to face our target for maximum spooky effect
set_dir(get_dir(src,targeting_mob))
/obj/effect/fake_attacker/human/Destroy()
STOP_PROCESSING(SSobj, src)
. = ..()
/obj/effect/fake_attacker/human/process()
// check if valid
var/mob/living/M = target?.resolve()
if(!M)
qdel(src)
return null
if(requires_hallucinating)
if(!ishuman(M))
qdel(src)
return null
var/mob/living/carbon/human/H = M
if(!H.hallucination)
qdel(src)
return null
return M
/obj/effect/fake_attacker/human/proc/set_target(var/mob/M)
target = WEAKREF(M)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Attacker: Performs hostile shoves and attacks
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/obj/effect/fake_attacker/human/attacker/process()
var/mob/living/M = ..()
if(get_dist(src,M) > 1)
step_towards(src,M)
else if(prob(15))
M << sound(pick('sound/weapons/punch1.ogg','sound/weapons/punch2.ogg','sound/weapons/punch3.ogg','sound/weapons/punch4.ogg'))
M.show_message(span_bolddanger("\The [src] has punched \the [M]!"), 1)
M.halloss += 4
if(prob(15))
step_away(src,M)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Fleeing: Runs away when you get close
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/obj/effect/fake_attacker/human/fleeing
VAR_PRIVATE/flee = FALSE
/obj/effect/fake_attacker/human/fleeing/process()
var/mob/living/M = ..()
set_dir(get_dir(src,M))
if(get_dist(src,M) < 6 || flee)
flee = TRUE
step_away(src,M)
if(get_dist(src,M) > 10 || get_dist(src,M) < 2 || (flee && prob(10)))
target = null
qdel(src)
@@ -0,0 +1,302 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Hallucination events
/////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/component/hallucinations/proc/event_hudscrew()
PROTECTED_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
//Screwy HUD
//to_chat(our_human, "Screwy HUD")
hal_screwyhud = pick(list(
HUD_HALLUCINATION_CRIT,
HUD_HALLUCINATION_TOXIN,
HUD_HALLUCINATION_OXY,
HUD_HALLUCINATION_ONFIRE
))
VARSET_IN(src, hal_screwyhud, HUD_HALLUCINATION_NONE, rand(15,35) SECONDS)
/datum/component/hallucinations/proc/event_painmessage()
PROTECTED_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
//Fake pain messages
//to_chat(our_human, "fake messages")
var/list/pain_message = list("The inside of your head hurts...",
"Your stomach hurts.",
"You feel sick.",
"Your upper body hurts.",
"Your head hurts.",
"Your arm hurts.",
"Your leg hurts.",
"Your lower body hurts.",
"Your foot hurts.",
"Your hand hurts.",
"The pain is excruciating!",
"Your whole body is going numb!",
"You feel like you could die any moment now!",
"Please, just end the pain!",
"You feel a sudden pain across your body.",
"You cringe as a violent pain takes over your body.",
"It feels like your body is eating itself inside out.",
"IT HURTS.",
"Your body feels as if it's trying to rip itself open..."
)
if(prob(20) && our_human.jitteriness < 50)
our_human.make_jittery(120)
to_chat(our_human, span_danger(pick(pain_message)))
/datum/component/hallucinations/proc/event_fake_item()
PROTECTED_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
//Strange items
//to_chat(our_human, "Traitor Items")
if(halitem.len)
return
var/list/slots_free = list(ui_lhand,ui_rhand)
if(our_human.l_hand)
slots_free -= ui_lhand
if(our_human.r_hand)
slots_free -= ui_rhand
if(!our_human.belt)
slots_free += ui_belt
if(!our_human.l_store)
slots_free += ui_storage1
if(!our_human.r_store)
slots_free += ui_storage2
if(!slots_free.len)
return
var/obj/CI = new()
CI.screen_loc = pick(slots_free)
CI.hud_layerise()
switch(rand(1,6))
if(1) //revolver
CI.icon = 'icons/obj/gun.dmi'
CI.icon_state = "revolver"
CI.name = "Revolver"
if(2) //c4
CI.icon = 'icons/obj/assemblies.dmi'
CI.icon_state = "plastic-explosive0"
CI.name = "Mysterious Package"
if(prob(25))
CI.icon_state = "c4small_1"
if(3) //sword
CI.icon = 'icons/obj/weapons.dmi'
CI.icon_state = "longsword"
CI.name = "Sword"
if(4) //stun baton
CI.icon = 'icons/obj/weapons.dmi'
CI.icon_state = "stunbaton"
CI.name = "Stun Baton"
if(5) //emag
CI.icon = 'icons/obj/card.dmi'
CI.icon_state = "emag"
CI.name = "Cryptographic Sequencer"
if(6) //flashbang
CI.icon = 'icons/obj/grenade.dmi'
CI.icon_state = "flashbang1"
CI.name = "Flashbang"
halitem[WEAKREF(CI)] = WEAKREF(our_human.client)
our_human.client.screen += CI
addtimer(CALLBACK(src, PROC_REF(remove_hallucination_item)), rand(10,25) SECONDS, TIMER_DELETE_ME)
/datum/component/hallucinations/proc/remove_hallucination_item()
// I can't manage this with /image/client_only due to screenloc, so key-value weakref pair it is! Called on both timer and destroying this component.
PRIVATE_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
var/datum/weakref/first = halitem[1]
var/obj/itm = first?.resolve()
var/datum/weakref/CW = halitem[first]
var/client/C = CW?.resolve()
C.screen -= itm
qdel(itm)
halitem.Cut()
/datum/component/hallucinations/proc/event_strange_sound()
PROTECTED_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
//Strange audio
var/send_sound
switch(rand(1,12))
if(1)
send_sound = 'sound/machines/door/old_airlock.ogg'
if(2)
if(prob(50))
send_sound = 'sound/effects/Explosion1.ogg'
else
send_sound = 'sound/effects/Explosion2.ogg'
if(3)
send_sound = 'sound/effects/explosionfar.ogg'
if(4)
send_sound = 'sound/effects/Glassbr1.ogg'
if(5)
send_sound = 'sound/effects/Glassbr2.ogg'
if(6)
send_sound = 'sound/effects/Glassbr3.ogg'
if(7)
send_sound = 'sound/machines/twobeep.ogg'
if(8)
send_sound = 'sound/machines/door/windowdoor.ogg'
if(9)
//To make it more realistic, I added two gunshots (enough to kill)
send_sound = 'sound/weapons/Gunshot1.ogg'
addtimer(CALLBACK(src, PROC_REF(secondary_sound), 'sound/weapons/Gunshot2.ogg'), rand(1,3) SECONDS, TIMER_DELETE_ME)
if(10)
send_sound = 'sound/weapons/smash.ogg'
if(11)
//Same as above, but with tasers.
send_sound = 'sound/weapons/Taser.ogg'
addtimer(CALLBACK(src, PROC_REF(secondary_sound), 'sound/weapons/Taser.ogg'), rand(1,3) SECONDS, TIMER_DELETE_ME)
//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')
send_sound = pick(creepyasssounds)
our_human.playsound_local(get_turf(our_human), send_sound, vol = 75, channel = CHANNEL_AMBIENCE_FORCED)
/datum/component/hallucinations/proc/secondary_sound(var/sound_path)
PRIVATE_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
our_human.playsound_local(get_turf(our_human), sound_path, vol = 75, channel = CHANNEL_AMBIENCE_FORCED)
/datum/component/hallucinations/proc/event_flash_environmental_threats()
PROTECTED_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
//Flashes of danger
//to_chat(our_human, "Danger Flash")
if(halimage?.resolve())
return
var/list/possible_points = list()
for(var/turf/simulated/floor/F in view(our_human,world.view))
possible_points += F
if(!possible_points.len)
return
var/turf/simulated/floor/target = pick(possible_points)
var/image/client_only/CI
switch(rand(1,3))
if(1)
//to_chat(our_human, "Space")
CI = new('icons/turf/space.dmi',target,"[rand(1,25)]",TURF_LAYER)
if(2)
//to_chat(our_human, "Fire")
CI = new('icons/effects/fire.dmi',target,"1",TURF_LAYER)
if(3)
//to_chat(our_human, "C4")
CI = new('icons/obj/assemblies.dmi',target,"plastic-explosive2",OBJ_LAYER+0.01)
halimage = WEAKREF(CI)
CI.append_client(our_human.client)
QDEL_IN(CI, rand(1,5) SECONDS) //Only seen for a brief moment.
/datum/component/hallucinations/proc/event_flash_monsters()
PROTECTED_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
//Flashes of danger
//to_chat(our_human, "Danger Flash")
if(halbody?.resolve())
return
var/list/possible_points = list()
for(var/turf/simulated/floor/F in view(our_human,world.view))
possible_points += F
if(!possible_points.len)
return
var/turf/simulated/floor/target = pick(possible_points)
var/image/client_only/CI
switch(rand(1,4))
if(1)
CI = new('icons/mob/human.dmi',target,"husk_l",TURF_LAYER)
if(2,3)
CI = new('icons/mob/human.dmi',target,"husk_s",TURF_LAYER)
if(4)
CI = new('icons/mob/alien.dmi',target,"alienother",TURF_LAYER)
// if(5)
// CI = new('xcomalien.dmi',target,"chryssalid",TURF_LAYER)
halbody = WEAKREF(CI)
CI.append_client(our_human.client)
QDEL_IN(CI, rand(5,8) SECONDS) //Only seen for a brief moment.
/datum/component/hallucinations/proc/event_sleeping()
PROTECTED_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
//Fake death
//to_chat(our_human, "fake death")
our_human.SetSleeping(20)
hal_crit = TRUE
hal_screwyhud = HUD_HALLUCINATION_CRIT
addtimer(CALLBACK(src, PROC_REF(reset_hallucination_sleeping)), rand(5,10) SECONDS, TIMER_DELETE_ME)
/datum/component/hallucinations/proc/reset_hallucination_sleeping()
PRIVATE_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
our_human.SetSleeping(0)
hal_crit = FALSE
hal_screwyhud = HUD_HALLUCINATION_NONE
/datum/component/hallucinations/proc/event_attacker()
PROTECTED_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
var/attacker = our_human.create_hallucination_attacker(forced_type = /obj/effect/fake_attacker/human/attacker) // Currently just uses the attacker type for now, remove argument to use any other subtype
if(attacker)
QDEL_IN(attacker, rand(25,30) SECONDS)
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Xenochimera feral events
/////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/component/hallucinations/proc/event_hunger()
PROTECTED_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
//food
if(halbody?.resolve())
return
var/list/possible_points = list()
for(var/turf/simulated/floor/F in view(our_human,world.view))
possible_points += F
if(!possible_points.len)
return
var/turf/simulated/floor/target = pick(possible_points)
var/image/client_only/CI
switch(rand(1,10))
if(1)
CI = new('icons/mob/animal.dmi',target,"cow",TURF_LAYER)
if(2)
CI = new('icons/mob/animal.dmi',target,"chicken",TURF_LAYER)
if(3)
CI = new('icons/obj/food.dmi',target,"bigbiteburger",TURF_LAYER)
if(4)
CI = new('icons/obj/food.dmi',target,"meatbreadslice",TURF_LAYER)
if(5)
CI = new('icons/obj/food.dmi',target,"sausage",TURF_LAYER)
if(6)
CI = new('icons/obj/food.dmi',target,"bearmeat",TURF_LAYER)
if(7)
CI = new('icons/obj/food.dmi',target,"fishfillet",TURF_LAYER)
if(8)
CI = new('icons/obj/food.dmi',target,"meat",TURF_LAYER)
if(9)
CI = new('icons/obj/food.dmi',target,"meatstake",TURF_LAYER)
if(10)
CI = new('icons/obj/food.dmi',target,"monkeysdelight",TURF_LAYER)
halbody = WEAKREF(CI)
CI.append_client(our_human.client)
QDEL_IN(CI, rand(5,8) SECONDS) //Only seen for a brief moment.
/datum/component/hallucinations/proc/event_hear_voices()
PROTECTED_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
//hear voices. Could make the voice pick from nearby creatures, but nearby creatures make feral hallucinations rare so don't bother.
var/list/hiddenspeakers = list("Someone distant", "A voice nearby","A familiar voice", "An echoing voice", "A cautious voice", "A scared voice", "Someone around the corner", "Someone", "Something", "Something scary", "An urgent voice", "An angry voice")
var/list/speakerverbs = list("calls out", "yells", "screams", "exclaims", "shrieks", "shouts", "hisses", "snarls")
var/list/spookyphrases = list("It's over here!","Stop it!", "Hunt it down!", "Get it!", "Quick, over here!", "Anyone there?", "Who's there?", "Catch that thing!", "Stop it! Kill it!", "Anyone there?", "Where is it?", "Find it!", "There it is!")
to_chat(our_human, span_game(span_say(span_name(pick(hiddenspeakers)) + " [pick(speakerverbs)], \"[pick(spookyphrases)]\"")))