Refactors weather into a subsystem (#19003)

Weather has been refactored from a weather control machine on the lavaland z-level into a subsystem. All existing weather has been changed to accommodate this change, and their code has been optimized by using addtimer() instead of sleep().

The new subsystem also supports adding weather to any z-level; for instance, if you made a weather called /datum/weather/rain_storm and made it target z-level 3 with a 100% probability, the rain storm would occur as much as possible with 5-10 minute intermissions. These intermissions take into account the weather's duration.
This commit is contained in:
Xhuis
2016-07-03 21:55:36 -04:00
committed by oranges
parent 1aa7a312c1
commit 2ab3f554d9
11 changed files with 322 additions and 275 deletions
+50
View File
@@ -0,0 +1,50 @@
//Used for all kinds of weather, ex. lavaland ash storms.
var/datum/subsystem/weather/SSweather
/datum/subsystem/weather
name = "Weather"
flags = SS_BACKGROUND
wait = 10
var/list/processing = list()
var/list/existing_weather = list()
var/list/eligible_zlevels = list(ZLEVEL_LAVALAND)
/datum/subsystem/weather/New()
NEW_SS_GLOBAL(SSweather)
/datum/subsystem/weather/fire()
for(var/V in processing)
var/datum/weather/W = V
if(W.aesthetic)
continue
for(var/mob/living/L in mob_list)
var/area/A = get_area(L)
if(L.z == W.target_z && !W.immunity_type in L.weather_immunities && A in W.impacted_areas)
W.impact(L)
for(var/Z in eligible_zlevels)
var/list/possible_weather_for_this_z = list()
for(var/V in existing_weather)
var/datum/weather/WE = V
if(WE.target_z == Z && WE.probability) //Another check so that it doesn't run extra weather
possible_weather_for_this_z[WE] = WE.probability
var/datum/weather/W = pickweight(possible_weather_for_this_z)
run_weather(W.name)
eligible_zlevels -= Z
addtimer(src, "make_z_eligible", rand(3000, 6000) + W.weather_duration_upper, Z) //Around 5-10 minutes between weathers
/datum/subsystem/weather/Initialize(start_timeofday)
..()
for(var/V in subtypesof(/datum/weather))
var/datum/weather/W = V
existing_weather |= new W
/datum/subsystem/weather/proc/run_weather(weather_name)
if(!weather_name)
return
for(var/V in existing_weather)
var/datum/weather/W = V
if(W.name == weather_name)
W.telegraph()
/datum/subsystem/weather/proc/make_z_eligible(zlevel)
eligible_zlevels |= zlevel
+125
View File
@@ -0,0 +1,125 @@
//The effects of weather occur across an entire z-level. For instance, lavaland has periodic ash storms that scorch most unprotected creatures.
#define STARTUP_STAGE 1
#define MAIN_STAGE 2
#define WIND_DOWN_STAGE 3
#define END_STAGE 4
/datum/weather
var/name = "space wind"
var/desc = "Heavy gusts of wind blanket the area, periodically knocking down anyone caught in the open."
var/telegraph_message = "<span class='warning'>The wind begins to pick up.</span>" //The message displayed in chat to foreshadow the weather's beginning
var/telegraph_duration = 300 //In deciseconds, how long from the beginning of the telegraph until the weather begins
var/telegraph_sound //The sound file played to everyone on an affected z-level
var/telegraph_overlay //The overlay applied to all tiles on the z-level
var/weather_message = "<span class='userdanger'>The wind begins to blow ferociously!</span>" //Displayed in chat once the weather begins in earnest
var/weather_duration = 1200 //In deciseconds, how long the weather lasts once it begins
var/weather_duration_lower = 1200 //See above - this is the lowest possible duration
var/weather_duration_upper = 1500 //See above - this is the highest possible duration
var/weather_sound
var/weather_overlay
var/end_message = "<span class='danger'>The wind relents its assault.</span>" //Displayed once the wather is over
var/end_duration = 300 //In deciseconds, how long the "wind-down" graphic will appear before vanishing entirely
var/end_sound
var/end_overlay
var/area_type = /area/space //Types of area to affect
var/list/impacted_areas = list() //Areas to be affected by the weather, calculated when the weather begins
var/target_z = ZLEVEL_STATION //The z-level to affect
var/overlay_layer = AREA_LAYER //Since it's above everything else, this is the layer used by default. TURF_LAYER is below mobs and walls if you need to use that.
var/aesthetic = FALSE //If the weather has no purpose other than looks
var/immunity_type = "storm" //Used by mobs to prevent them from being affected by the weather
var/stage = END_STAGE //The stage of the weather, from 1-4
var/probability = FALSE //Percent chance to happen if there are other possible weathers on the z-level
/datum/weather/New()
..()
SSweather.existing_weather |= src
/datum/weather/Destroy()
SSweather.existing_weather -= src
..()
/datum/weather/proc/telegraph()
if(stage == STARTUP_STAGE)
return
stage = STARTUP_STAGE
for(var/V in get_areas(area_type))
var/area/A = V
if(A.z == target_z)
impacted_areas |= A
weather_duration = rand(weather_duration_lower, weather_duration_upper)
update_areas()
for(var/V in player_list)
var/mob/M = V
if(M.z == target_z)
if(telegraph_message)
M << telegraph_message
if(telegraph_sound)
M << sound(telegraph_sound)
addtimer(src, "start", telegraph_duration)
/datum/weather/proc/start()
if(stage >= MAIN_STAGE)
return
stage = MAIN_STAGE
update_areas()
for(var/V in player_list)
var/mob/M = V
if(M.z == target_z)
if(weather_message)
M << weather_message
if(weather_sound)
M << sound(weather_sound)
START_PROCESSING(SSweather, src)
addtimer(src, "wind_down", weather_duration)
/datum/weather/proc/wind_down()
if(stage >= WIND_DOWN_STAGE)
return
stage = WIND_DOWN_STAGE
update_areas()
for(var/V in player_list)
var/mob/M = V
if(M.z == target_z)
if(end_message)
M << end_message
if(end_sound)
M << sound(end_sound)
STOP_PROCESSING(SSweather, src)
addtimer(src, "end", end_duration)
/datum/weather/proc/end()
if(stage == END_STAGE)
return
stage = END_STAGE
update_areas()
/datum/weather/proc/impact(mob/living/L) //What effect does this weather have on the hapless mob?
return
/datum/weather/proc/update_areas()
for(var/V in impacted_areas)
var/area/N = V
N.layer = overlay_layer
N.icon = 'icons/effects/weather_effects.dmi'
N.invisibility = 0
switch(stage)
if(STARTUP_STAGE)
N.icon_state = telegraph_overlay
if(MAIN_STAGE)
N.icon_state = weather_overlay
if(WIND_DOWN_STAGE)
N.icon_state = end_overlay
if(END_STAGE)
N.icon_state = initial(N.icon_state)
N.icon = 'icons/turf/areas.dmi'
N.layer = AREA_LAYER //Just default back to normal area stuff since I assume setting a var is faster than initial
N.invisibility = INVISIBILITY_MAXIMUM
N.opacity = 0
+113
View File
@@ -0,0 +1,113 @@
//Different types of weather.
/datum/weather/floor_is_lava //The Floor is Lava: Makes all turfs damage anyone on them unless they're standing on a solid object.
name = "the floor is lava"
desc = "The ground turns into surprisingly cool lava, lightly damaging anything on the floor."
telegraph_message = "<span class='warning'>Waves of heat emanate from the ground...</span>"
telegraph_duration = 150
weather_message = "<span class='userdanger'>The floor is lava! Get on top of something!</span>"
weather_duration_lower = 300
weather_duration_upper = 600
weather_overlay = "lava"
end_message = "<span class='danger'>The ground cools and returns to its usual form.</span>"
end_duration = 0
area_type = /area
target_z = ZLEVEL_STATION
overlay_layer = ABOVE_OPEN_TURF_LAYER //Covers floors only
immunity_type = "lava"
/datum/weather/floor_is_lava/impact(mob/living/L)
for(var/obj/structure/O in L.loc)
if(O.density)
return
if(L.loc.density)
return
if(!L.client) //Only sentient people are going along with it!
return
L.adjustFireLoss(3)
/datum/weather/advanced_darkness //Advanced Darkness: Restricts the vision of all affected mobs to a single tile in the cardinal directions.
name = "advanced darkness"
desc = "Everything in the area is effectively blinded, unable to see more than a foot or so around itself."
telegraph_message = "<span class='warning'>The lights begin to dim... is the power going out?</span>"
telegraph_duration = 150
weather_message = "<span class='userdanger'>This isn't your everday darkness... this is <i>advanced</i> darkness!</span>"
weather_duration_lower = 300
weather_duration_upper = 300
end_message = "<span class='danger'>At last, the darkness recedes.</span>"
end_duration = 0
area_type = /area
target_z = ZLEVEL_STATION
/datum/weather/advanced_darkness/update_areas()
for(var/V in impacted_areas)
var/area/A = V
if(stage == MAIN_STAGE)
A.invisibility = 0
A.opacity = 1
A.layer = overlay_layer
A.icon = 'icons/effects/weather_effects.dmi'
A.icon_state = "darkness"
else
A.invisibility = INVISIBILITY_MAXIMUM
A.opacity = 0
/datum/weather/ash_storm //Ash Storms: Common happenings on lavaland. Heavily obscures vision and deals heavy fire damage to anyone caught outside.
name = "ash storm"
desc = "An intense atmospheric storm lifts ash off of the planet's surface and billows it down across the area, dealing intense fire damage to the unprotected."
telegraph_message = "<span class='boldwarning'>An eerie moan rises on the wind. Sheets of burning ash blacken the horizon. Seek shelter.</span>"
telegraph_duration = 300
telegraph_sound = 'sound/lavaland/ash_storm_windup.ogg'
telegraph_overlay = "light_ash"
weather_message = "<span class='userdanger'><i>Smoldering clouds of scorching ash billow down around you! Get inside!</i></span>"
weather_duration_lower = 600
weather_duration_upper = 1500
weather_sound = 'sound/lavaland/ash_storm_start.ogg'
weather_overlay = "ash_storm"
end_message = "<span class='boldannounce'>The shrieking wind whips away the last of the ash falls to its usual murmur. It should be safe to go outside now.</span>"
end_duration = 300
end_sound = 'sound/lavaland/ash_storm_end.ogg'
end_overlay = "light_ash"
area_type = /area/lavaland/surface/outdoors
target_z = ZLEVEL_LAVALAND
immunity_type = "ash"
probability = 90
/datum/weather/ash_storm/impact(mob/living/L)
if(istype(L.loc, /obj/mecha))
return
if(ishuman(L))
var/mob/living/carbon/human/H = L
var/thermal_protection = H.get_thermal_protection()
if(thermal_protection >= FIRE_IMMUNITY_SUIT_MAX_TEMP_PROTECT)
return
L.adjustFireLoss(4)
/datum/weather/ash_storm/emberfall //Emberfall: An ash storm passes by, resulting in harmless embers falling like snow. 10% to happen in place of an ash storm.
name = "emberfall"
desc = "A passing ash storm blankets the area in harmless embers."
weather_message = "<span class='notice'>Gentle embers waft down around you like grotesque snow. The storm seems to have passed you by...</span>"
end_message = "<span class='notice'>The emberfall slows, stops. Another layer of hardened soot to the basalt beneath your feet.</span>"
aesthetic = TRUE
probability = 10
+1 -2
View File
@@ -427,8 +427,7 @@
L.fix()
if("floorlava")
var/datum/weather/floor_is_lava/storm = new /datum/weather/floor_is_lava
storm.weather_start_up()
SSweather.run_weather("the floor is lava")
if("virus")
if(!check_rights(R_FUN))
@@ -13,5 +13,4 @@
/datum/round_event/wizard/darkness/start()
if(!started)
started = TRUE
var/datum/weather/advanced_darkness/darkness = new
darkness.weather_start_up()
SSweather.run_weather("advanced darkness")
+1 -2
View File
@@ -12,5 +12,4 @@
/datum/round_event/wizard/lava/start()
if(!started)
started = TRUE
var/datum/weather/floor_is_lava/LAVA = new /datum/weather/floor_is_lava
LAVA.weather_start_up()
SSweather.run_weather("the floor is lava")
-110
View File
@@ -1,110 +0,0 @@
#define STARTUP_STAGE 1
#define MAIN_STAGE 2
#define WIND_DOWN_STAGE 3
#define END_STAGE 4
/datum/weather
var/name = "storm"
var/start_up_time = 300 //30 seconds
var/start_up_message = "The wind begins to pick up."
var/start_up_sound
var/duration = 120 //2 minutes
var/duration_lower = 120
var/duration_upper = 120
var/duration_sound
var/duration_message = "A storm has started!"
var/wind_down = 300 // 30 seconds
var/wind_down_message = "The storm is passing."
var/wind_down_sound
var/target_z = 1
var/exclude_walls = TRUE
var/area_type = /area/space
var/stage = STARTUP_STAGE
var/start_up_overlay = "lava"
var/duration_overlay = "lava"
var/overlay_layer = AREA_LAYER //This is the default area layer, and above everything else. TURF_LAYER is floors/below walls and mobs.
var/purely_aesthetic = FALSE //If we just want gentle rain that doesn't hurt people
var/list/impacted_areas = list()
var/immunity_type = "storm"
/datum/weather/proc/weather_start_up()
for(var/area/N in get_areas(area_type))
if(N.z == target_z)
impacted_areas += N
duration = rand(duration_lower,duration_upper)
update_areas()
for(var/mob/M in player_list)
if(M.z == target_z)
M << "<span class='warning'><B>[start_up_message]</B></span>"
if(start_up_sound)
M << start_up_sound
sleep(start_up_time)
if(src && stage != MAIN_STAGE)
stage = MAIN_STAGE
weather_main()
/datum/weather/proc/weather_main()
update_areas()
for(var/mob/M in player_list)
if(M.z == target_z)
M << "<span class='userdanger'><i>[duration_message]</i></span>"
if(duration_sound)
M << duration_sound
if(purely_aesthetic)
sleep(duration*10)
else //Storm effects
for(var/i in 1 to duration-1)
for(var/mob/living/L in living_mob_list)
var/area/storm_area = get_area(L)
if(storm_area in impacted_areas)
storm_act(L)
sleep(10)
if(src && stage != WIND_DOWN_STAGE)
stage = WIND_DOWN_STAGE
weather_wind_down()
/datum/weather/proc/weather_wind_down()
update_areas()
for(var/mob/M in player_list)
if(M.z == target_z)
M << "<span class='danger'><B>[wind_down_message]</B></span>"
if(wind_down_sound)
M << wind_down_sound
sleep(wind_down)
if(src && stage != END_STAGE)
stage = END_STAGE
update_areas()
/datum/weather/proc/storm_act(mob/living/L)
if(immunity_type in L.weather_immunities)
return
/datum/weather/proc/update_areas()
for(var/area/N in impacted_areas)
N.layer = overlay_layer
N.icon = 'icons/effects/weather_effects.dmi'
N.invisibility = 0
switch(stage)
if(STARTUP_STAGE)
N.icon_state = start_up_overlay
if(MAIN_STAGE)
N.icon_state = duration_overlay
if(WIND_DOWN_STAGE)
N.icon_state = start_up_overlay
if(END_STAGE)
N.icon_state = initial(N.icon_state)
N.icon = 'icons/turf/areas.dmi'
N.layer = AREA_LAYER //Just default back to normal area stuff since I assume setting a var is faster than initial
N.invisibility = INVISIBILITY_MAXIMUM
N.opacity = 0
@@ -1,108 +0,0 @@
///The floor is lava
/datum/weather/floor_is_lava
name = "floor is lava"
start_up_time = 30 //3 seconds
start_up_message = "The ground begins to bubble."
duration_lower = 45
duration_upper = 60 //1 minute
duration_message = "The floor is lava!"
wind_down = 30// 3 seconds
wind_down_message = "The ground begins to cool."
target_z = 1
exclude_walls = TRUE
area_type = /area
start_up_overlay = "lava"
duration_overlay = "lava"
overlay_layer = ABOVE_OPEN_TURF_LAYER //Covers floors only
immunity_type = "lava"
/datum/weather/floor_is_lava/storm_act(mob/living/L)
if(immunity_type in L.weather_immunities)
return
var/turf/F = get_turf(L)
for(var/obj/structure/O in F.contents)
if(O.level > F.level && !istype(O, /obj/structure/window)) // Something to stand on and it isn't under the floor!
return
L.adjustFireLoss(3)
/datum/weather/advanced_darkness
name = "advanced darkness"
start_up_time = 100 //10 seconds
start_up_message = "The lights begin to dim... is power going out?"
duration_lower = 45
duration_upper = 60 //1 minute
duration_message = "This isn't average everyday darkness... this is advanced darkness!"
wind_down = 100 // 10 seconds
wind_down_message = "The darkness recedes."
purely_aesthetic = TRUE
target_z = 1
exclude_walls = TRUE
area_type = /area
start_up_overlay = ""
duration_overlay = ""
overlay_layer = AREA_LAYER
/datum/weather/advanced_darkness/update_areas()
for(var/area/A in impacted_areas)
if(stage == MAIN_STAGE)
A.invisibility = 0
A.opacity = 1
A.layer = overlay_layer
A.icon = 'icons/effects/weather_effects.dmi'
A.icon_state = start_up_overlay
else
A.invisibility = INVISIBILITY_MAXIMUM
A.opacity = 0
//Ash storms
/datum/weather/ash_storm
name = "ash storm"
start_up_time = 300 //30 seconds
start_up_message = "An eerie moan rises on the wind. Sheets of burning ash blacken the horizon. Seek shelter."
start_up_sound = 'sound/lavaland/ash_storm_windup.ogg'
duration_lower = 60 //1 minute
duration_upper = 150 //2.5 minutes
duration_message = "Smoldering clouds of scorching ash billow down around you! Get inside!"
duration_sound = 'sound/lavaland/ash_storm_start.ogg'
wind_down = 300 // 30 seconds
wind_down_message = "The shrieking wind whips away the last of the ash and falls to its usual murmur. It should be safe to go outside now."
wind_down_sound = 'sound/lavaland/ash_storm_end.ogg'
target_z = ZLEVEL_LAVALAND
area_type = /area/lavaland/surface/outdoors
start_up_overlay = "light_ash"
duration_overlay = "ash_storm"
overlay_layer = AREA_LAYER
immunity_type = "ash"
/datum/weather/ash_storm/false_alarm //No storm, just light ember fall
purely_aesthetic = TRUE
duration_overlay = "light_ash"
duration_message = "<span class='notice'>Gentle ashfall surrounds you like grotesque snow. The storm seems to have passed you by.</span>"
wind_down_message = "The ashfall quietly slows, then stops. Another layer of hardened soot to the volcanic rock beneath you."
/datum/weather/ash_storm/storm_act(mob/living/L)
if(immunity_type in L.weather_immunities)
return
if(istype(L.loc, /obj/mecha))
return
if(ishuman(L))
var/mob/living/carbon/human/H = L
var/thermal_protection = H.get_thermal_protection()
if(thermal_protection >= FIRE_IMMUNITY_SUIT_MAX_TEMP_PROTECT)
return
L.adjustFireLoss(4)
@@ -118,37 +118,38 @@
force = 25
damtype = BURN
hitsound = 'sound/weapons/sear.ogg'
var/obj/machinery/lavaland_controller/linked_machine
var/storm_cooldown = 0
/obj/item/weapon/staff_of_storms/attack_self(mob/user)
if(storm_cooldown > world.time)
user << "<span class='warning'>The staff is still recharging!</span>"
return
if(user.z != ZLEVEL_LAVALAND)
user << "<span class='warning'>You can't seem to control the weather here!</span>"
return
if(!linked_machine || linked_machine.z != user.z)
for(var/obj/machinery/lavaland_controller/controller in machines)
if(controller.z == user.z)
linked_machine = controller
break
var/datum/weather/ash_storm/A
for(var/V in SSweather.existing_weather)
var/datum/weather/W = V
if(W.name == "ash storm")
A = W
break
if(!A)
user << "<span class='warning'>How odd! The planet seems to have lost its atmosphere!</span>"
return
if(linked_machine && linked_machine.ongoing_weather)
if(linked_machine.ongoing_weather.stage == WIND_DOWN_STAGE || linked_machine.ongoing_weather.stage == END_STAGE)
user << "<span class='warning'>The storm is already ending. It would be a waste to use the staff now.</span>"
if(A.stage != END_STAGE)
if(A.stage == WIND_DOWN_STAGE)
user << "<span class='warning'>The storm is already ending! It would be a waste to use the staff now.</span>"
return
user.visible_message("<span class='warning'>[user] holds [src] skywards, causing its orb to flare!</span>", \
"<span class='notice'>With an appropriately dramatic flourish, you dispel the storm!</span>")
playsound(get_turf(src),'sound/magic/Staff_Change.ogg', 200, 0)
storm_cooldown = world.time + 600
linked_machine.ongoing_weather.stage = WIND_DOWN_STAGE
linked_machine.ongoing_weather.weather_wind_down()
else if (linked_machine && !linked_machine.ongoing_weather)
user.visible_message("<span class='warning'>[user] holds [src] skywards, causing its orb to flare!</span>", \
"<span class='danger'>You lift your staff skywards, calling down a terrible storm!</span>")
playsound(get_turf(src),'sound/magic/Staff_Chaos.ogg', 200, 0)
storm_cooldown = world.time + 600
linked_machine.weather_cooldown = 0
user.visible_message("<span class='warning'>[user] holds [src] skywards as an orange beam travels into the sky!</span>", \
"<span class='notice'>You hold [src] skyward, dispelling the ash storm!</span>")
playsound(user, 'sound/magic/Staff_Change.ogg', 200, 0)
A.wind_down()
else
user << "You can't seem to control the weather here."
user.visible_message("<span class='warning'>[user] holds [src] skywards as red lightning crackles into the sky!</span>", \
"<span class='notice'>You hold [src] skyward, calling down a terrible storm!</span>")
playsound(user, 'sound/magic/Staff_Chaos.ogg', 200, 0)
A.telegraph()
storm_cooldown = world.time + 600
+3 -25
View File
@@ -1,32 +1,10 @@
//If you're looking for spawners like ash walker eggs, check ghost_role_spawners.dm
/obj/machinery/lavaland_controller
name = "weather control machine"
desc = "Controls the weather."
name = "weather machine"
desc = "Controls the weather... when it's on, at any rate. A sticky note on the side proclaims \"DISABLED IN FAVOR OF AN ACTUAL ATMOSPHERE\"."
icon = 'icons/obj/machines/telecomms.dmi'
icon_state = "processor"
var/datum/weather/ongoing_weather = FALSE
var/weather_cooldown = 0
/obj/machinery/lavaland_controller/process()
if(ongoing_weather || weather_cooldown > world.time)
return
weather_cooldown = world.time + rand(3500, 6500)
var/datum/weather/ash_storm/LAVA
if(prob(10)) //10% chance for the ash storm to miss the area entirely
LAVA = new /datum/weather/ash_storm/false_alarm
else
LAVA = new /datum/weather/ash_storm
ongoing_weather = LAVA
LAVA.weather_start_up()
ongoing_weather = null
/obj/machinery/lavaland_controller/Destroy(force)
if(force)
. = ..()
else
return QDEL_HINT_LETMELIVE
icon_state = "processor_off"
/obj/structure/fans/tiny/invisible //For blocking air in ruin doorways
invisibility = INVISIBILITY_ABSTRACT
+3 -2
View File
@@ -156,6 +156,7 @@
#include "code\controllers\subsystem\ticker.dm"
#include "code\controllers\subsystem\timer.dm"
#include "code\controllers\subsystem\voting.dm"
#include "code\controllers\subsystem\weather.dm"
#include "code\datums\action.dm"
#include "code\datums\ai_laws.dm"
#include "code\datums\beam.dm"
@@ -238,6 +239,8 @@
#include "code\datums\martial\wrestling.dm"
#include "code\datums\ruins\lavaland.dm"
#include "code\datums\ruins\space.dm"
#include "code\datums\weather\weather.dm"
#include "code\datums\weather\weather_types.dm"
#include "code\datums\wires\airalarm.dm"
#include "code\datums\wires\airlock.dm"
#include "code\datums\wires\apc.dm"
@@ -1254,8 +1257,6 @@
#include "code\modules\mining\laborcamp\laborstacker.dm"
#include "code\modules\mining\lavaland\lavaland_areas.dm"
#include "code\modules\mining\lavaland\necropolis_chests.dm"
#include "code\modules\mining\lavaland\weather.dm"
#include "code\modules\mining\lavaland\weather_types.dm"
#include "code\modules\mining\lavaland\ruins\gym.dm"
#include "code\modules\mob\death.dm"
#include "code\modules\mob\interactive.dm"