Adds a visual hint to watcher gaze ability showing if you're going to be affected or not (#96636)

This commit is contained in:
SmArtKar
2026-06-24 19:43:44 -04:00
committed by GitHub
parent 9d5e241a62
commit 8525920dde
2 changed files with 97 additions and 5 deletions
@@ -17,12 +17,22 @@
var/animation_time = 0.8 SECONDS
/// How long after pressing the button do we give people to turn around?
var/wait_delay = 1.6 SECONDS
/// What are we currently displaying?
/// What are we currently displaying to all mobs?
var/image/current_overlay
/// Image displayed for to-be-blinded victims
var/image/danger_overlay
/// Timer until we go to the next stage
var/stage_timer
/// Proximity monitor we use to keep track of possible targets
var/datum/proximity_monitor/watcher_gaze/proximity_monitor
/// List of textrefs to mobs in range we're currently displaying warnings to -> are they currently under threat or not
var/list/tracked_mobs = list()
/datum/action/cooldown/mob_cooldown/watcher_gaze/Activate(mob/living/target)
proximity_monitor = new(owner, effect_radius, ability = src)
// Start tracking all potential victims in range as proxmon won't trigger on them
for (var/mob/living/victim in viewers(effect_radius, owner))
on_entered(victim)
show_indicator_overlay("eye_open")
stage_timer = addtimer(CALLBACK(src, PROC_REF(show_indicator_overlay), "eye_pulse"), animation_time, TIMER_STOPPABLE)
StartCooldown(360 SECONDS, 360 SECONDS)
@@ -33,14 +43,20 @@
deltimer(stage_timer)
clear_current_overlay()
StartCooldown()
tracked_mobs.Cut()
QDEL_NULL(proximity_monitor)
return TRUE
/datum/action/cooldown/mob_cooldown/watcher_gaze/Destroy()
tracked_mobs.Cut()
QDEL_NULL(proximity_monitor)
deltimer(stage_timer)
clear_current_overlay()
return ..()
/datum/action/cooldown/mob_cooldown/watcher_gaze/Remove(mob/removed_from)
tracked_mobs.Cut()
QDEL_NULL(proximity_monitor)
deltimer(stage_timer)
clear_current_overlay()
return ..()
@@ -50,8 +66,7 @@
deltimer(stage_timer)
show_indicator_overlay("eye_flash")
for (var/mob/living/viewer in viewers(effect_radius, owner))
var/view_dir = get_dir(viewer, owner)
if (!(viewer.dir & view_dir) || viewer.stat != CONSCIOUS)
if (!valid_target(viewer))
continue
if (!apply_effect(viewer))
continue
@@ -67,6 +82,13 @@
var/mob/living/living_owner = owner
living_owner.Stun(1.5 SECONDS, ignore_canstun = TRUE)
/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/valid_target(mob/living/viewer)
if (!istype(viewer) || viewer.stat || viewer == owner)
return FALSE
if (!(viewer.dir & get_dir(viewer, owner)))
return FALSE
return TRUE
/// Do something bad to someone who was looking at us
/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/apply_effect(mob/living/viewer)
if (!viewer.flash_act(intensity = 4, affect_silicon = TRUE, visual = TRUE, length = 3 SECONDS))
@@ -83,18 +105,88 @@
/// Display an animated overlay over our head to indicate what's going on
/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/show_indicator_overlay(overlay_state)
clear_current_overlay()
current_overlay = image(icon = 'icons/effects/eldritch.dmi', loc = owner, icon_state = overlay_state, layer = ABOVE_ALL_MOB_LAYER)
current_overlay = image(icon = 'icons/effects/eldritch.dmi', loc = owner, icon_state = "[overlay_state]_y", layer = ABOVE_ALL_MOB_LAYER)
current_overlay.pixel_w = -owner.pixel_x
current_overlay.pixel_z = 28
SET_PLANE_EXPLICIT(current_overlay, ABOVE_LIGHTING_PLANE, owner)
// This will cause turning to reset the animation *but* this is the best option
// as modifying alpha requires readding the image to client.images for it to actually update
danger_overlay = image(icon = 'icons/effects/eldritch.dmi', loc = owner, icon_state = overlay_state, layer = ABOVE_ALL_MOB_LAYER)
danger_overlay.pixel_w = -owner.pixel_x
danger_overlay.pixel_z = 28
SET_PLANE_EXPLICIT(danger_overlay, ABOVE_LIGHTING_PLANE, owner)
for(var/client/add_to in GLOB.clients)
add_to.images += current_overlay
var/mob/living/victim = add_to.mob
if (istype(victim) && tracked_mobs[REF(victim)])
add_to.images += danger_overlay
else
add_to.images += current_overlay
/// Hide whatever overlay we are showing
/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/clear_current_overlay()
if (!isnull(current_overlay))
remove_image_from_clients(current_overlay, GLOB.clients)
remove_image_from_clients(danger_overlay, GLOB.clients)
current_overlay = null
danger_overlay = null
/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/on_entered(mob/living/arrived)
if (arrived == owner)
return
// Already tracked
if (isnull(tracked_mobs[REF(arrived)]))
RegisterSignals(arrived, list(COMSIG_ATOM_POST_DIR_CHANGE, COMSIG_MOB_STATCHANGE), PROC_REF(update_state))
update_state(arrived)
/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/on_exited(mob/living/exited)
if (exited == owner)
return
UnregisterSignal(exited, list(COMSIG_ATOM_POST_DIR_CHANGE, COMSIG_MOB_STATCHANGE))
tracked_mobs -= REF(exited)
if (current_overlay && exited.client)
exited.client.images += current_overlay
exited.client.images -= danger_overlay
/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/update_state(mob/living/target)
SIGNAL_HANDLER
// Don't do viewers(), too costly and only applies for thermals anyways
if (valid_target(target))
if (!tracked_mobs[REF(target)])
tracked_mobs[REF(target)] = TRUE
if (current_overlay && target.client)
target.client.images -= current_overlay
target.client.images += danger_overlay
else if (tracked_mobs[REF(target)] != FALSE) // Can be null
tracked_mobs[REF(target)] = FALSE
if (current_overlay && target.client)
target.client.images += current_overlay
target.client.images -= danger_overlay
// No need to refresh targets when the owner moves as the ability uses a do_after and will stop if the owner moves anyways
/datum/proximity_monitor/watcher_gaze
/// Ability we're linked to
var/datum/action/cooldown/mob_cooldown/watcher_gaze/gaze = null
/datum/proximity_monitor/watcher_gaze/Destroy()
gaze = null
return ..()
/datum/proximity_monitor/watcher_gaze/New(atom/_host, range, _ignore_if_not_on_turf = TRUE, datum/action/cooldown/mob_cooldown/watcher_gaze/ability = null)
. = ..()
gaze = ability
/datum/proximity_monitor/watcher_gaze/on_entered(atom/source, atom/movable/arrived, turf/old_loc)
if (source != host && arrived != host && isliving(arrived))
gaze.on_entered(arrived)
/datum/proximity_monitor/watcher_gaze/on_uncrossed(atom/source, atom/movable/gone, direction)
if (source != host && gone != host && isliving(gone))
gaze.on_exited(gone)
/datum/proximity_monitor/watcher_gaze/on_initialized(turf/location, atom/created, init_flags)
if (isliving(created))
gaze.on_entered(created)
/// Magmawing glare burns you
/datum/action/cooldown/mob_cooldown/watcher_gaze/fire