Merge branch 'master' of github.com:tgstation/tgstation into upstream-2026-06-23

This commit is contained in:
Roxy
2026-06-23 15:51:55 -04:00
1007 changed files with 40287 additions and 33507 deletions
@@ -435,14 +435,16 @@
/datum/config_entry/flag/disallow_circuit_sounds
/datum/config_entry/flag/tts_no_whisper
/datum/config_entry/string/tts_http_url
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/string/tts_http_token
protection = CONFIG_ENTRY_LOCKED|CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/tts_tram_announcer_override
/datum/config_entry/string/tts_computer_voice_override
/datum/config_entry/number/tts_max_concurrent_requests
default = 4
min_val = 1
@@ -101,6 +101,9 @@
/// log game events
/datum/config_entry/flag/log_game
/// log minimap drawing events
/datum/config_entry/flag/log_minimap_drawing
/// log mech data
/datum/config_entry/flag/log_mecha
+7 -3
View File
@@ -20,13 +20,17 @@ SUBSYSTEM_DEF(ambience)
var/client/client_iterator = cached_clients[cached_clients.len]
cached_clients.len--
//Check to see if the client exists and isn't held by a new player
var/mob/client_mob = client_iterator?.mob
if(isnull(client_iterator) || !client_mob || isnewplayer(client_mob))
//Check to see if the client exists
if(isnull(client_iterator))
ambience_listening_clients -= client_iterator
client_old_areas -= client_iterator
continue
// skip them this tick if they're on the lobby screen or somehow dont have a mob??
var/mob/client_mob = client_iterator?.mob
if(!client_mob || isnewplayer(client_mob))
continue
if(HAS_TRAIT(client_mob, TRAIT_DEAF)) //WHAT? I CAN'T HEAR YOU
continue
@@ -1032,7 +1032,7 @@
candidate_role = "Slaughter Demon"
// preview_antag_datum = /datum/antagonist/slaughter // Doesn't actually have its own pref
midround_type = HEAVY_MIDROUND
jobban_flag = ROLE_ALIEN
jobban_flag = ROLE_SENTIENCE
ruleset_flags = RULESET_INVADER
weight = 0
min_pop = 20
@@ -442,6 +442,7 @@
new_head.give_flash = TRUE
new_head.give_hud = TRUE
new_head.remove_clumsy = TRUE
new_head.roundstart = TRUE
candidate.add_antag_datum(new_head, GLOB.revolution_handler.revs)
GLOB.revolution_handler.start_revolution()
+1 -1
View File
@@ -374,7 +374,7 @@ SUBSYSTEM_DEF(garbage)
var/start_time = world.time
var/start_tick = world.tick_usage
SEND_SIGNAL(to_delete, COMSIG_QDELETING, force) // Let the (remaining) components know about the result of Destroy
var/hint = to_delete.Destroy(force) // Let our friend know they're about to get fucked up.
var/hint = UNLINT(to_delete.Destroy(force)) // Let our friend know they're about to get fucked up.
if(world.time != start_time)
trash.slept_destroy++
@@ -204,12 +204,10 @@ SUBSYSTEM_DEF(greyscale_previews)
#endif
var/filepath = "icons/map_icons/[filename].dmi"
#ifdef UNIT_TESTS
var/old_md5 = rustg_hash_file(RUSTG_HASH_MD5, filepath)
#endif
fcopy(holder, filepath)
#ifdef UNIT_TESTS
var/new_md5 = rustg_hash_file(RUSTG_HASH_MD5, filepath)
var/new_md5 = rustg_hash_file(RUSTG_HASH_MD5, holder)
if(old_md5 != new_md5)
fcopy(holder, filepath)
#ifdef UNIT_TESTS
stack_trace("Generated map icons were different than what is currently saved. If you see this in a CI run it means you need to run the game once through initialization and commit the resulting files in 'icons/map_icons/'")
#endif
+7 -2
View File
@@ -22,6 +22,9 @@ SUBSYSTEM_DEF(map_vote)
/// Stores the last amount of potential players to compare next time we're called
var/player_cache = -1
/// Cached list of votable maps
var/list/votable_map_cache
/// Stores a formatted html string of the tally counts
var/tally_printout = span_red("Loading...")
@@ -113,8 +116,9 @@ SUBSYSTEM_DEF(map_vote)
else
filter_threshold = length(GLOB.clients)
if(filter_threshold == player_cache)
return null
// Cached because it's called off ui_data, I really don't think this is worth it but whatever
if(filter_threshold == player_cache && !isnull(votable_map_cache))
return votable_map_cache.Copy()
player_cache = filter_threshold
var/list/valid_maps = list()
@@ -130,6 +134,7 @@ SUBSYSTEM_DEF(map_vote)
continue
valid_maps += possible_config.map_name
votable_map_cache = valid_maps.Copy()
return valid_maps
/datum/controller/subsystem/map_vote/proc/filter_cache_to_valid_maps()
+4 -1
View File
@@ -17,6 +17,9 @@ SUBSYSTEM_DEF(mapping)
var/list/ruins_templates = list()
///Assoc list of all ruins spawned, key center of ruin spawn -> value ruin instance
var/list/active_ruins = alist()
///List of ruins, separated by their theme
var/list/themed_ruins = list()
@@ -141,7 +144,7 @@ SUBSYSTEM_DEF(mapping)
if(current_map.wilderness_levels)
var/list/FailedZs = list()
LoadGroup(FailedZs, "Wilderness Area", current_map.wilderness_directory, current_map.maps_to_spawn, default_traits = ZTRAITS_WILDS, height_autosetup = FALSE)
LoadGroup(FailedZs, "Wilderness Area", current_map.wilderness_directory, current_map.wilderness_maps_to_spawn, default_traits = current_map.wilderness_z_traits, height_autosetup = FALSE)
if(LAZYLEN(FailedZs))
CRASH("Ice wilds failed to load!")
+4 -3
View File
@@ -23,11 +23,12 @@ SUBSYSTEM_DEF(materials)
new /datum/stack_recipe("Material floor tile", /obj/item/stack/tile/material, 1, 4, 20, crafting_flags = CRAFT_SKIP_MATERIALS_PARITY, category = CAT_TILES),
new /datum/stack_recipe("Material airlock assembly", /obj/structure/door_assembly/door_assembly_material, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_SKIP_MATERIALS_PARITY, category = CAT_DOORS),
new /datum/stack_recipe("Material platform", /obj/structure/platform/material, 2, time = 3 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_SKIP_MATERIALS_PARITY, trait_booster = TRAIT_QUICK_BUILD, trait_modifier = 0.75, category = CAT_STRUCTURE), \
)
/// List of stackcrafting recipes for materials using rigid recipes
var/list/rigid_stack_recipes = list(
new /datum/stack_recipe("Carving block", /obj/structure/carving_block, 5, time = 3 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_SKIP_MATERIALS_PARITY, category = CAT_STRUCTURE),
)
/// List of stackcrafting recipes for materials using rigid recipes (none yet)
var/list/rigid_stack_recipes = list(
)
/// A list of dimensional themes used by the dimensional anomaly and other things, most of which require materials to function.
var/list/datum/dimension_theme/dimensional_themes
+2 -1
View File
@@ -11,7 +11,8 @@ SUBSYSTEM_DEF(minor_mapping)
var/list/vermin_chances = list(
/mob/living/basic/mouse = 72,
/mob/living/basic/snail = 16,
/mob/living/basic/stoat = 10,
/mob/living/basic/stoat = 7,
/mob/living/basic/stoat/kit = 3,
/mob/living/basic/regal_rat/controlled = 2,
)
@@ -461,6 +461,94 @@
if(length(movement_path))
movement_path.Cut(1,2)
else
return handle_move_attempt_failure()
/datum/move_loop/has_target/jps/proc/handle_move_attempt_failure()
EVLOG_TEXT(moving, EVLOG_CATEGORY_MOVELOOPS, "Path recalculating due to obstruction")
INVOKE_ASYNC(src, PROC_REF(recalculate_path))
return MOVELOOP_FAILURE
/datum/move_loop/has_target/jps/frustrations
///maximum amount of frustrations before we recalculate path
var/maximum_frustrations
///what is our current frustration?
var/current_frustrations = 0
///how long before we're able to increment frustration?
var/frustration_delay
///have we drawn our initial path?
var/initial_path_drawn = FALSE
///cooldown between frustration increments
COOLDOWN_DECLARE(frustration_cooldown)
/datum/move_manager/proc/frustrations_move(moving,
chasing,
delay,
timeout,
repath_delay,
max_path_length,
minimum_distance,
list/access,
simulated_only,
turf/avoid,
skip_first,
subsystem,
diagonal_handling,
priority,
flags,
datum/extra_info,
initial_path)
return add_to_loop(moving,
subsystem,
/datum/move_loop/has_target/jps/frustrations,
priority,
flags,
extra_info,
delay,
timeout,
chasing,
repath_delay,
max_path_length,
minimum_distance,
access,
simulated_only,
avoid,
skip_first,
diagonal_handling,
initial_path)
/datum/move_loop/has_target/jps/frustrations/setup(delay, timeout, atom/chasing, maximum_frustrations = 10, frustration_delay = 2 SECONDS)
. = ..()
if(!.)
return
src.maximum_frustrations = maximum_frustrations
src.frustration_delay = frustration_delay
/datum/move_loop/has_target/jps/frustrations/recalculate_path()
if(initial_path_drawn && current_frustrations < maximum_frustrations)
return
return ..()
/datum/move_loop/has_target/jps/frustrations/loop_stopped()
. = ..()
/datum/move_loop/has_target/jps/frustrations/on_finish_pathing(list/path)
. = ..()
if(movement_path)
initial_path_drawn = TRUE
/datum/move_loop/has_target/jps/frustrations/handle_move_attempt_failure()
if(!initial_path_drawn)
INVOKE_ASYNC(src, PROC_REF(recalculate_path))
return MOVELOOP_FAILURE
if(!COOLDOWN_FINISHED(src, frustration_cooldown))
return NONE
COOLDOWN_START(src, frustration_cooldown, frustration_delay)
current_frustrations++
SEND_SIGNAL(src, COMSIG_MOVELOOP_JPS_FRUSTRATION_INCREMENTED, current_frustrations)
if(current_frustrations >= maximum_frustrations)
current_frustrations = 0
EVLOG_TEXT(moving, EVLOG_CATEGORY_MOVELOOPS, "Path recalculating due to obstruction")
INVOKE_ASYNC(src, PROC_REF(recalculate_path))
return MOVELOOP_FAILURE
@@ -22,7 +22,6 @@
determin_ph_range = R.determin_ph_range,
H_ion_release = R.H_ion_release,
purity_min = R.purity_min,
results = R.results,
required_container = R.required_container
)
@@ -52,6 +52,6 @@ PROCESSING_SUBSYSTEM_DEF(instruments)
/datum/controller/subsystem/processing/instruments/proc/reserve_instrument_channel(datum/instrument/I)
if(current_instrument_channels > max_instrument_channels)
return
. = SSsounds.reserve_sound_channel(I)
. = SSsounds.reserve_sound_channel_for_datum(I)
if(!isnull(.))
current_instrument_channels++
@@ -0,0 +1,24 @@
SUBSYSTEM_DEF(sound_tokens)
name = "Sound Tokens"
wait = 1
ss_flags = SS_TICKER | SS_BACKGROUND | SS_NO_INIT
var/list/clients_needing_update = list()
var/list/currentrun = list()
/datum/controller/subsystem/sound_tokens/fire(resumed)
if(!resumed)
currentrun = clients_needing_update
clients_needing_update = list()
while(length(currentrun))
var/client/client = currentrun[currentrun.len]
currentrun.len--
var/mob/owned_mob = client.mob
if(!owned_mob)
continue
for(var/datum/sound_token/token in client.sound_tokens)
token.update_listener(owned_mob)
if(MC_TICK_CHECK)
break
+23 -6
View File
@@ -114,10 +114,10 @@ SUBSYSTEM_DEF(sounds)
var/text_channel = num2text(channel)
var/using = using_channels[text_channel]
using_channels -= text_channel
if(using != TRUE) // datum channel
if(using != DATUMLESS) // datum channel
using_channels_by_datum[using] -= channel
if(!length(using_channels_by_datum[using]))
using_channels_by_datum -= using
stop_tracking_datum(using)
free_channel(channel)
/// Frees all the channels a datum is using.
@@ -128,14 +128,14 @@ SUBSYSTEM_DEF(sounds)
for(var/channel in L)
using_channels -= num2text(channel)
free_channel(channel)
using_channels_by_datum -= D
stop_tracking_datum(D)
/// Frees all datumless channels
/datum/controller/subsystem/sounds/proc/free_datumless_channels()
free_datum_channels(DATUMLESS)
/// NO AUTOMATIC CLEANUP - If you use this, you better manually free it later! Returns an integer for channel.
/datum/controller/subsystem/sounds/proc/reserve_sound_channel_datumless()
/// Reserve a sound channel. Free it later with free_sound_channel()
/datum/controller/subsystem/sounds/proc/reserve_sound_channel()
. = reserve_channel()
if(!.) //oh no..
return FALSE
@@ -145,7 +145,7 @@ SUBSYSTEM_DEF(sounds)
using_channels_by_datum[DATUMLESS] += .
/// Reserves a channel for a datum. Automatic cleanup only when the datum is deleted. Returns an integer for channel.
/datum/controller/subsystem/sounds/proc/reserve_sound_channel(datum/D)
/datum/controller/subsystem/sounds/proc/reserve_sound_channel_for_datum(datum/D)
if(!D) //i don't like typechecks but someone will fuck it up
CRASH("Attempted to reserve sound channel without datum using the managed proc.")
.= reserve_channel()
@@ -156,6 +156,8 @@ SUBSYSTEM_DEF(sounds)
LAZYINITLIST(using_channels_by_datum[D])
using_channels_by_datum[D] += .
RegisterSignal(D, COMSIG_QDELETING, PROC_REF(tracked_datum_deleted))
/**
* Reserves a channel and updates the datastructure. Private proc.
*/
@@ -260,4 +262,19 @@ SUBSYSTEM_DEF(sounds)
if(!isnull(sfx.key))
GLOB.sfx_datum_by_key[sfx.key] = new sfx()
///Call to free all channels reserved by a datum.
/datum/controller/subsystem/sounds/proc/stop_tracking_datum(datum/D)
PRIVATE_PROC(TRUE)
using_channels_by_datum -= D
UnregisterSignal(D, COMSIG_QDELETING)
/// Handles a tracked datum being deleted, automatically freeing the channels.
/datum/controller/subsystem/sounds/proc/tracked_datum_deleted(datum/source)
SIGNAL_HANDLER
PRIVATE_PROC(TRUE)
free_datum_channels(source)
#undef DATUMLESS
+1 -17
View File
@@ -114,22 +114,6 @@ SUBSYSTEM_DEF(ticker)
else
set_lobby_music("[global.config.directory]/title_music/sounds/[pick(music)]")
if(!GLOB.syndicate_code_phrase)
GLOB.syndicate_code_phrase = generate_code_phrase(return_list=TRUE)
var/codewords = jointext(GLOB.syndicate_code_phrase, "|")
var/regex/codeword_match = new("([codewords])", "ig")
GLOB.syndicate_code_phrase_regex = codeword_match
if(!GLOB.syndicate_code_response)
GLOB.syndicate_code_response = generate_code_phrase(return_list=TRUE)
var/codewords = jointext(GLOB.syndicate_code_response, "|")
var/regex/codeword_match = new("([codewords])", "ig")
GLOB.syndicate_code_response_regex = codeword_match
start_at = world.time + (CONFIG_GET(number/lobby_countdown) * (1 SECONDS))
return SS_INIT_SUCCESS
@@ -146,6 +130,7 @@ SUBSYSTEM_DEF(ticker)
// send2chat(new /datum/tgs_message_content("New round starting on [SSmapping.current_map.map_name]!"), channel_tag)
send2chat(new /datum/tgs_message_content("Round **[GLOB.round_id]** starting on [SSmapping.current_map.map_name], [CONFIG_GET(string/servername)]! \nIf you wish to be pinged for game related stuff, go to <#[CONFIG_GET(string/role_assign_channel_id)]> and assign yourself the roles."), channel_tag)
// BUBBER EDIT CHANGE END - Replace with more rich message
current_state = GAME_STATE_PREGAME
// BUBBERSTATION EDIT START
var/storyteller = CONFIG_GET(string/default_storyteller)
@@ -158,7 +143,6 @@ SUBSYSTEM_DEF(ticker)
addtimer(CALLBACK(SStitle, TYPE_PROC_REF(/datum/controller/subsystem/title, change_title_screen)), 1 SECONDS) //SKYRAT EDIT ADDITION - Title screen
//Everyone who wants to be an observer is now spawned
SEND_SIGNAL(src, COMSIG_TICKER_ENTER_PREGAME)
fire()
if(GAME_STATE_PREGAME)
//lobby stats for statpanels
+13
View File
@@ -21,11 +21,24 @@ SUBSYSTEM_DEF(traitor)
var/list/datum/uplink_handler/uplink_handlers = list()
/// The current scaling per minute of progression.
var/current_progression_scaling = 1 MINUTES
/// List of code words for traitors
var/syndicate_code_phrase
/// List of code responses for traitors
var/syndicate_code_response
/// Regex of code words for traitors
var/regex/syndicate_code_phrase_regex
/// Regex of code responses for traitors
var/regex/syndicate_code_response_regex
/datum/controller/subsystem/traitor/Initialize()
current_progression_scaling = 1 MINUTES * CONFIG_GET(number/traitor_scaling_multiplier)
for(var/theft_item in subtypesof(/datum/objective_item/steal))
new theft_item
syndicate_code_phrase = generate_code_phrase(return_list = TRUE)
syndicate_code_phrase_regex = new("([jointext(syndicate_code_phrase, "|")])", "ig")
syndicate_code_response = generate_code_phrase(return_list = TRUE)
syndicate_code_response_regex = new("([jointext(syndicate_code_response, "|")])", "ig")
return SS_INIT_SUCCESS
/datum/controller/subsystem/traitor/fire(resumed)
+2
View File
@@ -183,6 +183,8 @@ PROCESSING_SUBSYSTEM_DEF(transport)
transport_controller.dispatch_transport()
return
else
playsound(transport_controller.nav_beacon, 'sound/machines/tram/door_chime.ogg', 45, vary = FALSE, extrarange = MEDIUM_RANGE_SOUND_EXTRARANGE)
stoplag(1.4 SECONDS)
transport_controller.set_status_code(DOORS_READY, FALSE)
transport_controller.cycle_doors(CYCLE_CLOSED)
+258 -39
View File
@@ -19,6 +19,15 @@ SUBSYSTEM_DEF(tts)
/// HTTP requests that are being processed to see if they've been finished
var/list/current_processing_http_messages = list()
/// TTS requests for radio TTS audio playback. Cleared when it's been in here for 30 seconds. list("identifier" = list("ref" = [ref], "expiry_time" = world.time))
var/list/completed_tts_messages = list()
/// TTS requests for radios who heard a TTS message. list("identifier" = list("radio" = [ref], "hearers" = list([hearer_ref], ...)))
var/list/list/queued_radio_messages = list()
/// List of radio TTS messages with compression, used for fixing radio TTS during ionosphere storms to conceal text.
var/list/list/queued_radio_messages_compression = list()
/// A list of available speakers, which are string identifiers of the TTS voices that can be used to generate TTS messages.
var/list/available_speakers = list()
@@ -37,6 +46,10 @@ SUBSYSTEM_DEF(tts)
/// For tts messages which time out, it won't keep tracking the tts message and will just assume that the message took
/// 7 seconds (or whatever the value of message_timeout is) to receive back a response.
var/average_tts_messages_time = 0
/// Used as the Tram voice, to keep narration the same across tram devices.
var/tram_voice = null
/// Used as the Computer voice, to keep narration the same across computers.
var/computer_voice = null
/datum/controller/subsystem/tts/vv_edit_var(var_name, var_value)
// tts being enabled depends on whether it actually exists
@@ -73,6 +86,14 @@ SUBSYSTEM_DEF(tts)
if(available_speakers.Find(voice))
log_config("Removed speaker [voice] from the TTS voice pool per config.")
available_speakers.Remove(voice)
if(CONFIG_GET(string/tts_tram_announcer_override))
tram_voice = CONFIG_GET(string/tts_tram_announcer_override)
else
tram_voice = pick(available_speakers)
if(CONFIG_GET(string/tts_computer_voice_override))
computer_voice = CONFIG_GET(string/tts_computer_voice_override)
else
computer_voice = pick(available_speakers)
var/datum/http_request/request_pitch = new()
var/list/headers_pitch = list()
headers_pitch["Authorization"] = CONFIG_GET(string/tts_http_token)
@@ -98,32 +119,58 @@ SUBSYSTEM_DEF(tts)
return SS_INIT_FAILURE
return SS_INIT_SUCCESS
/datum/controller/subsystem/tts/proc/play_tts(target, list/listeners, sound/audio, sound/audio_blips, datum/language/language, range = 7, volume_offset = 0)
var/turf/turf_source = get_turf(target)
if(!turf_source)
/datum/controller/subsystem/tts/proc/play_tts(datum/weakref/target, list/listeners, sound/audio, sound/audio_blips, datum/language/language, range = 7, volume_offset = 0, ignore_observers = FALSE, source_speaker = null, audio_length = 10 SECONDS, audio_length_blips = 10 SECONDS, volume_preference = /datum/preference/numeric/volume/sound_tts_volume, volume_signal = COMSIG_MOB_TTS_VOLUME_PREFERENCE_APPLIED)
var/atom/actual_target = target?.resolve()
var/turf/turf_source
if(actual_target)
turf_source = get_turf(actual_target)
if(!turf_source && actual_target) // if there's a target, we better have a turf
return
var/channel = SSsounds.random_available_channel()
for(var/atom/movable/hearer in listeners | SSmobs.dead_players_by_zlevel[turf_source.z])//observers always hear through walls
var/mob/listening_mob = hearer.get_listening_mob()
if(isnull(listening_mob))
var/list/final_listeners = listeners
if(!ignore_observers && actual_target)
final_listeners += SSmobs.dead_players_by_zlevel[turf_source.z] //observers always hear through walls
var/list/blips_hearers = list()
var/list/voice_hearers = list()
for(var/hearer in final_listeners)
if(isnull(hearer))
continue
if(QDELING(listening_mob))
var/atom/movable/hearer_atom
if(isweakref(hearer))
var/datum/weakref/weakref = hearer
hearer_atom = weakref?.resolve()
else
hearer_atom = hearer
if(!hearer_atom || QDELING(hearer_atom))
stack_trace("TTS tried to play a sound to a deleted mob.")
continue
if(!ismob(hearer_atom))
continue
var/mob/listening_mob = hearer_atom.get_listening_mob()
/// volume modifier for TTS as set by the player in preferences.
var/volume_modifier = listening_mob.client?.prefs.read_preference(/datum/preference/numeric/volume/sound_tts_volume)/100
var/volume_modifier = listening_mob.client?.prefs.read_preference(volume_preference)/100
var/tts_pref = listening_mob.client?.prefs.read_preference(/datum/preference/choiced/sound_tts)
var/hear_self_pref = listening_mob.client?.prefs.read_preference(/datum/preference/toggle/sound_tts_hear_self_radio)
if(volume_modifier == 0 || (tts_pref == TTS_SOUND_OFF))
continue
if(listening_mob == source_speaker && !hear_self_pref)
continue // don't hear your own radio tts if you got it turned off
var/sound_volume = ((hearer == target)? 60 : 85) + volume_offset
var/sound_volume = ((listening_mob == actual_target)? 60 : 85) + volume_offset
sound_volume = sound_volume*volume_modifier
var/datum/language_holder/holder = listening_mob.get_language_holder()
var/audio_to_use = (tts_pref == TTS_SOUND_BLIPS) ? audio_blips : audio
var/sound/audio_to_use = (tts_pref == TTS_SOUND_BLIPS) ? audio_blips : audio
if(!holder.has_language(language))
continue
if(get_dist(hearer, turf_source) <= range)
if (tts_pref == TTS_SOUND_OFF)
continue
else
audio_to_use = audio_blips
if(actual_target && get_dist(listening_mob, turf_source) <= range)
if(tts_pref == TTS_SOUND_BLIPS || !holder.has_language(language))
blips_hearers += listening_mob
else
voice_hearers += listening_mob
listening_mob.playsound_local(
turf_source,
vol = sound_volume,
@@ -136,6 +183,45 @@ SUBSYSTEM_DEF(tts)
distance_multiplier = 1,
use_reverb = TRUE
)
else if(!actual_target)
listening_mob.playsound_local(
null, //play it locally
vol = sound_volume,
falloff_exponent = SOUND_FALLOFF_EXPONENT,
channel = channel,
pressure_affected = FALSE,
sound_to_use = audio_to_use,
max_distance = SOUND_RANGE,
falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE,
distance_multiplier = 1,
use_reverb = TRUE
)
if(actual_target)
new /datum/threed_sound(
new_parent = actual_target,
new_sound = audio,
current_listeners = voice_hearers,
can_add_new_listeners = FALSE,
volume = 85 + volume_offset,
sound_range = SOUND_RANGE,
sound_length = audio_length,
channel = channel,
preference_volume = volume_preference,
preference_signal = volume_signal
)
new /datum/threed_sound(
new_parent = actual_target,
new_sound = audio_blips,
current_listeners = blips_hearers,
can_add_new_listeners = FALSE,
volume = 85 + volume_offset,
sound_range = SOUND_RANGE,
sound_length = audio_length_blips,
channel = channel,
preference_volume = volume_preference,
preference_signal = volume_signal
)
// Need to wait for all HTTP requests to complete here because of a rustg crash bug that causes crashes when dd restarts whilst HTTP requests are ongoing.
/datum/controller/subsystem/tts/Shutdown()
@@ -143,7 +229,9 @@ SUBSYSTEM_DEF(tts)
for(var/datum/tts_request/data in in_process_http_messages)
var/datum/http_request/request = data.request
var/datum/http_request/request_blips = data.request_blips
UNTIL(request.is_complete() && request_blips.is_complete())
var/datum/http_request/request_radio = data.request_radio
var/datum/http_request/request_blips_radio = data.request_blips_radio
UNTIL(request.is_complete() && request_blips.is_complete() && request_radio.is_complete() && request_blips_radio.is_complete())
#define SHIFT_DATA_ARRAY(tts_message_queue, target, data) \
popleft(##data); \
@@ -178,27 +266,51 @@ SUBSYSTEM_DEF(tts)
if(!current_request.requests_completed())
continue
var/datum/http_response/response = current_request.get_primary_response()
in_process_http_messages -= current_request
average_tts_messages_time = MC_AVERAGE(average_tts_messages_time, world.time - current_request.start_time)
var/identifier = current_request.identifier
var/datum/http_response/normal_response = current_request.request.into_response()
var/datum/http_response/blips_response = current_request.request_blips.into_response()
var/datum/http_response/radio_response = current_request.request_radio.into_response()
var/datum/http_response/radio_blips_response = current_request.request_blips_radio.into_response()
var/datum/http_response/radio_gibberish_response = current_request.request_radio_gibberish.into_response()
if(current_request.requests_errored())
if(queued_radio_messages[identifier])
queued_radio_messages.Remove(identifier)
if(queued_radio_messages_compression[identifier])
queued_radio_messages_compression.Remove(identifier)
current_request.timed_out = TRUE
var/datum/http_response/normal_response = current_request.request.into_response()
var/datum/http_response/blips_response = current_request.request_blips.into_response()
log_tts("TTS HTTP request errored | Normal: [normal_response.error] | Blips: [blips_response.error]", list(
log_tts("TTS HTTP request errored | Normal: [normal_response.error] | Blips: [blips_response.error] | Radio: [radio_response.error] | Radio Blips: [radio_blips_response.error] | Radio Gibberish [radio_gibberish_response.error]", list(
"normal" = normal_response,
"blips" = blips_response
"blips" = blips_response,
"radio" = radio_response,
"radio_blips" = radio_blips_response,
"radio_gibberish" = radio_gibberish_response
))
continue
current_request.audio_length = text2num(response.headers["audio-length"]) * 10
if(!current_request.audio_length)
current_request.audio_length = 0
if(length(normal_response.headers) && normal_response.headers.Find("audio-length"))
current_request.audio_length = text2num(normal_response.headers["audio-length"]) * 10 || 0
if(length(blips_response.headers) && blips_response.headers.Find("audio-length"))
current_request.audio_length_blips = text2num(blips_response.headers["audio-length"]) * 10 || 0
if(length(radio_response.headers) && radio_response.headers.Find("audio-length"))
current_request.audio_length_radio = text2num(radio_response.headers["audio-length"]) * 10 || 0
if(length(radio_blips_response.headers) && radio_blips_response.headers.Find("audio-length"))
current_request.audio_length_blips_radio = text2num(radio_blips_response.headers["audio-length"]) * 10 || 0
if(length(radio_gibberish_response.headers) && radio_gibberish_response.headers.Find("audio-length"))
current_request.audio_length_radio_gibberish = text2num(radio_gibberish_response.headers["audio-length"]) * 10
if(!current_request.audio_length_radio_gibberish)
current_request.audio_length_radio_gibberish = 0
current_request.audio_file = "tmp/tts/[identifier].ogg"
current_request.audio_file_blips = "tmp/tts/[identifier]_blips.ogg" // We aren't as concerned about the audio length for blips as we are with actual speech
current_request.audio_file_radio = "tmp/tts/[identifier]_radio.ogg"
current_request.audio_file_blips_radio = "tmp/tts/[identifier]_blips_radio.ogg"
current_request.audio_file_radio_gibberish = "tmp/tts/[identifier]_radio_gibberish.ogg"
// Don't need the request anymore so we can deallocate it
current_request.request = null
current_request.request_blips = null
current_request.request_radio = null
current_request.request_blips_radio = null
current_request.request_radio_gibberish = null
if(MC_TICK_CHECK)
return
@@ -232,6 +344,10 @@ SUBSYSTEM_DEF(tts)
// If current_target.timed_out is set to TRUE, it means the request failed in some way
// and there is no TTS audio file to play.
if(timeout < world.time || current_target.timed_out)
if(queued_radio_messages[current_target.identifier])
queued_radio_messages.Remove(current_target.identifier)
if(queued_radio_messages_compression[current_target.identifier])
queued_radio_messages_compression.Remove(current_target.identifier)
SHIFT_DATA_ARRAY(queued_tts_messages, tts_target, data)
continue
@@ -243,7 +359,7 @@ SUBSYSTEM_DEF(tts)
var/sound/audio_file
var/sound/audio_file_blips
if(current_target.local)
if(current_target.use_blips)
if(current_target.use_blips || current_target.force_blips)
audio_file_blips = new(current_target.audio_file_blips)
SEND_SOUND(current_target.target, audio_file_blips)
else
@@ -253,7 +369,8 @@ SUBSYSTEM_DEF(tts)
else if(current_target.when_to_play < world.time)
audio_file = new(current_target.audio_file)
audio_file_blips = new(current_target.audio_file_blips)
play_tts(tts_target, current_target.listeners, audio_file, audio_file_blips, current_target.language, current_target.message_range, current_target.volume_offset)
play_tts(tts_target, current_target.listeners, audio_file, audio_file_blips, current_target.language, current_target.message_range, current_target.volume_offset, FALSE, null, current_target.audio_length, current_target.audio_length_blips)
completed_tts_messages[current_target.identifier] = list("ref" = current_target, "expiry_time" = world.time + 300)
if(length(data) != 1)
var/datum/tts_request/next_target = data[2]
next_target.when_to_play = world.time + current_target.audio_length
@@ -266,10 +383,47 @@ SUBSYSTEM_DEF(tts)
queued_tts_messages[tts_target] += arbritrary_delay
SHIFT_DATA_ARRAY(queued_tts_messages, tts_target, data)
for(var/identifier in queued_radio_messages)
if(MC_TICK_CHECK)
return
if(completed_tts_messages[identifier])
var/list/all_radios = queued_radio_messages[identifier]
for(var/radio in all_radios)
var/list/hearers = all_radios[radio]
if(!istext(radio) && isweakref(radio))
var/datum/weakref/weakref = radio
var/obj/radio_obj = weakref?.resolve()
if(radio_obj && QDELETED(radio_obj))
queued_radio_messages[identifier].Remove(radio)
queued_radio_messages_compression[identifier].Remove(radio)
continue
var/datum/tts_request/tts_request = completed_tts_messages[identifier]["ref"]
var/sound/audio_file
var/sound/audio_file_blips
if(queued_radio_messages_compression[identifier] > 30)
audio_file = new(tts_request.audio_file_radio_gibberish)
else
audio_file = new(tts_request.audio_file_radio)
audio_file_blips = new(tts_request.audio_file_blips_radio)
play_tts(radio == TTS_GHOST_RADIO ? null : radio, hearers, audio_file, audio_file_blips, tts_request.language, INFINITY, tts_request.volume_offset, ignore_observers = TRUE, source_speaker = tts_request.target, audio_length = tts_request.audio_length_radio, audio_length_blips = tts_request.audio_length_blips_radio, volume_preference = /datum/preference/numeric/volume/sound_tts_radio_volume, volume_signal = COMSIG_MOB_TTS_RADIO_VOLUME_PREFERENCE_APPLIED)
queued_radio_messages.Remove(identifier)
completed_tts_messages.Remove(identifier)
queued_radio_messages_compression.Remove(identifier)
for(var/identifier, request in completed_tts_messages)
if(MC_TICK_CHECK)
return
if (completed_tts_messages[identifier]["expiry_time"] >= world.time + 300)
completed_tts_messages[identifier]["ref"] = null
completed_tts_messages[identifier] = null
completed_tts_messages.Remove(identifier)
queued_radio_messages.Remove(identifier)
queued_radio_messages_compression.Remove(identifier)
#undef TTS_ARBRITRARY_DELAY
/datum/controller/subsystem/tts/proc/queue_tts_message(datum/target, message, datum/language/language, speaker, filter, list/listeners, local = FALSE, message_range = 7, volume_offset = 0, pitch = 0, special_filters = "")
/datum/controller/subsystem/tts/proc/queue_tts_message(datum/target, message, datum/language/language, speaker, filter, list/listeners, local = FALSE, message_range = 7, volume_offset = 0, pitch = 0, special_filters = "", blip_base = "male", blip_number = "1", force_blips = FALSE, identifier = "invalid")
if(!tts_enabled)
return
// TGS updates can clear out the tmp folder, so we need to create the folder again if it no longer exists.
@@ -283,25 +437,34 @@ SUBSYSTEM_DEF(tts)
return
var/shell_scrubbed_input = tts_speech_filter(message)
shell_scrubbed_input = copytext(shell_scrubbed_input, 1, 300)
var/identifier = "[sha1(speaker + filter + num2text(pitch) + special_filters + shell_scrubbed_input)].[world.time]"
if(!(speaker in available_speakers))
return
var/list/listener_weakrefs = list()
for(var/listener in listeners)
listener_weakrefs += WEAKREF(listener)
var/list/headers = list()
headers["Content-Type"] = "application/json"
headers["Authorization"] = CONFIG_GET(string/tts_http_token)
var/datum/http_request/request = new()
var/datum/http_request/request_blips = new()
var/datum/http_request/request_radio = new()
var/datum/http_request/request_blips_radio = new()
var/datum/http_request/request_radio_gibberish = new()
var/file_name = "tmp/tts/[identifier].ogg"
var/file_name_blips = "tmp/tts/[identifier]_blips.ogg"
var/file_name_radio = "tmp/tts/[identifier]_radio.ogg"
var/file_name_blips_radio = "tmp/tts/[identifier]_blips_radio.ogg"
var/file_name_radio_gibberish = "tmp/tts/[identifier]_radio_gibberish.ogg"
request.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/tts_http_url)]/tts?voice=[speaker]&identifier=[identifier]&filter=[tts_filter_encode(filter, speaker, pitch)]&pitch=[pitch]&special_filters=[url_encode(special_filters)]", json_encode(list("text" = shell_scrubbed_input)), headers, file_name, timeout_seconds = CONFIG_GET(number/tts_http_timeout_seconds))
request_blips.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/tts_http_url)]/tts-blips?voice=[speaker]&identifier=[identifier]&filter=[tts_filter_encode(filter, speaker, pitch, blips = TRUE)]&pitch=[pitch]&special_filters=[url_encode(special_filters)]", json_encode(list("text" = shell_scrubbed_input)), headers, file_name_blips, timeout_seconds = CONFIG_GET(number/tts_http_timeout_seconds))
var/datum/tts_request/current_request = new /datum/tts_request(identifier, request, request_blips, shell_scrubbed_input, target, local, language, message_range, volume_offset, listeners, pitch)
var/list/player_queued_tts_messages = queued_tts_messages[target]
request_blips.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/tts_http_url)]/tts-blips?voice=[speaker]&identifier=[identifier]&filter=[tts_filter_encode(filter, speaker, pitch, blips = TRUE)]&pitch=[pitch]&special_filters=[url_encode(special_filters)]&blip_base=[blip_base]&blip_number=[blip_number]", json_encode(list("text" = shell_scrubbed_input)), headers, file_name_blips, timeout_seconds = CONFIG_GET(number/tts_http_timeout_seconds))
request_radio.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/tts_http_url)]/tts-radio?voice=[speaker]&identifier=[identifier]&filter=[tts_filter_encode(filter, speaker, pitch)]&pitch=[pitch]&special_filters=[url_encode(special_filters)]", json_encode(list("text" = shell_scrubbed_input)), headers, file_name_radio, timeout_seconds = CONFIG_GET(number/tts_http_timeout_seconds))
request_blips_radio.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/tts_http_url)]/tts-blips-radio?voice=[speaker]&identifier=[identifier]&filter=[tts_filter_encode(filter, speaker, pitch, blips = TRUE)]&pitch=[pitch]&special_filters=[url_encode(special_filters)]&blip_base=[blip_base]&blip_number=[blip_number]", json_encode(list("text" = shell_scrubbed_input)), headers, file_name_blips_radio, timeout_seconds = CONFIG_GET(number/tts_http_timeout_seconds))
request_radio_gibberish.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/tts_http_url)]/tts-radio?voice=[speaker]&identifier=[identifier]&filter=[tts_filter_encode(filter, speaker, pitch)]&pitch=[pitch]&special_filters=[url_encode(special_filters)]", json_encode(list("raw_text" = shell_scrubbed_input, "gibberish_text" = shell_scrubbed_input)), headers, file_name_radio_gibberish, timeout_seconds = CONFIG_GET(number/tts_http_timeout_seconds))
var/datum/tts_request/current_request = new /datum/tts_request(identifier, request, request_blips, request_radio, request_blips_radio, request_radio_gibberish, shell_scrubbed_input, target, local, language, message_range, volume_offset, listener_weakrefs, pitch, force_blips)
var/list/player_queued_tts_messages = queued_tts_messages[WEAKREF(target)]
if(!player_queued_tts_messages)
player_queued_tts_messages = list()
queued_tts_messages[target] = player_queued_tts_messages
queued_tts_messages[WEAKREF(target)] = player_queued_tts_messages
player_queued_tts_messages += current_request
if(length(in_process_http_messages) < max_concurrent_requests)
current_request.start_requests()
@@ -338,6 +501,12 @@ SUBSYSTEM_DEF(tts)
var/datum/http_request/request
/// The HTTP request of this message for blips
var/datum/http_request/request_blips
/// The HTTP request of this message's radio version
var/datum/http_request/request_radio
/// The HTTP request of this blip message's radio version
var/datum/http_request/request_blips_radio
/// The HTTP request of this message's radio gibberish version
var/datum/http_request/request_radio_gibberish
/// The language to limit this TTS message to
var/datum/language/language
/// The message itself
@@ -357,8 +526,18 @@ SUBSYSTEM_DEF(tts)
var/sound/audio_file
/// The blips audio file of this tts request.
var/sound/audio_file_blips
/// The radio audio file of this tts request.
var/sound/audio_file_radio
/// The blips radio audio file of this tts request.
var/sound/audio_file_blips_radio
/// The gibberish radio audio file of this tts request.
var/sound/audio_file_radio_gibberish
/// The audio length of this tts request.
var/audio_length
var/audio_length_blips
var/audio_length_radio
var/audio_length_blips_radio
var/audio_length_radio_gibberish
/// When the audio file should play at the minimum
var/when_to_play = 0
/// Whether this request was timed out or not
@@ -367,13 +546,18 @@ SUBSYSTEM_DEF(tts)
var/use_blips = FALSE
/// What's the pitch adjustment?
var/pitch = 0
/// Should we force play blips? Used for the blips preview.
var/force_blips = FALSE
/datum/tts_request/New(identifier, datum/http_request/request, datum/http_request/request_blips, message, target, local, datum/language/language, message_range, volume_offset, list/listeners, pitch)
/datum/tts_request/New(identifier, datum/http_request/request, datum/http_request/request_blips, datum/http_request/request_radio, datum/http_request/request_blips_radio, datum/http_request/request_radio_gibberish, message, target, local, datum/language/language, message_range, volume_offset, list/listeners, pitch, force_blips = FALSE)
. = ..()
src.identifier = identifier
src.request = request
src.request_blips = request_blips
src.request_radio = request_radio
src.request_blips_radio = request_blips_radio
src.request_radio_gibberish = request_radio_gibberish
src.message = message
src.language = language
src.target = target
@@ -382,6 +566,7 @@ SUBSYSTEM_DEF(tts)
src.volume_offset = volume_offset
src.listeners = listeners
src.pitch = pitch
src.force_blips = force_blips
start_time = world.time
/datum/tts_request/proc/start_requests()
@@ -391,17 +576,20 @@ SUBSYSTEM_DEF(tts)
else if(istype(target, /mob))
use_blips = (target.client?.prefs.read_preference(/datum/preference/choiced/sound_tts) == TTS_SOUND_BLIPS)
if(local)
if(use_blips)
if(use_blips || force_blips)
request_blips.begin_async()
else
request.begin_async()
else
request.begin_async()
request_blips.begin_async()
request_radio.begin_async()
request_blips_radio.begin_async()
request_radio_gibberish.begin_async()
/datum/tts_request/proc/get_primary_request()
if(local)
if(use_blips)
if(use_blips || force_blips)
return request_blips
else
return request
@@ -410,7 +598,7 @@ SUBSYSTEM_DEF(tts)
/datum/tts_request/proc/get_primary_response()
if(local)
if(use_blips)
if(use_blips || force_blips)
return request_blips.into_response()
else
return request.into_response()
@@ -420,7 +608,7 @@ SUBSYSTEM_DEF(tts)
/datum/tts_request/proc/requests_errored()
if(local)
var/datum/http_response/response
if(use_blips)
if(use_blips || force_blips)
response = request_blips.into_response()
else
response = request.into_response()
@@ -428,15 +616,46 @@ SUBSYSTEM_DEF(tts)
else
var/datum/http_response/response = request.into_response()
var/datum/http_response/response_blips = request_blips.into_response()
return response.errored || response_blips.errored
var/datum/http_response/response_radio = request_radio.into_response()
var/datum/http_response/response_blips_radio = request_blips_radio.into_response()
var/datum/http_response/response_radio_gibberish = request_radio_gibberish.into_response()
return response.errored || response_blips.errored || response_radio.errored || response_blips_radio.errored || response_radio_gibberish.errored
/datum/tts_request/proc/requests_completed()
if(local)
if(use_blips)
if(use_blips || force_blips)
return request_blips.is_complete()
else
return request.is_complete()
else
return request.is_complete() && request_blips.is_complete()
return request.is_complete() && request_blips.is_complete() && request_blips_radio.is_complete() && request_radio.is_complete() && request_radio_gibberish.is_complete()
/proc/filter_tts_listeners(list/listeners, radio_frequency = null)
if(!SStts.tts_enabled || !listeners)
return
if(isweakref(listeners))
listeners = list(listeners)
var/list/filtered_listeners = list()
for(var/datum/weakref/listener as anything in listeners)
if(!isweakref(listener))
continue
var/mob/possible_listener = listener?.resolve()
if(!ismob(possible_listener) || !possible_listener.client)
continue
var/tts_pref = possible_listener.client?.prefs.read_preference(/datum/preference/choiced/sound_tts)
var/radio_tts_pref = possible_listener.client?.prefs.read_preference(/datum/preference/choiced/sound_tts_radio)
if(tts_pref == TTS_SOUND_OFF)
continue
if(isliving(possible_listener) && (possible_listener.stat >= UNCONSCIOUS || HAS_TRAIT(possible_listener, TRAIT_DEAF)))
continue
if(radio_tts_pref == TTS_SOUND_NO_RADIO)
continue
if(radio_tts_pref == TTS_SOUND_DEPARTMENTAL_RADIO && radio_frequency == FREQ_COMMON) // don't give them the full common firehose if they turned it off
continue
filtered_listeners += listener
return filtered_listeners
#undef SHIFT_DATA_ARRAY
+48 -16
View File
@@ -8,13 +8,26 @@ SUBSYSTEM_DEF(weather)
wait = 10
runlevels = RUNLEVEL_GAME
var/list/processing = list()
/// Z levels on which weather can occur -> weather that can occur -> probability of said weather occuring
var/list/eligible_zlevels = list()
var/list/next_hit_by_zlevel = list() //Used by barometers to know when the next storm is coming
/// Used by barometers to know when the next storm is coming
var/list/next_hit_by_zlevel = list()
/// Alist of all particle holders per Z-stack offset for particle weather to be shown to clients
var/alist/particle_holders = alist()
/// List of all RENDER_PLANE_PARTICLE_WEATHER and RENDER_PLANE_EMISSIVE_PARTICLE_WEATHER planes
var/list/particle_planemasters = list()
/datum/controller/subsystem/weather/fire(resumed = FALSE)
// process active weather
for(var/datum/weather/weather_event as anything in processing)
if(!length(weather_event.subsystem_tasks) || weather_event.stage != MAIN_STAGE)
if(!length(weather_event.subsystem_tasks))
continue
if(istype(weather_event, /datum/weather/particle))
var/datum/weather/particle/particle_event = weather_event
particle_event.process_particles()
if(weather_event.stage != MAIN_STAGE)
continue
if(weather_event.subsystem_tasks[weather_event.task_index] == SSWEATHER_MOBS)
@@ -69,21 +82,42 @@ SUBSYSTEM_DEF(weather)
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))
var/datum/weather/W = V
var/probability = initial(W.probability)
var/target_trait = initial(W.target_trait)
for(var/datum/weather/weather as anything in valid_subtypesof(/datum/weather))
var/probability = initial(weather.probability)
var/target_trait = initial(weather.target_trait)
// any weather with a probability set may occur at random
if (probability)
for(var/z in SSmapping.levels_by_trait(target_trait))
LAZYINITLIST(eligible_zlevels["[z]"])
eligible_zlevels["[z]"][W] = probability
eligible_zlevels["[z]"][weather] = probability
return SS_INIT_SUCCESS
/datum/controller/subsystem/weather/proc/add_weather_objects(list/new_holders, z_level)
for (var/offset in 1 to length(new_holders))
var/list/holder_list = new_holders[offset]
if (isnull(particle_holders[offset]))
particle_holders[offset] = list()
particle_holders[offset] += holder_list
// We add it to vis_contents of planemasters rather than client screen as planemasters already
// manage their own visibility based on owner's z level
for (var/atom/movable/screen/plane_master/plane_master as anything in particle_planemasters)
for (var/obj/effect/abstract/weather_holder/holder as anything in holder_list)
if (holder.plane == plane_master.plane)
plane_master.vis_contents |= holder
/datum/controller/subsystem/weather/proc/remove_weather_objects(list/old_holders)
for (var/offset in 1 to length(old_holders))
var/list/holder_list = old_holders[offset]
particle_holders[offset] -= holder_list
for (var/atom/movable/screen/plane_master/plane_master as anything in particle_planemasters)
plane_master.vis_contents -= holder_list
/datum/controller/subsystem/weather/proc/update_z_level(datum/space_level/level)
var/z = level.z_value
for(var/datum/weather/weather as anything in subtypesof(/datum/weather))
for(var/datum/weather/weather as anything in valid_subtypesof(/datum/weather))
var/probability = initial(weather.probability)
var/target_trait = initial(weather.target_trait)
if(probability && level.traits[target_trait])
@@ -92,10 +126,9 @@ SUBSYSTEM_DEF(weather)
/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
if (initial(W.name) == weather_datum_type)
weather_datum_type = V
for (var/datum/weather/weather as anything in valid_subtypesof(/datum/weather))
if (initial(weather.name) == weather_datum_type)
weather_datum_type = weather
break
if (!ispath(weather_datum_type, /datum/weather))
CRASH("run_weather called with invalid weather_datum_type: [weather_datum_type || "null"]")
@@ -107,10 +140,9 @@ 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, weather_data)
W.telegraph(weather_data)
return W
var/datum/weather/weather = new weather_datum_type(z_levels, weather_data)
weather.telegraph(weather_data)
return weather
/datum/controller/subsystem/weather/proc/make_eligible(z, possible_weather)
eligible_zlevels[z] = possible_weather