diff --git a/code/_onclick/hud/follow_alert.dm b/code/_onclick/hud/follow_alert.dm
new file mode 100644
index 00000000000..d8f95d06986
--- /dev/null
+++ b/code/_onclick/hud/follow_alert.dm
@@ -0,0 +1,158 @@
+/obj/screen/alert/augury
+ name = "Something interesting!"
+ desc = "Click to follow."
+ /// The atom being followed
+ var/atom/movable/follow_target
+ /// The atoms following it
+ var/list/followers = list()
+ /// Queue of next atoms to follow
+ var/list/next_targets = list()
+ /// Title for the thing that will be followed.
+ var/thing_followed = "debris"
+ /// After a followed item is qdeleted, wait this long before switching to the next target, allowing you to see aftermath.
+ var/time_between_switches = 1 SECONDS
+
+/**
+ * Create a new augury alert.
+ *
+ * Arguments:
+ * * follow_target: The atom to start out following. Can be null, in which case change_targets() should be used at some point.
+ * * alert_overlay_override: If follow_target is provided (or not), use this for the alert image.
+ */
+/obj/screen/alert/augury/Initialize(mapload, atom/movable/follow_target, image/alert_overlay_override)
+ . = ..()
+ src.follow_target = follow_target
+
+ if(!alert_overlay_override && follow_target)
+ var/image/source = image(follow_target)
+ var/old_layer = source.layer
+ var/old_plane = source.plane
+ source.layer = FLOAT_LAYER
+ source.plane = FLOAT_PLANE
+ overlays += source
+ source.layer = old_layer
+ source.plane = old_plane
+ else if(alert_overlay_override)
+ alert_overlay_override.layer = FLOAT_LAYER
+ alert_overlay_override.plane = FLOAT_PLANE
+ overlays += alert_overlay_override
+
+/obj/screen/alert/augury/Click(location, control, params)
+ . = ..()
+ if(!usr || !usr.client || !isobserver(usr))
+ return
+ if(usr in followers)
+ to_chat(usr, "You will now not auto-follow [thing_followed].")
+ remove_follower(usr)
+ else
+ to_chat(usr, "You are now auto-following [thing_followed]. Click again to stop.")
+ add_follower(usr)
+
+/obj/screen/alert/augury/Destroy(force)
+ for(var/atom/movable/follower in followers) // in case something was nulled
+ follower.stop_orbit()
+
+ followers = null
+
+ for(var/atom/movable/target in next_targets)
+ UnregisterSignal(target, COMSIG_PARENT_QDELETING)
+ next_targets = null
+
+ if(follow_target)
+ UnregisterSignal(follow_target, COMSIG_ATOM_ORBIT_STOP)
+ UnregisterSignal(follow_target, COMSIG_PARENT_QDELETING)
+ follow_target = null
+
+ return ..()
+
+/// Executed when the parent is deleted.
+/// Don't immediately kill ourselves, since it's possible that we might want to move somewhere else
+/// (for example, after a meteor strike)
+/obj/screen/alert/augury/proc/on_following_qdel(atom/movable/A)
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
+ for(var/atom/movable/follower in followers)
+ follower.stop_orbit()
+
+ follow_target = get_next_target()
+ if(follow_target)
+ if(time_between_switches > 0)
+ addtimer(CALLBACK(src, PROC_REF(change_targets), follow_target), time_between_switches) // add a second so people can see what exploded
+ else
+ change_targets(follow_target)
+
+/**
+ * Change the atom that everyone is currently following, moving everyone to the new object.
+ *
+ * Arguments:
+ * * next_to - The next atom to follow.
+ */
+/obj/screen/alert/augury/proc/change_targets(atom/movable/next_to)
+ // unregister first so we aren't bombarded when changing orbits
+ if(isnull(next_to))
+ return
+ if(follow_target)
+ UnregisterSignal(follow_target, COMSIG_ATOM_ORBIT_STOP)
+ UnregisterSignal(follow_target, COMSIG_PARENT_QDELETING)
+ follow_target = next_to
+ RegisterSignal(follow_target, COMSIG_PARENT_QDELETING, PROC_REF(on_following_qdel), override = TRUE) // override our qdeleting signal
+ for(var/atom/movable/M in followers)
+ M.orbit(follow_target)
+
+ RegisterSignal(follow_target, COMSIG_ATOM_ORBIT_STOP, PROC_REF(remove_follower_on_stop_orbit))
+
+/obj/screen/alert/augury/proc/add_follower(atom/movable/follower)
+ followers |= follower
+ follower.orbit(follow_target)
+
+/obj/screen/alert/augury/proc/remove_follower(atom/movable/follower)
+ followers -= follower
+ follower.stop_orbit()
+
+/obj/screen/alert/augury/proc/get_next_target()
+ if(!length(next_targets))
+ return
+
+ var/atom/movable/target
+
+ while(QDELETED(target) && length(next_targets))
+ target = next_targets[length(next_targets)] // pop the (hopefully most recent)
+ next_targets.Remove(target)
+
+ return target
+
+/// Called when someone stops orbiting our followed object, so they can actually get out of the loop.
+/obj/screen/alert/augury/proc/remove_follower_on_stop_orbit(atom/movable/followed, atom/movable/follower)
+ SIGNAL_HANDLER // COMSIG_ATOM_ORBIT_STOP
+ if(locateUID(follower.orbiting_uid) != follow_target)
+ remove_follower(follower) // don't try to stop the orbit again
+
+/// Meteor alert.
+/// Appears during a meteor storm and allows for auto-following of debris.
+/obj/screen/alert/augury/meteor
+ name = "Meteors incoming!"
+ desc = "Click to automatically follow debris, and click again to stop."
+
+/obj/screen/alert/augury/meteor/Initialize(mapload)
+ var/image/meteor_img = image(icon = 'icons/obj/meteor.dmi', icon_state = "flaming")
+ . = ..(mapload, alert_overlay_override = meteor_img)
+ START_PROCESSING(SSfastprocess, src)
+
+/obj/screen/alert/augury/meteor/Destroy(force)
+ . = ..()
+ STOP_PROCESSING(SSfastprocess, src)
+
+/obj/screen/alert/augury/meteor/process()
+ var/overridden = FALSE
+ for(var/obj/effect/meteor/M in GLOB.meteor_list)
+ if(!is_station_level(M.z))
+ continue // don't worry about endlessly looping
+ if(istype(M, /obj/effect/meteor/tunguska) && !overridden)
+ if(M != follow_target) // keep following it, but don't force an orbit change
+ change_targets(M) // TUNGUSKAAAAAAA
+ overridden = TRUE
+ continue
+ if(!(M in next_targets))
+ next_targets.Add(M)
+
+ if(QDELETED(follow_target) && length(next_targets) && !overridden)
+ change_targets(get_next_target())
diff --git a/code/modules/events/meteors_event.dm b/code/modules/events/meteors_event.dm
index 4c8d3b32039..fe34ab5fede 100644
--- a/code/modules/events/meteors_event.dm
+++ b/code/modules/events/meteors_event.dm
@@ -3,8 +3,14 @@
endWhen = 7
var/next_meteor = 6
var/waves = 1
+ var/obj/screen/alert/augury/meteor/screen_alert
/datum/event/meteor_wave/setup()
+ for(var/mob/dead/observer/O in GLOB.dead_mob_list)
+ var/obj/screen/alert/augury/meteor/A = O.throw_alert("\ref[src]_augury", /obj/screen/alert/augury/meteor)
+ if(A)
+ screen_alert = A
+
waves = severity * rand(1,3)
/datum/event/meteor_wave/announce()
@@ -16,6 +22,9 @@
//meteor showers are lighter and more common,
/datum/event/meteor_wave/tick()
+ // keep observers updated with the alert
+ for(var/mob/dead/observer/O in GLOB.dead_mob_list)
+ O.throw_alert("\ref[src]_augury", /obj/screen/alert/augury/meteor)
if(waves && activeFor >= next_meteor)
INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(spawn_meteors), get_meteor_count(), get_meteors())
next_meteor += rand(15, 30) / severity
@@ -23,6 +32,9 @@
endWhen = (waves ? next_meteor + 1 : activeFor + 15)
/datum/event/meteor_wave/end()
+ for(var/mob/M in GLOB.dead_mob_list)
+ M.clear_alert("\ref[src]_augury")
+ QDEL_NULL(screen_alert)
switch(severity)
if(EVENT_LEVEL_MAJOR)
GLOB.minor_announcement.Announce("The station has cleared the meteor storm.", "Meteor Alert")
diff --git a/paradise.dme b/paradise.dme
index 36f01556075..5d5800d442e 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -188,6 +188,7 @@
#include "code\_onclick\hud\alien_hud.dm"
#include "code\_onclick\hud\alien_larva.dm"
#include "code\_onclick\hud\blob_overmind.dm"
+#include "code\_onclick\hud\follow_alert.dm"
#include "code\_onclick\hud\bot_hud.dm"
#include "code\_onclick\hud\constructs_hud.dm"
#include "code\_onclick\hud\fullscreen.dm"