Sounds and voice chat
Play the game's own sounds with Audio, tune proximity voice chat on the client, and enforce who hears whom on the server.
Audio plays the game’s own sound
triggers on this machine. Voice
is the built-in proximity voice chat: player settings on the client, rules on
the server.
const TRIGGER = "your_trigger_name"; // as the game's audio data spells it
if (Audio.hasTrigger(TRIGGER)) { Audio.play(TRIGGER);} else { console.log(`${TRIGGER} is not a trigger this install knows`);}Play a sound
Section titled “Play a sound”A trigger is a name from the game’s audio data, not display text. A patch or
another mod can change which exist, so Audio.hasTrigger lets you fail early.
It also answers false while the audio system is not up. Triggers a server
ships in a resource’s stream/ folder work like the game’s own once they
arrive; see Custom assets.
| Call | Heard | Can stop it? |
|---|---|---|
play(trigger) |
everywhere at full volume, like a UI cue | no |
playAt(trigger, position) |
from a point in the world, until it ends | no |
playOnEntity(trigger, entityId) |
from an entity, following it | yes, stopOnEntity(trigger, entityId) |
play and playAt do not check occlusion: a wall does not muffle them.
Play a sound on a player or prop
Section titled “Play a sound on a player or prop”playOnEntity takes an engine entity id, such as a player’s entityId:
function ringOn(target: Player, trigger: string): boolean { if (target.entityId === 0) { return false; // no body right now } return Audio.playOnEntity(trigger, target.entityId);}stopAllOnEntity(entityId)silences everything the entity plays, the game’s own sounds included. PreferstopOnEntitywhen you know the trigger.setEntityParameter(entityId, parameter, value)andsetEntitySwitch(entityId, switchName, state)feed the game’s sound events for one entity: parameters such ashorse_speedorplayer_stamina, switches such assword_mat. They affect sounds already playing too.
Play a sound file
Section titled “Play a sound file”Audio.playFile(file, entityId, purpose?) plays an .ogg on an entity, with
no trigger needed. It is placed and faded like the game’s own spoken lines and
follows the same volume settings. The path is from the game’s root, with or
without .ogg; for a file a server streams, it is sounds/kcdc/<resource>/<name>.
It returns false when the entity is not there to take it.
const LINE = "sounds/kcdc/my-assets/greeting";const me = LocalPlayer;
if (me && Assets.has(`${LINE}.ogg`)) { Audio.playFile(LINE, me.entityId, "20"); // heard up to about 20 m}purpose picks which of the game’s voice channels carries it: 10, 20,
35, 50 (the default), 100 or 200 for a line heard up to that many
metres, or inner, sfp_dialog, sfp_cutscene or sfp_music. An entity takes one
file every 0.1 seconds.
Voice chat settings (client)
Section titled “Voice chat settings (client)”The server relays a player’s voice only to players within range, and playback
fades with distance. Push-to-talk is on v by default. The client side is the
player’s own settings:
Voice.setEnabled(true); // off closes the mic and stops receivingVoice.setVolume(1.5); // playback gain, 0 to 4, 1 is unchangedVoice.setPushToTalkKey("b"); // same names as Key.bind; unknown names throwVoice.setPushToTalkReleaseDelay(250); // keep sending briefly after release, 0 to 2000 msVoice.setHearingRange(15); // can only narrow the server's range; 0 removes the limit
if (!Voice.hasMicrophone()) { Hud.showInfoText("No microphone found: you can listen but not talk.");}Each setter has a getter (isEnabled, getVolume, getPushToTalkKey, …).
Voice.getRange() reads the server’s range, and Voice.isTalking() whether
the local player is sending now.
Voice chat rules (server)
Section titled “Voice chat rules (server)”The server’s Voice sets ranges and mutes. Ranges are in metres, 25 by
default.
// serverVoice.setRange(30); // for everyone without an override; clients are told
Events.onClient("my-mode:voice.mode", (sender, payload) => { const player = sender as Player; const mode = typeof payload === "object" && payload !== null ? (payload as { mode?: unknown }).mode : undefined; if (mode === "whisper") Voice.setPlayerRange(player, 5); else if (mode === "shout") Voice.setPlayerRange(player, 60); else Voice.setPlayerRange(player, 0); // 0 goes back to the server-wide range});const modes = ["normal", "whisper", "shout"];let current = 0;
Key.bind("n", () => { current = (current + 1) % modes.length; Events.emitServer("my-mode:voice.mode", { mode: modes[current] }); Hud.showInfoText(`Voice: ${modes[current]}`);});| Server call | Effect |
|---|---|
setPlayerMuted(player, muted) |
nobody hears this player |
setPlayerDeaf(player, deaf) |
this player hears nobody |
setLocalMute(listener, target, muted) |
one player stops hearing one other, enforced by the server |
getPlayerRange(player) |
the range with the default resolved |
isPlayerVoiceEnabled(player) |
whether they left voice on in their settings |
isPlayerTalking(player) |
whether their voice is reaching the server now |
Each setter has an is... reader to match.
React to someone talking
Section titled “React to someone talking”| Side | Events | Follows |
|---|---|---|
| client | voiceStart, voiceStop |
the local player sending |
| server | playerVoiceStart, playerVoiceStop (with the player) |
actual speech; silence is dropped before the server |
// serverEvents.on("playerVoiceStart", (who) => { const player = who as Player; player.state.set("my-mode:talking", true);});
Events.on("playerVoiceStop", (who) => { const player = who as Player; player.state.set("my-mode:talking", false);});Voice while a UI has focus
Section titled “Voice while a UI has focus”Push-to-talk is blocked while a web view has input focus or controls are locked. Players can still hear others, and voice activation still transmits speech. If a menu should mute voice chat, your game mode must do that separately.
Related
Section titled “Related”- Discord Rich Presence, for the player’s Discord status
- Particle effects, for the visual half
- Custom assets, to ship sounds, triggers and FMOD banks
- Send data between server and client
- Entity state bags