mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 05:00:55 +01:00
Merge branch 'master' of github.com:tgstation/tgstation into upstream-2026-06-23
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user