Files
Bubberstation/code/datums/looping_sounds/_looping_sound.dm
LemonInTheDark ae5a4f955d Pulls apart the vestiges of components still hanging onto signals (#75914)
## About The Pull Request

Signals were initially only usable with component listeners, which while
no longer the case has lead to outdated documentation, names, and a
similar location in code.

This pr pulls the two apart. Partially because mso thinks we should, but
also because they really aren't directly linked anymore, and having them
in this midstate just confuses people.

[Renames comp_lookup to listen_lookup, since that's what it
does](102b79694f)

[Moves signal procs over to their own
file](33d07d01fd)

[Renames the PREQDELETING and QDELETING comsigs to drop the parent bit
since they can hook to more then just comps
now](335ea4ad08)

[Does something similar to the attackby comsigs (PARENT ->
ATOM)](210e57051d)

[And finally passes over the examine
signals](65917658fb)

## Why It's Good For The Game

Code makes more sense, things are better teased apart, s just good imo

## Changelog
🆑
refactor: Pulled apart the last vestiges of names/docs directly linking
signals to components
/🆑
2023-06-09 06:14:31 +00:00

249 lines
8.0 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
var/audio_index = 1
// 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)