Files
Fluffy 1b40dbce82 Updated the sound system (#18724)
* sdaf

* sdaf

* sdfa

* sadf

* sfda

* gfd

* reduce thrusters volume

* sdafsadsdaf

* sdfa

* Reduced some sound ranges and made some/more not ignore walls for loops

* health analyzers too

* ivdrip adjustment

* most tools now use play_tool_sound to have the sound played, reduced range for it
2024-03-25 20:40:36 +00:00

250 lines
8.1 KiB
Plaintext

/**
* A datum for sounds that need to loop, with a high amount of configurability.
*/
/datum/looping_sound
/// (list or soundfile) Since this can be either a list or a single soundfile you can have random sounds. May contain further lists but must contain a soundfile at the end.
var/mid_sounds
/// The length of time to wait between playing mid_sounds.
var/mid_length
/// Amount of time to add/take away from the mid length, randomly
var/mid_length_vary = 0
/// If we should always play each sound once per loop of all sounds. Weights here only really effect order, and could be disgarded
var/each_once = FALSE
/// Whether if the sounds should be played in order or not. Defaults to FALSE.
var/in_order = FALSE
/// Override for volume of start sound.
var/start_volume
/// (soundfile) Played before starting the mid_sounds loop.
var/start_sound
/// How long to wait before starting the main loop after playing start_sound.
var/start_length
/// Override for volume of end sound.
var/end_volume
/// (soundfile) The sound played after the main loop has concluded.
var/end_sound
/// Chance per loop to play a mid_sound.
var/chance
/// Sound output volume.
var/volume = 100
/// Whether or not the sounds will vary in pitch when played.
var/vary = FALSE
/// The max amount of loops to run for.
var/max_loops
/// The extra range of the sound in tiles, defaults to 0.
var/extra_range = 0
/// How much the sound will be affected by falloff per tile.
var/falloff_exponent
/// The falloff distance of the sound,
var/falloff_distance
/// Are the sounds affected by pressure? Defaults to TRUE.
var/pressure_affected = TRUE
/// Are the sounds subject to reverb? Defaults to TRUE.
var/use_reverb = TRUE
/// Are we ignoring walls? Defaults to TRUE.
var/ignore_walls = TRUE
// State stuff
/// The source of the sound, or the recipient of the sound.
var/atom/parent
/// The ID of the timer that's used to loop the sounds.
var/timer_id
/// Has the looping started yet?
var/loop_started = FALSE
/// If we're using cut_mid, this is the list we cut from
var/list/cut_list
///The index of the current song we're playing in the mid_sounds list, only used if in_order is used
///This is immediately set to 1, so we start the index at 0
var/audio_index = 0
// Args
/// Do we skip the starting sounds?
var/skip_starting_sounds = FALSE
/// If true, plays directly to provided atoms instead of from them.
var/direct
/// Sound channel to play on, random if not provided
var/sound_channel
/datum/looping_sound/New(_parent, start_immediately = FALSE, _direct = FALSE, _skip_starting_sounds = FALSE)
if(!mid_sounds)
WARNING("A looping sound datum was created without sounds to play.")
return
set_parent(_parent)
direct = _direct
skip_starting_sounds = _skip_starting_sounds
if(start_immediately)
start()
/datum/looping_sound/Destroy()
stop(TRUE)
return ..()
/**
* The proc to actually kickstart the whole sound sequence. This is what you should call to start the `looping_sound`.
*
* Arguments:
* * on_behalf_of - The new object to set as a parent.
*/
/datum/looping_sound/proc/start(on_behalf_of)
if(on_behalf_of)
set_parent(on_behalf_of)
if(timer_id)
return
on_start()
/**
* The proc to call to stop the sound loop.
*
* Arguments:
* * null_parent - Whether or not we should set the parent to null (useful when destroying the `looping_sound` itself). Defaults to FALSE.
*/
/datum/looping_sound/proc/stop(null_parent = FALSE)
stop_current()
if(null_parent)
set_parent(null)
if(!timer_id)
return
on_stop()
deltimer(timer_id, SSsound_loops)
timer_id = null
loop_started = FALSE
/// The proc that handles starting the actual core sound loop.
/datum/looping_sound/proc/start_sound_loop()
loop_started = TRUE
sound_loop()
timer_id = addtimer(CALLBACK(src, PROC_REF(sound_loop), world.time), mid_length, TIMER_CLIENT_TIME | TIMER_STOPPABLE | TIMER_LOOP | TIMER_DELETE_ME, SSsound_loops)
/**
* A simple proc handling the looping of the sound itself.
*
* Arguments:
* * start_time - The time at which the `mid_sounds` started being played (so we know when to stop looping).
*/
/datum/looping_sound/proc/sound_loop(start_time)
if(max_loops && world.time >= start_time + mid_length * max_loops)
stop()
return
// If we have a timer, we're varying mid length, and this is happening while we're runnin mid_sounds
if(timer_id && mid_length_vary && start_time)
updatetimedelay(timer_id, mid_length + rand(-mid_length_vary, mid_length_vary), timer_subsystem = SSsound_loops)
if(!chance || prob(chance))
play(get_sound())
/**
* Applies a new mid length to the sound
*/
/datum/looping_sound/proc/set_mid_length(new_mid)
mid_length = new_mid
if(!timer_id)
return
updatetimedelay(timer_id, mid_length + rand(-mid_length_vary, mid_length_vary), timer_subsystem = SSsound_loops)
/**
* The proc that handles actually playing the sound.
*
* Arguments:
* * soundfile - The soundfile we want to play.
* * volume_override - The volume we want to play the sound at, overriding the `volume` variable.
*/
/datum/looping_sound/proc/play(soundfile, volume_override)
var/sound/sound_to_play = sound(soundfile)
if(direct)
sound_to_play.channel = sound_channel || SSsounds.random_available_channel()
sound_to_play.volume = volume_override || volume //Use volume as fallback if theres no override
SEND_SOUND(parent, sound_to_play)
else
playsound(
parent,
sound_to_play,
volume,
vary,
extra_range,
falloff_exponent = falloff_exponent,
pressure_affected = pressure_affected,
ignore_walls = ignore_walls,
falloff_distance = falloff_distance,
use_reverb = use_reverb
)
/// Returns the sound we should now be playing.
/datum/looping_sound/proc/get_sound(_mid_sounds)
var/list/play_from = _mid_sounds || mid_sounds
if(!each_once)
. = play_from
while(!isfile(.) && !isnull(.))
. = pick_weight(.)
return .
if(in_order)
. = play_from
audio_index++
if(audio_index > length(play_from))
audio_index = 1
return .[audio_index]
if(!length(cut_list))
cut_list = shuffle(play_from.Copy())
var/list/tree = list()
. = cut_list
while(!isfile(.) && !isnull(.))
// Tree is a list of lists containign files
// If an entry in the tree goes to 0 length, we cut it from the list
tree += list(.)
. = pick_weight(.)
if(!isfile(.))
return
// Remove the sound file
tree[length(tree)] -= .
// Walk the tree bottom up, remove any lists that are empty
// Don't do anything for the topmost list, cause we do not care
for(var/i in length(tree) to 2 step -1)
var/list/branch = tree[i]
if(length(branch))
break
tree[i - 1] -= list(branch) // Remove the empty list
return .
/// A proc that's there to handle delaying the main sounds if there's a start_sound, and simply starting the sound loop in general.
/datum/looping_sound/proc/on_start()
var/start_wait = 0
if(start_sound && !skip_starting_sounds)
play(start_sound, start_volume)
start_wait = start_length
timer_id = addtimer(CALLBACK(src, PROC_REF(start_sound_loop)), start_wait, TIMER_CLIENT_TIME | TIMER_DELETE_ME | TIMER_STOPPABLE, SSsound_loops)
/// Stops sound playing on current channel, if specified
/datum/looping_sound/proc/stop_current()
if(!sound_channel || !ismob(parent))
return
var/mob/mob_parent = parent
mob_parent.stop_sound_channel(sound_channel)
/// Simple proc that's executed when the looping sound is stopped, so that the `end_sound` can be played, if there's one.
/datum/looping_sound/proc/on_stop()
if(end_sound && loop_started)
play(end_sound, end_volume)
/// A simple proc to change who our parent is set to, also handling registering and unregistering the QDELETING signals on the parent.
/datum/looping_sound/proc/set_parent(new_parent)
if(parent)
UnregisterSignal(parent, COMSIG_QDELETING)
parent = new_parent
if(parent)
RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(handle_parent_del))
/// A simple proc that lets us know whether the sounds are currently active or not.
/datum/looping_sound/proc/is_active()
return !!timer_id
/// A simple proc to handle the deletion of the parent, so that it does not force it to hard-delete.
/datum/looping_sound/proc/handle_parent_del(datum/source)
SIGNAL_HANDLER
set_parent(null)