[PTBF] Adds blob hallucinations in dark areas (#27722)

* [PTBF] Adds blob hallucinations in dark areas

This hallucination looks for dark, unobstructed tiles within view of the
hallucinating target, and gradually expands a blob from one of those
tiles. It also spawns a blob zombie nearby. If the blob zombie knocks
the target down, the target will hallucinate becoming a blob zombie.

* Blob hallucination icon fix/change, add vomit sfx

Blob zombies now appropriately spawn their heads, and as requested in PR
review, the first blob spawned will have a core icon, and will be
accompanied by a vomit sound effect, like actual blobs. Additionally,
the blob zombie will no longer attack the target after already once
knocking them down.

* Apply suggestions from code review

Remove unnecessary nulls
Change return signature of blob_zombie#think to match convention
Set blob_zombie's name in typedef instead of initializer
Declare source_turf in loop body in blob#expand
Add nullchecks and safety check for deleted targets in blob#Destroy
Format blob hallucination colors list
Return qdel hint in blob#Initialize instead of qdeling self

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: asciodev <81930475+asciodev@users.noreply.github.com>

* Prevent blob zombie hallucination name from being overwritten when created

Signed-off-by: asciodev <81930475+asciodev@users.noreply.github.com>

* DM doesn't like parentheses on their own lines apparently

* Enable dynamic blob hallucination expansion count

Co-authored-by: Drsmail <60036448+Drsmail@users.noreply.github.com>

---------

Signed-off-by: asciodev <81930475+asciodev@users.noreply.github.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: Drsmail <60036448+Drsmail@users.noreply.github.com>
This commit is contained in:
asciodev
2025-01-10 14:38:50 -05:00
committed by GitHub
parent 1110903044
commit 74a5c0d0af
2 changed files with 148 additions and 0 deletions
@@ -329,3 +329,150 @@
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)
/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, "<span class='userdanger'>[name] has hit [target]!</span>")
/obj/effect/hallucination/chaser/attacker/blob_zombie/on_knockdown()
if(!QDELETED(owning_hallucination))
target.visible_message("<span class='warning'>[target] recoils as if hit by something, before suddenly collapsing!</span>",
"<span class='warning'>The corpse of [target.name] suddenly rises!</span>")
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
@@ -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
)
))