/** * # 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 /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_USERDANGER("[name] bites you!")) /obj/effect/hallucination/chaser/attacker/terror_spider/on_knockdown() target.visible_message(SPAN_WARNING("[target] recoils as if hit by something, before suddenly collapsing!"), SPAN_USERDANGER("[name] bites you!")) /** * # 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_WARNING("[target] trips over nothing."), SPAN_USERDANGER("You get stuck in [src]!")) /obj/effect/hallucination/tripper/spider_web/item_interaction(mob/living/user, obj/item/used, list/modifiers) if(user != target) return ITEM_INTERACT_COMPLETE step_towards(target, get_turf(src)) target.Weaken(4 SECONDS) target.visible_message(SPAN_WARNING("[target] flails [target.p_their()] [used.name] as if striking something, only to trip!"), SPAN_USERDANGER("[src] vanishes as you strike it with [used], causing you to stumble forward!")) qdel(src) return ITEM_INTERACT_COMPLETE /** * # 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(!T.is_blocked_turf()) 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(!T.is_blocked_turf()) 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_REF(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_WARNING("[target] recoils as if hit by something, before suddenly collapsing!"), SPAN_USERDANGER("[name] has stunned you with the advanced baton!")) 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_REF(phase_2)), length_phase_1) addtimer(CALLBACK(src, PROC_REF(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_REF(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(!T.is_blocked_turf()) 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("arctic_skeleton", "templar", "skeleton", "sovietmelee", "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("arctic_skeleton", "templar", "sovietmelee", "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_USERDANGER("[name] has [attack_verb] [target]!")) /obj/effect/hallucination/chaser/attacker/assaulter/on_knockdown() target.visible_message(SPAN_WARNING("[target] recoils as if hit by something, before suddenly collapsing!"), SPAN_USERDANGER("[name] has [attack_verb] [target]!")) QDEL_IN(src, 3 SECONDS) /obj/effect/hallucination/blob duration = 30 SECONDS /// The blob zombie hallucination. var/obj/effect/hallucination/chaser/attacker/blob_zombie/zombie /// The self delusion blob zombie hallucination, triggers on knockdown. var/obj/effect/hallucination/blob_zombify/player_zombie /// 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 image for the chaser zombie's blob head var/image/chaser_blob_head /// The image for the player zombie's blob head var/image/target_blob_head /// The delay at which the blob expands in deciseconds. Shouldn't be too low to prevent lag. var/expand_delay /// The amount of time in deciseconds the hallucination should continue after final expansion. var/conclude_delay = 2 SECONDS /// Expand timer handle. var/expand_timer /obj/effect/hallucination/blob/Initialize(mapload, mob/living/carbon/target, expansions = 4) . = ..() expand_delay = (duration - conclude_delay) / max(1, expansions) var/list/locs = list() for(var/turf/T in oview(world.view / 2, target)) var/light_amount = T.get_lumcount() if(!T.is_blocked_turf() && light_amount <= 0.5) locs += T if(!length(locs)) return INITIALIZE_HINT_QDEL var/turf/T = get_turf(pick(locs)) color = pick(COLOR_BLACK, COLOR_RIPPING_TENDRILS, COLOR_BOILING_OIL, COLOR_ENVENOMED_FILAMENTS, COLOR_LEXORIN_JELLY, COLOR_KINETIC_GELATIN, COLOR_CRYOGENIC_LIQUID, COLOR_SORIUM, COLOR_TESLIUM_PASTE) create_blob(T, core = TRUE) target.playsound_local(T, 'sound/effects/splat.ogg', 50, 1) create_zombie(T) expand_queue += T processed[T] = TRUE expand_timer = addtimer(CALLBACK(src, PROC_REF(expand)), expand_delay, TIMER_LOOP | TIMER_STOPPABLE) /obj/effect/hallucination/blob/Destroy() deltimer(expand_timer) expand_queue.Cut() processed.Cut() QDEL_NULL(zombie) QDEL_NULL(player_zombie) if(!QDELETED(target)) target.client?.images -= chaser_blob_head target.client?.images -= target_blob_head chaser_blob_head = null target_blob_head = null return ..() /** * Called regularly in a timer to process the blob expanding. */ /obj/effect/hallucination/blob/proc/expand() // Brace for potentially expensive proc for(var/turf/source_turf as anything in expand_queue) expand_queue -= source_turf //Expand to each dir for(var/direction in GLOB.cardinal) var/turf/target_turf = get_step(source_turf, direction) if(processed[target_turf] || !source_turf.CanAtmosPass(direction) || !target_turf.CanAtmosPass(turn(direction, 180))) continue create_blob(target_turf) expand_queue += target_turf processed[target_turf] = TRUE /** * Creates a fake blob overlay on the given turf. * * Arguments: * * T - The turf to create a fake blob overlay on. */ /obj/effect/hallucination/blob/proc/create_blob(turf/T, core = FALSE) var/blob_icon_state = "blob" if(core) blob_icon_state = "blob_core" var/image/I = image('icons/mob/blob.dmi', T, blob_icon_state, layer = FLY_LAYER) I.plane = GAME_PLANE I.color = color add_icon(I) /obj/effect/hallucination/blob/proc/create_zombie(turf/T) zombie = new(T, target, src) /obj/effect/hallucination/blob/proc/zombify(turf/T) player_zombie = new(T, target, src) /obj/effect/hallucination/chaser/attacker/blob_zombie name = "blob zombie" hallucination_icon = 'icons/mob/human.dmi' hallucination_icon_state = "zombie2_s" duration = 45 SECONDS /// The hallucination that spawned us. var/obj/effect/hallucination/blob/owning_hallucination = null /// Whether or not the target has been zombified already. var/has_zombified = FALSE /obj/effect/hallucination/chaser/attacker/blob_zombie/Initialize(mapload, mob/living/carbon/target, obj/effect/hallucination/blob/blob) . = ..() name = "blob zombie" var/image/I = image('icons/mob/blob.dmi', src, "blob_head") I.color = blob.color target.client.images += I owning_hallucination = blob blob.chaser_blob_head = I /obj/effect/hallucination/chaser/attacker/blob_zombie/attack_effects() do_attack_animation(target) target.playsound_local(get_turf(src), 'sound/weapons/genhit1.ogg', 50, TRUE) to_chat(target, SPAN_USERDANGER("[name] has hit [target]!")) /obj/effect/hallucination/chaser/attacker/blob_zombie/on_knockdown() if(!QDELETED(owning_hallucination)) target.visible_message(SPAN_WARNING("[target] recoils as if hit by something, before suddenly collapsing!"), SPAN_WARNING("The corpse of [target.name] suddenly rises!")) owning_hallucination.zombify(target) has_zombified = TRUE else qdel(src) /obj/effect/hallucination/chaser/attacker/blob_zombie/think() if(has_zombified) return return ..() /obj/effect/hallucination/blob_zombify duration = 20 SECONDS /obj/effect/hallucination/blob_zombify/Initialize(mapload, mob/living/carbon/target, obj/effect/hallucination/blob/blob) . = ..() var/image/I = image('icons/mob/blob.dmi', target, icon_state = "blob_head") I.color = blob.color target.client.images += I blob.target_blob_head = I /** * # Hallucination - Sniper * * Fires a penetrator round at the target. On hit, knockdown + stam loss + hallucinated blood splatter for a bit. */ /obj/effect/hallucination/sniper /obj/effect/hallucination/sniper/Initialize(mapload, mob/living/carbon/target) . = ..() // Make sure the target has a client. Otherwise, stop the hallucination if(!target.client) qdel(src) return // Find a start spot for the sniper bullet var/list/possible_turfs = list() for(var/turf/T in RANGE_EDGE_TURFS(13, target.loc)) possible_turfs += T if(!length(possible_turfs)) log_debug("Unable to find possible turf for [src].") qdel(src) return var/turf/shot_loc = get_turf(pick(possible_turfs)) fire_bullet(shot_loc, target) /obj/effect/hallucination/sniper/proc/fire_bullet(turf/shot_loc, mob/living/carbon/target) // Fire the bullet var/obj/projectile/bullet/sniper/penetrator/hallucination/bullet = new(shot_loc) bullet.hallucinator = target bullet.def_zone = BODY_ZONE_HEAD bullet.suppressed = TRUE // Turn right away var/matrix/M = new var/angle = round(get_angle(shot_loc, target)) M.Turn(angle) bullet.transform = M // Handle who can see the bullet bullet.bullet_image = image(bullet.icon, bullet, bullet.icon_state, OBJ_LAYER, bullet.dir) bullet.bullet_image.transform = M target.client.images += bullet.bullet_image // Start flying bullet.trajectory = new(bullet.x, bullet.y, bullet.z, bullet.pixel_x, bullet.pixel_y, angle, SSprojectiles.global_pixel_speed) bullet.last_projectile_move = world.time bullet.has_been_fired = TRUE target.playsound_local(target.loc, 'sound/weapons/gunshots/gunshot_sniper.ogg', 50) START_PROCESSING(SSprojectiles, bullet) /obj/effect/hallucination/sniper_bloodsplatter hallucination_icon = 'icons/effects/blood.dmi' hallucination_icon_state = "mfloor1" hallucination_color = "#A10808" /obj/effect/hallucination/sniper_bloodsplatter/Initialize(mapload, mob/living/carbon/target) var/list/b_data = target.get_blood_data(target.get_blood_id()) if(b_data && !isnull(b_data["blood_color"])) hallucination_color = b_data["blood_color"] . = ..() hallucination_icon_state = pick("mfloor1", "mfloor2", "mfloor3", "mfloor4", "mfloor5", "mfloor6", "mfloor7") /obj/projectile/bullet/sniper/penetrator/hallucination nodamage = TRUE invisibility = INVISIBILITY_MAXIMUM // You no see boolet /// The hallucinator var/mob/living/carbon/hallucinator = null /// Handles only the victim seeing it var/image/bullet_image = null /obj/projectile/bullet/sniper/penetrator/hallucination/on_hit(atom/target, blocked, hit_zone) if(!isliving(target)) return if(target != hallucinator) return var/mob/living/hit_target = target var/organ_hit_text = "" if(hit_target.has_limbs) organ_hit_text = " in \the [parse_zone(def_zone)]" hit_target.playsound_local(loc, hitsound, 5, TRUE) hit_target.apply_damage(60, STAMINA, def_zone) hit_target.KnockDown(2 SECONDS) new /obj/effect/hallucination/sniper_bloodsplatter(get_turf(src), hit_target) to_chat(hit_target, SPAN_USERDANGER("You're shot by \a [src][organ_hit_text]!")) /obj/projectile/bullet/sniper/penetrator/hallucination/Bump(atom/A, yes) if(!yes) // prevents double bumps. return var/turf/target_turf = get_turf(A) prehit(A) var/mob/living/hit_target = A if(hit_target == hallucinator) hit_target.bullet_act(src, def_zone) loc = target_turf if(A) permutated += A return 0