diff --git a/code/controllers/subsystem/weather.dm b/code/controllers/subsystem/weather.dm
index 7c220cf1c2..0d5a68f027 100644
--- a/code/controllers/subsystem/weather.dm
+++ b/code/controllers/subsystem/weather.dm
@@ -11,6 +11,7 @@ SUBSYSTEM_DEF(weather)
runlevels = RUNLEVEL_GAME
var/list/processing = list()
var/list/eligible_zlevels = list()
+ var/list/next_hit_by_zlevel = list() //Used by barometers to know when the next storm is coming
/datum/controller/subsystem/weather/fire()
// process active weather
@@ -29,7 +30,9 @@ SUBSYSTEM_DEF(weather)
var/datum/weather/W = pickweight(possible_weather)
run_weather(W, list(text2num(z)))
eligible_zlevels -= z
- addtimer(CALLBACK(src, .proc/make_eligible, z, possible_weather), rand(3000, 6000) + initial(W.weather_duration_upper), TIMER_UNIQUE) //Around 5-10 minutes between weathers
+ var/randTime = rand(3000, 6000)
+ addtimer(CALLBACK(src, .proc/make_eligible, z, possible_weather), randTime + initial(W.weather_duration_upper), TIMER_UNIQUE) //Around 5-10 minutes between weathers
+ next_hit_by_zlevel["[z]"] = world.time + randTime + initial(W.telegraph_duration)
/datum/controller/subsystem/weather/Initialize(start_timeofday)
for(var/V in subtypesof(/datum/weather))
@@ -68,3 +71,13 @@ SUBSYSTEM_DEF(weather)
/datum/controller/subsystem/weather/proc/make_eligible(z, possible_weather)
eligible_zlevels[z] = possible_weather
+ next_hit_by_zlevel["[z]"] = null
+
+/datum/controller/subsystem/weather/proc/get_weather(z, area/active_area)
+ var/datum/weather/A
+ for(var/V in processing)
+ var/datum/weather/W = V
+ if((z in W.impacted_z_levels) && W.area_type == active_area.type)
+ A = W
+ break
+ return A
diff --git a/code/datums/weather/weather.dm b/code/datums/weather/weather.dm
index b26d5f45c8..9f4d8f891f 100644
--- a/code/datums/weather/weather.dm
+++ b/code/datums/weather/weather.dm
@@ -37,6 +37,9 @@
var/probability = 0 // Weight amongst other eligible weather. If zero, will never happen randomly.
var/target_trait = ZTRAIT_STATION // The z-level trait to affect when run randomly or when not overridden.
+ var/barometer_predictable = FALSE
+ var/next_hit_time = 0 //For barometers to know when the next storm will hit
+
/datum/weather/New(z_levels)
..()
impacted_z_levels = z_levels
diff --git a/code/datums/weather/weather_types/acid_rain.dm b/code/datums/weather/weather_types/acid_rain.dm
index 3b43e47815..93e5c2556e 100644
--- a/code/datums/weather/weather_types/acid_rain.dm
+++ b/code/datums/weather/weather_types/acid_rain.dm
@@ -22,6 +22,8 @@
immunity_type = "acid" // temp
+ barometer_predictable = TRUE
+
/datum/weather/acid_rain/weather_act(mob/living/L)
var/resist = L.getarmor(null, "acid")
diff --git a/code/datums/weather/weather_types/ash_storm.dm b/code/datums/weather/weather_types/ash_storm.dm
index 79f0a963bd..2a00f51314 100644
--- a/code/datums/weather/weather_types/ash_storm.dm
+++ b/code/datums/weather/weather_types/ash_storm.dm
@@ -23,6 +23,8 @@
probability = 90
+ barometer_predictable = TRUE
+
var/datum/looping_sound/active_outside_ashstorm/sound_ao = new(list(), FALSE, TRUE)
var/datum/looping_sound/active_inside_ashstorm/sound_ai = new(list(), FALSE, TRUE)
var/datum/looping_sound/weak_outside_ashstorm/sound_wo = new(list(), FALSE, TRUE)
diff --git a/code/datums/weather/weather_types/snow_storm.dm b/code/datums/weather/weather_types/snow_storm.dm
index 0b2a1780cf..a8d627bbca 100644
--- a/code/datums/weather/weather_types/snow_storm.dm
+++ b/code/datums/weather/weather_types/snow_storm.dm
@@ -20,6 +20,8 @@
immunity_type = "snow"
+ barometer_predictable = TRUE
+
/datum/weather/snow_storm/weather_act(mob/living/L)
L.bodytemperature -=(rand(5,15))
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index 3c778818a5..f5c14db8a8 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -334,7 +334,7 @@ GAS ANALYZER
advanced = TRUE
/obj/item/device/analyzer
- desc = "A hand-held environmental scanner which reports current gas levels."
+ desc = "A hand-held environmental scanner which reports current gas levels. Alt-Click to use the built in barometer function."
name = "analyzer"
icon_state = "atmos"
item_state = "analyzer"
@@ -348,6 +348,9 @@ GAS ANALYZER
throw_range = 7
materials = list(MAT_METAL=30, MAT_GLASS=20)
grind_results = list("mercury" = 5, "iron" = 5, "silicon" = 5)
+ var/cooldown = FALSE
+ var/cooldown_time = 250
+ var/accuracy // 0 is the best accuracy.
/obj/item/device/analyzer/suicide_act(mob/living/carbon/user)
user.visible_message("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!")
@@ -412,6 +415,70 @@ GAS ANALYZER
to_chat(user, "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] %")
to_chat(user, "Temperature: [round(environment.temperature-T0C)] °C")
+/obj/item/device/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens
+ ..()
+
+ if(user.canUseTopic(src))
+
+ if(cooldown)
+ to_chat(user, "[src]'s barometer function is prepraring itself.")
+ return
+
+ var/turf/T = get_turf(user)
+ if(!T)
+ return
+
+ playsound(src, 'sound/effects/pop.ogg', 100)
+ var/area/user_area = T.loc
+ var/datum/weather/ongoing_weather = null
+ for(var/V in SSweather.processing)
+ var/datum/weather/W = V
+ if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE))
+ ongoing_weather = W
+ break
+
+ if(ongoing_weather)
+ if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE))
+ to_chat(user, "[src] can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]")
+ return
+
+ var/time = butchertime((ongoing_weather.next_hit_time - world.time)/10)
+ to_chat(user, "The next [ongoing_weather] will hit in [round(time)] seconds.")
+ if(ongoing_weather.aesthetic)
+ to_chat(user, "[src] says that the next storm will breeze on by.")
+ else if(user_area.outdoors)
+ var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"]
+ var/fixed = next_hit ? next_hit - world.time : -1
+ if(fixed < 0)
+ to_chat(user, "[src] was unable to trace any weather patterns.")
+ else
+ fixed = butchertime(round(fixed / 10))
+ to_chat(user, "A storm will land in approximately [fixed] seconds.")
+ else
+ to_chat(user, "[src]'s barometer function won't work indoors!")
+ cooldown = TRUE
+ addtimer(src, /obj/item/device/analyzer/proc/ping, cooldown_time)
+
+/obj/item/device/analyzer/proc/ping()
+ if(isliving(loc))
+ var/mob/living/L = loc
+ to_chat(L, "[src] is ready!")
+ playsound(src, 'sound/machines/click.ogg', 100)
+ cooldown = FALSE
+
+/obj/item/device/analyzer/proc/butchertime(amount)
+ if(!amount)
+ return
+ if(accuracy)
+ var/time = amount
+ var/inaccurate = round(accuracy*(1/3))
+ if(prob(50))
+ time -= inaccurate
+ if(prob(50))
+ time += inaccurate
+ return time
+ else
+ return amount
/obj/item/device/slime_scanner
name = "slime scanner"
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index b8fb490a3f..2e7a9219aa 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