mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-18 10:37:54 +01:00
## About The Pull Request `remove_listener` would fail on logout so instead I figure we could track the tokens on the mob, rather than the client Functionally not much is changed - but if you go from ghost to mob it'll remove the token from the ghost and adds the token to the new mob, so it should fix the issue There also doesn't need to be any code in `/mob/destroy` because we listen for `listener_deleted` and remove listeners on the token itself
308 lines
11 KiB
Plaintext
308 lines
11 KiB
Plaintext
// Sound tokens, a datumized handler for spatial sound.
|
|
// Uses the spatial grid to track clients in range and add them as listeners
|
|
// Updated by the SSsound_tokens subsystem every tick when requested by client so that if the source or listener moves, the sound updates accordingly.
|
|
/datum/sound_token
|
|
/// The atom playing the sound.
|
|
var/atom/source
|
|
/// k:v list of mob : sound status
|
|
var/list/listeners = list()
|
|
///k:v list of mobs : bool. Used to quickly check whether a mob is allowed to hear this noise. This is null by default which means ANY MOB can hear this.
|
|
var/list/allowed_listeners
|
|
/// Sound maximum range
|
|
var/range
|
|
/// Sound volume
|
|
var/volume
|
|
/// Sound falloff
|
|
var/falloff_exponent
|
|
/// Sound falloff distance
|
|
var/falloff_distance
|
|
|
|
/// The master copy of the playing sound.
|
|
var/sound/sound
|
|
/// Null sound for cancelling the sound entirely.
|
|
var/sound/null_sound
|
|
|
|
/// Status of the playing sound
|
|
var/sound_status = NONE
|
|
/// The channel being used.
|
|
var/sound_channel
|
|
/// world.time when the sound started (or when the sound file was last changed). Used to calculate playback offset for new listeners.
|
|
var/start_time
|
|
/// Duration of the current sound file in deciseconds. Used to wrap offset for looping sounds.
|
|
var/sound_duration
|
|
/// Duration of the current sound file in deciseconds. Used to wrap offset for looping sounds.
|
|
var/sound_duration_override
|
|
/// Cell tracker managing spatial grid cells within range of the source. The wizards say this is the fastest.
|
|
var/datum/cell_tracker/cell_tracker
|
|
///Should we destroy the datum when the sound is done?
|
|
var/delete_on_end = FALSE
|
|
|
|
/datum/sound_token/New(atom/_source, _sound, _range = 10, _volume = 50, _falloff_exponent = SOUND_FALLOFF_EXPONENT, _falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE, _allowed_listeners, _sound_duration_override, _delete_on_end)
|
|
source = _source
|
|
RegisterSignal(source, COMSIG_QDELETING, PROC_REF(source_deleted))
|
|
RegisterSignal(source, COMSIG_MOVABLE_MOVED, PROC_REF(source_moved))
|
|
RegisterSignal(source, COMSIG_ENTER_AREA, PROC_REF(on_enter_area))
|
|
|
|
range = _range
|
|
volume = _volume
|
|
falloff_exponent = _falloff_exponent
|
|
falloff_distance = _falloff_distance
|
|
sound_duration_override = _sound_duration_override
|
|
if(_delete_on_end)
|
|
delete_on_end = _delete_on_end
|
|
|
|
if(_allowed_listeners)
|
|
for(var/allowed_mob in _allowed_listeners)
|
|
allowed_listeners[allowed_mob] = TRUE
|
|
|
|
update_sound(_sound)
|
|
|
|
null_sound = sound(channel = sound_channel)
|
|
|
|
cell_tracker = new /datum/cell_tracker(range, range)
|
|
update_tracked_cells()
|
|
|
|
RegisterSignal(SSdcs, COMSIG_GLOB_PLAYER_LOGIN, PROC_REF(player_login))
|
|
RegisterSignal(SSdcs, COMSIG_GLOB_PLAYER_LOGOUT, PROC_REF(player_logout))
|
|
|
|
|
|
/datum/sound_token/Destroy(force, ...)
|
|
for(var/listener in listeners)
|
|
remove_listener(listener)
|
|
|
|
listeners = null
|
|
source = null
|
|
return ..()
|
|
|
|
///Lets us update the sound to a new one.
|
|
/datum/sound_token/proc/update_sound(_sound, start_playing = FALSE)
|
|
sound = sound(_sound)
|
|
if(!sound_channel)
|
|
sound_channel = SSsounds.reserve_sound_channel_for_datum(src)
|
|
sound.channel = sound_channel
|
|
sound_duration = sound_duration_override || SSsounds.get_sound_length(_sound)
|
|
start_time = REALTIMEOFDAY
|
|
if(start_playing)
|
|
force_update_all_listeners(FALSE)
|
|
if(delete_on_end)
|
|
addtimer(CALLBACK(src, PROC_REF(on_sound_ended)), sound_duration, TIMER_UNIQUE | TIMER_OVERRIDE)
|
|
|
|
|
|
/// Updates the data of a listener, or adds them if they are not present.
|
|
/datum/sound_token/proc/add_or_update_listener(mob/listener_mob)
|
|
if(isnull(listeners[listener_mob]))
|
|
if(!add_listener(listener_mob))
|
|
return FALSE
|
|
else
|
|
update_listener(listener_mob)
|
|
|
|
/// Adds a listener to the sound. returns TRUE if we already were added, or for some reason couldnt be added.
|
|
/datum/sound_token/proc/add_listener(mob/listener_mob)
|
|
if(!isnull(listeners[listener_mob]))
|
|
return TRUE
|
|
|
|
if(!listener_mob.client || isnewplayer(listener_mob))
|
|
return FALSE
|
|
|
|
if(allowed_listeners && !allowed_listeners[listener_mob])
|
|
return FALSE
|
|
|
|
listeners[listener_mob] = NONE
|
|
LAZYOR(listener_mob.sound_tokens, src)
|
|
if(source != listener_mob) //this is possible...yea... :/
|
|
RegisterSignal(listener_mob, COMSIG_QDELETING, PROC_REF(listener_deleted))
|
|
RegisterSignals(listener_mob, list(SIGNAL_ADDTRAIT(TRAIT_DEAF), SIGNAL_REMOVETRAIT(TRAIT_DEAF)), PROC_REF(listener_deafness_update))
|
|
update_listener(listener_mob, FALSE)
|
|
return TRUE
|
|
|
|
/// Remove a listener from the sound.
|
|
/datum/sound_token/proc/remove_listener(mob/listener_mob)
|
|
|
|
listeners -= listener_mob
|
|
LAZYREMOVE(listener_mob.sound_tokens, src)
|
|
|
|
UnregisterSignal(listener_mob, list(COMSIG_QDELETING, SIGNAL_ADDTRAIT(TRAIT_DEAF),SIGNAL_REMOVETRAIT(TRAIT_DEAF)))
|
|
SEND_SOUND(listener_mob, null_sound)
|
|
|
|
/datum/sound_token/proc/update_listener(mob/listener_mob, update_sound = TRUE)
|
|
if(QDELETED(src))
|
|
return
|
|
if(isnull(listeners[listener_mob]))
|
|
return
|
|
|
|
var/turf/source_turf = get_turf(source)
|
|
var/turf/listener_turf = get_turf(listener_mob)
|
|
|
|
if(!source_turf || !listener_turf)
|
|
return
|
|
|
|
var/is_muted = listeners[listener_mob] & SOUND_MUTE
|
|
var/should_be_muted = FALSE
|
|
|
|
if(source_turf.z != listener_turf.z)
|
|
should_be_muted = TRUE
|
|
|
|
var/distance = get_dist_euclidean(source_turf, listener_turf)
|
|
if(distance > range)
|
|
should_be_muted = TRUE
|
|
if(should_be_muted && is_muted)
|
|
return
|
|
|
|
should_be_muted ||= HAS_TRAIT(listener_mob, TRAIT_DEAF)
|
|
if(should_be_muted && is_muted)
|
|
return
|
|
|
|
set_listener_status(listener_mob, should_be_muted ? SOUND_MUTE : NONE)
|
|
send_listener_sound(listener_mob, update_sound)
|
|
|
|
/datum/sound_token/proc/send_listener_sound(mob/listener_mob, update_sound)
|
|
PRIVATE_PROC(TRUE)
|
|
|
|
sound.status = sound_status|listeners[listener_mob]
|
|
if(update_sound)
|
|
sound.status |= SOUND_UPDATE
|
|
else
|
|
sound.offset = calculate_offset()
|
|
|
|
if(sound.status & SOUND_MUTE)
|
|
SEND_SOUND(listener_mob, sound)
|
|
return
|
|
|
|
if(!listener_mob.playsound_local(get_turf(source), vol = volume, falloff_exponent = falloff_exponent, channel = sound_channel, sound_to_use = sound, max_distance = range, falloff_distance = falloff_distance, use_reverb = TRUE))
|
|
sound.status = SOUND_UPDATE|SOUND_MUTE
|
|
SEND_SOUND(listener_mob, sound)
|
|
sound.offset = null
|
|
|
|
/datum/sound_token/proc/update_all_listeners()
|
|
for(var/mob/listener_mob in listeners)
|
|
if(listener_mob.client)
|
|
SSsound_tokens.clients_needing_update[listener_mob.client] = TRUE
|
|
|
|
/datum/sound_token/proc/force_update_all_listeners(update_sound = TRUE)
|
|
for(var/mob/listener_mob in listeners)
|
|
if(listener_mob.client)
|
|
update_listener(listener_mob, update_sound)
|
|
|
|
/// Setter for volume
|
|
/datum/sound_token/proc/set_volume(new_volume, update_listeners = TRUE)
|
|
volume = new_volume
|
|
if(update_listeners)
|
|
update_all_listeners()
|
|
|
|
/// Set the status of a listener. Does not update the sound.
|
|
/datum/sound_token/proc/set_listener_status(mob/listener_mob, new_status)
|
|
if(isnull(listeners[listener_mob]))
|
|
return
|
|
|
|
listeners[listener_mob] = new_status
|
|
|
|
/// Respond to TRAIT_DEAF addition/removal
|
|
/datum/sound_token/proc/listener_deafness_update(atom/movable/source)
|
|
SIGNAL_HANDLER
|
|
update_listener(source)
|
|
|
|
/datum/sound_token/proc/listener_deleted(datum/source)
|
|
SIGNAL_HANDLER
|
|
remove_listener(source)
|
|
|
|
/// Respond to any mob in the world being logged into. Only adds if the mob is within range.
|
|
/datum/sound_token/proc/player_login(datum/source, mob/player)
|
|
SIGNAL_HANDLER
|
|
var/turf/player_turf = get_turf(player)
|
|
var/turf/source_turf = get_turf(src.source)
|
|
if(!player_turf || !source_turf)
|
|
return
|
|
if(player_turf.z != source_turf.z)
|
|
return
|
|
if(get_dist_euclidean(source_turf, player_turf) > range)
|
|
return
|
|
add_or_update_listener(player)
|
|
|
|
/// Respond to any cliented mob becoming uncliented
|
|
/datum/sound_token/proc/player_logout(datum/source, mob/player)
|
|
SIGNAL_HANDLER
|
|
remove_listener(player)
|
|
|
|
/// If the sound source moves, update tracked cells then refresh all listener positions.
|
|
/datum/sound_token/proc/source_moved()
|
|
SIGNAL_HANDLER
|
|
update_tracked_cells()
|
|
update_all_listeners()
|
|
|
|
/datum/sound_token/proc/source_deleted()
|
|
SIGNAL_HANDLER
|
|
|
|
qdel(src)
|
|
|
|
///Update env when source is entering new area
|
|
/datum/sound_token/proc/on_enter_area(datum/source, area/area_to_register)
|
|
SIGNAL_HANDLER
|
|
set_new_environment(area_to_register.sound_environment || SOUND_ENVIRONMENT_NONE)
|
|
|
|
/datum/sound_token/proc/set_new_environment(new_env)
|
|
if(sound.environment == new_env)
|
|
return
|
|
sound.environment = new_env
|
|
update_all_listeners()
|
|
|
|
///Calculates the offset to give the sound for people who start hearing it mid-play
|
|
/datum/sound_token/proc/calculate_offset()
|
|
var/elapsed = REALTIMEOFDAY - start_time
|
|
var/freq_factor = (sound.frequency || 100) / 100
|
|
var/pitch_factor = (sound.pitch || 100) / 100
|
|
var/offset = elapsed * freq_factor * pitch_factor
|
|
return offset
|
|
|
|
///Update tracked cells; happens on movement. We need to check if anyone is now out of cell range and kick them out.
|
|
/datum/sound_token/proc/update_tracked_cells()
|
|
if(!get_turf(source))
|
|
return
|
|
|
|
var/list/new_and_old = cell_tracker.recalculate_cells(get_turf(source))
|
|
var/list/datum/spatial_grid_cell/added_cells = new_and_old[1]
|
|
var/list/datum/spatial_grid_cell/removed_cells = new_and_old[2]
|
|
|
|
for(var/datum/spatial_grid_cell/cell as anything in removed_cells)
|
|
UnregisterSignal(cell, list(SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS),))
|
|
|
|
// Remove listeners whose mob is no longer in any remaining member cell
|
|
if(removed_cells.len)
|
|
for(var/mob/listener_mob as anything in listeners)
|
|
var/still_in_range = FALSE
|
|
for(var/datum/spatial_grid_cell/cell as anything in cell_tracker.member_cells)
|
|
if(listener_mob in cell.client_contents)
|
|
still_in_range = TRUE
|
|
break
|
|
if(!still_in_range)
|
|
remove_listener(listener_mob)
|
|
|
|
for(var/datum/spatial_grid_cell/cell as anything in added_cells)
|
|
RegisterSignal(cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), PROC_REF(on_cell_client_entered))
|
|
RegisterSignal(cell, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), PROC_REF(on_cell_client_exited))
|
|
for(var/mob/listener_mob as anything in cell.client_contents)
|
|
add_or_update_listener(listener_mob)
|
|
|
|
/// Signal handler for SPATIAL_GRID_CELL_ENTERED on tracked cells. Adds newly arriving mobs as listeners.
|
|
/datum/sound_token/proc/on_cell_client_entered(datum/source, list/entering_mobs)
|
|
SIGNAL_HANDLER
|
|
|
|
for(var/mob/listener_mob as anything in entering_mobs)
|
|
if(!isnull(listeners[listener_mob])) // already added
|
|
continue
|
|
add_or_update_listener(listener_mob)
|
|
|
|
/// Signal handler for SPATIAL_GRID_CELL_EXITED on tracked cells. Removes mobs who have left all member cells.
|
|
/datum/sound_token/proc/on_cell_client_exited(datum/source, list/exiting_mobs)
|
|
SIGNAL_HANDLER
|
|
for(var/mob/listener_mob as anything in exiting_mobs)
|
|
var/still_in_range = FALSE
|
|
if(SSspatial_grid.get_cell_of(listener_mob) in cell_tracker.member_cells)
|
|
still_in_range = TRUE
|
|
|
|
if(!still_in_range)
|
|
remove_listener(listener_mob)
|
|
|
|
///The sound should have ended on all clients. Time to destroy the sound token.
|
|
/datum/sound_token/proc/on_sound_ended()
|
|
qdel(src)
|