adds a playsoundtoken proc that allows one-shot sound tokens playing, and adds support for it to emotes. (#96519)

This commit is contained in:
CabinetOnFire
2026-06-16 12:06:13 +02:00
committed by GitHub
parent 60f9cf09ca
commit d5b366d47a
6 changed files with 79 additions and 18 deletions
+9
View File
@@ -1096,6 +1096,15 @@ GLOBAL_LIST_INIT(layers_to_offset, list(
/// Helper macro that determines if the mob is at the threshold to start vomitting due to high toxin levels
#define AT_TOXIN_VOMIT_THRESHOLD(mob) (mob.get_tox_loss() > 45 && mob.nutrition > 20)
/// Shared cooldown for manually triggered emote audio
#define MANUAL_GENERAL_EMOTE_AUDIO_COOLDOWN "manual_general_emote_audio_cooldown"
/// Per emote cooldown for manually triggered emote audio
#define MANUAL_SPECIFIC_EMOTE_AUDIO_COOLDOWN(type) "manual_specific_emote_audio_cooldown_[type]"
/// Shared cooldown for forced emote audio
#define FORCED_GENERAL_EMOTE_AUDIO_COOLDOWN "forced_general_emote_audio_cooldown"
/// Per emote cooldown for forced emote audio
#define FORCED_SPECIFIC_EMOTE_AUDIO_COOLDOWN(type) "forced_specific_emote_audio_cooldown_[type]"
/// The duration of the flip emote animation
#define FLIP_EMOTE_DURATION 0.7 SECONDS
///The duration of a taunt emote, so how long they can deflect projectiles
+26 -8
View File
@@ -61,12 +61,18 @@
var/cooldown = 0.8 SECONDS
/// Does this message have a message that can be modified by the user?
var/can_message_change = FALSE
/// How long is the shared emote cooldown triggered by this emote?
var/general_emote_audio_cooldown = 2 SECONDS
/// How long is the specific emote cooldown triggered by this emote?
var/specific_emote_audio_cooldown = 5 SECONDS
/// How long is the shared emote cooldown triggered by this emote when used intentionally?
var/manual_general_emote_audio_cooldown = 2 SECONDS
/// How long is the specific emote cooldown triggered by this emote when used intentionally?
var/manual_specific_emote_audio_cooldown = 5 SECONDS
/// How long is the shared emote cooldown triggered by this emote when forced?
var/forced_general_emote_audio_cooldown = 2 SECONDS
/// How long is the specific emote cooldown triggered by this emote when forced?
var/forced_specific_emote_audio_cooldown = 2 SECONDS
/// Does this emote's sound ignore walls?
var/sound_wall_ignore = FALSE
///Does this emote use sound tokens? this means it also ignores walls.
var/use_sound_tokens = FALSE
/datum/emote/New()
switch(mob_type_allowed_typecache)
@@ -112,15 +118,27 @@
user.log_message(msg, LOG_EMOTE)
var/tmp_sound = get_sound(user)
if(tmp_sound && should_play_sound(user, intentional) && TIMER_COOLDOWN_FINISHED(user, "general_emote_audio_cooldown") && TIMER_COOLDOWN_FINISHED(user, type))
TIMER_COOLDOWN_START(user, type, specific_emote_audio_cooldown)
TIMER_COOLDOWN_START(user, "general_emote_audio_cooldown", general_emote_audio_cooldown)
if(tmp_sound && should_play_sound(user, intentional))
if(intentional)
if(!TIMER_COOLDOWN_FINISHED(user, MANUAL_GENERAL_EMOTE_AUDIO_COOLDOWN) || !TIMER_COOLDOWN_FINISHED(user, MANUAL_SPECIFIC_EMOTE_AUDIO_COOLDOWN(type)) || !TIMER_COOLDOWN_FINISHED(user, FORCED_GENERAL_EMOTE_AUDIO_COOLDOWN) || !TIMER_COOLDOWN_FINISHED(user, FORCED_SPECIFIC_EMOTE_AUDIO_COOLDOWN(type)))
return FALSE
else
if(!TIMER_COOLDOWN_FINISHED(user, FORCED_GENERAL_EMOTE_AUDIO_COOLDOWN) || !TIMER_COOLDOWN_FINISHED(user, FORCED_SPECIFIC_EMOTE_AUDIO_COOLDOWN(type)))
return FALSE
TIMER_COOLDOWN_START(user, MANUAL_SPECIFIC_EMOTE_AUDIO_COOLDOWN(type), manual_specific_emote_audio_cooldown)
TIMER_COOLDOWN_START(user, MANUAL_GENERAL_EMOTE_AUDIO_COOLDOWN, manual_general_emote_audio_cooldown)
TIMER_COOLDOWN_START(user, FORCED_SPECIFIC_EMOTE_AUDIO_COOLDOWN(type), forced_specific_emote_audio_cooldown)
TIMER_COOLDOWN_START(user, FORCED_GENERAL_EMOTE_AUDIO_COOLDOWN, forced_general_emote_audio_cooldown)
var/frequency = null
if (affected_by_pitch && SStts.tts_enabled && SStts.pitch_enabled)
frequency = rand(MIN_EMOTE_PITCH, MAX_EMOTE_PITCH) * (1 + sqrt(abs(user.pitch)) * sign(user.pitch) * EMOTE_TTS_PITCH_MULTIPLIER)
else if(vary)
frequency = rand(MIN_EMOTE_PITCH, MAX_EMOTE_PITCH)
playsound(source = user,soundin = tmp_sound,vol = 50, vary = FALSE, ignore_walls = sound_wall_ignore, frequency = frequency)
if(use_sound_tokens && sound_wall_ignore)
playsoundtoken(source = user, soundin = tmp_sound, range = SOUND_RANGE, volume = 50)
else
playsound(source = user,soundin = tmp_sound,vol = 50, vary = FALSE, ignore_walls = sound_wall_ignore, frequency = frequency)
var/is_important = running_emote_type & EMOTE_IMPORTANT
+30 -7
View File
@@ -6,7 +6,8 @@
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
@@ -29,10 +30,14 @@
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)
/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))
@@ -42,6 +47,13 @@
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)
@@ -68,25 +80,32 @@
if(!sound_channel)
sound_channel = SSsounds.reserve_sound_channel_for_datum(src)
sound.channel = sound_channel
sound_duration = SSsounds.get_sound_length(_sound)
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]))
add_listener(listener_mob)
if(!add_listener(listener_mob))
return FALSE
else
update_listener(listener_mob)
/// Adds a listener to the sound.
/// 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 FALSE
return TRUE
if(!listener_mob.client || isnewplayer(listener_mob))
return
return FALSE
if(allowed_listeners && !allowed_listeners[listener_mob])
return FALSE
listeners[listener_mob] = NONE
listener_mob.client.sound_tokens += src
@@ -284,3 +303,7 @@
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)
+9
View File
@@ -219,3 +219,12 @@
return soundin
var/datum/sound_effect/sfx = GLOB.sfx_datum_by_key[soundin]
return sfx?.return_sfx() || soundin
/**
* Creates a soundtoken datum (a sound that updates for movement).
* allowed_listeners is an optional list of mobs that are the only ones that can hear this sound ever.
* sound_length is an optional length of the sound. Things like TTS need to pass this since we can't dynamically grab the length in that case.
*/
/proc/playsoundtoken(atom/source, soundin, volume, range, falloff_exponent = SOUND_FALLOFF_EXPONENT, falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE, allowed_listeners, sound_length)
return new /datum/sound_token(source, soundin, range, volume, falloff_exponent, falloff_distance, allowed_listeners, sound_length, _delete_on_end = TRUE)
@@ -63,7 +63,7 @@
message = "screeches!"
message_mime = "screeches silently."
emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE
specific_emote_audio_cooldown = 10 SECONDS
manual_specific_emote_audio_cooldown = 10 SECONDS
vary = FALSE
/datum/emote/living/carbon/human/screech/get_sound(mob/living/carbon/human/user)
+4 -2
View File
@@ -303,7 +303,7 @@
message = "laughs."
message_mime = "laughs silently!"
emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE
specific_emote_audio_cooldown = 8 SECONDS
manual_specific_emote_audio_cooldown = 8 SECONDS
vary = TRUE
/datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE , intentional, params)
@@ -419,7 +419,9 @@
emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE
mob_type_blacklist_typecache = list(/mob/living/brain)
sound_wall_ignore = TRUE
specific_emote_audio_cooldown = 10 SECONDS
use_sound_tokens = TRUE
manual_specific_emote_audio_cooldown = 10 SECONDS
forced_specific_emote_audio_cooldown = 4 SECONDS
vary = TRUE
/datum/emote/living/scream/run_emote(mob/user, params, type_override, intentional = FALSE)