diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm index a6dfe60eab5..dd4c1fadccb 100644 --- a/code/__DEFINES/maps.dm +++ b/code/__DEFINES/maps.dm @@ -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" diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm index a96a950559a..c8f3ae2282c 100644 --- a/code/__DEFINES/maths.dm +++ b/code/__DEFINES/maths.dm @@ -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))) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index efc9bcef44e..df450ab43a2 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -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 diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 0b7040ba421..7540444e170 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -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 diff --git a/code/__DEFINES/weather.dm b/code/__DEFINES/weather.dm index 18f14d1b45b..cfc163dcdb4 100644 --- a/code/__DEFINES/weather.dm +++ b/code/__DEFINES/weather.dm @@ -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) diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 36a0e9d3593..105645b82fd 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -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, +)) diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 99107f33a0f..acd393087eb 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -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, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index 739035e8fb4..93e90333b45 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -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, diff --git a/code/controllers/subsystem/weather.dm b/code/controllers/subsystem/weather.dm index 516722c8d00..7c7e8b0cb21 100644 --- a/code/controllers/subsystem/weather.dm +++ b/code/controllers/subsystem/weather.dm @@ -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") diff --git a/code/datums/components/object_possession.dm b/code/datums/components/object_possession.dm index 85609c0d6d9..256f2952670 100644 --- a/code/datums/components/object_possession.dm +++ b/code/datums/components/object_possession.dm @@ -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) diff --git a/code/datums/components/weatherannouncer.dm b/code/datums/components/weatherannouncer.dm index 4ac103a7758..b30e636e245 100644 --- a/code/datums/components/weatherannouncer.dm +++ b/code/datums/components/weatherannouncer.dm @@ -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) diff --git a/code/datums/elements/weather_listener.dm b/code/datums/elements/weather_listener.dm index d2095ad3546..05a4507f1a4 100644 --- a/code/datums/elements/weather_listener.dm +++ b/code/datums/elements/weather_listener.dm @@ -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) diff --git a/code/datums/looping_sounds/weather.dm b/code/datums/looping_sounds/weather.dm index 4b164865fc9..648651c56d8 100644 --- a/code/datums/looping_sounds/weather.dm +++ b/code/datums/looping_sounds/weather.dm @@ -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 diff --git a/code/datums/weather/weather.dm b/code/datums/weather/weather.dm index 7b18e184a59..3128b116ebe 100644 --- a/code/datums/weather/weather.dm +++ b/code/datums/weather/weather.dm @@ -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 diff --git a/code/datums/weather/weather_types/ash_storm.dm b/code/datums/weather/weather_types/ash_storm.dm index 6ada1044e62..78b084e2aad 100644 --- a/code/datums/weather/weather_types/ash_storm.dm +++ b/code/datums/weather/weather_types/ash_storm.dm @@ -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 diff --git a/code/datums/weather/weather_types/floor_is_lava.dm b/code/datums/weather/weather_types/floor_is_lava.dm index 2541463f405..14130421e6e 100644 --- a/code/datums/weather/weather_types/floor_is_lava.dm +++ b/code/datums/weather/weather_types/floor_is_lava.dm @@ -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 ..() diff --git a/code/datums/weather/weather_types/radiation_storm.dm b/code/datums/weather/weather_types/radiation_storm.dm index dc4722f3d02..409a1893057 100644 --- a/code/datums/weather/weather_types/radiation_storm.dm +++ b/code/datums/weather/weather_types/radiation_storm.dm @@ -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)) diff --git a/code/datums/weather/weather_types/rain_storm.dm b/code/datums/weather/weather_types/rain_storm.dm new file mode 100644 index 00000000000..db97b1df50e --- /dev/null +++ b/code/datums/weather/weather_types/rain_storm.dm @@ -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("Rain pours down around you!") + 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("Acidic rain pours down around you! Get inside!") + 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 + + diff --git a/code/datums/weather/weather_types/sand_storm.dm b/code/datums/weather/weather_types/sand_storm.dm new file mode 100644 index 00000000000..89d71c09ae5 --- /dev/null +++ b/code/datums/weather/weather_types/sand_storm.dm @@ -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("Hot sand and wind batter you! Get inside!") + 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 diff --git a/code/datums/weather/weather_types/snow_storm.dm b/code/datums/weather/weather_types/snow_storm.dm index 92551db3043..01a07df6133 100644 --- a/code/datums/weather/weather_types/snow_storm.dm +++ b/code/datums/weather/weather_types/snow_storm.dm @@ -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 diff --git a/code/datums/weather/weather_types/void_storm.dm b/code/datums/weather/weather_types/void_storm.dm index eb43198e191..fd68b818152 100644 --- a/code/datums/weather/weather_types/void_storm.dm +++ b/code/datums/weather/weather_types/void_storm.dm @@ -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) diff --git a/code/game/objects/items/devices/scanners/gas_analyzer.dm b/code/game/objects/items/devices/scanners/gas_analyzer.dm index 1c389739700..161d9b9a88e 100644 --- a/code/game/objects/items/devices/scanners/gas_analyzer.dm +++ b/code/game/objects/items/devices/scanners/gas_analyzer.dm @@ -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]"] diff --git a/code/modules/admin/verbs/adminevents.dm b/code/modules/admin/verbs/adminevents.dm index 649ae3e3a34..b3af08d8889 100644 --- a/code/modules/admin/verbs/adminevents.dm +++ b/code/modules/admin/verbs/adminevents.dm @@ -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. diff --git a/code/modules/admin/verbs/adminweather.dm b/code/modules/admin/verbs/adminweather.dm new file mode 100644 index 00000000000..e5c77625b80 --- /dev/null +++ b/code/modules/admin/verbs/adminweather.dm @@ -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") diff --git a/code/modules/hydroponics/hydroponics_chemreact.dm b/code/modules/hydroponics/hydroponics_chemreact.dm index c05e0ab0ff4..0af7c258f8f 100644 --- a/code/modules/hydroponics/hydroponics_chemreact.dm +++ b/code/modules/hydroponics/hydroponics_chemreact.dm @@ -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) diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index cd661869463..46772ecda51 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -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) diff --git a/icons/effects/weather_effects.dmi b/icons/effects/weather_effects.dmi index fa21b2d12eb..1b21f852dd7 100644 Binary files a/icons/effects/weather_effects.dmi and b/icons/effects/weather_effects.dmi differ diff --git a/sound/ambience/weather/rain/attribution.txt b/sound/ambience/weather/rain/attribution.txt new file mode 100644 index 00000000000..7a3c1cc76f9 --- /dev/null +++ b/sound/ambience/weather/rain/attribution.txt @@ -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 diff --git a/sound/ambience/weather/rain/rain_end.ogg b/sound/ambience/weather/rain/rain_end.ogg new file mode 100644 index 00000000000..d02299c6c0d Binary files /dev/null and b/sound/ambience/weather/rain/rain_end.ogg differ diff --git a/sound/ambience/weather/rain/rain_mid.ogg b/sound/ambience/weather/rain/rain_mid.ogg new file mode 100644 index 00000000000..13dd3a59cb8 Binary files /dev/null and b/sound/ambience/weather/rain/rain_mid.ogg differ diff --git a/sound/ambience/weather/rain/rain_start.ogg b/sound/ambience/weather/rain/rain_start.ogg new file mode 100644 index 00000000000..27e47372944 Binary files /dev/null and b/sound/ambience/weather/rain/rain_start.ogg differ diff --git a/sound/attributions.txt b/sound/attributions.txt index dd1da1897a1..2a08140df95 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -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 diff --git a/sound/effects/siren.ogg b/sound/effects/siren.ogg new file mode 100644 index 00000000000..b5cabfadced Binary files /dev/null and b/sound/effects/siren.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 5fb6f9d4bf2..e135f6765a0 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -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"