diff --git a/code/modules/hallucinations/effects/major.dm b/code/modules/hallucinations/effects/major.dm index 547b0b1e33e..c17b03e7667 100644 --- a/code/modules/hallucinations/effects/major.dm +++ b/code/modules/hallucinations/effects/major.dm @@ -329,3 +329,150 @@ target.visible_message("[target] recoils as if hit by something, before suddenly collapsing!", "[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(!is_blocked_turf(T) && 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 + damage = 25 + /// 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, "[name] has hit [target]!") + +/obj/effect/hallucination/chaser/attacker/blob_zombie/on_knockdown() + if(!QDELETED(owning_hallucination)) + target.visible_message("[target] recoils as if hit by something, before suddenly collapsing!", + "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 diff --git a/code/modules/hallucinations/hallucinations.dm b/code/modules/hallucinations/hallucinations.dm index 4bf7b41d60a..bd9f5a9dc62 100644 --- a/code/modules/hallucinations/hallucinations.dm +++ b/code/modules/hallucinations/hallucinations.dm @@ -32,6 +32,7 @@ GLOBAL_LIST_INIT(hallucinations, list( /obj/effect/hallucination/fake_grenade/spawner = 10, /obj/effect/hallucination/terror_infestation = 10, /obj/effect/hallucination/loose_energy_ball = 10, + /obj/effect/hallucination/blob = 10, /datum/hallucination_manager/xeno_pounce = 10 ) ))