[READY] Reworks and Refactors Hallucinations (#15360)

* Hallucination rework start

* Chaser/Attacker, Abduction

* Moderates

* Majors

* saber alter

* address SteelSlayer

* Fix admin logging and runtime

* put hallucination logging on ATKLOG_ALL
This commit is contained in:
dearmochi
2021-07-13 14:27:14 +01:00
committed by GitHub
parent 5124f5ef7e
commit 712721fbc6
23 changed files with 1479 additions and 1087 deletions
+8
View File
@@ -743,3 +743,11 @@
///SSalarm signals
#define COMSIG_TRIGGERED_ALARM "ssalarm_triggered"
#define COMSIG_CANCELLED_ALARM "ssalarm_cancelled"
// /obj/machinery/door signals
#define COMSIG_DOOR_OPEN "door_open"
#define COMSIG_DOOR_CLOSE "door_close"
// /obj/machinery/door/airlock signals
#define COMSIG_AIRLOCK_OPEN "airlock_open"
#define COMSIG_AIRLOCK_CLOSE "airlock_close"
+5
View File
@@ -489,3 +489,8 @@
/// Send to the mentor Discord webhook
#define DISCORD_WEBHOOK_MENTOR "MENTOR"
// Hallucination severities
#define HALLUCINATE_MINOR 1
#define HALLUCINATE_MODERATE 2
#define HALLUCINATE_MAJOR 3
+5
View File
@@ -277,6 +277,11 @@
#define HEARING_PROTECTION_MAJOR 2
#define HEARING_PROTECTION_TOTAL 3
// Defines used in /mob/living/carbon/human/update_health_hud to override the health status
#define HEALTH_HUD_OVERRIDE_NONE 0
#define HEALTH_HUD_OVERRIDE_CRIT 1
#define HEALTH_HUD_OVERRIDE_DEAD 2
#define HEALTH_HUD_OVERRIDE_HEALTHY 3
// Eye protection
#define FLASH_PROTECTION_SENSITIVE -1
#define FLASH_PROTECTION_NONE 0
+18
View File
@@ -742,6 +742,24 @@
log_admin("[key_name_admin(usr)] has turned [key_name_admin(H)] into a skeleton")
href_list["datumrefresh"] = href_list["make_skeleton"]
else if(href_list["hallucinate"])
if(!check_rights(R_SERVER | R_EVENT))
return
var/mob/living/carbon/C = locateUID(href_list["hallucinate"])
if(!istype(C))
to_chat(usr, "<span class='warning'>This can only be used on instances of type /mob/living/carbon</span>")
return
var/haltype = input(usr, "Select the hallucination type:", "Hallucinate") as null|anything in subtypesof(/obj/effect/hallucination)
if(!haltype)
return
C.hallucinate(haltype)
message_admins("[key_name(usr)] has given [key_name(C)] the [haltype] hallucination")
log_admin("[key_name_admin(usr)] has given [key_name_admin(C)] the [haltype] hallucination")
href_list["datumrefresh"] = href_list["hallucinate"]
else if(href_list["offer_control"])
if(!check_rights(R_ADMIN)) return
@@ -30,9 +30,8 @@
/mob/living/simple_animal/hostile/guardian/fire/AttackingTarget()
. = ..()
if(toggle)
if(. && ishuman(target) && !summoner)
spawn(0)
new /obj/effect/hallucination/delusion(target.loc, target, force_kind = "custom", duration = 200, skip_nearby = 0, custom_icon = icon_state, custom_icon_file = icon)
if(. && iscarbon(target))
new /obj/effect/hallucination/delusion(get_turf(target), target, icon, icon_state)
else
if(prob(45))
if(ismovable(target))
+2
View File
@@ -1188,6 +1188,7 @@ About the new airlock wires panel:
if(!density)
return TRUE
SEND_SIGNAL(src, COMSIG_AIRLOCK_OPEN)
operating = TRUE
update_icon(AIRLOCK_OPENING, 1)
sleep(1)
@@ -1228,6 +1229,7 @@ About the new airlock wires panel:
if(killthis)
killthis.ex_act(EXPLODE_HEAVY)//Smashin windows
SEND_SIGNAL(src, COMSIG_AIRLOCK_CLOSE)
operating = TRUE
update_icon(AIRLOCK_CLOSING, 1)
layer = CLOSED_DOOR_LAYER
+2
View File
@@ -266,6 +266,7 @@
return TRUE
if(operating)
return
SEND_SIGNAL(src, COMSIG_DOOR_OPEN)
operating = TRUE
do_animate("opening")
set_opacity(0)
@@ -295,6 +296,7 @@
autoclose_in(60)
return
SEND_SIGNAL(src, COMSIG_DOOR_CLOSE)
operating = TRUE
do_animate("closing")
+1 -2
View File
@@ -135,8 +135,7 @@
var/red_splash = list(1,0,0,0.8,0.2,0, 0.8,0,0.2,0.1,0,0)
var/pure_red = list(0,0,0,0,0,0,0,0,0,1,0,0)
spawn(0)
new /obj/effect/hallucination/delusion(victim.loc, victim, force_kind = "demon", duration = duration, skip_nearby = 0)
new /obj/effect/hallucination/delusion(get_turf(victim), victim, 'icons/mob/mob.dmi', "daemon")
var/obj/item/twohanded/required/chainsaw/doomslayer/chainsaw = new(victim.loc)
chainsaw.flags |= NODROP | DROPDEL
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,133 @@
/**
* # Hallucination - Tripper
*
* A generic hallucination that causes the target to trip if they cross it.
*/
/obj/effect/hallucination/tripper
anchored = TRUE
/// Chance to trip when crossing.
var/trip_chance = 100
/// Stun to add when crossed.
var/stun = 4 SECONDS_TO_LIFE_CYCLES
/// Weaken to add when crossed.
var/weaken = 4 SECONDS_TO_LIFE_CYCLES
/obj/effect/hallucination/tripper/CanPass(atom/movable/mover, turf/T)
. = TRUE
if(isliving(mover) && mover == target)
var/mob/living/M = mover
if(M.lying || !prob(trip_chance))
return
M.Stun(stun)
M.Weaken(weaken)
on_crossed()
/**
* Called when the target crosses this hallucination.
*/
/obj/effect/hallucination/tripper/proc/on_crossed()
return
/**
* # Hallucination - Chaser
*
* A generic hallucination that chases the target.
*/
/obj/effect/hallucination/chaser
hallucination_icon = 'icons/mob/monkey.dmi'
hallucination_icon_state = "monkey1"
hallucination_override = TRUE
// Settings
// Minimum distance required between the target and us to keep chasing them.
var/min_distance = 1
/// Interval between two thinks in deciseconds. Shouldn't be too low to prevent lag.
var/think_interval = 1 SECONDS
// Variables
/// Think timer handle.
var/think_timer = null
/obj/effect/hallucination/chaser/Initialize(mapload, mob/living/carbon/target)
. = ..()
name = "\proper monkey ([rand(100, 999)])"
think_timer = addtimer(CALLBACK(src, .proc/think), think_interval, TIMER_LOOP | TIMER_STOPPABLE)
/obj/effect/hallucination/chaser/Destroy()
deltimer(think_timer)
return ..()
/**
* Called at regular intervals to determine what to do.
*/
/obj/effect/hallucination/chaser/proc/think()
if(QDELETED(src))
return
else if(QDELETED(target))
qdel(src)
return
if(get_dist(src, target) > min_distance)
chase()
else
within_range()
/**
* Called every Think when we are not close enough to the target.
*/
/obj/effect/hallucination/chaser/proc/chase()
step_towards(src, target)
/**
* Called every Think when we are close enough to the target.
*/
/obj/effect/hallucination/chaser/proc/within_range()
return
/**
* # Hallucination - Attacker
*
* A generic hallucination based on the Chaser that attacks if close enough.
*/
/obj/effect/hallucination/chaser/attacker
/// Chance to attack per Think spent in range.
var/attack_chance = 100
/// Stamina damage to heal on hit.
var/damage = 25
/// Whether to attack if the target is knocked down.
var/should_attack_weakened = FALSE
/obj/effect/hallucination/chaser/attacker/within_range()
if(!prob(attack_chance))
return
var/was_weakened = target.IsWeakened()
if(was_weakened && !should_attack_weakened)
return
attack(was_weakened)
/**
* Called every Think when we are attacking the target.
*
* Arguments:
* * was_weakened - Whether the target was already knocked down prior to this attack.
*/
/obj/effect/hallucination/chaser/attacker/proc/attack(was_weakened)
dir = get_dir(src, target)
attack_effects()
target.adjustStaminaLoss(damage)
if(!was_weakened && target.IsWeakened())
on_knockdown()
/**
* Called to handle the visual and audio effects of an attack.
*/
/obj/effect/hallucination/chaser/attacker/proc/attack_effects()
do_attack_animation(target, ATTACK_EFFECT_PUNCH)
target.playsound_local(get_turf(src), get_sfx("punch"), 25, TRUE)
to_chat(target, "<span class='userdanger'>[name] has punched [target]!</span>")
/**
* Called when one of our attacks put the target in stamina crit.
*/
/obj/effect/hallucination/chaser/attacker/proc/on_knockdown()
target.visible_message("<span class='warning'>[target] recoils as if hit by something, before suddenly collapsing!</span>",
"<span class='userdanger'>[src]'s blow was too much for you, causing you to collapse!</span>")
@@ -0,0 +1,410 @@
/**
* # Hallucination - Terror Infestation
*
* Creates spider webs and a terror spider near a random vent around the target.
*/
/obj/effect/hallucination/terror_infestation
duration = 30 SECONDS
/obj/effect/hallucination/terror_infestation/Initialize(mapload, mob/living/carbon/target)
. = ..()
// Find a vent around us
var/list/vents = list()
for(var/obj/machinery/atmospherics/unary/vent_pump/vent in range(world.view, target))
vents += vent
if(!length(vents))
return
// Spawn webs around a random vent
var/obj/vent = pick(vents)
for(var/t in RANGE_TURFS(1, vent))
var/turf/T = t
if(!isfloorturf(T))
continue
new /obj/effect/hallucination/tripper/spider_web(T, target)
new /obj/effect/hallucination/chaser/attacker/terror_spider(get_turf(vent), target)
/obj/effect/hallucination/chaser/attacker/terror_spider
hallucination_icon = 'icons/mob/terrorspider.dmi'
hallucination_icon_state = "terror_green"
duration = 30 SECONDS
damage = 25
/obj/effect/hallucination/chaser/attacker/terror_spider/Initialize(mapload, mob/living/carbon/target)
. = ..()
name = "Green Terror spider ([rand(100, 999)])"
/obj/effect/hallucination/chaser/attacker/terror_spider/attack_effects()
do_attack_animation(target, ATTACK_EFFECT_BITE)
target.playsound_local(get_turf(src), 'sound/weapons/bite.ogg', 50, TRUE)
to_chat(target, "<span class='userdanger'>[name] bites you!</span>")
/obj/effect/hallucination/chaser/attacker/terror_spider/on_knockdown()
target.visible_message("<span class='warning'>[target] recoils as if hit by something, before suddenly collapsing!</span>",
"<span class='userdanger'>[name] bites you!</span>")
/**
* # Hallucination - Spider Web
*
* A fake spider web that trips the target if crossed.
*/
/obj/effect/hallucination/tripper/spider_web
name = "spider web"
desc = "It's stringy and sticky."
hallucination_icon = 'icons/effects/effects.dmi'
hallucination_icon_state = "stickyweb1"
hallucination_override = TRUE
hallucination_layer = OBJ_LAYER
trip_chance = 80
/obj/effect/hallucination/tripper/spider_web/Initialize(mapload, mob/living/carbon/target)
if(prob(50))
hallucination_icon_state = "stickyweb2"
. = ..()
/obj/effect/hallucination/tripper/spider_web/on_crossed()
target.visible_message("<span class='warning'>[target] trips over nothing.</span>",
"<span class='userdanger'>You get stuck in [src]!</span>")
/obj/effect/hallucination/tripper/spider_web/attackby(obj/item/I, mob/user, params)
if(user != target)
return
step_towards(target, get_turf(src))
target.Weaken(4 SECONDS_TO_LIFE_CYCLES)
target.visible_message("<span class='warning'>[target] flails [target.p_their()] [I.name] as if striking something, only to trip!</span>",
"<span class='userdanger'>[src] vanishes as you strike it with [I], causing you to stumble forward!</span>")
qdel(src)
/**
* # Hallucination - Abduction
*
* Sends an abductor agent after the target. On knockdown, spawns an abductor scientist next to the target. Nothing else happens.
*/
/obj/effect/hallucination/abduction
duration = 45 SECONDS
/// The abductor agent hallucination.
var/obj/effect/hallucination/chaser/attacker/abductor/agent = null
/// The abductor scientist image handle.
var/image/scientist = null
/obj/effect/hallucination/abduction/Initialize(mapload, mob/living/carbon/target)
. = ..()
var/list/locs = list()
for(var/turf/T in oview(world.view, target))
if(!is_blocked_turf(T))
locs += T
if(!length(locs))
qdel(src)
return
// Spawn agent
var/turf/T = pick(locs)
agent = new(T, target)
agent.owning_hallucination = src
// Teleport effect
var/image/teleport_end = image('icons/mob/mob.dmi', T, "uncloak", layer = ABOVE_MOB_LAYER)
teleport_end.plane = GAME_PLANE
add_icon(teleport_end)
clear_icon_in(teleport_end, 0.9 SECONDS)
/obj/effect/hallucination/abduction/Destroy()
QDEL_NULL(agent)
QDEL_NULL(scientist)
return ..()
/**
* Called when the fake abductor scientist should spawn.
*/
/obj/effect/hallucination/abduction/proc/spawn_scientist()
// Find a spot for the scientist to spawn
var/list/locs = list()
for(var/turf/T in orange(1, target))
if(!is_blocked_turf(T))
locs += T
locs -= get_turf(agent)
if(!length(locs))
qdel(src)
return
QDEL_IN(src, 10 SECONDS)
var/turf/T = pick(locs)
// Spawn the scientist in
var/image/teleport = image('icons/obj/abductor.dmi', T, "teleport", layer = ABOVE_MOB_LAYER)
teleport.plane = GAME_PLANE
add_icon(teleport)
clear_icon_in(teleport, 4 SECONDS)
addtimer(CALLBACK(src, .proc/do_spawn_scientist, T), 4 SECONDS)
playsound(T, "sparks", 100, TRUE)
/**
* Timer called to actually spawn the scientist.
*
* Arguments:
* * T - Where to spawn the scientist.
*/
/obj/effect/hallucination/abduction/proc/do_spawn_scientist(turf/T)
if(QDELETED(target))
qdel(src)
return
else if(scientist)
return
var/image/teleport_end = image('icons/mob/mob.dmi', T, "uncloak", layer = ABOVE_MOB_LAYER)
teleport_end.plane = GAME_PLANE
add_icon(teleport_end)
clear_icon_in(teleport_end, 0.9 SECONDS)
scientist = image('icons/mob/simple_human.dmi', T, "abductor_scientist", layer = MOB_LAYER)
scientist.plane = GAME_PLANE
scientist.dir = get_dir(T, target)
add_icon(scientist)
/obj/effect/hallucination/chaser/attacker/abductor
hallucination_icon = 'icons/mob/simple_human.dmi'
hallucination_icon_state = "abductor_agent"
duration = 45 SECONDS
damage = 100
/// The hallucination that spawned us.
var/obj/effect/hallucination/abduction/owning_hallucination = null
/obj/effect/hallucination/chaser/attacker/abductor/Initialize(mapload, mob/living/carbon/target)
. = ..()
name = "Unknown"
/obj/effect/hallucination/chaser/attacker/abductor/attack_effects()
do_attack_animation(target)
target.playsound_local(get_turf(src), 'sound/weapons/egloves.ogg', 50, TRUE)
/obj/effect/hallucination/chaser/attacker/abductor/on_knockdown()
target.visible_message("<span class='warning'>[target] recoils as if hit by something, before suddenly collapsing!</span>",
"<span class='userdanger'>[name] has stunned you with the advanced baton!</span>")
if(!QDELETED(owning_hallucination))
owning_hallucination.spawn_scientist()
else
qdel(src)
/**
* # Hallucination - Loose Energy Ball
*
* A progressive hallucination that begins with intermittent explosions, before displaying an energy ball that shocks the target.
*/
/obj/effect/hallucination/loose_energy_ball
duration = 30 SECONDS
/// Length of phase 1 in deciseconds.
var/length_phase_1 = 10 SECONDS
/// Length of phase 2 in deciseconds.
var/length_phase_2 = 10 SECONDS
/// Length of phase 3 in deciseconds.
var/length_phase_3 = 6 SECONDS
/obj/effect/hallucination/loose_energy_ball/Initialize(mapload)
. = ..()
phase_1()
addtimer(CALLBACK(src, .proc/phase_2), length_phase_1)
addtimer(CALLBACK(src, .proc/phase_3), length_phase_1 + length_phase_2)
/**
* First phase of the hallucination: intermittent, far-away explosion sounds.
*/
/obj/effect/hallucination/loose_energy_ball/proc/phase_1()
for(var/i in 0 to (length_phase_1 / 20) - 1)
play_sound_in(i * 2 SECONDS, null, 'sound/effects/explosionfar.ogg', min(50 + i * 5, 100))
/**
* Second phase of the hallucination: closer explosions and zap sounds from a random direction.
*/
/obj/effect/hallucination/loose_energy_ball/proc/phase_2()
var/turf/source = get_step_rand(get_turf(target))
for(var/i in 0 to (length_phase_2 / 20) - 1)
if(prob(33))
play_sound_in(i * 2 SECONDS, source, pick('sound/effects/explosion1.ogg', 'sound/effects/explosion2.ogg'), min(5 + i * 3, 100))
play_sound_in(i * 2 SECONDS, source, 'sound/magic/lightningbolt.ogg', min(5 + i * 3, 100))
/**
* Third and final phase of the hallucination: an energy ball that approaches the target before shocking it.
*/
/obj/effect/hallucination/loose_energy_ball/proc/phase_3()
// Create the image
var/image/ball = image('icons/obj/tesla_engine/energy_ball.dmi', src, "energy_ball")
ball.layer = MASSIVE_OBJ_LAYER
ball.plane = GAME_PLANE
add_icon(ball)
var/steps = (length_phase_3 / 20) - 1
for(var/i in 0 to steps)
addtimer(CALLBACK(src, .proc/phase_3_inner, ball, steps - i, i), i * 2 SECONDS)
/**
* Called during phase 3 to approach the energy ball towards the target.
*
* Arguments:
* * ball - The energy ball image.
* * distance - The remaining distance.
* * step - The current step.
*/
/obj/effect/hallucination/loose_energy_ball/proc/phase_3_inner(image/ball, distance, step)
if(QDELETED(ball) || QDELETED(target))
return
var/turf/T = get_turf(target)
var/list/turfs = RANGE_TURFS(distance + 1, T) // expensive?
var/turf/dest = pick(turfs) || T // uh oh
ball.loc = dest
target.playsound_local(dest, 'sound/magic/lightningbolt.ogg', 15 + step * 10)
target.playsound_local(dest, 'sound/magic/lightningshock.ogg', 15 + step * 10)
if(distance == 0)
target.electrocute_act(100, src, flags = SHOCK_ILLUSION)
/**
* # Hallucination - Assault
*
* An imaginary attacker spawns close to the target and attacks them to stamcrit.
*/
/obj/effect/hallucination/assault
duration = 30 SECONDS
/// The attacker hallucination.
var/obj/effect/hallucination/chaser/attacker/assaulter/fake_attacker = null
/obj/effect/hallucination/assault/Initialize(mapload, mob/living/carbon/target)
. = ..()
var/list/locs = list()
for(var/turf/T in oview(world.view / 2, target))
if(!is_blocked_turf(T))
locs += T
if(!length(locs))
qdel(src)
return
// Spawn attacker
var/turf/T = pick(locs)
fake_attacker = new(T, target)
/obj/effect/hallucination/chaser/attacker/assaulter
duration = 30 SECONDS
damage = 40
/// The attack verb to display.
var/attack_verb = "punches"
/// The attack sound to play. Can be a file or text (passed to [/proc/get_sfx]).
var/attack_sound = "punch"
/obj/effect/hallucination/chaser/attacker/assaulter/Initialize(mapload, mob/living/carbon/target)
var/new_name
// 80% chance to use a simple human sprite
if(prob(80))
new_name = "Unknown"
hallucination_icon = 'icons/mob/simple_human.dmi'
hallucination_icon_state = pick("eskimo", "templar", "skeleton", "russianmelee", "piratemelee", "plasma_miner_tool", "cat_butcher", "syndicate_space_sword", "syndicate_stormtrooper_sword", "zombie", "scary_clown")
// Adjust the attack verb and sound depending on the "mob"
switch(hallucination_icon_state)
if("eskimo", "templar", "russianmelee", "plasma_miner_tool")
attack_verb = "slashed"
attack_sound = 'sound/weapons/bladeslice.ogg'
if("cat_butcher")
attack_verb = "sawed"
attack_sound = 'sound/weapons/circsawhit.ogg'
if("piratemelee", "syndicate_space_sword", "syndicate_stormtrooper_sword")
attack_verb = "slashed"
attack_sound = 'sound/weapons/blade1.ogg'
// If nothing else we'll stay a monke
. = ..()
name = new_name || name
/obj/effect/hallucination/chaser/attacker/assaulter/attack_effects()
do_attack_animation(target)
target.playsound_local(get_turf(src), istext(attack_sound) ? get_sfx(attack_sound) : attack_sound, 25, TRUE)
to_chat(target, "<span class='userdanger'>[name] has [attack_verb] [target]!</span>")
/obj/effect/hallucination/chaser/attacker/assaulter/on_knockdown()
target.visible_message("<span class='warning'>[target] recoils as if hit by something, before suddenly collapsing!</span>",
"<span class='userdanger'>[name] has [attack_verb] [target]!</span>")
QDEL_IN(src, 3 SECONDS)
/**
* # Hallucination - Xeno Pounce
*
* An imaginary alien hunter pounces towards the target.
*/
/obj/effect/hallucination/xeno_pounce
duration = 15 SECONDS
// Settings
/// Maximum number of times the alien will pounce.
var/num_pounces = 3
/// How often to pounce in deciseconds.
var/pounce_interval = 5 SECONDS
// Variables
/// The xeno hallucination reference.
var/obj/effect/hallucination/xeno_pouncer/xeno = null
/obj/effect/hallucination/xeno_pounce/Initialize(mapload, mob/living/carbon/target)
. = ..()
// Find a vent around us
var/list/vents = list()
for(var/obj/machinery/atmospherics/unary/vent_pump/vent in range(world.view / 2, target))
vents += vent
if(!length(vents))
return
var/turf/T = get_turf(pick(vents))
xeno = new(T, target)
xeno.dir = get_dir(T, target)
addtimer(CALLBACK(src, .proc/do_pounce), pounce_interval)
/obj/effect/hallucination/xeno_pounce/proc/do_pounce()
if(QDELETED(xeno) || QDELETED(target))
return
xeno.leap_to(target)
if(--num_pounces > 0)
addtimer(CALLBACK(src, .proc/do_pounce), pounce_interval)
/obj/effect/hallucination/xeno_pouncer
hallucination_icon = 'icons/mob/alien.dmi'
hallucination_icon_state = "alienh_pounce"
hallucination_override = TRUE
/obj/effect/hallucination/xeno_pouncer/Initialize(mapload, mob/living/carbon/target)
. = ..()
name = "\proper alien hunter ([rand(100, 999)])"
/obj/effect/hallucination/xeno_pouncer/throw_impact(A)
if(A == target)
forceMove(get_turf(target))
target.Weaken(5)
target.visible_message("<span class='danger'>[target] recoils backwards and falls flat!</span>",
"<span class='userdanger'>[name] pounces on you!</span>")
to_chat(target, "<span class='notice'>[name] begins climbing into the ventilation system...</span>")
QDEL_IN(src, 2 SECONDS)
/**
* Throws the xeno towards the given loc.
*
* Arguments:
* * dest - The loc to leap to.
*/
/obj/effect/hallucination/xeno_pouncer/proc/leap_to(dest)
if(images && images[1])
images[1].icon = 'icons/mob/alienleap.dmi'
images[1].icon_state = "alienh_leap"
dir = get_dir(get_turf(src), dest)
throw_at(dest, 7, 1, spin = FALSE, diagonals_first = TRUE, callback = CALLBACK(src, .proc/reset_icon))
/**
* Resets the xeno's icon to a resting state.
*/
/obj/effect/hallucination/xeno_pouncer/proc/reset_icon()
if(images && images[1])
images[1].icon = 'icons/mob/alien.dmi'
images[1].icon_state = "alienh_pounce"
@@ -0,0 +1,198 @@
/**
* # Hallucination - Audio
*
* Plays a random sound.
*/
/obj/effect/hallucination/audio
duration = 0
/// Associative list of sounds that may be played. Value corresponds to the volume.
var/list/sounds = list(
'sound/effects/explosionfar.ogg' = 50,
'sound/effects/pray_chaplain.ogg' = 50,
'sound/machines/alarm.ogg' = 100,
'sound/magic/summon_guns.ogg' = 50,
)
/obj/effect/hallucination/audio/Initialize(mapload, mob/living/carbon/target, atom/source = null)
. = ..()
var/snd = pick(sounds)
target.playsound_local(source, snd, sounds[snd])
/**
* # Hallucination - Audio (Localized)
*
* Plays a random sound at a random location around the target.
*/
/obj/effect/hallucination/audio/localized
sounds = list(
'sound/effects/explosion1.ogg' = 50,
'sound/effects/explosion2.ogg' = 50,
'sound/effects/glassbr1.ogg' = 50,
'sound/effects/glassbr2.ogg' = 50,
'sound/effects/glassbr3.ogg' = 50,
'sound/machines/airlock_open.ogg' = 50,
)
/obj/effect/hallucination/audio/localized/Initialize(mapload, mob/living/carbon/target)
var/list/turfs = list()
for(var/turf/T in range(world.view, target))
turfs += T
if(length(turfs))
. = ..(mapload, target, pick(turfs))
else
. = ..(mapload, target)
/**
* # Hallucination - Bolts
*
* Visually bolts a random number of airlocks around the target.
*/
/obj/effect/hallucination/bolts
duration = 15 SECONDS
/// The maximum amount of airlocks to fake bolt.
var/bolt_amount = 2
/// The duration of fake bolt in deciseconds.
var/bolt_duration = 10 SECONDS
/// Lazy list of fake bolted airlocks. Key is airlock, value is bolt overlay.
var/list/bolted
/obj/effect/hallucination/bolts/Initialize(mapload, mob/living/carbon/target)
. = ..()
var/list/airlocks = list()
for(var/obj/machinery/door/airlock/A in oview(world.view, target))
airlocks += A
var/num_bolted = 0
while(bolt_amount && length(airlocks))
var/obj/machinery/door/airlock/A = pick_n_take(airlocks)
if(A.locked)
continue
addtimer(CALLBACK(src, .proc/do_bolt, A), num_bolted++ * rand(5, 7))
bolt_amount--
/**
* Called in a timer to fake bolt the given airlock.
*
* Arguments:
* * A - The airlock to fake bolt.
*/
/obj/effect/hallucination/bolts/proc/do_bolt(obj/machinery/door/airlock/A)
if(QDELETED(A) || (A.locked && A.arePowerSystemsOn()) || A.operating || !A.density)
return
var/bolt_overlay = image(get_airlock_overlay("lights_bolts", A.overlays_file), A)
add_icon(bolt_overlay)
target?.playsound_local(get_turf(A), A.boltDown, 30, FALSE, 3)
LAZYSET(bolted, A, bolt_overlay)
// Timer and signal to turn it off (only one can happen)
RegisterSignal(A, COMSIG_AIRLOCK_OPEN, .proc/do_unbolt)
addtimer(CALLBACK(src, .proc/do_unbolt, A, bolt_overlay), bolt_duration)
/**
* Called in a timer to fake unbolt the given airlock.
*
* Arguments:
* * A - The airlock to fake unbolt.
* * bolt_overlay - The bolt overlay image currently displayed on A.
*/
/obj/effect/hallucination/bolts/proc/do_unbolt(obj/machinery/door/airlock/A, image/bolt_overlay)
if(QDELETED(A))
return
// bolt_overlay is null if this proc is called from the signal, so use the lookup table to retrieve it
bolt_overlay = bolt_overlay || bolted[A]
if(QDELETED(bolt_overlay))
return
UnregisterSignal(A, COMSIG_AIRLOCK_CLOSE)
clear_icon(bolt_overlay)
target?.playsound_local(get_turf(A), A.boltUp, 30, FALSE, 3)
bolted[A] = null
/**
* # Hallucination - Speech
*
* Causes the target to hear a fake message from a random mob around them.
*/
/obj/effect/hallucination/speech
duration = 3 SECONDS
/// List of messages that may be heard.
var/list/messages = list(
"I'm watching you...",
"I'm going to kill you!",
"Get out!",
"Kchck-Chkck? Kchchck!",
"Did you hear that?",
"What did you do?",
"Why?",
"Give me that!",
"Honk!",
"Kill me!",
"HELP!!",
"RUN!!",
"EI NATH!!",
"O bidai nabora se'sma!",
"I have the disk!",
)
/obj/effect/hallucination/speech/Initialize(mapload, mob/living/carbon/target)
. = ..()
var/list/mobs = list()
for(var/mob/living/M in oview(world.view, target))
mobs += M
if(!length(mobs))
return
var/mob/living/M = pick(mobs)
var/message = pick(messages + "[target]!")
target.hear_say(message_to_multilingual(message, pick(target.languages)), speaker = M)
// Speech bubble
var/image/speech_bubble = image('icons/mob/talk.dmi', M, "[target.bubble_icon][say_test(message)]", layer = FLY_LAYER)
speech_bubble.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
add_icon(speech_bubble)
/**
* # Hallucination - Fake Danger
*
* Sends a random danger message to the target's chat.
*/
/obj/effect/hallucination/fake_danger
duration = 0
/// List of messages that may be displayed.
var/list/messages = list(
"The light burns you!",
"You experience a stabbing sensation and your ears begin to ring...",
"You get the feeling this is a bad idea.",
"Your blood boils in your veins!",
"You hear a loud buzz in your head, silencing your thoughts!",
"You feel an awful sense of being watched...",
"You suddenly feel very hot.",
"You feel like you could blow up at any moment!",
"You feel hotter than usual. Maybe you should lowe-wait, is that your hand melting?",
"You hear battle shouts. The tramping of boots on cold metal. Screams of agony. The rush of venting air. Are you going insane?",
)
/obj/effect/hallucination/fake_danger/Initialize(mapload, mob/living/carbon/target)
. = ..()
to_chat(target, "<span class='userdanger'>[pick(messages)]</span>")
/**
* # Hallucination - Fake Health
*
* Visually changes the target's health status to something it shouldn't be.
*/
/obj/effect/hallucination/fake_health
duration = list(10 SECONDS, 25 SECONDS)
/obj/effect/hallucination/fake_health/Initialize(mapload, mob/living/carbon/target)
. = ..()
if(target.health > HEALTH_THRESHOLD_CRIT)
target.health_hud_override = pick(HEALTH_HUD_OVERRIDE_CRIT, HEALTH_HUD_OVERRIDE_DEAD)
else
target.health_hud_override = HEALTH_HUD_OVERRIDE_HEALTHY // You think you're fine, but you're not
target.update_health_hud()
/obj/effect/hallucination/fake_health/Destroy()
target?.health_hud_override = HEALTH_HUD_OVERRIDE_NONE
target?.update_health_hud()
return ..()
@@ -0,0 +1,478 @@
/**
* # Hallucination - Bolts (Moderate)
*
* A variation that affects more airlocks.
*/
/obj/effect/hallucination/bolts/moderate
bolt_amount = 7
/**
* # Hallucination - Fake Alert
*
* Displays a random alert on the target's HUD.
*/
/obj/effect/hallucination/fake_alert
duration = list(10 SECONDS, 25 SECONDS)
/// The possible alerts to be displayed. Key is alert type, value is alert category.
var/list/alerts = list(
/obj/screen/alert/not_enough_oxy = "not_enough_oxy",
/obj/screen/alert/not_enough_tox = "not_enough_tox",
/obj/screen/alert/not_enough_co2 = "not_enough_co2",
/obj/screen/alert/not_enough_nitro = "not_enough_nitro",
/obj/screen/alert/too_much_oxy = "too_much_oxy",
/obj/screen/alert/too_much_co2 = "too_much_co2",
/obj/screen/alert/too_much_tox = "too_much_tox",
/obj/screen/alert/fat = "nutrition",
/obj/screen/alert/starving = "nutrition",
/obj/screen/alert/hot = "temp",
/obj/screen/alert/cold = "temp",
/obj/screen/alert/highpressure = "pressure",
/obj/screen/alert/lowpressure = "pressure",
)
/// Alert severities. Only needed for some alerts such as temperature or pressure. Key is alert category, value is severity.
var/list/severities = list(
"temp" = 3,
"pressure" = 2,
)
/// The alert category that was affected(arc) as part of this hallucination.
var/alert_category
/obj/effect/hallucination/fake_alert/Initialize(mapload, mob/living/carbon/target)
. = ..()
var/alert_type = pick(alerts)
alert_category = alerts[alert_type]
target.throw_alert(alert_category, alert_type, override = TRUE, severity = severities[alert_category])
/obj/effect/hallucination/fake_alert/Destroy()
target?.clear_alert(alert_category, clear_override = TRUE)
return ..()
/**
* # Hallucination - Fake Item
*
* Displays a random fake item around the target. If it's on the floor and they try to pick it up, they will trip and fall.
*/
/obj/effect/hallucination/fake_item
hallucination_override = TRUE
hallucination_layer = OBJ_LAYER
/// Static list of items this hallucination can be.
var/static/list/items = list(
"\improper .357 revolver" = list('icons/obj/guns/projectile.dmi', "revolver"),
"\improper ARG" = list('icons/obj/guns/projectile.dmi', "arg-30"),
"\improper C4" = list('icons/obj/grenade.dmi', "plastic-explosive0"),
"\improper L6 SAW" = list('icons/obj/guns/projectile.dmi', "l6closed100"),
"chainsaw" = list('icons/obj/items.dmi', "chainsaw0"),
"combat shotgun" = list('icons/obj/guns/projectile.dmi', "cshotgun"),
"double-bladed energy sword" = list('icons/obj/items.dmi', "dualsaberred1"),
"energy sword" = list('icons/obj/items.dmi', "swordred"),
"fireaxe" = list('icons/obj/items.dmi', "fireaxe1"),
"ritual dagger" = list('icons/obj/cult.dmi', "blood_dagger"),
"ritual dagger" = list('icons/obj/cult.dmi', "death_dagger"),
"ritual dagger" = list('icons/obj/cult.dmi', "hell_dagger"),
"sniper rifle" = list('icons/obj/guns/projectile.dmi', "sniper"),
)
/obj/effect/hallucination/fake_item/Initialize(mapload, mob/living/carbon/target)
name = pick(items)
var/list/icon_data = items[name]
hallucination_icon = icon_data[1]
hallucination_icon_state = icon_data[2]
. = ..()
var/list/locs = list()
for(var/turf/T in oview(world.view, target))
if(!is_blocked_turf(T))
locs += T
if(!length(locs))
qdel(src)
return
loc = pick(locs)
/obj/effect/hallucination/fake_item/attack_hand(mob/living/user)
if(user != target)
return
if(hasorgans(user))
var/mob/living/carbon/human/H = user
var/obj/item/organ/external/temp = H.bodyparts_by_name["r_hand"]
if(user.hand)
temp = H.bodyparts_by_name["l_hand"]
if(!temp)
to_chat(user, "<span class='warning'>You try to use your hand, but it's missing!</span>")
return
if(!temp.is_usable())
to_chat(user, "<span class='warning'>You try to move your [temp.name], but cannot!</span>")
return
user.Weaken(4 SECONDS_TO_LIFE_CYCLES)
user.visible_message("<span class='warning'>[user] does a grabbing motion towards [get_turf(src)] but [user.p_they()] stumble[user.p_s()] - nothing is there!</span>",
"<span class='userdanger'>[src] vanishes as you try grabbing it, causing you to stumble!</span>")
qdel(src)
/**
* # Hallucination - Fake Weapon
*
* Displays a random fake weapon wielded by a human around the target.
*/
/obj/effect/hallucination/fake_weapon
/// Static list of weapons this hallucination can be. Key is icon state, value is LEFT-HAND icon file.
var/static/list/weapons = list(
"advtaserstun4" = 'icons/mob/inhands/guns_lefthand.dmi',
"arm_blade" = null,
"blood_blade" = null,
"crossbow" = 'icons/mob/inhands/guns_lefthand.dmi',
"death_blade" = null,
"disintegrate" = null,
"fireaxe0" = null,
"hell_blade" = null,
"ling_shield" = null,
"nucgun" = 'icons/mob/inhands/guns_lefthand.dmi',
"prod" = null,
"staffofslipping" = null,
"staffofstorms" = null,
"swordred" = null,
"ttv" = null,
)
/// The default LEFT-HAND icon file for weapons. Static.
var/static/default_icon = 'icons/mob/inhands/items_lefthand.dmi'
/// Static list of RIGHT-HAND counterpart for any LEFT-HAND icon files used above.
var/static/right_hand_icons = list(
'icons/mob/inhands/items_lefthand.dmi' = 'icons/mob/inhands/items_righthand.dmi',
'icons/mob/inhands/guns_lefthand.dmi' = 'icons/mob/inhands/guns_righthand.dmi',
)
/// The mob wielding the fake weapon.
var/mob/living/carbon/human/wielder = null
/obj/effect/hallucination/fake_weapon/Initialize(mapload, mob/living/carbon/target)
. = ..()
// Find able-bodied mobs first
var/list/mobs = list()
for(var/mob/living/carbon/human/H in oview(world.view, target))
if(H.stat || !((H.has_left_hand() && !H.l_hand) || (H.has_right_hand() && !H.r_hand)))
continue
mobs += H
if(!length(mobs))
qdel(src)
return
// Pick a hand if it exists of course
wielder = pick(mobs)
var/right = FALSE
if(!(wielder.bodyparts_by_name["l_hand"] && !wielder.l_hand) || ((wielder.bodyparts_by_name["r_hand"] && !wielder.r_hand) && prob(50)))
right = TRUE
// Create the icon
hallucination_icon_state = pick(weapons)
var/icon = weapons[hallucination_icon_state] || default_icon
if(right)
icon = right_hand_icons[icon]
var/image/I = image(icon, wielder, hallucination_icon_state)
I = center_image(I, 32, 32)
add_icon(I)
if(hallucination_icon_state == "swordred")
target.playsound_local(get_turf(wielder), 'sound/weapons/saberon.ogg', 35, TRUE)
/obj/effect/hallucination/fake_weapon/Destroy()
if(!QDELETED(wielder) && hallucination_icon_state == "swordred")
target.playsound_local(get_turf(wielder), 'sound/weapons/saberoff.ogg', 35, TRUE)
return ..()
/**
* # Hallucination - Chasms
*
* Displays fake chasms around the target that if crossed, cause them to trip.
*/
/obj/effect/hallucination/chasms
/// Minimum number of chasms to create.
var/min_amount = 3
/// Maximum number of chasms to create.
var/max_amount = 7
/obj/effect/hallucination/chasms/Initialize(mapload, mob/living/carbon/target)
. = ..()
// Let's check if we can spawn somewhere first
var/list/locs = list()
for(var/turf/T in oview(world.view, target))
if(isfloorturf(T) && !is_blocked_turf(T))
locs += T
if(!length(locs))
qdel(src)
return
var/amount = rand(min_amount, max_amount)
while(amount-- && length(locs))
new /obj/effect/hallucination/tripper/chasm(pick_n_take(locs), target)
/**
* # Hallucination - Chasm
*
* A fake chasm that if crossed by the target, causes them to trip.
*/
/obj/effect/hallucination/tripper/chasm
name = "chasm"
hallucination_icon = 'icons/turf/floors/Chasms.dmi'
hallucination_icon_state = "smooth"
hallucination_override = TRUE
hallucination_layer = HIGH_TURF_LAYER
stun = 8 SECONDS_TO_LIFE_CYCLES
weaken = 8 SECONDS_TO_LIFE_CYCLES
/obj/effect/hallucination/tripper/chasm/on_crossed()
target.visible_message("<span class='warning'>[target] trips over nothing and flails on [get_turf(target)] as if they were falling!</span>",
"<span class='userdanger'>You stumble and stare into an abyss before you. It stares back, and you fall into the enveloping dark!</span>")
/**
* # Hallucination - Delamination Alarm
*
* A fake radio message and audio that alerts of an increasing SM unstability.
*/
/obj/effect/hallucination/delamination_alarm
duration = 0
/obj/effect/hallucination/delamination_alarm/Initialize(mapload, mob/living/carbon/target)
. = ..()
target.playsound_local(target, 'sound/machines/engine_alert2.ogg', 25, FALSE, 30, 30)
target.hear_radio(message_to_multilingual("Danger! Crystal hyperstructure integrity faltering! Integrity: [rand(30, 50)]%"), vname = "supermatter crystal", part_a = "<span class='[SSradio.frequency_span_class(PUB_FREQ)]'><b>\[[get_frequency_name(PUB_FREQ)]\]</b> <span class='name'>", part_b = "</span> <span class='message'>")
/**
* # Hallucination - Plasma Flood
*
* A fake plasma flood emanating from a nearby vent.
*/
/obj/effect/hallucination/plasma_flood
duration = 25 SECONDS
/// List of turfs that need expanding from.
var/list/turf/expand_queue = list()
/// Associative list of turfs that have already been processed.
var/list/turf/processed = list()
/// The delay at which the plasma flood expands in deciseconds. Shouldn't be too low to prevent lag.
var/expand_delay = 2.5 SECONDS // Expand 10 times
/// Expand timer handle.
var/expand_timer = null
/obj/effect/hallucination/plasma_flood/Initialize(mapload, mob/living/carbon/target)
. = ..()
var/list/vents = list()
for(var/obj/machinery/atmospherics/unary/vent_pump/vent in oview(world.view, target))
if(!is_blocked_turf(vent) && !vent.welded)
vents += vent
if(!length(vents))
qdel(src)
return
var/turf/T = get_turf(pick(vents))
create_plasma(T)
expand_queue += T
processed[T] = TRUE
expand_timer = addtimer(CALLBACK(src, .proc/expand), expand_delay, TIMER_LOOP | TIMER_STOPPABLE)
/obj/effect/hallucination/plasma_flood/Destroy()
deltimer(expand_timer)
QDEL_NULL(expand_queue)
QDEL_NULL(processed)
return ..()
/**
* Called regularly in a timer to process the plasma flooding.
*/
/obj/effect/hallucination/plasma_flood/proc/expand()
// Brace for potentially expensive proc
for(var/t in expand_queue)
var/turf/source_turf = t
expand_queue -= source_turf
// Expand to each dir
for(var/dir in GLOB.cardinal)
var/turf/target_turf = get_step(source_turf, dir)
if(processed[target_turf] || !source_turf.CanAtmosPass(target_turf))
continue
create_plasma(target_turf)
expand_queue += target_turf
processed[target_turf] = TRUE
/**
* Creates a fake plasma overlay on the given turf.
*
* Arguments:
* * T - The turf to create a fake plasma overlay on.
*/
/obj/effect/hallucination/plasma_flood/proc/create_plasma(turf/T)
var/image/I = image('icons/effects/tile_effects.dmi', T, "plasma", layer = FLY_LAYER)
I.plane = GAME_PLANE
add_icon(I)
/**
* # Hallucination - Husks
*
* A random number of fake husks around the target.
*/
/obj/effect/hallucination/husks
duration = 25 SECONDS
/// The base number of husks to create.
var/num_base = 3
/// The husk number variation, both negative and positive.
var/variation = 1
/obj/effect/hallucination/husks/Initialize(mapload, mob/living/carbon/target)
. = ..()
var/list/locs = list()
for(var/turf/T in oview(world.view, target))
if(isfloorturf(T) && !is_blocked_turf(T))
locs += T
if(!length(locs))
qdel(src)
return
var/to_spawn = num_base + rand(-variation, variation)
while(to_spawn-- && length(locs))
var/image/I = image('icons/mob/human.dmi', pick_n_take(locs), "husk_s", layer = MOB_LAYER, dir = pick(GLOB.cardinal))
I.plane = GAME_PLANE
I.transform = turn(I.transform, pick(-90, 90))
add_icon(I)
/**
* # Hallucination - Stunprodding
*
* A series of localized audio playback simulating a kidnapping with a stunprod.
*/
/obj/effect/hallucination/stunprodding
duration = 3 SECONDS
/obj/effect/hallucination/stunprodding/Initialize(mapload, mob/living/carbon/target)
. = ..()
var/list/turfs = list()
for(var/turf/T in range(world.view, target))
turfs += T
var/turf/T = pick(turfs)
target.playsound_local(T, 'sound/weapons/Egloves.ogg', 25, TRUE)
target.playsound_local(T, get_sfx("bodyfall"), 25, TRUE)
target.playsound_local(T, "sparks", 50, TRUE)
if(prob(50))
var/snd = pick('sound/goonstation/voice/female_scream.ogg', 'sound/goonstation/voice/male_scream.ogg')
play_sound_in(rand(13, 20), T, snd, 50, TRUE, rand(9, 11) / 10)
play_sound_in(rand(17, 20), T, 'sound/weapons/cablecuff.ogg', 15, TRUE)
/**
* # Hallucination - Energy Sword
*
* A series of localized audio playback simulating an energy sword murder.
*/
/obj/effect/hallucination/energy_sword
duration = 10 SECONDS
/obj/effect/hallucination/energy_sword/Initialize(mapload, mob/living/carbon/target)
. = ..()
var/list/turfs = list()
for(var/turf/T in range(world.view, target))
turfs += T
var/turf/T = pick(turfs)
loc = T
target.playsound_local(T, 'sound/weapons/saberon.ogg', 20, TRUE)
var/scream_sound = pick('sound/goonstation/voice/female_scream.ogg', 'sound/goonstation/voice/male_scream.ogg')
var/scream_pitch = rand(9, 11) / 10
var/num_hits = rand(5, 6)
var/scream_cd = 0
for(var/i in 1 to num_hits)
var/time = i * CLICK_CD_MELEE + rand(3, 7)
play_sound_in(time, T, 'sound/weapons/blade1.ogg', 15, TRUE)
if(i == num_hits)
play_sound_in(time, T, pick('sound/goonstation/voice/deathgasp_1.ogg', 'sound/goonstation/voice/deathgasp_2.ogg'), 50, TRUE, scream_pitch)
else if(scream_sound && scream_cd-- <= 0 && prob(20))
scream_cd = 2
play_sound_in(time, T, scream_sound, 50, TRUE, scream_pitch)
/obj/effect/hallucination/energy_sword/Destroy()
target.playsound_local(loc, 'sound/weapons/saberoff.ogg', 20, TRUE)
return ..()
/**
* # Hallucination - Gunfire
*
* A series of localized audio playback simulating a gunshot murder.
*/
/obj/effect/hallucination/gunfire
duration = 10 SECONDS
/obj/effect/hallucination/gunfire/Initialize(mapload, mob/living/carbon/target)
. = ..()
var/list/turfs = list()
for(var/turf/T in range(world.view, target))
turfs += T
var/turf/T = pick(turfs)
loc = T
var/gun_sound = pick('sound/weapons/gunshots/gunshot_pistol.ogg', 'sound/weapons/gunshots/gunshot_strong.ogg')
var/scream_sound = pick('sound/goonstation/voice/female_scream.ogg', 'sound/goonstation/voice/male_scream.ogg')
var/scream_pitch = rand(9, 11) / 10
var/num_hits = rand(7, 8)
var/scream_cd = 0
for(var/i in 1 to num_hits)
var/time = i * CLICK_CD_RANGE + rand(2, 4)
play_sound_in(time, T, gun_sound, 25, TRUE)
if(i == num_hits)
play_sound_in(time, T, pick('sound/goonstation/voice/deathgasp_1.ogg', 'sound/goonstation/voice/deathgasp_2.ogg'), 50, TRUE, scream_pitch)
else if(scream_sound && scream_cd-- <= 0 && prob(20))
scream_cd = 2
play_sound_in(time, T, scream_sound, 50, TRUE, scream_pitch)
/**
* # Hallucination - Self Delusion
*
* Changes the target's appearance to something else temporarily.
*/
/obj/effect/hallucination/self_delusion
duration = 15 SECONDS
/obj/effect/hallucination/self_delusion/Initialize(mapload, mob/living/carbon/target)
. = ..()
var/image/I = get_image()
I.override = TRUE
add_icon(I)
to_chat(target, "<span class='italics'>...wabbajack...wabbajack...</span>")
target.playsound_local(get_turf(target), 'sound/magic/staff_change.ogg', 50, TRUE, -1)
/**
* Returns the image to use as override to the target's appearance.
*/
/obj/effect/hallucination/self_delusion/proc/get_image()
return image('icons/mob/animal.dmi', target, pick("bear", "brownbear", "corgi", "cow", "deer", "goat", "goose", "pig", "blank-body"))
/**
* # Hallucination - Delusion
*
* Changes the appearance of all humans around the target.
*/
/obj/effect/hallucination/delusion
duration = 15 SECONDS
/obj/effect/hallucination/delusion/Initialize(mapload, mob/living/carbon/target, override_icon, override_icon_state)
. = ..()
for(var/mob/living/carbon/human/H in orange(world.view, target))
var/image/I
if(override_icon && override_icon_state)
I = image(override_icon, H, override_icon_state)
else
I = get_image(H)
I.override = TRUE
add_icon(I)
/**
* Returns the image to use as override to the target's appearance.
*/
/obj/effect/hallucination/delusion/proc/get_image(mob/living/carbon/human/H)
return image('icons/mob/animal.dmi', H, pick("bear", "brownbear", "corgi", "cow", "deer", "goat", "goose", "pig", "blank-body"))
@@ -0,0 +1,198 @@
#define HALLUCINATE_COOLDOWN_MIN 20 SECONDS
#define HALLUCINATE_COOLDOWN_MAX 50 SECONDS
/// This is multiplied with [/mob/var/hallucination] to determine the final cooldown. A higher hallucination value means shorter cooldown.
#define HALLUCINATE_COOLDOWN_FACTOR 0.03
/// Percentage defining the chance at which an hallucination may spawn past the cooldown.
#define HALLUCINATE_CHANCE 80
// Severity weights, should sum up to 100!
#define HALLUCINATE_MINOR_WEIGHT 60
#define HALLUCINATE_MODERATE_WEIGHT 30
#define HALLUCINATE_MAJOR_WEIGHT 10
GLOBAL_LIST_INIT(hallucinations, list(
HALLUCINATE_MINOR = list(
/obj/effect/hallucination/bolts = 10,
/obj/effect/hallucination/fake_danger = 10,
/obj/effect/hallucination/fake_health = 15,
/obj/effect/hallucination/speech = 15,
/obj/effect/hallucination/audio = 25,
/obj/effect/hallucination/audio/localized = 25,
),
HALLUCINATE_MODERATE = list(
/obj/effect/hallucination/delusion = 5,
/obj/effect/hallucination/self_delusion = 5,
/obj/effect/hallucination/bolts/moderate = 10,
/obj/effect/hallucination/chasms = 10,
/obj/effect/hallucination/fake_alert = 10,
/obj/effect/hallucination/gunfire = 10,
/obj/effect/hallucination/plasma_flood = 10,
/obj/effect/hallucination/stunprodding = 10,
/obj/effect/hallucination/delamination_alarm = 15,
/obj/effect/hallucination/fake_item = 15,
/obj/effect/hallucination/fake_weapon = 15,
/obj/effect/hallucination/husks = 15,
),
HALLUCINATE_MAJOR = list(
/obj/effect/hallucination/abduction = 10,
/obj/effect/hallucination/assault = 10,
/obj/effect/hallucination/terror_infestation = 10,
/obj/effect/hallucination/loose_energy_ball = 10,
)
))
/**
* Called as part of [/mob/living/proc/handle_status_effects] to handle hallucinations.
*/
/mob/living/carbon/proc/handle_hallucinations()
if(!hallucination || next_hallucination > world.time)
return
next_hallucination = world.time + rand(HALLUCINATE_COOLDOWN_MIN, HALLUCINATE_COOLDOWN_MAX) / (hallucination * HALLUCINATE_COOLDOWN_FACTOR)
if(!prob(HALLUCINATE_CHANCE))
return
// Pick a severity
var/severity = HALLUCINATE_MINOR
switch(rand(100))
if(0 to HALLUCINATE_MINOR_WEIGHT)
severity = HALLUCINATE_MINOR
if((HALLUCINATE_MINOR_WEIGHT + 1) to HALLUCINATE_MODERATE_WEIGHT)
severity = HALLUCINATE_MODERATE
if((HALLUCINATE_MINOR_WEIGHT + HALLUCINATE_MODERATE_WEIGHT + 1) to 100)
severity = HALLUCINATE_MAJOR
hallucinate(pickweight(GLOB.hallucinations[severity]))
/**
* Spawns an hallucination for the mob.
*
* Arguments:
* * H - The type path of the hallucination to spawn.
*/
/mob/living/carbon/proc/hallucinate(obj/effect/hallucination/H)
ASSERT(ispath(H))
if(ckey)
add_attack_logs(null, src, "Received hallucination [H]", ATKLOG_ALL)
return new H(get_turf(src), src)
/**
* # Hallucination
*
* Base object for hallucinations. Contains basic behaviour to display an icon only to the target.
*/
/obj/effect/hallucination
density = FALSE
invisibility = INVISIBILITY_OBSERVER
/// Duration in deciseconds. Can also be a list with the form [lower bound, upper bound] for a random duration.
var/duration = 15 SECONDS
/// Hallucination icon.
var/hallucination_icon
/// Hallucination icon state.
var/hallucination_icon_state
/// Hallucination override.
var/hallucination_override = FALSE
/// Hallucination layer.
var/hallucination_layer = MOB_LAYER
/// The mob that sees this hallucination.
var/mob/living/carbon/target = null
/// Lazy list of images created as part of the hallucination. Cleared on destruction.
var/list/image/images = null
/obj/effect/hallucination/Initialize(mapload, mob/living/carbon/target)
. = ..()
src.target = target
if(hallucination_icon && hallucination_icon_state)
var/image/I = image(hallucination_icon, hallucination_override ? src : get_turf(src), hallucination_icon_state)
I.override = hallucination_override
I.layer = hallucination_layer
add_icon(I)
// Lifetime
if(islist(duration))
duration = rand(duration[1], duration[2])
QDEL_IN(src, duration)
/obj/effect/hallucination/Destroy()
clear_icons()
return ..()
/obj/effect/hallucination/examine(mob/user, infix, suffix)
if(user != target)
return list()
// Overriding to not include call to [/proc/bicon] as it lags the client due to invalid image.
. = list(
"That's \a [name].",
"<span class='whisper'>Something seems odd about this...</span>"
)
/obj/effect/hallucination/singularity_pull()
return
/obj/effect/hallucination/singularity_act()
return
/**
* Adds an image to the hallucination. Cleared on destruction.
*
* Arguments:
* * I - The image to add.
*/
/obj/effect/hallucination/proc/add_icon(image/I)
LAZYADD(images, I)
target?.client?.images |= I
/**
* Clears an image from the hallucination.
*
* Arguments:
* * I - The image to clear.
*/
/obj/effect/hallucination/proc/clear_icon(image/I)
LAZYREMOVE(images, I)
target?.client?.images -= I
qdel(I)
/**
* Clears an image from the hallucination after a delay.
*
* Arguments:
* * I - The image to clear.
* * delay - Delay in deciseconds.
*/
/obj/effect/hallucination/proc/clear_icon_in(image/I, delay)
addtimer(CALLBACK(src, .proc/clear_icon, I), delay)
/**
* Clears all images from the hallucination.
*/
/obj/effect/hallucination/proc/clear_icons()
if(!images)
return
target?.client?.images -= images
QDEL_LIST(images)
/**
* Plays a sound to the target only.
*
* Arguments:
* * time - Deciseconds before the sound plays.
* * source - The turf to play the sound from. Optional.
* * snd - The sound file to play.
* * volume - The sound volume.
* * vary - Whether to randomize the sound's pitch.
* * frequency - The sound's pitch.
*/
/obj/effect/hallucination/proc/play_sound_in(time, turf/source = null, snd, volume, vary, frequency)
ASSERT(time >= 0)
if(time == 0) // whatever
target?.playsound_local(source, snd, volume, vary, frequency)
return
addtimer(CALLBACK(target, /mob/.proc/playsound_local, source, snd, volume, vary, frequency), time)
#undef HALLUCINATE_COOLDOWN_MIN
#undef HALLUCINATE_COOLDOWN_MAX
#undef HALLUCINATE_COOLDOWN_FACTOR
#undef HALLUCINATE_CHANCE
#undef HALLUCINATE_MINOR_WEIGHT
#undef HALLUCINATE_MODERATE_WEIGHT
#undef HALLUCINATE_MAJOR_WEIGHT
@@ -68,7 +68,6 @@
var/mob/living/carbon/C = loc
if(istype(C) && prob(2)) //cursed by bubblegum
if(prob(15))
new /obj/effect/hallucination/oh_yeah(get_turf(C), C)
to_chat(C, "<span class='colossus'><b>[pick("I AM IMMORTAL.","I SHALL TAKE BACK WHAT'S MINE.","I SEE YOU.","YOU CANNOT ESCAPE ME FOREVER.","DEATH CANNOT HOLD ME.")]</b></span>")
else
to_chat(C, "<span class='warning'>[pick("You hear faint whispers.","You smell ash.","You feel hot.","You hear a roar in the distance.")]</span>")
@@ -27,4 +27,7 @@
var/dreaming = 0 //How many dream images we have left to send
var/nightmare = 0
/// The world.time after which the mob can hallucinate again.
var/next_hallucination = 0
blood_volume = BLOOD_VOLUME_NORMAL
+4 -4
View File
@@ -746,12 +746,12 @@
if(healths)
var/health_amount = get_perceived_trauma()
if(..(health_amount)) //not dead
switch(hal_screwyhud)
if(SCREWYHUD_CRIT)
switch(health_hud_override)
if(HEALTH_HUD_OVERRIDE_CRIT)
healths.icon_state = "health6"
if(SCREWYHUD_DEAD)
if(HEALTH_HUD_OVERRIDE_DEAD)
healths.icon_state = "health7"
if(SCREWYHUD_HEALTHY)
if(HEALTH_HUD_OVERRIDE_HEALTHY)
healths.icon_state = "health0"
if(healthdoll)
+2 -3
View File
@@ -315,8 +315,7 @@
AdjustJitter(-restingpwr)
if(hallucination)
spawn handle_hallucinations()
handle_hallucinations()
AdjustHallucinate(-2)
// Keep SSD people asleep
@@ -347,7 +346,7 @@
if(comfort > 1 && prob(3))//You don't heal if you're just sleeping on the floor without a blanket.
adjustBruteLoss(-1 * comfort, FALSE)
adjustFireLoss(-1 * comfort)
if(prob(10) && health && hal_screwyhud != SCREWYHUD_CRIT)
if(prob(10) && health && health_hud_override != HEALTH_HUD_OVERRIDE_CRIT)
emote("snore")
return sleeping
+2 -1
View File
@@ -598,7 +598,7 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \
if(next_move >= world.time)
return
if(!isturf(loc) || istype(A, /obj/effect/temp_visual/point))
if(!isturf(loc) || istype(A, /obj/effect/temp_visual/point) || istype(A, /obj/effect/hallucination))
return FALSE
var/tile = get_turf(A)
@@ -1327,6 +1327,7 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \
.["Toggle Build Mode"] = "?_src_=vars;build_mode=[UID()]"
.["Make 2spooky"] = "?_src_=vars;make_skeleton=[UID()]"
.["Hallucinate"] = "?_src_=vars;hallucinate=[UID()]"
.["Assume Direct Control"] = "?_src_=vars;direct_control=[UID()]"
.["Offer Control to Ghosts"] = "?_src_=vars;offer_control=[UID()]"
+2
View File
@@ -199,5 +199,7 @@
var/obj/effect/proc_holder/ranged_ability //Any ranged ability the mob has, as a click override
/// Overrides the health HUD element state if set.
var/health_hud_override = HEALTH_HUD_OVERRIDE_NONE
/// The location our runechat message should appear. Should be src by default.
var/atom/runechat_msg_location
+1 -1
View File
@@ -252,7 +252,7 @@ GLOBAL_DATUM_INIT(multispin_words, /regex, regex("like a record baby"))
else if((findtext(message, GLOB.hallucinate_words)))
for(var/V in listeners)
var/mob/living/L = V
new /obj/effect/hallucination/delusion(get_turf(L),L,duration=150 * power_multiplier,skip_nearby=0)
new /obj/effect/hallucination/delusion(get_turf(L), L)
next_command = world.time + cooldown_meme
//WAKE UP
Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 116 KiB

+5 -1
View File
@@ -1521,7 +1521,6 @@
#include "code\modules\fish\fish_types.dm"
#include "code\modules\fish\fishtank.dm"
#include "code\modules\flufftext\Dreaming.dm"
#include "code\modules\flufftext\Hallucination.dm"
#include "code\modules\flufftext\TextFilters.dm"
#include "code\modules\food_and_drinks\food.dm"
#include "code\modules\food_and_drinks\drinks\drinks.dm"
@@ -1574,6 +1573,11 @@
#include "code\modules\games\52card.dm"
#include "code\modules\games\cards.dm"
#include "code\modules\games\tarot.dm"
#include "code\modules\hallucinations\hallucinations.dm"
#include "code\modules\hallucinations\effects\common.dm"
#include "code\modules\hallucinations\effects\major.dm"
#include "code\modules\hallucinations\effects\minor.dm"
#include "code\modules\hallucinations\effects\moderate.dm"
#include "code\modules\holiday\christmas.dm"
#include "code\modules\holiday\holiday.dm"
#include "code\modules\hydroponics\biogenerator.dm"