mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-19 03:55:11 +01:00
21b4095dfd
Upstream 04/17/2026 fixes https://github.com/Bubberstation/Bubberstation/issues/5549 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com> Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com> Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com> Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com> Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com> Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com> Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com> Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com> Co-authored-by: loganuk <fakeemail123@aol.com> Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com> Co-authored-by: Lucy <lucy@absolucy.moe> Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com> Co-authored-by: Isratosh <Isratosh@hotmail.com> Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com> Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com> Co-authored-by: Alexander V. <volas@ya.ru> Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com> Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> Co-authored-by: Cameron Lennox <killer65311@gmail.com> Co-authored-by: Tim <timothymtorres@gmail.com> Co-authored-by: Iamgoofball <iamgoofball@gmail.com> Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com> Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com> Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com> Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com> Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com> Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com> Co-authored-by: Josh <josh.adam.powell@gmail.com> Co-authored-by: Josh Powell <josh.powell@softwire.com> Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com> Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com> Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com> Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
590 lines
23 KiB
Plaintext
590 lines
23 KiB
Plaintext
/// 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
|
|
*
|
|
* The effects of weather occur across an entire z-level. For instance, lavaland has periodic ash storms that scorch most unprotected creatures.
|
|
* Weather always occurs on different z levels at different times, regardless of weather type.
|
|
* Can have custom durations, targets, and can automatically protect indoor areas.
|
|
*
|
|
*/
|
|
|
|
/datum/weather
|
|
/// name of weather
|
|
var/name = "space wind"
|
|
/// description of weather
|
|
var/desc = "Heavy gusts of wind blanket the area, periodically knocking down anyone caught in the open."
|
|
/// The message displayed in chat to foreshadow the weather's beginning
|
|
var/telegraph_message = span_warning("The wind begins to pick up.")
|
|
/// How long from the beginning of the telegraph until the weather begins
|
|
var/telegraph_duration = 30 SECONDS
|
|
/// The sound file played to everyone on an affected z-level
|
|
var/telegraph_sound
|
|
/// Volume of the telegraph sound
|
|
var/telegraph_sound_vol
|
|
/// The overlay applied to all tiles on the z-level
|
|
var/telegraph_overlay
|
|
|
|
/// Displayed in chat once the weather begins in earnest
|
|
var/weather_message = span_userdanger("The wind begins to blow ferociously!")
|
|
/// How long the weather lasts once it begins
|
|
var/weather_duration = 2 MINUTES
|
|
/// See above - this is the lowest possible duration
|
|
var/weather_duration_lower = 2 MINUTES
|
|
/// See above - this is the highest possible duration
|
|
var/weather_duration_upper = 2.5 MINUTES
|
|
/// 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
|
|
/// Color to apply to the area while weather is occuring
|
|
var/weather_color = null
|
|
|
|
/// Displayed once the weather is over
|
|
var/end_message = span_danger("The wind relents its assault.")
|
|
/// How long the "wind-down" graphic will appear before vanishing entirely
|
|
var/end_duration = 30 SECONDS
|
|
/// Sound that plays while weather is ending
|
|
var/end_sound
|
|
/// Volume of the sound that plays while weather is ending
|
|
var/end_sound_vol
|
|
/// Area overlay while weather is ending
|
|
var/end_overlay
|
|
|
|
/// Types of area to affect
|
|
var/area_type = /area/space
|
|
/// Areas to be affected by the weather, calculated when the weather begins
|
|
VAR_FINAL/list/impacted_areas = list()
|
|
/// A weighted list of areas impacted by weather, where weights reflect the total turf count in each area.
|
|
VAR_FINAL/list/impacted_areas_weighted = list()
|
|
/// The total number of turfs impacted by weather across all z-levels and areas.
|
|
VAR_FINAL/total_impacted_turfs = 0
|
|
/// Areas affected by weather have their blend modes changed
|
|
VAR_FINAL/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_FINAL/impacted_z_levels
|
|
/// A weighted list of z-levels impacted by weather, where weights reflect the total turf count on each level
|
|
VAR_FINAL/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
|
|
/// 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
|
|
/// Taken from weather_glow.dmi
|
|
var/use_glow = TRUE
|
|
/// List of all overlays to apply to our turfs
|
|
var/list/overlay_cache
|
|
|
|
/// The stage of the weather, from 1-4
|
|
var/stage = END_STAGE
|
|
|
|
/// Weight amongst other eligible weather. If zero, will never happen randomly.
|
|
var/probability = 0
|
|
/// The z-level trait to affect when run randomly or when not overridden.
|
|
var/target_trait = ZTRAIT_STATION
|
|
/// For barometers to know when the next storm will hit
|
|
var/next_hit_time = 0
|
|
/// 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
|
|
|
|
/// 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_FINAL/list/current_mobs = list()
|
|
/// The weather turf counter to keep track of how many turfs we have processed so far
|
|
VAR_FINAL/turf_iteration = 0
|
|
/// The weather thunder counter to keep track of how much thunder we have processed so far
|
|
VAR_FINAL/thunder_iteration = 0
|
|
/// Index of the current section our weather subsystem is processing from our subsystem_tasks
|
|
VAR_FINAL/task_index = 1
|
|
/// The list of allowed tasks our weather subsystem is allowed to process (determined by weather_flags)
|
|
VAR_FINAL/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
|
|
weather_flags = isnull(weather_data?[WEATHER_FORCED_FLAGS]) ? weather_flags : weather_data?[WEATHER_FORCED_FLAGS]
|
|
turf_thunder_chance = isnull(weather_data?[WEATHER_FORCED_THUNDER]) ? turf_thunder_chance : weather_data?[WEATHER_FORCED_THUNDER]
|
|
telegraph_duration = isnull(weather_data?[WEATHER_FORCED_TELEGRAPH]) ? telegraph_duration : weather_data?[WEATHER_FORCED_TELEGRAPH]
|
|
end_duration = isnull(weather_data?[WEATHER_FORCED_END]) ? end_duration : weather_data?[WEATHER_FORCED_END]
|
|
|
|
var/datum/reagent/custom_reagent = weather_data?[WEATHER_FORCED_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 = blacklist_weather_reagents)
|
|
|
|
if(reagent_id)
|
|
weather_reagent = GLOB.chemical_reagents_list[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
|
|
|
|
setup_weather_areas(weather_data?[WEATHER_FORCED_AREAS])
|
|
setup_weather_turfs()
|
|
|
|
/datum/weather/Destroy()
|
|
QDEL_NULL(weather_reagent_holder)
|
|
return ..()
|
|
|
|
/**
|
|
* Telegraphs the beginning of the weather on the impacted z levels
|
|
*
|
|
* Sends sounds and details to mobs in the area
|
|
* Calculates duration and hit areas, and makes a callback for the actual weather to start
|
|
*
|
|
*/
|
|
/datum/weather/proc/telegraph(list/weather_data)
|
|
if(stage == STARTUP_STAGE)
|
|
return
|
|
stage = STARTUP_STAGE
|
|
SEND_GLOBAL_SIGNAL(COMSIG_WEATHER_TELEGRAPH(type), src)
|
|
weather_duration = isnull(weather_data?[WEATHER_FORCED_DURATION]) ? rand(weather_duration_lower, weather_duration_upper) : weather_data?[WEATHER_FORCED_DURATION]
|
|
SSweather.processing |= src
|
|
update_areas()
|
|
if(telegraph_duration)
|
|
send_alert(telegraph_message, telegraph_sound, telegraph_sound_vol)
|
|
addtimer(CALLBACK(src, PROC_REF(start)), telegraph_duration, TIMER_UNIQUE)
|
|
|
|
// so /datum/element/weather_listener relies on z traits to determine when to set up sound managers
|
|
// thus if we force weather on a z level that doesn't match the target trait, we need to manually set up sound managers
|
|
if(target_trait)
|
|
manually_setup_sound_manager()
|
|
|
|
/// Manually add a sound manager to all mobs
|
|
/datum/weather/proc/manually_setup_sound_manager()
|
|
var/list/filtered_zs = get_impacted_zs_without_trait()
|
|
var/list/playlist = get_playlist_ref()
|
|
// we only need to manually handle sound managers if there are zs without the trait being affected, fortunately
|
|
if(!length(filtered_zs) || isnull(playlist))
|
|
return
|
|
|
|
// add in dead mobs so we can get observers covered too
|
|
for(var/mob/living/affected as anything in GLOB.mob_living_list | GLOB.dead_mob_list)
|
|
if(isnull(affected.client))
|
|
// this registers 400+ odd signals... maybe we should reconsider
|
|
RegisterSignal(affected, COMSIG_MOB_LOGIN, PROC_REF(handle_mob_log_in))
|
|
continue
|
|
|
|
manually_setup_sound_manager_on_mob(affected, playlist, filtered_zs)
|
|
|
|
RegisterSignal(SSdcs, COMSIG_GLOB_MOB_CREATED, PROC_REF(handle_new_mob_sound_manager))
|
|
|
|
/// Returns a reference to the "sound playlist" for this weather type
|
|
/datum/weather/proc/get_playlist_ref()
|
|
return null
|
|
|
|
/// Returns a list of z-levels impacted that do not have the target trait
|
|
/datum/weather/proc/get_impacted_zs_without_trait()
|
|
var/list/zs_without_trait = list()
|
|
for(var/z in impacted_z_levels)
|
|
if(!SSmapping.level_trait(z, target_trait))
|
|
zs_without_trait += z
|
|
return zs_without_trait
|
|
|
|
/datum/weather/proc/handle_new_mob_sound_manager(datum/source, mob/the_mob)
|
|
SIGNAL_HANDLER
|
|
|
|
if(isnull(the_mob.client))
|
|
RegisterSignal(the_mob, COMSIG_MOB_LOGIN, PROC_REF(handle_mob_log_in))
|
|
return
|
|
|
|
manually_setup_sound_manager_on_mob(the_mob)
|
|
|
|
/datum/weather/proc/handle_mob_log_in(mob/source)
|
|
SIGNAL_HANDLER
|
|
|
|
if(stage >= END_STAGE)
|
|
stack_trace("Attempted to add a sound manager to a mob after weather ended")
|
|
UnregisterSignal(source, COMSIG_MOB_LOGIN)
|
|
return
|
|
|
|
manually_setup_sound_manager_on_mob(source)
|
|
|
|
/datum/weather/proc/manually_setup_sound_manager_on_mob(mob/living/affected, list/playlist = get_playlist_ref(), list/filtered_zs = get_impacted_zs_without_trait())
|
|
PRIVATE_PROC(TRUE)
|
|
|
|
var/list/sound_change_signals = list(
|
|
COMSIG_WEATHER_TELEGRAPH(type),
|
|
COMSIG_WEATHER_START(type),
|
|
COMSIG_WEATHER_WINDDOWN(type),
|
|
)
|
|
|
|
var/datum/component/our_comp = affected.AddComponent( \
|
|
/datum/component/area_sound_manager, \
|
|
area_loop_pairs = playlist, \
|
|
acceptable_zs = filtered_zs, \
|
|
)
|
|
our_comp.RegisterSignals(SSdcs, sound_change_signals, TYPE_PROC_REF(/datum/component/area_sound_manager, handle_change))
|
|
our_comp.RegisterSignal(SSdcs, COMSIG_WEATHER_END(type), TYPE_PROC_REF(/datum/component/area_sound_manager, handle_removal))
|
|
|
|
/datum/weather/proc/setup_weather_areas(list/forced_areas)
|
|
for(var/area/affected_area as anything in (forced_areas || get_areas(area_type)))
|
|
if(is_type_in_list(affected_area, protected_areas))
|
|
continue
|
|
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
|
|
*
|
|
* Updates area overlays and sends sounds and messages to mobs to notify them
|
|
* Begins dealing effects from weather to mobs in the area
|
|
*
|
|
*/
|
|
/datum/weather/proc/start()
|
|
if(stage >= MAIN_STAGE)
|
|
return
|
|
SEND_GLOBAL_SIGNAL(COMSIG_WEATHER_START(type), src)
|
|
stage = MAIN_STAGE
|
|
update_areas()
|
|
send_alert(weather_message, weather_sound)
|
|
if(!(weather_flags & (WEATHER_ENDLESS)))
|
|
addtimer(CALLBACK(src, PROC_REF(wind_down)), weather_duration, TIMER_UNIQUE)
|
|
for(var/area/impacted_area as anything in impacted_areas)
|
|
SEND_SIGNAL(impacted_area, COMSIG_WEATHER_BEGAN_IN_AREA(type), src)
|
|
|
|
/**
|
|
* Weather enters the winding down phase, stops effects
|
|
*
|
|
* Updates areas to be in the winding down phase
|
|
* Sends sounds and messages to mobs to notify them
|
|
*
|
|
*/
|
|
/datum/weather/proc/wind_down()
|
|
if(stage >= WIND_DOWN_STAGE)
|
|
return
|
|
SEND_GLOBAL_SIGNAL(COMSIG_WEATHER_WINDDOWN(type), src)
|
|
stage = WIND_DOWN_STAGE
|
|
update_areas()
|
|
send_alert(end_message, end_sound, end_sound_vol)
|
|
addtimer(CALLBACK(src, PROC_REF(end)), end_duration, TIMER_UNIQUE)
|
|
|
|
/**
|
|
* Fully ends the weather
|
|
*
|
|
* Effects no longer occur and area overlays are removed
|
|
* Removes weather from processing completely
|
|
*
|
|
*/
|
|
/datum/weather/proc/end()
|
|
if(stage == END_STAGE)
|
|
return
|
|
SEND_GLOBAL_SIGNAL(COMSIG_WEATHER_END(type), src)
|
|
UnregisterSignal(SSdcs, COMSIG_GLOB_MOB_CREATED)
|
|
stage = END_STAGE
|
|
SSweather.processing -= src
|
|
update_areas()
|
|
for(var/area/impacted_area as anything in impacted_areas)
|
|
SEND_SIGNAL(impacted_area, COMSIG_WEATHER_ENDED_IN_AREA(type), src)
|
|
|
|
if(target_trait)
|
|
for(var/mob/living/affected as anything in GLOB.mob_living_list | GLOB.dead_mob_list)
|
|
UnregisterSignal(affected, COMSIG_MOB_LOGIN)
|
|
|
|
// handles sending all alerts
|
|
/datum/weather/proc/send_alert(alert_msg, alert_sfx, alert_sfx_vol = 100)
|
|
for(var/z_level in impacted_z_levels)
|
|
for(var/mob/player as anything in SSmobs.clients_by_zlevel[z_level])
|
|
if(!can_get_alert(player))
|
|
continue
|
|
if(alert_msg)
|
|
to_chat(player, alert_msg)
|
|
if(alert_sfx)
|
|
player.stop_sound_channel(CHANNEL_WEATHER)
|
|
SEND_SOUND(player, sound(alert_sfx, channel = CHANNEL_WEATHER, volume = alert_sfx_vol))
|
|
|
|
// the checks for if a mob should receive alerts, returns TRUE if can
|
|
/datum/weather/proc/can_get_alert(mob/player)
|
|
var/turf/mob_turf = get_turf(player)
|
|
if(isnull(mob_turf))
|
|
return FALSE
|
|
|
|
if((weather_flags & WEATHER_STRICT_ALERT) && !can_see_weather(player))
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/// Checks if the player is in or can see an area affected by the weather
|
|
/datum/weather/proc/can_see_weather(mob/player)
|
|
if(HAS_MIND_TRAIT(player, TRAIT_DETECT_STORM))
|
|
return TRUE
|
|
|
|
for(var/area/nearby in view(player))
|
|
if(nearby in impacted_areas)
|
|
return TRUE
|
|
|
|
return FALSE
|
|
|
|
/**
|
|
* Returns TRUE if the living mob can be affected by the weather
|
|
*/
|
|
/datum/weather/proc/can_weather_act_mob(mob/living/mob_to_check)
|
|
var/turf/mob_turf = get_turf(mob_to_check)
|
|
|
|
if(!mob_turf)
|
|
return
|
|
|
|
if(!(mob_turf.z in impacted_z_levels))
|
|
return
|
|
|
|
if(!(mob_turf.loc in impacted_areas))
|
|
return
|
|
|
|
var/atom/to_check = mob_to_check
|
|
while(!isturf(to_check))
|
|
if(recursive_weather_protection_check(to_check))
|
|
return
|
|
to_check = to_check.loc
|
|
return TRUE
|
|
|
|
/**
|
|
* Returns TRUE if the atom should protect itself or its contents from weather
|
|
*/
|
|
/datum/weather/proc/recursive_weather_protection_check(atom/to_check)
|
|
return HAS_TRAIT(to_check, TRAIT_WEATHER_IMMUNE) || (immunity_type && HAS_TRAIT(to_check, immunity_type))
|
|
|
|
/**
|
|
* Returns TRUE if the turf can be affected by the weather
|
|
*/
|
|
/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/item/stack/ore/hit_ore in weather_turf)
|
|
if(QDELETED(hit_ore))
|
|
continue
|
|
// ores that get struck by thunder are smelted
|
|
// a bolt of lightning can reach temperatures of 30,000 Kelvin which is 5x hotter than the sun
|
|
hit_ore.fire_act(30000)
|
|
|
|
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]!"))
|
|
new /obj/effect/hotspot(weather_turf)
|
|
|
|
/**
|
|
* Updates the overlays on impacted areas
|
|
*/
|
|
/datum/weather/proc/update_areas()
|
|
var/list/new_overlay_cache = generate_overlay_cache()
|
|
for(var/area/impacted as anything in impacted_areas)
|
|
if(length(overlay_cache))
|
|
impacted.overlays -= overlay_cache
|
|
if(impacted_areas_blend_modes[impacted])
|
|
// revert the blend mode to the old state
|
|
impacted.blend_mode = impacted_areas_blend_modes[impacted]
|
|
impacted_areas_blend_modes[impacted] = null
|
|
if(length(new_overlay_cache))
|
|
impacted.overlays += new_overlay_cache
|
|
// only change the blend mode if it's not default or overlay
|
|
if(impacted.blend_mode > BLEND_OVERLAY)
|
|
// save the old blend mode state
|
|
impacted_areas_blend_modes[impacted] = impacted.blend_mode
|
|
impacted.blend_mode = BLEND_OVERLAY
|
|
|
|
overlay_cache = new_overlay_cache
|
|
|
|
/// Returns a list of visual offset -> overlays to use
|
|
/datum/weather/proc/generate_overlay_cache()
|
|
// We're ending, so no overlays at all
|
|
if(stage == END_STAGE)
|
|
return list()
|
|
|
|
var/weather_state = ""
|
|
switch(stage)
|
|
if(STARTUP_STAGE)
|
|
weather_state = telegraph_overlay
|
|
if(MAIN_STAGE)
|
|
weather_state = weather_overlay
|
|
if(WIND_DOWN_STAGE)
|
|
weather_state = end_overlay
|
|
|
|
// Use all possible offsets
|
|
// Yes this is a bit annoying, but it's too slow to calculate and store these from turfs, and it shouldn't (I hope) look weird
|
|
var/list/gen_overlay_cache = list()
|
|
for(var/offset in 0 to SSmapping.max_plane_offset)
|
|
// Note: what we do here is effectively apply two overlays to each area, for every unique multiz layer they inhabit
|
|
// One is the base, which will be masked by lighting. the other is "glowing", and provides a nice contrast
|
|
// This method of applying one overlay per z layer has some minor downsides, in that it could lead to improperly doubled effects if some have alpha
|
|
// I prefer it to creating 2 extra plane masters however, so it's a cost I'm willing to pay
|
|
// LU
|
|
if(use_glow)
|
|
var/mutable_appearance/glow_overlay = mutable_appearance('icons/effects/glow_weather.dmi', weather_state, overlay_layer, null, WEATHER_GLOW_PLANE, 100, offset_const = offset)
|
|
glow_overlay.color = weather_color
|
|
gen_overlay_cache += glow_overlay
|
|
|
|
var/mutable_appearance/new_weather_overlay = mutable_appearance('icons/effects/weather_effects.dmi', weather_state, overlay_layer, plane = overlay_plane, offset_const = offset)
|
|
new_weather_overlay.color = weather_color
|
|
gen_overlay_cache += new_weather_overlay
|
|
|
|
return gen_overlay_cache
|
|
|
|
#undef MAX_TURFS_PER_TICK
|