diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm index 53fef0e912b..27687114d28 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm @@ -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 diff --git a/icons/effects/eldritch.dmi b/icons/effects/eldritch.dmi index 9676a833588..9d76f065af0 100644 Binary files a/icons/effects/eldritch.dmi and b/icons/effects/eldritch.dmi differ