Sound and music

Deadlock ships 79,222 soundevents and you can play any of them per player from the server with no client files at all. Custom audio is a completely different problem, and this page says exactly where the line is and what crossing it would take.

Playing a stock sound

// To one player. The Deadworks source documents this as "Plays a soundevent to just this player".
uint guid = Players.FromSlot(slot)?.PlaySound(name) ?? 0;

// Other routes
pawn.EmitSound(name, pitch, volume, delay);
Sounds.Play(name, filter, volume, pitch);
Sounds.PlayAt(name, position, ...);
The return value is the single most useful debugging tool here

PlaySound returns the soundevent GUID. A zero means the engine resolved nothing for that name. That is the difference between "the name is wrong" and "the name is fine and you cannot hear it", which are otherwise identical from the outside and which you will otherwise spend an hour confusing.

private readonly HashSet<string> _missing = new();
private uint PlayForSlot(int slot, string sound) {
    uint guid = Players.FromSlot(slot)?.PlaySound(sound) ?? 0;
    if (guid == 0 && _missing.Add(sound))
        Console.WriteLine($"[sound] '{sound}' returned GUID 0, the engine has no such soundevent");
    return guid;
}

The HashSet is there so a bad name in a per-tick path logs once rather than forever.

A valid GUID and still silence: the music mixer

Every Music.* and Stinger.* event sits on a music mix group. Read out of the game's own music.vsndevts:

Base.Music.2d        mixer_mixgroup = "Music"   send_name = "Music"
Music.Brawl.Round.Won  mixer_mixgroup = "Music-Objective"

A player with music turned down in their audio settings hears nothing from those events even when the GUID is valid and the server did everything right. If a sound must be heard, pick one that is not on a music group.

Worth noting as a nuance rather than a contradiction: those same events carry event_use_music_convar = false, so the music convar does not gate them. The mix group volume does.

The round-based soundtrack that is already in the game

Deadlock has a Brawl mode, so it already contains a complete set of round music. Verified present in pak01_dir/soundevents/music.vsndevts. This is why neither of our gamemodes needs any custom audio to feel finished.

SoundeventUse
Music.Brawl.RoundStart1 .. 5Five different round-start tracks. Index by round number for an escalating arc.
Music.Brawl.Round.Won / Music.Brawl.Round.LostPer-squad round result
Music.Brawl.Match.Won / Music.Brawl.Match.LostPer-squad match result
Music.Brawl.DraftBuy or draft phase
Stinger.Brawl.Overtime.AnnouncePairs well with an on-screen card
Stinger.DeathDeath
Stinger.KillStreak.FirstBloodFirst blood
Stinger.Idol.PickedUpGood generic "a thing has appeared"
Powerup.Pickup.PunchPunch feedback
Powerup.Pickup.GenericGeneric pickup
Gameplay.Pause.Resume.Countdown.TickThe buzzer tick for the last seconds of a countdown
Keep every name in config

A wrong soundevent name should be a config edit, not a redeploy. Both gamemodes hold every name in their config object, so a GUID 0 in the log is fixed from the panel in seconds.

Finding a soundevent name

The whole list, greppable

Every soundevent name in the build, extracted from the game's own 149 .vsndevts files on 2026-10-27, is in 04_scripting/soundevents_all.txt as name<TAB>file.

79,220 names, and the split is the useful part: 75,710 of them are generated hero voice lines, which is why the number is so absurd. The other 3,510 are everything a gamemode would ever reach for, and they are listed first in the file under a ### NON-VO EVENTS ### header so you can stop reading at the second header.

grep -i brawl      04_scripting/soundevents_all.txt
grep -iP '^UI\.'    04_scripting/soundevents_all.txt
grep -i powerup    04_scripting/soundevents_all.txt

Rough shape of the 3,510 by prefix: Mods 283, Base 123, UI 117, Stinger 51, Player 45, Ability 36, NPC 41, amb 79, plus 30 to 45 per hero under that hero's name (Haze, Bebop, Lash and so on).

To go to the source, the files themselves are the authority:

cd 09_research_log/github_clones/GameTracking-Deadlock/game/citadel/pak01_dir/soundevents
ls                       # ambience/ base/ hero/ npc/ mods/ music.vsndevts damage.vsndevts ...
grep -rn "Brawl" .       # every Brawl event, with the file and line

They are KV3 text, so they are readable as-is. The header tells you the format:

<!-- kv3 encoding:text:version{...} format:generic:version{...} -->
{
    Music.Brawl.Round.Won =
    {
        base = "Base.Music.2d"
        volume = 4.000000
        event_voice_layer = "ui"
        mixer_mixgroup = "Music-Objective"
        vsnd_files = [ ... ]
    }
}

Note base: soundevents inherit. If an event looks thin, read its base to find the mix group and the volume it is really using.

Confirming one in game before you write code

Both gamemodes have a test command for exactly this. The pattern is worth copying:

/bbtest sound Music.Brawl.RoundStart3

// which reports one of two things, and they mean different things:
//   "played, guid=12345. Silence now means the MUSIC slider, not the name."
//   "resolved to NOTHING (guid 0), that soundevent does not exist"

Positional sound

Sounds.PlayAt(name, position) plays at a point. There is also a point_soundevent entity for positional sound with a self-kill output, confirmed by the community, if you want the sound tied to a thing in the world rather than fired once.

Custom audio, with the launcher and nothing else

Settled by experiment, 2026-10-27

Deadlock does load an extra soundevents file out of a search path, and custom soundevent names resolve. This is no longer an open question.

A file was placed at <install>/game/citadel/soundevents/soundevents_addon.vsndevts declaring two invented names, both pointing at a sound the game already ships. On a live server both resolved, repeatedly:

