Files
CabinetOnFire 5a68cb5ff3 Implements "Sound Tokens", a way to have spatial audio. And implements it on looping sounds. (And also some tweaks to fall-off) (#96018)
## About The Pull Request

This pull request implements sound tokens, based on the implementation
by Kapu at https://github.com/DaedalusDock/daedalusdock/pull/1379

I've taken that implementation and extended it to looping_sounds,
allowing us to have spatial audio that updates much more nicely when
players walk closer. This system also lets players that walk into range
after the sound has started playing to also hear the sound, before if
you missed the sound, you would never hear it. This is particularly
noticeable with long sounds (Which is lame!)

**VIDEOS**:
griddle:
https://streamable.com/v7ayqm

engineering / SM:
https://streamable.com/5lr1oj


The system essentially sends the sound to every client, and changes the
volume based on distance to the sound. There are some performance
concerns here, so we will need to testmerge this and see how impactful
it is. It is in a subsystem though, so there is some limit to the amount
of cpu it will use. Do keep this in mind when watching the videos; it
will probably not be AS responsive as in there.

I've also changed how intense our fall-off is, making sounds lose their
volume slower. Before taking one step away from a 50 volume sound would
make it 18 volume, this is way too big of a drop and it became
particularly jarring with the changes in this PR.

Some sounds might be too long range now, but I think we should just
reduce the max range of those sounds as we were basically playing them
at <5 volume after 10 tiles, at which point we should maybe just reduce
the range a bit since at those volumes theyre really not audible.

**OLD FALLOFF;** note the MASSIVE drop from 1 tile to 2 tiles
<img width="1416" height="897" alt="image"
src="https://github.com/user-attachments/assets/805b270e-6aa5-4e84-868e-39585966f0a4"
/>

**NEW FALLOFF:**
<img width="1418" height="905" alt="image"
src="https://github.com/user-attachments/assets/eabbd592-ae9c-4690-8945-fbd0b4f6614f"
/>

Also tweaked a few looping sounds (deep-fryer in particular got some
clean-up, as its mid-length was way too short for the sounds and it had
a double-definition)


## Why It's Good For The Game

This system allows us to have a richer audio experience, I really don't
like how choppy our looping sounds are, especially in some areas where
you hear them a lot (kitchen and engineering)



## Changelog

🆑 CabinetOnFire, Kapu
sound: Changed our falloff to be less intense, as it was punching volume
down way too fast.
refactor: Implements a system for spatial audio to improve our looping
sounds
/🆑

---------

Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
2026-06-05 16:24:25 -07:00

266 lines
10 KiB
Plaintext

/**
* Client datum
*
* A datum that is created whenever a user joins a BYOND world, one will exist for every active connected
* player
*
* when they first connect, this client object is created and [/client/New] is called
*
* When they disconnect, this client object is deleted and [/client/Del] is called
*
* All client topic calls go through [/client/Topic] first, so a lot of our specialised
* topic handling starts here
*/
/client
/**
* This line makes clients parent type be a datum
*
* By default in byond if you define a proc on datums, that proc will exist on nearly every single type
* from icons to images to atoms to mobs to objs to turfs to areas, it won't however, appear on client
*
* instead by default they act like their own independent type so while you can do isdatum(icon)
* and have it return true, you can't do isdatum(client), it will always return false.
*
* This makes writing oo code hard, when you have to consider this extra special case
*
* This line prevents that, and has never appeared to cause any ill effects, while saving us an extra
* pain to think about
*
* This line is widely considered black fucking magic, and the fact it works is a puzzle to everyone
* involved, including the current engine developer, lummox
*
* If you are a future developer and the engine source is now available and you can explain why this
* is the way it is, please do update this comment
*/
parent_type = /datum
////////////////
//ADMIN THINGS//
////////////////
/// hides the byond verb panel as we use our own custom version
show_verb_panel = FALSE
///Contains admin info. Null if client is not an admin.
var/datum/admins/holder = null
///Needs to implement InterceptClickOn(user,params,atom) proc
var/datum/click_intercept = null
///Time when the click was intercepted
var/click_intercept_time = 0
///Used to cache this client's bans to save on DB queries
var/ban_cache = null
///If we are currently building this client's ban cache, this var stores the timeofday we started at
var/ban_cache_start = 0
///Contains the last message sent by this client - used to protect against copy-paste spamming.
var/last_message = ""
///contins a number of how many times a message identical to last_message was sent.
var/last_message_count = 0
///How many messages sent in the last 10 seconds
var/total_message_count = 0
///Next tick to reset the total message counter
var/total_count_reset = 0
///Internal counter for clients sending external (IRC/Discord) relay messages via ahelp to prevent spamming. Set to a number every time an admin reply is sent, decremented for every client send.
var/externalreplyamount = 0
///Tracks say() usage for ic/dchat while slowmode is enabled
COOLDOWN_DECLARE(say_slowmode)
/// The last urgent ahelp that this player sent
COOLDOWN_DECLARE(urgent_ahelp_cooldown)
/////////
//OTHER//
/////////
///Player preferences datum for the client
var/datum/preferences/prefs = null
///Move delay of controlled mob, any keypresses inside this period will persist until the next proper move
var/move_delay = 0
///The visual delay to use for the current client.Move(), mostly used for making a client based move look like it came from some other slower source
var/visual_delay = 0
///////////////
//SOUND STUFF//
///////////////
/// Sound tokens currently playing for this client. Managed by /datum/sound_token and the soundtoken subsystem!! SOUND TOKENS 2026
var/list/datum/sound_token/sound_tokens = list()
////////////
//SECURITY//
////////////
// comment out the line below when debugging locally to enable the options & messages menu
control_freak = 1
////////////////////////////////////
//things that require the database//
////////////////////////////////////
///Used to determine how old the account is - in days.
var/player_age = -1
///Date that this account was first seen in the server
var/player_join_date = null
///So admins know why it isn't working - Used to determine what other accounts previously logged in from this ip
var/related_accounts_ip = "Requires database"
///So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id
var/related_accounts_cid = "Requires database"
///Date of byond account creation in ISO 8601 format
var/account_join_date = null
///Age of byond account in days
var/account_age = -1
preload_rsc = PRELOAD_RSC
var/atom/movable/screen/click_catcher/void
///used to make a special mouse cursor, this one for mouse up icon
var/mouse_up_icon = null
///used to make a special mouse cursor, this one for mouse up icon
var/mouse_down_icon = null
///used to override the mouse cursor so it doesnt get reset
var/mouse_override_icon = null
///Used for ip intel checking to identify evaders, disabled because of issues with traffic
var/ip_intel = "Disabled"
///datum that controls the displaying and hiding of tooltips
var/datum/tooltip/tooltips
///Last ping of the client
var/lastping = 0
///Average ping of the client
var/avgping = 0
///world.time they connected
var/connection_time
///world.realtime they connected
var/connection_realtime
///world.timeofday they connected
var/connection_timeofday
///Used for limiting the rate of topic sends by the client to avoid abuse
var/list/topiclimiter
///Used for limiting the rate of clicks sends by the client to avoid abuse
var/list/clicklimiter
///lazy list of all credit object bound to this client
var/list/credits
///these persist between logins/logouts during the same round.
var/datum/persistent_client/persistent_client
///Should only be a key-value list of north/south/east/west = atom/movable/screen.
var/list/char_render_holders
///Amount of keydowns in the last keysend checking interval
var/client_keysend_amount = 0
///World tick time where client_keysend_amount will reset
var/next_keysend_reset = 0
///World tick time where keysend_tripped will reset back to false
var/next_keysend_trip_reset = 0
///When set to true, user will be autokicked if they trip the keysends in a second limit again
var/keysend_tripped = FALSE
///custom movement keys for this client
var/list/movement_keys = list()
///Autoclick list of two elements, first being the clicked thing, second being the parameters.
var/list/atom/selected_target[2]
///Used in MouseDrag to preserve the original mouse click parameters
var/mouseParams = ""
///Used in MouseDrag to preserve the last mouse-entered location. Weakref
var/datum/weakref/mouse_location_ref = null
///Used in MouseDrag to preserve the last mouse-entered object. Weakref
var/datum/weakref/mouse_object_ref
//Middle-mouse-button click dragtime control for aimbot exploit detection.
var/middragtime = 0
//Middle-mouse-button clicked object control for aimbot exploit detection. Weakref
var/datum/weakref/middle_drag_atom_ref
//When we started the currently active drag
var/drag_start = 0
//The params we were passed at the start of the drag, in list form
var/list/drag_details
/// Messages currently seen by this client
var/list/seen_messages
/// datum wrapper for client view
var/datum/view_data/view_size
/// our current tab
var/stat_tab
/// list of all tabs
var/list/panel_tabs = list()
///A lazy list of atoms we've examined in the last RECENT_EXAMINE_MAX_WINDOW (default 2) seconds, so that we will call [/atom/proc/examine_more] instead of [/atom/proc/examine] on them when examining
var/list/recent_examines
var/atom/movable/screen/parallax_home/parallax_rock
///this is the last recorded client eye by SSparallax/fire()
var/atom/movable/movingmob
var/turf/previous_turf
///world.time of when we can state animate()ing parallax again
var/dont_animate_parallax
/// Direction our current area wants to move parallax
var/parallax_movedir = 0
/// Timers for the area directional animation, one for each layer
var/list/parallax_animate_timers
///Are we locking our movement input?
var/movement_locked = FALSE
/**
* Assoc list with all the active maps - when a screen obj is added to
* a map, it's put in here as well.
*
* Format: list(<mapname> = list(/atom/movable/screen))
*/
var/list/screen_maps = list()
// List of all asset filenames sent to this client by the asset cache, along with their assoicated md5s
var/list/sent_assets = list()
/// List of all completed blocking send jobs awaiting acknowledgement by send_asset
var/list/completed_asset_jobs = list()
/// Last asset send job id.
var/last_asset_job = 0
var/last_completed_asset_job = 0
/// A buffer of currently held keys.
var/list/keys_held = list()
/// A buffer for combinations such of modifiers + keys (ex: CtrlD, AltE, ShiftT). Format: `"key"` -> `"combo"` (ex: `"D"` -> `"CtrlD"`)
var/list/key_combos_held = list()
/// The direction we WANT to move, based off our keybinds
/// Will be udpated to be the actual direction later on
var/intended_direction = NONE
/*
** These next two vars are to apply movement for keypresses and releases made while move delayed.
** Because discarding that input makes the game less responsive.
*/
/// On next move, add this dir to the move that would otherwise be done
var/next_move_dir_add
/// On next move, subtract this dir from the move that would otherwise be done
var/next_move_dir_sub
/// If the client is currently under the restrictions of the interview system
var/interviewee = FALSE
/// Whether or not this client has standard hotkeys enabled
var/hotkeys = TRUE
/// Whether or not this client has the combo HUD enabled
var/combo_hud_enabled = FALSE
/// If this client has been fully initialized or not
var/fully_created = FALSE
/// Does this client have typing indicators enabled?
var/typing_indicators = FALSE
/// Loot panel for the client
var/datum/lootpanel/loot_panel
///Which ambient sound this client is currently being provided.
var/current_ambient_sound
/// The DPI scale of the client. 1 is equivalent to 100% window scaling, 2 will be 200% window scaling
var/window_scaling
var/datum/tgui_window/stat_panel
/// OOC colour of the clients messages.
var/ooc_colour = null