Weather DLC - Make it Rain Anything! (#89550)

Introducing more weather types! And yes, you can now have rain be
reagent based!

<details>
<summary>Regular Rain</summary>

![dreamseeker_9S1WzdBW8Z](https://github.com/user-attachments/assets/83aaa1fe-9105-4e15-8455-0337c5c592ef)

</details>

<details>
<summary>Blood Rain</summary>

![dreamseeker_SZufAoXDFt](https://github.com/user-attachments/assets/209404dc-34dd-4381-9bab-9b84eaec2658)

</details>

<details>
<summary>Acid Rain</summary>

![dreamseeker_ngiOqdB5n2](https://github.com/user-attachments/assets/0ab953c6-a215-444a-bdbd-addfc2b1ddbb)

</details>

You can even make it rain ants, plasma, or drugs. All of their effects
get applied to turfs, objects, and mobs depending on the weather options
you select.

Did I mention... there is thunder?

<details>
<summary>Thunder Strikes</summary>

![Untitled video - Made with
Clipchamp](https://github.com/user-attachments/assets/7c5c5930-6e0a-4706-a41b-3cbcc277bfd5)

</details>

<details>
<summary>Sand Storms</summary>

![dreamseeker_ZEFUS73dSA](https://github.com/user-attachments/assets/4a754f2d-c4e5-4b2f-9add-4742628cfa74)

</details>

Despite all this new stuff, none of it has been directly added to the
game but the code can be used in the future for:
- Wizard event - Similar to lava on floor, but this time make the
reagent random or picked from a list and give wizard immunity
- Chaplain ability - Maybe make this a benefit or ability once enough
favor has been obtained
- Admin events - I have added a BUNCH of admin tooling to run customized
weather events that let you control a lot of options
- New station maps/biomes for downstreams (Jungle Station, etc.)
- Change Ion storms to use the new weather system that triggers
EMP/thunder effects across the station
- IceBox could get plasma rain
- Lavaland could get thunder effects applied to ash storms

Relevant PRs that removed/added some of the weather stuff I used:
- #60303
- #25222

---

- Rain sprites were added via [novaepee](https://github.com/novaepee) in
https://github.com/tgstation/TerraGov-Marine-Corps/pull/9675
- Sand sprites were added via [TiviPlus](https://github.com/TiviPlus)
(who commissioned them from bimmer) in
https://github.com/tgstation/TerraGov-Marine-Corps/pull/4645
- Rain sounds [already existed on
tg](https://github.com/tgstation/tgstation/pull/25222#discussion_r106794579)
and were provided by provided by Cuboos, using Royalty Free sounds,
presumed under default tg sound license - Creative Commons 3.0 BY-SA
- Siren sound is from siren.wav by IFartInUrGeneralDirection --
[Freesound](https://freesound.org/s/46092/) -- License: Attribution 4.0

The original `siren.ogg` sound used on a lot of SS13 servers doesn't
seem to have any attribution that I could locate. The sound was added
about 15 years ago. So I just looked for a somewhat similar sounding
siren noise on Freesound.

More weather customization!

🆑
add: Added new weather types for rain and sandstorms. Rain now uses a
reagent that gets exposed to the turfs, mobs, and objects. There is also
a thunder strike setting you can apply to any weather.
add: Hydro trays and soil will now add reagents to their trays when
exposed to a chemical reaction. (weather, shower, chem spills, foam
grenades, etc.)
add: Weather temperature now affects weather reagents and mobs body
temperature.
bal: Snowstorm temperature calculations were tweaked to allow universal
weather temperature effects.
sound: Added weather sounds from TGMC for rain and sirens (attributed to
Cuboos and IFartInUrGeneralDirection )
image: Added weather images from TGMC for rain and sand storms
(attributed to Novaepee and Bimmer)
refactor: Refactored a lot of the weather code to be more robust
admin: Admins can now control more weather related options when running
weather events. The weather admin verbs have been moved to their own
"Weather" category under the Admin tab.
/🆑

---------

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
This commit is contained in:
Tim
2025-04-29 17:44:46 -06:00
committed by Shadow-Quill
co-authored by LemonInTheDark Ghom
parent 92ac59a8dd
commit 1f855eb9ff
34 changed files with 701 additions and 121 deletions
+2
View File
@@ -88,6 +88,8 @@ Always compile, always use that verb, and always make sure that it works for wha
#define ZTRAIT_SNOWSTORM "Weather_Snowstorm"
#define ZTRAIT_ASHSTORM "Weather_Ashstorm"
#define ZTRAIT_VOIDSTORM "Weather_Voidstorm"
#define ZTRAIT_RAINSTORM "Weather_Rainstorm"
#define ZTRAIT_SANDSTORM "Weather_Sandstorm"
/// boolean - does this z prevent ghosts from observing it
#define ZTRAIT_SECRET "Secret"
+4
View File
@@ -36,6 +36,10 @@
#define ROUND_UP(x) ( -round(-(x)))
/// Probabilistic rounding: Adds 1 to the integer part of x with a probability equal to the decimal part of x.
/// ie. ROUND_PROB(40.25) returns 40 with 75% probability, and 41 with 25% probability.
#define ROUND_PROB(x) ( floor(x) + (prob(fract(x) * 100)) )
/// Returns the number of digits in a number. Only works on whole numbers.
/// This is marginally faster than string interpolation -> length
#define DIGITS(x) (ROUND_UP(log(10, x)))
+5
View File
@@ -244,6 +244,11 @@
#define SSMACHINES_MACHINES 7
#define SSMACHINES_MACHINES_LATE 8
// Weather susbsytem tasks
#define SSWEATHER_MOBS 1
#define SSWEATHER_TURFS 2
#define SSWEATHER_THUNDER 3
// Wardrobe subsystem tasks
#define SSWARDROBE_STOCK 1
#define SSWARDROBE_INSPECT 2
+2
View File
@@ -700,6 +700,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_ASHSTORM_IMMUNE "ashstorm_immune"
#define TRAIT_SNOWSTORM_IMMUNE "snowstorm_immune"
#define TRAIT_RADSTORM_IMMUNE "radstorm_immune"
#define TRAIT_SANDSTORM_IMMUNE "sandstorm_immune"
#define TRAIT_RAINSTORM_IMMUNE "rainstorm_immune"
#define TRAIT_WEATHER_IMMUNE "weather_immune" //Immune to ALL weather effects.
/// Cannot be grabbed by goliath tentacles
+47
View File
@@ -1,5 +1,7 @@
//A reference to this list is passed into area sound managers, and it's modified in a manner that preserves that reference in ash_storm.dm
GLOBAL_LIST_EMPTY(ash_storm_sounds)
GLOBAL_LIST_EMPTY(rain_storm_sounds)
GLOBAL_LIST_EMPTY(sand_storm_sounds)
/// Tracks where we should play snowstorm sounds for the area sound listener
GLOBAL_LIST_EMPTY(snowstorm_sounds)
@@ -7,3 +9,48 @@ GLOBAL_LIST_EMPTY(snowstorm_sounds)
#define MAIN_STAGE 2
#define WIND_DOWN_STAGE 3
#define END_STAGE 4
/// The amount of reagent units that is applied when an object comes into contact with rain
#define WEATHER_REAGENT_VOLUME 5
/// Weather reagent volume applied to randomly selected turfs/objects is scaled by this multiplier to compensate for reduced processing frequency
#define TURF_REAGENT_VOLUME_MULTIPLIER 3
/// 1 / 400 chance for a turf to get a thunder strike per tick (death and destruction to mobs/equipment in area)
#define THUNDER_CHANCE_INSANE 0.0025
/// 1 / 1,000 chance for a turf to get a thunder strike per tick (damage to mobs/equipment in area)
#define THUNDER_CHANCE_HIGH 0.001
/// 1 / 5,000 chance for a turf to get a thunder strike per tick (sporadic damage to mobs/equipment in area)
#define THUNDER_CHANCE_AVERAGE 0.0002
/// 1 / 20,000 chance for a turf to get a thunder strike per tick (rare damage to mobs/equipment in area)
#define THUNDER_CHANCE_RARE 0.00005
/// 1 / 50,000 chance for a turf to get a thunder strike per tick (almost no damage to mobs/equipment in area)
#define THUNDER_CHANCE_VERY_RARE 0.00002
/// admin verb to control thunder via the run_weather command
GLOBAL_LIST_INIT(thunder_chance_options, list(
"Relentless - The Sky is Angry, Very Angry" = THUNDER_CHANCE_INSANE,
"Abundant - A shocking amount of thunder" = THUNDER_CHANCE_HIGH,
"Regular - Standard Storm Activity" = THUNDER_CHANCE_AVERAGE,
"Occasional - A polite amount of thunder" = THUNDER_CHANCE_RARE,
"Rare - Like finding a four-leaf clover, but in the sky" = THUNDER_CHANCE_VERY_RARE,
"None - Admin Safe Space (Thunder Disabled)" = NONE,
))
//WEATHER FLAGS
/// If weather will affect turfs
#define WEATHER_TURFS (1<<0)
/// If weather will affect mobs
#define WEATHER_MOBS (1<<1)
/// If weather will apply thunder strikes to turfs
#define WEATHER_THUNDER (1<<2)
/// If weather will be allowed to affect indoor areas
#define WEATHER_INDOORS (1<<3)
/// If weather is endless and can only be stopped manually
#define WEATHER_ENDLESS (1<<4)
/// If weather will be detected by a barometer
#define WEATHER_BAROMETER (1<<5)
/// If weather temperature ignores clothing insulation when adjusting bodytemperature
#define WEATHER_TEMPERATURE_BYPASS_CLOTHING (1<<6)
/// Does weather have any type of processing related to mobs, turfs, or thunder?
#define FUNCTIONAL_WEATHER (WEATHER_TURFS|WEATHER_MOBS|WEATHER_THUNDER)
+10
View File
@@ -642,3 +642,13 @@ DEFINE_BITFIELD(construction_upgrades, list(
"RCD_UPGRADE_ANTI_INTERRUPT" = RCD_UPGRADE_ANTI_INTERRUPT,
"RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN" = RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN,
))
DEFINE_BITFIELD(weather_flags, list(
"WEATHER_TURFS" = WEATHER_TURFS,
"WEATHER_MOBS" = WEATHER_MOBS,
"WEATHER_THUNDER" = WEATHER_THUNDER,
"WEATHER_INDOORS" = WEATHER_INDOORS,
"WEATHER_ENDLESS" = WEATHER_ENDLESS,
"WEATHER_BAROMETER" = WEATHER_BAROMETER,
"WEATHER_TEMPERATURE_BYPASS_CLOTHING" = WEATHER_TEMPERATURE_BYPASS_CLOTHING,
))
+2
View File
@@ -75,7 +75,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_NOT_ENGRAVABLE" = TRAIT_NOT_ENGRAVABLE,
"TRAIT_ODD_CUSTOMIZABLE_FOOD_INGREDIENT" = TRAIT_ODD_CUSTOMIZABLE_FOOD_INGREDIENT,
"TRAIT_ON_HIT_EFFECT" = TRAIT_ON_HIT_EFFECT,
"TRAIT_RAINSTORM_IMMUNE" = TRAIT_RAINSTORM_IMMUNE,
"TRAIT_RUNECHAT_HIDDEN" = TRAIT_RUNECHAT_HIDDEN,
"TRAIT_SANDSTORM_IMMUNE" = TRAIT_SANDSTORM_IMMUNE,
"TRAIT_SCARY_FISHERMAN" = TRAIT_SCARY_FISHERMAN,
"TRAIT_SECLUDED_LOCATION" = TRAIT_SECLUDED_LOCATION,
"TRAIT_SILENT_REACTIONS" = TRAIT_SILENT_REACTIONS,
+2
View File
@@ -18,7 +18,9 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_MOVE_PHASING" = TRAIT_MOVE_PHASING,
"TRAIT_MOVE_VENTCRAWLING" = TRAIT_MOVE_VENTCRAWLING,
"TRAIT_MOVE_UPSIDE_DOWN" = TRAIT_MOVE_UPSIDE_DOWN,
"TRAIT_RAINSTORM_IMMUNE" = TRAIT_RAINSTORM_IMMUNE,
"TRAIT_RUNECHAT_HIDDEN" = TRAIT_RUNECHAT_HIDDEN,
"TRAIT_SANDSTORM_IMMUNE" = TRAIT_SANDSTORM_IMMUNE,
"TRAIT_SCARY_FISHERMAN" = TRAIT_SCARY_FISHERMAN,
"TRAIT_SNOWSTORM_IMMUNE" = TRAIT_SNOWSTORM_IMMUNE,
"TRAIT_WEATHER_IMMUNE" = TRAIT_WEATHER_IMMUNE,
+52 -21
View File
@@ -11,24 +11,62 @@ SUBSYSTEM_DEF(weather)
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()
/datum/controller/subsystem/weather/fire(resumed = FALSE)
// process active weather
for(var/V in processing)
var/datum/weather/our_event = V
if(our_event.aesthetic || our_event.stage != MAIN_STAGE)
for(var/datum/weather/weather_event as anything in processing)
if(!length(weather_event.subsystem_tasks) || weather_event.stage != MAIN_STAGE)
continue
for(var/mob/act_on as anything in GLOB.mob_living_list)
if(our_event.can_weather_act(act_on))
our_event.weather_act(act_on)
if(weather_event.currentpart == SSWEATHER_MOBS)
if(!resumed)
weather_event.current_mobs = GLOB.mob_living_list.Copy()
var/list/current_mobs_cache = weather_event.current_mobs // cache for performance
while(current_mobs_cache.len)
var/mob/living/target = current_mobs_cache[current_mobs_cache.len]
current_mobs_cache.len--
if(QDELETED(target))
continue
if(weather_event.can_weather_act_mob(target))
weather_event.weather_act_mob(target)
if(MC_TICK_CHECK)
return
resumed = FALSE
weather_event.currentpart = weather_event.subsystem_tasks[WRAP_UP(weather_event.currentpart, weather_event.subsystem_tasks.len)]
if(weather_event.currentpart == SSWEATHER_TURFS)
if(!resumed)
weather_event.turf_iteration = ROUND_PROB(weather_event.weather_turfs_per_tick)
while(weather_event.turf_iteration)
weather_event.turf_iteration--
var/turf/selected_turf = weather_event.pick_turf()
if(selected_turf && weather_event.can_weather_act_turf(selected_turf))
weather_event.weather_act_turf(selected_turf)
if(MC_TICK_CHECK)
return
resumed = FALSE
weather_event.currentpart = weather_event.subsystem_tasks[WRAP_UP(weather_event.currentpart, weather_event.subsystem_tasks.len)]
if(weather_event.currentpart == SSWEATHER_THUNDER)
if(!resumed)
weather_event.thunder_iteration = ROUND_PROB(weather_event.thunder_turfs_per_tick)
while(weather_event.thunder_iteration)
weather_event.thunder_iteration--
var/turf/selected_turf = weather_event.pick_turf()
if(selected_turf && weather_event.can_weather_act_turf(selected_turf))
weather_event.thunder_act_turf(selected_turf)
if(MC_TICK_CHECK)
return
resumed = FALSE
weather_event.currentpart = weather_event.subsystem_tasks[WRAP_UP(weather_event.currentpart, weather_event.subsystem_tasks.len)]
// start random weather on relevant levels
for(var/z in eligible_zlevels)
var/possible_weather = eligible_zlevels[z]
var/datum/weather/our_event = pick_weight(possible_weather)
run_weather(our_event, list(text2num(z)))
var/datum/weather/weather_event = pick_weight(possible_weather)
run_weather(weather_event, list(text2num(z)))
eligible_zlevels -= z
var/randTime = rand(3000, 6000)
next_hit_by_zlevel["[z]"] = addtimer(CALLBACK(src, PROC_REF(make_eligible), z, possible_weather), randTime + initial(our_event.weather_duration_upper), TIMER_UNIQUE|TIMER_STOPPABLE) //Around 5-10 minutes between weathers
var/randTime = rand(5 MINUTES, 10 MINUTES)
next_hit_by_zlevel["[z]"] = addtimer(CALLBACK(src, PROC_REF(make_eligible), z, possible_weather), randTime + initial(weather_event.weather_duration_upper), TIMER_UNIQUE|TIMER_STOPPABLE)
/datum/controller/subsystem/weather/Initialize()
for(var/V in subtypesof(/datum/weather))
@@ -52,7 +90,7 @@ SUBSYSTEM_DEF(weather)
LAZYINITLIST(eligible_zlevels["[z]"])
eligible_zlevels["[z]"][weather] = probability
/datum/controller/subsystem/weather/proc/run_weather(datum/weather/weather_datum_type, z_levels)
/datum/controller/subsystem/weather/proc/run_weather(datum/weather/weather_datum_type, z_levels, list/weather_data)
if (istext(weather_datum_type))
for (var/V in subtypesof(/datum/weather))
var/datum/weather/W = V
@@ -69,7 +107,8 @@ SUBSYSTEM_DEF(weather)
else if (!islist(z_levels))
CRASH("run_weather called with invalid z_levels: [z_levels || "null"]")
var/datum/weather/W = new weather_datum_type(z_levels)
var/datum/weather/W = new weather_datum_type(z_levels, weather_data)
W.telegraph()
/datum/controller/subsystem/weather/proc/make_eligible(z, possible_weather)
@@ -88,11 +127,3 @@ SUBSYSTEM_DEF(weather)
///Returns an active storm by its type
/datum/controller/subsystem/weather/proc/get_weather_by_type(type)
return locate(type) in processing
ADMIN_VERB(stop_weather, R_DEBUG|R_ADMIN, "Stop All Active Weather", "Stop all currently active weather.", ADMIN_CATEGORY_DEBUG)
log_admin("[key_name(user)] stopped all currently active weather.")
message_admins("[key_name_admin(user)] stopped all currently active weather.")
for(var/datum/weather/current_weather as anything in SSweather.processing)
if(current_weather in SSweather.processing)
current_weather.end()
BLACKBOX_LOG_ADMIN_VERB("Stop All Active Weather")
@@ -71,6 +71,8 @@
target.AddElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds)
target.AddElement(/datum/element/weather_listener, /datum/weather/snow_storm, ZTRAIT_SNOWSTORM, GLOB.snowstorm_sounds)
target.AddElement(/datum/element/weather_listener, /datum/weather/rain_storm, ZTRAIT_RAINSTORM, GLOB.rain_storm_sounds)
target.AddElement(/datum/element/weather_listener, /datum/weather/sand_storm, ZTRAIT_SANDSTORM, GLOB.sand_storm_sounds)
RegisterSignal(target, COMSIG_QDELETING, PROC_REF(end_possession))
return TRUE
@@ -83,6 +85,8 @@
var/mob/poltergeist = parent
possessed.RemoveElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds)
possessed.RemoveElement(/datum/element/weather_listener, /datum/weather/rain_storm, ZTRAIT_RAINSTORM, GLOB.rain_storm_sounds)
possessed.RemoveElement(/datum/element/weather_listener, /datum/weather/sand_storm, ZTRAIT_SANDSTORM, GLOB.sand_storm_sounds)
possessed.RemoveElement(/datum/element/weather_listener, /datum/weather/snow_storm, ZTRAIT_SNOWSTORM, GLOB.snowstorm_sounds)
UnregisterSignal(possessed, COMSIG_QDELETING)
+3 -3
View File
@@ -123,7 +123,7 @@
for(var/datum/weather/check_weather as anything in SSweather.processing)
if(!check_weather.barometer_predictable || check_weather.stage == WIND_DOWN_STAGE || check_weather.stage == END_STAGE)
if(!(check_weather.weather_flags & WEATHER_BAROMETER) || check_weather.stage == WIND_DOWN_STAGE || check_weather.stage == END_STAGE)
continue
for (var/mining_level in mining_z_levels)
if(mining_level in check_weather.impacted_z_levels)
@@ -154,12 +154,12 @@
warning_level = WEATHER_ALERT_IMMINENT_OR_ACTIVE
for(var/datum/weather/check_weather as anything in SSweather.processing)
if(!check_weather.barometer_predictable || check_weather.stage == WIND_DOWN_STAGE || check_weather.stage == END_STAGE)
if(!(check_weather.weather_flags & WEATHER_BAROMETER) || check_weather.stage == WIND_DOWN_STAGE || check_weather.stage == END_STAGE)
continue
var/list/mining_z_levels = SSmapping.levels_by_trait(ZTRAIT_MINING)
for(var/mining_level in mining_z_levels)
if(mining_level in check_weather.impacted_z_levels)
is_weather_dangerous = !check_weather.aesthetic
is_weather_dangerous = !(check_weather.weather_flags & FUNCTIONAL_WEATHER)
return
/datum/component/weather_announcer/proc/on_examine(atom/radio, mob/examiner, list/examine_texts)
-1
View File
@@ -27,7 +27,6 @@
RegisterSignal(target, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(handle_z_level_change), override = TRUE)
RegisterSignal(target, COMSIG_MOB_LOGOUT, PROC_REF(handle_logout), override = TRUE)
var/mob/target_mob = target
handle_z_level_change(target_mob, null, target_mob.loc)
+21
View File
@@ -93,3 +93,24 @@
play(picked_sound)
if(sound_to_length[picked_sound])
timer_id = addtimer(CALLBACK(src, PROC_REF(sound_loop)), sound_to_length[picked_sound], TIMER_CLIENT_TIME | TIMER_STOPPABLE | TIMER_DELETE_ME, SSsound_loops)
/datum/looping_sound/rain
start_sound = 'sound/ambience/weather/rain/rain_start.ogg'
start_length = 12.5 SECONDS
mid_sounds = 'sound/ambience/weather/rain/rain_mid.ogg'
mid_length = 15 SECONDS
end_sound = 'sound/ambience/weather/rain/rain_end.ogg'
volume = 70
sound_channel = CHANNEL_WEATHER
/datum/looping_sound/rain/start
mid_sounds = 'sound/ambience/weather/rain/rain_start.ogg'
mid_length = 12.5 SECONDS
/datum/looping_sound/rain/middle
mid_sounds = 'sound/ambience/weather/rain/rain_mid.ogg'
mid_length = 15 SECONDS
/datum/looping_sound/rain/end
mid_sounds = 'sound/ambience/weather/rain/rain_end.ogg'
mid_length = 17 SECONDS
+238 -34
View File
@@ -1,3 +1,8 @@
/// The maximum amount of turfs that can be processed in a single tick regardless of
/// the number of turfs determined by turf_weather_chance and turf_thunder_chance
/// increasing this too high can result in severe lag so please be careful
#define MAX_TURFS_PER_TICK 500
/**
* Causes weather to occur on a z level in certain area types
*
@@ -31,7 +36,7 @@
var/weather_duration_lower = 2 MINUTES
/// See above - this is the highest possible duration
var/weather_duration_upper = 2.5 MINUTES
/// Looping sound while weather is occuring
/// The sound played to everyone on an affected z-level when weather is occuring (does not loop)
var/weather_sound
/// Area overlay while the weather is occuring
var/weather_overlay
@@ -51,23 +56,25 @@
/// Types of area to affect
var/area_type = /area/space
/// TRUE value protects areas with outdoors marked as false, regardless of area type
var/protect_indoors = FALSE
/// Areas to be affected by the weather, calculated when the weather begins
var/list/impacted_areas = list()
/// A weighted list of areas impacted by weather, where weights reflect the total turf count in each area.
var/list/impacted_areas_weighted = list()
/// The total number of turfs impacted by weather across all z-levels and areas.
var/total_impacted_turfs = 0
/// Areas affected by weather have their blend modes changed
var/list/impacted_areas_blend_modes = list()
/// Areas that are protected and excluded from the affected areas.
var/list/protected_areas = list()
/// The list of z-levels that this weather is actively affecting
var/impacted_z_levels
/// A weighted list of z-levels impacted by weather, where weights reflect the total turf count on each level
var/list/impacted_z_levels_weighted = list()
/// Since it's above everything else, this is the layer used by default.
var/overlay_layer = AREA_LAYER
/// Plane for the overlay
var/overlay_plane = WEATHER_PLANE
/// If the weather has no purpose other than looks
var/aesthetic = FALSE
/// Used by mobs (or movables containing mobs, such as enviro bags) to prevent them from being affected by the weather.
var/immunity_type
/// If this bit of weather should also draw an overlay that's uneffected by lighting onto the area
@@ -83,17 +90,90 @@
var/probability = 0
/// The z-level trait to affect when run randomly or when not overridden.
var/target_trait = ZTRAIT_STATION
/// Whether a barometer can predict when the weather will happen
var/barometer_predictable = FALSE
/// For barometers to know when the next storm will hit
var/next_hit_time = 0
/// This causes the weather to only end if forced to
var/perpetual = FALSE
/// The chance, per tick, a turf will have weather effects applied to it. This is a decimal value, 1.00 = 100%, 0.50 = 50%, etc.
/// Recommend setting this low near 0.01 (results in 1 in 100 affected turfs having weather reagents applied per tick)
var/turf_weather_chance = 0.01
/// The chance, per tick, a turf will have a thunder strike applied to it. This is a decimal value, 1.00 = 100%, 0.50 = 50%, etc.
/// Recommend setting this really low near 0.001 (results in 1 in 1000 affected turfs having thunder strikes applied per tick)
var/turf_thunder_chance = THUNDER_CHANCE_AVERAGE // does nothing without the WEATHER_THUNDER weather_flag
/// The calculated amount of turfs that get weather effects processed each tick (this gets calculated do not manually set this var)
var/weather_turfs_per_tick = 0
/// The calculated amount of turfs that get thunder effects processed each tick (this gets calculated do not manually set this var)
var/thunder_turfs_per_tick = 0
/// Color to apply to thunder while weather is occuring
var/thunder_color = null
/datum/weather/New(z_levels)
/// List of weather bitflags that determines effects (see \code\__DEFINES\weather.dm)
var/weather_flags = NONE
/// List of current mobs being processed by weather
var/list/current_mobs = list()
/// The weather turf counter to keep track of how many turfs we have processed so far
var/turf_iteration = 0
/// The weather thunder counter to keep track of how much thunder we have processed so far
var/thunder_iteration = 0
/// The current section our weather subsystem is processing
var/currentpart
/// The list of allowed tasks our weather subsystem is allowed to process (determined by weather_flags)
var/list/subsystem_tasks = list()
/// The temperature of our weather that is applied to weather reagents and mobs using adjust_bodytemperature()
var/weather_temperature = T20C
/// A list (supports regular, nested, and weighted) of possible reagents that will rain down from the sky.
/// Only one of these will be selected to be used as the reagent
var/list/whitelist_weather_reagents
/// A list of reagents that are forbidden from being selected when there is no
/// whitelist and the reagents are randomized
var/list/blacklist_weather_reagents
/// The selected reagent that will be rained down
var/datum/reagent/weather_reagent
/// The actual atom that holds our reagents that is held in nullspace
var/obj/effect/abstract/weather_reagent_holder
/datum/weather/New(z_levels, list/weather_data)
..()
impacted_z_levels = z_levels
area_type = weather_data?["area"] || area_type
weather_flags = weather_data?["weather_flags"] || weather_flags
turf_thunder_chance = isnull(weather_data?["thunder_chance"]) ? turf_thunder_chance : weather_data?["thunder_chance"]
var/datum/reagent/custom_reagent = weather_data?["reagent"]
var/reagent_id
if(custom_reagent)
reagent_id = custom_reagent
else if(whitelist_weather_reagents)
reagent_id = pick_weight_recursive(whitelist_weather_reagents)
else if(blacklist_weather_reagents) // randomized
reagent_id = get_random_reagent_id(blacklist_weather_reagents)
if(reagent_id)
weather_reagent = find_reagent_object_from_type(reagent_id)
weather_color = weather_reagent.color
weather_reagent_holder = new(null) // spawns in nullspace
weather_reagent_holder.create_reagents(WEATHER_REAGENT_VOLUME, NO_REACT)
weather_reagent_holder.reagents.add_reagent(reagent_id, WEATHER_REAGENT_VOLUME)
weather_reagent_holder.reagents.set_temperature(weather_temperature)
if(weather_flags & (WEATHER_MOBS))
subsystem_tasks += SSWEATHER_MOBS
if(weather_flags & (WEATHER_TURFS))
subsystem_tasks += SSWEATHER_TURFS
if(weather_flags & (WEATHER_THUNDER))
subsystem_tasks += SSWEATHER_THUNDER
if(length(subsystem_tasks))
currentpart = subsystem_tasks[1]
setup_weather_areas()
setup_weather_turfs()
/datum/weather/Destroy()
QDEL_NULL(weather_reagent_holder)
return ..()
/**
* Telegraphs the beginning of the weather on the impacted z levels
@@ -105,22 +185,8 @@
/datum/weather/proc/telegraph()
if(stage == STARTUP_STAGE)
return
SEND_GLOBAL_SIGNAL(COMSIG_WEATHER_TELEGRAPH(type), src)
stage = STARTUP_STAGE
var/list/affectareas = list()
for(var/area/selected_area as anything in get_areas(area_type))
affectareas += selected_area
for(var/area/protected_area as anything in protected_areas)
affectareas -= get_areas(protected_area)
affectareas = enhanced_roleplay_filter(affectareas) // BUBBER EDIT ADDITION - enhanced roleplay check - modular_zubbers/code/modules/events/ev_roleplay_check.dm
for(var/area/affected_area as anything in affectareas)
if(protect_indoors && !affected_area.outdoors)
continue
for(var/z in impacted_z_levels)
if(length(affected_area.turfs_by_zlevel) >= z && length(affected_area.turfs_by_zlevel[z]))
impacted_areas |= affected_area
continue
SEND_GLOBAL_SIGNAL(COMSIG_WEATHER_TELEGRAPH(type), src)
weather_duration = rand(weather_duration_lower, weather_duration_upper)
SSweather.processing |= src
@@ -129,6 +195,63 @@
send_alert(telegraph_message, telegraph_sound, telegraph_sound_vol)
addtimer(CALLBACK(src, PROC_REF(start)), telegraph_duration)
/datum/weather/proc/setup_weather_areas()
var/list/affectareas = list()
for(var/area/selected_area as anything in get_areas(area_type))
affectareas += selected_area
for(var/area/protected_area as anything in protected_areas)
affectareas -= get_areas(protected_area)
affectareas = enhanced_roleplay_filter(affectareas) // BUBBER EDIT ADDITION - enhanced roleplay check - modular_zubbers/code/modules/events/ev_roleplay_check.dm
for(var/area/affected_area as anything in affectareas)
if(!(weather_flags & WEATHER_INDOORS) && !affected_area.outdoors)
continue
for(var/z in impacted_z_levels)
var/total_turfs = length(affected_area.turfs_by_zlevel) >= z && length(affected_area.turfs_by_zlevel[z])
if(!total_turfs)
continue
impacted_areas |= affected_area
if(!(weather_flags & (WEATHER_THUNDER|WEATHER_TURFS)))
continue
var/z_string = num2text(z)
if(!impacted_z_levels_weighted[z_string])
impacted_z_levels_weighted[z_string] = 0
if(!impacted_areas_weighted[z_string])
impacted_areas_weighted[z_string] = list()
impacted_z_levels_weighted[z_string] += total_turfs
impacted_areas_weighted[z_string][affected_area] = total_turfs
total_impacted_turfs += total_turfs
/// Selects a turf impacted by weather, if available, otherwise returns null
/datum/weather/proc/pick_turf()
var/z_string = pick_weight_recursive(impacted_z_levels_weighted)
var/area/selected_area = pick_weight_recursive(impacted_areas_weighted[z_string])
var/z = text2num(z_string)
var/list/available_turfs = selected_area.get_turfs_by_zlevel(z)
// Areas or turfs may change during weather events. For example, a shuttle
// landing and departing might leave an area in 'impacted_areas' but without
// turfs on the expected z-level, resulting in an empty 'available_turfs' list.
if(length(available_turfs))
return pick(available_turfs)
return
/datum/weather/proc/setup_weather_turfs()
if(!(weather_flags & (WEATHER_TURFS|WEATHER_THUNDER)))
return
if(!total_impacted_turfs)
return
if(weather_flags & (WEATHER_TURFS))
weather_turfs_per_tick = total_impacted_turfs * turf_weather_chance
weather_turfs_per_tick = min(weather_turfs_per_tick, MAX_TURFS_PER_TICK)
if(weather_flags & (WEATHER_THUNDER))
thunder_turfs_per_tick = total_impacted_turfs * turf_thunder_chance
thunder_turfs_per_tick = min(thunder_turfs_per_tick, MAX_TURFS_PER_TICK)
/**
* Starts the actual weather and effects from it
*
@@ -143,7 +266,7 @@
stage = MAIN_STAGE
update_areas()
send_alert(weather_message, weather_sound)
if(!perpetual)
if(!(weather_flags & (WEATHER_ENDLESS)))
addtimer(CALLBACK(src, PROC_REF(wind_down)), weather_duration)
for(var/area/impacted_area as anything in impacted_areas)
SEND_SIGNAL(impacted_area, COMSIG_WEATHER_BEGAN_IN_AREA(type), src)
@@ -200,9 +323,8 @@
/**
* Returns TRUE if the living mob can be affected by the weather
*
*/
/datum/weather/proc/can_weather_act(mob/living/mob_to_check)
/datum/weather/proc/can_weather_act_mob(mob/living/mob_to_check)
var/turf/mob_turf = get_turf(mob_to_check)
if(!mob_turf)
@@ -226,15 +348,95 @@
return TRUE
/**
* Affects the mob with whatever the weather does
*
* Returns TRUE if the turf can be affected by the weather
*/
/datum/weather/proc/weather_act(mob/living/L)
return
/datum/weather/proc/can_weather_act_turf(turf/valid_weather_turf)
// applying weather effects to solid walls is a waste since nothing will happen
if(isclosedturf(valid_weather_turf))
return
// same logic for space and openspace turfs
if(is_space_or_openspace(valid_weather_turf))
return
// solid windows are also worth skipping
var/obj/structure/window/window = locate() in valid_weather_turf
if(window?.fulltile)
return
return TRUE
/**
* Affects the mob with whatever the weather does
*/
/datum/weather/proc/weather_act_mob(mob/living/living)
var/temperature_delta = weather_temperature - living.bodytemperature
if(iscarbon(living))
var/mob/living/carbon/carbon_living = living
var/insulation_flag = !(weather_flags & WEATHER_TEMPERATURE_BYPASS_CLOTHING)
carbon_living.adjust_bodytemperature(temperature_delta, use_insulation=insulation_flag, use_steps=TRUE)
else // stolen from carbon/adjust_bodytemperature() which should really be universally applied to living/adjust_bodytemperature()
// Use the bodytemp divisors to get the change step, with max step size
temperature_delta = (temperature_delta > 0) ? (temperature_delta / BODYTEMP_HEAT_DIVISOR) : (temperature_delta / BODYTEMP_COLD_DIVISOR)
// Clamp the results to the min and max step size
temperature_delta = (temperature_delta > 0) ? min(temperature_delta, BODYTEMP_HEATING_MAX) : max(temperature_delta, BODYTEMP_COOLING_MAX)
living.adjust_bodytemperature(temperature_delta)
if(!weather_reagent || !weather_reagent_holder || living.IsObscured())
return
if(istype(weather_reagent, /datum/reagent/water))
living.wash()
weather_reagent_holder.reagents.expose(living, TOUCH)
/**
* Affects the turf with whatever the weather does
*/
/datum/weather/proc/weather_act_turf(turf/open/weather_turf)
if(!weather_reagent || !weather_reagent_holder)
return
weather_reagent_holder.reagents.expose(weather_turf, TOUCH, TURF_REAGENT_VOLUME_MULTIPLIER)
for(var/atom/thing as anything in weather_turf)
if(thing.IsObscured() || isliving(thing))
continue
weather_reagent_holder.reagents.expose(thing, TOUCH, TURF_REAGENT_VOLUME_MULTIPLIER)
// Time for the sophisticated art of catching sky-booze
if(!is_reagent_container(thing))
continue
var/obj/item/reagent_containers/container = thing
if(!container.is_open_container() || container.reagents.holder_full())
continue
container.reagents.add_reagent(weather_reagent.type, WEATHER_REAGENT_VOLUME, TURF_REAGENT_VOLUME_MULTIPLIER)
if(istype(weather_reagent, /datum/reagent/water))
weather_turf.wash(CLEAN_ALL, TRUE)
/**
* Affects the turf with thunder
*/
/datum/weather/proc/thunder_act_turf(turf/open/weather_turf)
var/obj/effect/temp_visual/thunderbolt/thunder = new(weather_turf)
thunder.flash_lighting_fx(6, 2, duration = thunder.duration)
if(thunder_color)
thunder.color = thunder_color
for(var/mob/living/hit_mob in weather_turf)
to_chat(hit_mob, span_userdanger("You've been struck by lightning!"))
hit_mob.electrocute_act(50, "thunder", flags = SHOCK_TESLA|SHOCK_NOGLOVES)
for(var/obj/hit_thing in weather_turf)
hit_thing.take_damage(20, BURN, ENERGY, FALSE)
playsound(weather_turf, 'sound/effects/magic/lightningbolt.ogg', 100, extrarange = 10, falloff_distance = 10)
weather_turf.visible_message(span_danger("A thunderbolt strikes [weather_turf]!"))
explosion(weather_turf, light_impact_range = 1, flame_range = 1, silent = TRUE, adminlog = FALSE)
/**
* Updates the overlays on impacted areas
*
*/
/datum/weather/proc/update_areas()
var/list/new_overlay_cache = generate_overlay_cache()
@@ -289,3 +491,5 @@
gen_overlay_cache += new_weather_overlay
return gen_overlay_cache
#undef MAX_TURFS_PER_TICK
+13 -19
View File
@@ -17,34 +17,27 @@
end_overlay = "light_ash"
area_type = /area
protect_indoors = TRUE
target_trait = ZTRAIT_ASHSTORM
immunity_type = TRAIT_ASHSTORM_IMMUNE
probability = 90
barometer_predictable = TRUE
weather_flags = (WEATHER_MOBS | WEATHER_BAROMETER)
var/list/weak_sounds = list()
var/list/strong_sounds = list()
/datum/weather/ash_storm/telegraph()
var/list/eligible_areas = list()
for (var/z in impacted_z_levels)
eligible_areas += SSmapping.areas_in_z["[z]"]
for(var/i in 1 to eligible_areas.len)
var/area/place = eligible_areas[i]
for(var/area/impacted_area as anything in impacted_areas)
//BUBBER ADDITION BEGIN - This is a HORRIBLE HACK to stop the weather from triggering for these locations
if(place.ignore_weather_sfx)
if(impacted_area.ignore_weather_sfx)
continue
//BUBBER ADDITION END
if(place.outdoors)
weak_sounds[place] = /datum/looping_sound/weak_outside_ashstorm
strong_sounds[place] = /datum/looping_sound/active_outside_ashstorm
if(impacted_area.outdoors)
weak_sounds[impacted_area] = /datum/looping_sound/weak_outside_ashstorm
strong_sounds[impacted_area] = /datum/looping_sound/active_outside_ashstorm
else
weak_sounds[place] = /datum/looping_sound/weak_inside_ashstorm
strong_sounds[place] = /datum/looping_sound/active_inside_ashstorm
CHECK_TICK
weak_sounds[impacted_area] = /datum/looping_sound/weak_inside_ashstorm
strong_sounds[impacted_area] = /datum/looping_sound/active_inside_ashstorm
//We modify this list instead of setting it to weak/stron sounds in order to preserve things that hold a reference to it
//It's essentially a playlist for a bunch of components that chose what sound to loop based on the area a player is in
@@ -61,7 +54,7 @@
GLOB.ash_storm_sounds += weak_sounds
return ..()
/datum/weather/ash_storm/can_weather_act(mob/living/mob_to_check)
/datum/weather/ash_storm/can_weather_act_mob(mob/living/mob_to_check)
. = ..()
if(!. || !ishuman(mob_to_check))
return
@@ -69,8 +62,9 @@
if(human_to_check.get_thermal_protection() >= FIRE_IMMUNITY_MAX_TEMP_PROTECT)
return FALSE
/datum/weather/ash_storm/weather_act(mob/living/victim)
/datum/weather/ash_storm/weather_act_mob(mob/living/victim)
victim.adjustFireLoss(4, required_bodytype = BODYTYPE_ORGANIC)
return ..()
/datum/weather/ash_storm/end()
GLOB.ash_storm_sounds -= weak_sounds
@@ -91,6 +85,6 @@
end_message = span_notice("The emberfall slows, stops. Another layer of hardened soot to the basalt beneath your feet.")
end_sound = null
aesthetic = TRUE
weather_flags = parent_type::weather_flags & ~WEATHER_MOBS
probability = 10
@@ -25,9 +25,10 @@
/// Can't really use like, the emissive system here because I am not about to make
/// all walls block emissive
use_glow = FALSE
weather_flags = (WEATHER_MOBS | WEATHER_INDOORS)
/datum/weather/floor_is_lava/can_weather_act(mob/living/mob_to_check)
/datum/weather/floor_is_lava/can_weather_act_mob(mob/living/mob_to_check)
if(!mob_to_check.client) //Only sentient people are going along with it!
return FALSE
. = ..()
@@ -42,5 +43,6 @@
if(mob_to_check.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)
return FALSE
/datum/weather/floor_is_lava/weather_act(mob/living/victim)
/datum/weather/floor_is_lava/weather_act_mob(mob/living/victim)
victim.adjustFireLoss(3)
return ..()
@@ -24,6 +24,7 @@
target_trait = ZTRAIT_STATION
immunity_type = TRAIT_RADSTORM_IMMUNE
weather_flags = (WEATHER_MOBS | WEATHER_INDOORS)
/// Chance we get a negative mutation, if we fail we get a positive one
var/negative_mutation_chance = 90
/// Chance we mutate
@@ -34,7 +35,7 @@
status_alarm(TRUE)
/datum/weather/rad_storm/weather_act(mob/living/living)
/datum/weather/rad_storm/weather_act_mob(mob/living/living)
if(!prob(mutate_chance))
return
@@ -57,6 +58,8 @@
if(prob(50))
do_mutate(human)
return ..()
/datum/weather/rad_storm/end()
if(..())
return
@@ -90,17 +93,14 @@
protected_areas = list(/area/shuttle, /area/station/maintenance/radshelter)
weather_overlay = "nebula_radstorm"
weather_duration_lower = 100 HOURS
weather_duration_upper = 100 HOURS
end_message = null
weather_flags = parent_type::weather_flags | WEATHER_ENDLESS
mutate_chance = 0.1
///Chance we pulse a living during the storm
var/radiation_chance = 5
/datum/weather/rad_storm/nebula/weather_act(mob/living/living)
/datum/weather/rad_storm/nebula/weather_act_mob(mob/living/living)
..()
if(!prob(radiation_chance))
@@ -0,0 +1,90 @@
/datum/weather/rain_storm
name = "rain"
desc = "Heavy thunderstorms rain down below, drenching anyone caught in it."
telegraph_message = span_danger("Thunder rumbles far above. You hear droplets drumming against the canopy.")
telegraph_overlay = "rain_low"
telegraph_duration = 30 SECONDS
weather_message = span_userdanger("<i>Rain pours down around you!</i>")
weather_overlay = "rain_high"
end_message = span_bolddanger("The downpour gradually slows to a light shower.")
end_overlay = "rain_low"
end_duration = 30 SECONDS
weather_duration_lower = 3 MINUTES
weather_duration_upper = 5 MINUTES
weather_color = null
thunder_color = null
area_type = /area
target_trait = ZTRAIT_RAINSTORM
immunity_type = TRAIT_RAINSTORM_IMMUNE
probability = 90
weather_flags = (WEATHER_TURFS | WEATHER_MOBS | WEATHER_THUNDER | WEATHER_BAROMETER)
whitelist_weather_reagents = list(/datum/reagent/water)
/datum/weather/rain_storm/telegraph()
GLOB.rain_storm_sounds.Cut()
for(var/area/impacted_area as anything in impacted_areas)
GLOB.rain_storm_sounds[impacted_area] = /datum/looping_sound/rain/start
return ..()
/datum/weather/rain_storm/start()
GLOB.rain_storm_sounds.Cut()
for(var/area/impacted_area as anything in impacted_areas)
GLOB.rain_storm_sounds[impacted_area] = /datum/looping_sound/rain/middle
return ..()
/datum/weather/rain_storm/wind_down()
GLOB.rain_storm_sounds.Cut()
for(var/area/impacted_area as anything in impacted_areas)
GLOB.rain_storm_sounds[impacted_area] = /datum/looping_sound/rain/end
return ..()
/datum/weather/rain_storm/end()
GLOB.rain_storm_sounds.Cut()
return ..()
/datum/weather/rain_storm/blood
whitelist_weather_reagents = list(/datum/reagent/blood)
probability = 0 // admeme event
// Fun fact - if you increase the weather_temperature higher than LIQUID_PLASMA_BP
// the plasma rain will vaporize into a gas on whichever turf it lands on
/datum/weather/rain_storm/plasma
whitelist_weather_reagents = list(/datum/reagent/toxin/plasma)
probability = 0 // maybe for icebox maps one day?
/datum/weather/rain_storm/deep_fried
weather_temperature = 455 // just hot enough to apply the fried effect
whitelist_weather_reagents = list(/datum/reagent/consumable/nutriment/fat/oil)
weather_flags = (WEATHER_TURFS | WEATHER_INDOORS)
probability = 0 // admeme event
/datum/weather/rain_storm/acid
desc = "The planet's thunderstorms are by nature acidic, and will incinerate anyone standing beneath them without protection."
telegraph_duration = 40 SECONDS
telegraph_message = span_warning("Thunder rumbles far above. You hear acidic droplets hissing against the canopy. Seek shelter!")
telegraph_sound = 'sound/effects/siren.ogg'
weather_message = span_userdanger("<i>Acidic rain pours down around you! Get inside!</i>")
weather_duration_lower = 1 MINUTES
weather_duration_upper = 2 MINUTES
end_duration = 10 SECONDS
end_message = span_bolddanger("The downpour gradually slows to a light shower. It should be safe outside now.")
// these are weighted by acidpwr which causes more damage the higher it is
whitelist_weather_reagents = list(
/datum/reagent/toxin/acid/nitracid = 3,
/datum/reagent/toxin/acid = 2,
/datum/reagent/toxin/acid/fluacid = 1,
)
probability = 0
@@ -0,0 +1,64 @@
//Darude sandstorm starts playing
/datum/weather/sand_storm
name = "severe sandstorm"
desc = "A severe dust storm that engulfs an area, dealing intense damage to the unprotected."
telegraph_message = span_danger("You see a dust cloud rising over the horizon. That can't be good...")
telegraph_duration = 30 SECONDS
telegraph_overlay = "dust_med"
telegraph_sound = 'sound/effects/siren.ogg'
weather_message = span_userdanger("<i>Hot sand and wind batter you! Get inside!</i>")
weather_duration_lower = 1 MINUTES
weather_duration_upper = 2 MINUTES
weather_overlay = "dust_high"
end_message = span_bolddanger("The shrieking wind whips away the last of the sand and falls to its usual murmur. It should be safe to go outside now.")
end_duration = 30 SECONDS
end_overlay = "dust_med"
area_type = /area
target_trait = ZTRAIT_SANDSTORM
immunity_type = TRAIT_SANDSTORM_IMMUNE
probability = 90
weather_flags = (WEATHER_MOBS | WEATHER_BAROMETER)
/datum/weather/sand_storm/telegraph()
GLOB.sand_storm_sounds.Cut()
for(var/area/impacted_area as anything in impacted_areas)
GLOB.sand_storm_sounds[impacted_area] = /datum/looping_sound/weak_outside_ashstorm
return ..()
/datum/weather/sand_storm/start()
GLOB.sand_storm_sounds.Cut()
for(var/area/impacted_area as anything in impacted_areas)
GLOB.sand_storm_sounds[impacted_area] = /datum/looping_sound/active_outside_ashstorm
return ..()
/datum/weather/sand_storm/wind_down()
GLOB.sand_storm_sounds.Cut()
for(var/area/impacted_area as anything in impacted_areas)
GLOB.sand_storm_sounds[impacted_area] = /datum/looping_sound/weak_outside_ashstorm
return ..()
/datum/weather/sand_storm/weather_act_mob(mob/living/victim)
victim.adjustBruteLoss(5, required_bodytype = BODYTYPE_ORGANIC)
return ..()
/datum/weather/sand_storm/harmless
name = "sandfall"
desc = "A passing sandstorm blankets the area in sand."
telegraph_message = span_danger("The wind begins to intensify, blowing sand up from the ground...")
telegraph_overlay = "dust_low"
telegraph_sound = null
weather_message = span_notice("Gentle sand wafts down around you like grotesque snow. The storm seems to have passed you by...")
weather_overlay = "dust_med"
end_message = span_notice("The sandfall slows, stops. Another layer of sand on the mesa beneath your feet.")
end_overlay = "dust_low"
probability = 10
weather_flags = parent_type::weather_flags & ~WEATHER_MOBS
@@ -21,20 +21,14 @@
end_sound_vol = /datum/looping_sound/snowstorm::volume + 10
area_type = /area
protect_indoors = TRUE
target_trait = ZTRAIT_SNOWSTORM
immunity_type = TRAIT_SNOWSTORM_IMMUNE
barometer_predictable = TRUE
///Lowest we can cool someone randomly per weather act. Positive values only
var/cooling_lower = 5
///Highest we can cool someone randomly per weather act. Positive values only
var/cooling_upper = 15
/datum/weather/snow_storm/weather_act(mob/living/living)
living.adjust_bodytemperature(-rand(cooling_lower, cooling_upper))
// snowstorms should be colder than default icebox atmos
weather_temperature = ICEBOX_MIN_TEMPERATURE - 40
// snowstorms temperature ignores any clothing insulation
weather_flags = (WEATHER_MOBS | WEATHER_BAROMETER | WEATHER_TEMPERATURE_BYPASS_CLOTHING)
/datum/weather/snow_storm/start()
GLOB.snowstorm_sounds.Cut() // it's passed by ref
@@ -72,9 +66,7 @@
///A storm that doesn't stop storming, and is a bit stronger
/datum/weather/snow_storm/forever_storm
telegraph_duration = 0 SECONDS
perpetual = TRUE
weather_flags = parent_type::weather_flags | WEATHER_ENDLESS
probability = 0
cooling_lower = 5
cooling_upper = 18
weather_temperature = parent_type::weather_temperature - 40 // faster cooling effects at lower temps
@@ -16,8 +16,6 @@
end_duration = 10 SECONDS
area_type = /area
protect_indoors = FALSE
target_trait = ZTRAIT_VOIDSTORM
barometer_predictable = FALSE
perpetual = TRUE
weather_flags = (WEATHER_INDOORS | WEATHER_BAROMETER | WEATHER_ENDLESS)
@@ -80,7 +80,7 @@
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))
if((W.weather_flags & WEATHER_BAROMETER) && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE))
ongoing_weather = W
break
@@ -90,7 +90,7 @@
return CLICK_ACTION_BLOCKING
to_chat(user, span_notice("The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)]."))
if(ongoing_weather.aesthetic)
if(!(ongoing_weather.weather_flags & FUNCTIONAL_WEATHER))
to_chat(user, span_warning("[src]'s barometer function says that the next storm will breeze on by."))
else
var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"]
-16
View File
@@ -152,22 +152,6 @@ ADMIN_VERB(change_sec_level, R_ADMIN, "Set Security Level", "Changes the securit
message_admins("[key_name_admin(user)] changed the security level to [level]")
BLACKBOX_LOG_ADMIN_VERB("Set Security Level [capitalize(level)]")
ADMIN_VERB(run_weather, R_FUN, "Run Weather", "Triggers specific weather on the z-level you choose.", ADMIN_CATEGORY_EVENTS)
var/weather_type = input(user, "Choose a weather", "Weather") as null|anything in sort_list(subtypesof(/datum/weather), GLOBAL_PROC_REF(cmp_typepaths_asc))
if(!weather_type)
return
var/turf/T = get_turf(user.mob)
var/z_level = input(user, "Z-Level to target?", "Z-Level", T?.z) as num|null
if(!isnum(z_level))
return
SSweather.run_weather(weather_type, z_level)
message_admins("[key_name_admin(user)] started weather of type [weather_type] on the z-level [z_level].")
log_admin("[key_name(user)] started weather of type [weather_type] on the z-level [z_level].")
BLACKBOX_LOG_ADMIN_VERB("Run Weather")
ADMIN_VERB(command_report_footnote, R_ADMIN, "Command Report Footnote", "Adds a footnote to the roundstart command report.", ADMIN_CATEGORY_EVENTS)
var/datum/command_footnote/command_report_footnote = new /datum/command_footnote()
GLOB.communications_controller.block_command_report += 1 //Add a blocking condition to the counter until the inputs are done.
+93
View File
@@ -0,0 +1,93 @@
ADMIN_VERB(run_weather, R_ADMIN|R_FUN, "Run Weather", "Triggers specific weather on the z-level you choose.", ADMIN_CATEGORY_EVENTS)
var/list/weather_choices = list()
if(!length(weather_choices))
for(var/datum/weather/weather_type as anything in subtypesof(/datum/weather))
weather_choices[initial(weather_type.type)] = weather_type
var/datum/weather/weather_choice = tgui_input_list(user, "Choose a weather to run", "Weather", weather_choices)
if(!weather_choice)
return
weather_choice = weather_choices[weather_choice]
var/turf/current_turf = get_turf(user.mob)
var/z_level = tgui_input_number(user, "Z-Level to target", "Z-Level", min_value = 1, max_value = world.maxz, default = current_turf?.z)
if(!isnum(z_level))
return
var/static/list/custom_options = list("Default", "Custom", "Cancel")
var/custom_choice = tgui_alert(user, "How would you like to run the weather settings?", "Custom Weather", custom_options)
switch(custom_choice)
if("Default")
SSweather.run_weather(weather_choice, z_level) // default settings
message_admins("[key_name_admin(user)] started weather of type [weather_choice] on the z-level [z_level].")
log_admin("[key_name(user)] started weather of type [weather_choice] on the z-level [z_level].")
BLACKBOX_LOG_ADMIN_VERB("Run Weather")
return
if("Cancel")
return
var/list/area_choices = list()
if(!length(area_choices))
for(var/area/area_type as anything in typesof(/area))
area_choices[initial(area_type.type)] = area_type
var/area/area_choice = tgui_input_list(user, "Select an area for weather to target", "Target Area", area_choices)
if(!area_choice)
return
area_choice = area_choices[area_choice]
var/weather_bitflags = input_bitfield(
user,
"Weather flags - Select the flags for your weather event",
"weather_flags",
weather_choice::weather_flags,
)
var/datum/reagent/reagent_choice
if((weather_bitflags & (WEATHER_TURFS|WEATHER_MOBS)))
var/static/list/reagent_options = list("Yes", "No", "Cancel")
var/reagent_option = tgui_alert(user, "Would you like to make the weather use a custom reagent?", "Weather Reagent", reagent_options)
switch(reagent_option)
if("Cancel")
return
if("Yes")
var/static/list/reagent_choices = list()
if(!length(reagent_choices))
for(var/datum/reagent/reagent_type as anything in subtypesof(/datum/reagent))
reagent_choices[initial(reagent_type.type)] = reagent_type
reagent_choice = tgui_input_list(user, "Select a reagent for the rain", "Rain Reagent", reagent_choices)
if(!reagent_choice)
return
reagent_choice = reagent_choices[reagent_choice]
var/thunder_value
if(weather_bitflags & (WEATHER_THUNDER))
var/static/list/thunder_choices = GLOB.thunder_chance_options
var/thunder_choice = tgui_input_list(user, "How much thunder would you like", "Thunder", thunder_choices)
if(!thunder_choice)
return
thunder_value = GLOB.thunder_chance_options[thunder_choice]
var/list/weather_data = list(
area = area_choice,
weather_flags = weather_bitflags,
thunder_chance = thunder_value,
reagent = reagent_choice,
)
SSweather.run_weather(weather_choice, z_level, weather_data)
message_admins("[key_name_admin(user)] started weather of type [weather_choice] on the z-level [z_level].")
log_admin("[key_name(user)] started weather of type [weather_choice] on the z-level [z_level].")
BLACKBOX_LOG_ADMIN_VERB("Run Weather")
ADMIN_VERB(stop_weather, R_ADMIN|R_DEBUG, "Stop All Active Weather", "Stop all currently active weather.", ADMIN_CATEGORY_EVENTS)
log_admin("[key_name(user)] stopped all currently active weather.")
message_admins("[key_name_admin(user)] stopped all currently active weather.")
for(var/datum/weather/current_weather as anything in SSweather.processing)
if(current_weather in SSweather.processing)
current_weather.end()
BLACKBOX_LOG_ADMIN_VERB("Stop All Active Weather")
@@ -11,6 +11,21 @@
continue
chem.on_hydroponics_apply(src, user)
/obj/machinery/hydroponics/expose_reagents(list/reagents, datum/reagents/source, methods = TOUCH, volume_modifier = 1, show_message = TRUE)
. = ..()
if(. & COMPONENT_NO_EXPOSE_REAGENTS)
return
if(src.reagents.holder_full())
return
for(var/datum/reagent/reagent as anything in reagents)
if(istype(reagent, /datum/reagent/water))
adjust_waterlevel(round(reagents[reagent]))
else
src.reagents.add_reagent(reagent.type, reagents[reagent])
update_appearance()
/obj/machinery/hydroponics/proc/mutation_roll(mob/user)
switch(rand(100))
if(91 to 100)
+2
View File
@@ -132,6 +132,8 @@
client.init_verbs()
AddElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds)
AddElement(/datum/element/weather_listener, /datum/weather/rain_storm, ZTRAIT_RAINSTORM, GLOB.rain_storm_sounds)
AddElement(/datum/element/weather_listener, /datum/weather/sand_storm, ZTRAIT_SANDSTORM, GLOB.sand_storm_sounds)
AddElement(/datum/element/weather_listener, /datum/weather/snow_storm, ZTRAIT_SNOWSTORM, GLOB.snowstorm_sounds)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_MOB_LOGGED_IN, src)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 45 KiB

@@ -0,0 +1,7 @@
These sounds were previously on tg but got deleted at one point and have now been added again. See - https://github.com/tgstation/tgstation/pull/25222#discussion_r106794579
Sounds provided by Cuboos, using Royalty Free sounds. Presumed under default tg sound license - Creative Commons 3.0 BY-SA
sound/effects/weather/rain/rain_end.ogg
sound/effects/weather/rain/rain_mid.ogg
sound/effects/weather/rain/rain_start.ogg
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
View File
@@ -229,3 +229,6 @@ sound/items/weapons/crystal_dagger_sound.ogg -- https://pixabay.com/sound-effect
'sound/items/weapons/effects/blood_wisp_explode.ogg' -- https://pixabay.com/sound-effects/fire-sound-effects-224089/ by Alice-soundz
sound/effect/plasticflaps.ogg -- door_plastic_tapes.wav by estupe -- https://freesound.org/s/186357/ -- License: Creative Commons 0
sound/effects/siren.ogg -- siren.wav by IFartInUrGeneralDirection -- https://freesound.org/s/46092/ -- License: Attribution 4.0
Binary file not shown.
+3
View File
@@ -2144,6 +2144,8 @@
#include "code\datums\weather\weather_types\ash_storm.dm"
#include "code\datums\weather\weather_types\floor_is_lava.dm"
#include "code\datums\weather\weather_types\radiation_storm.dm"
#include "code\datums\weather\weather_types\rain_storm.dm"
#include "code\datums\weather\weather_types\sand_storm.dm"
#include "code\datums\weather\weather_types\snow_storm.dm"
#include "code\datums\weather\weather_types\void_storm.dm"
#include "code\datums\wires\_wires.dm"
@@ -3167,6 +3169,7 @@
#include "code\modules\admin\verbs\adminpm.dm"
#include "code\modules\admin\verbs\adminsay.dm"
#include "code\modules\admin\verbs\adminshuttle.dm"
#include "code\modules\admin\verbs\adminweather.dm"
#include "code\modules\admin\verbs\ai_triumvirate.dm"
#include "code\modules\admin\verbs\anonymousnames.dm"
#include "code\modules\admin\verbs\atmosdebug.dm"