[BattleBox][bbtest] sound 'Test.Amir.Base'       guid=8
[BattleBox][bbtest] sound 'Test.Amir.Base'       guid=9
[BattleBox][bbtest] sound 'Test.Amir.Base'       guid=10
[BattleBox][bbtest] sound 'Test.Amir.Standalone' guid=11
[BattleBox][bbtest] sound 'Test.Amir.Standalone' guid=12

A non-zero GUID means the engine found the soundevent. A name that does not exist returns zero. Both the form that inherits Base.Ability from another file and the standalone form that inherits nothing worked, and the Counter Strike 2 filename convention (soundevents_addon.vsndevts) is the one Deadlock honoured.

It was silent, and that is the expected result. The file was only on the server. The server resolved the name and handed the client a sound id the client could not map to any audio, because the client had never seen our soundevents file. Resolution and playback are two halves, and this experiment proved the half that was in doubt.

So: yes, custom audio works, and players need nothing but the Deadworks launcher they already need for the HUD. What remains is content delivery to the client, which is the mechanism that already ships our custom maps.

The hard rule you cannot design around

The official Deadworks sound docs are blunt about it:

"A plugin can play any soundevent. It can't play a sound file directly."

So PlaySound resolves a name, and the client must already hold both halves of what that name points at:

  1. the audio itself, as a compiled .vsnd, and
  2. a soundevent declaring that name and pointing at that file

No server call can create either one. Custom audio is therefore a content delivery problem, not a scripting problem. Which is good news, because content delivery to the client is the thing the launcher exists to do.

What the launcher actually does to the client, which is the key fact

From the Deadworks content-discovery guide, in the section written for launcher developers (so this describes the launcher's own behaviour, not a manual step for players):

Why that matters so much

addonroot is Source 2's own native addon mount point. The launcher is not inventing a private delivery channel for maps; it is pointing the engine's standard addon system at a folder it controls, on the player's machine, automatically. Anything the engine normally loads out of an addon is therefore in scope, and the player does nothing but run the launcher.

The chain, with each link's status

#LinkStatus
1Compile your audio to a .vsnd. Needs the Deadlock SDK; there is no resourcecompiler.exe on a dedicated-server install.needs SDK tooling
2Declare a soundevent in a .vsndevts pointing at it. Readable KV3, shape above.done, uncompiled text worked
3Get that .vsndevts loaded by the enginePROVEN 2026-10-27
4Ship it to the client in a content addon vpk, advertised via ContentAddons and fetched by the launcher before connectmechanism proven by our maps
5Mount it on the server from deadworks_mods/vpks/<name>.vpk, or simply drop it loose in game/citadel/loose files work
Two findings that make this easier than expected
  • An uncompiled, loose .vsndevts was read. The probe was plain KV3 text dropped straight into game/citadel/soundevents/, with no vpk and no .vsndevts_c compile step. The server runs with -dev, which may be why. Worth retesting without -dev before relying on it in production.
  • No edit to the game's own manifest was needed. The five soundevents_manifest*.vrman_c files were left completely alone. The engine picked up the addon file by its filename, which means the frightening failure mode this page used to warn about, an addon replacing the game's manifest and killing all audio, never has to be risked.

Link 3, and why it was the one in doubt

Soundevent files are not found by scanning a folder. The base game lists them in resource manifests, and these five are in the pak:

soundevents/soundevents_manifest.vrman_c          971 bytes
soundevents/soundevents_manifest_citadel.vrman_c  5633
soundevents/soundevents_manifest_devonly.vrman_c  912
soundevents/soundevents_manifest_heroes.vrman_c   8982
soundevents/soundevents_manifest_tools.vrman_c    870

Source 2's answer for addons is a known filename the engine looks for inside an addon, rather than an edit to the game's manifest. In Counter Strike 2 that file is soundevents/soundevents_addon.vsndevts: you put your sounds in the addon, declare your events in that file pointing at the compiled .vsnd files, and per Valve's own addon documentation that is all that is required, including to override an existing soundevent.

A correction to an earlier version of this page

This page previously argued there is "no soundevents_addon hook in Deadlock" because no such file appears in the pak listing. That reasoning was wrong. In CS2 that file is not in the base pak either; it is a file you create in your addon and the engine looks for it. Its absence from Deadlock's pak proves nothing either way.

So the real question is narrower and much less frightening than "will this break all audio": it is simply whether Deadlock's build of the engine honours the addon soundevent file, since Valve enables these conventions per game.

What is left to do

Server-side resolution is proven. The remaining work is getting the same two files onto the client, and that is the mechanism our custom maps already use:

  1. Produce the audio. A .wav compiled to .vsnd. This needs the Deadlock SDK, so it happens on a machine with the authoring tools; a dedicated-server install has no resourcecompiler.exe and no vpk.exe.
  2. Pack a vpk containing soundevents/soundevents_addon.vsndevts and sounds/<yours>.vsnd.
  3. Advertise it from the plugin's ContentAddons override and publish it to the FastDL host, exactly as a map is published. The launcher then installs it to citadel/deadworks_addons/vpks/ and the addonroot entry it already writes into the client's gameinfo.gi makes the engine load it.
  4. Mount the same vpk server-side so both halves agree.

The one genuinely untested step is whether a compiled soundevents file inside a vpk behaves the same as the loose uncompiled one did. Given the loose file worked without touching any manifest, there is no reason to expect trouble, and the test is the same one command.

Two other things worth knowing before you start

What to do in the meantime, and where to test it

One more open question

not proven Whether looping music needs a start and stop user message rather than a plain PlaySound. In practice our gamemodes fire one-shot tracks at phase boundaries and have not needed a loop, so this has never been settled. If you want ambient music under a whole round rather than a sting at the start of it, this is the thing to find out first.