ConVars and commands

3,030 cvars exist. Perhaps forty matter for a gamemode. Every default and description on this page was read from a dump taken off a live server, which is also how we found that one of the cvars our own plugin sets does not exist.

Reading and writing them

ConVar.Find(name);                     // returns ConVar? . NULL means it does not exist.
cv.GetInt() / GetFloat() / GetString();
cv.SetInt(v) / SetFloat(v);
cv.IsValid;
ConVar.Create(name, def, desc, serverOnly);  // your own
The null-conditional is hiding your typos

This line looks like it does something and does nothing at all:

ConVar.Find("citadel_zipline_troopers_enabled")?.SetInt(0);

There is no cvar by that name. Find returned null, ?. skipped the call, nothing was logged, and it sat in BattleBox doing nothing from the day it was written until 2026-10-27, when every cvar the plugin touches was finally checked against the dump. The real one is citadel_trooper_use_ziplines.

Log the miss. Both gamemodes now do this for anything load-bearing:

var ai = ConVar.Find("ai_disabled");
if (ai == null) Console.WriteLine("WARNING: cvar ai_disabled NOT FOUND, the objective will fight back");
else ai.SetInt(1);

ConCommands are not ConVars

Some things that look like cvars are commands, and ConVar.Find will never find them. They have no type and no default in a dump, just a description. Use Server.ExecuteCommand:

Server.ExecuteCommand("citadel_enable_fast_cooldowns 1");
Server.ExecuteCommand("citadel_unlock_flex_slots");        // omit the team for both teams
Server.ExecuteCommand($"changelevel {map}");
ConCommandSignature or effect
citadel_enable_fast_cooldownsEnables fast cooldowns
citadel_disable_fast_cooldownsDisable fast cooldowns
citadel_unlock_flex_slots<team number>, or omit it for both teams
citadel_powerup_spawn_enabledThis one is a bool cvar, but our plugin drives it through ExecuteCommand alongside the two override timings, which also works
citadel_spawn_trooper_ziplineSpawn a trooper on a zipline
modifier_dump_listLists modifiers
ent_create / ent_fire / ent_removeSee Entities
modifier_createSee Modifiers

Your own cvar, and your own command

[ConVar] public int MyThing { get; set; }

[Command("mycmd", Description = "...", ServerOnly = false, ChatOnly = false,
          ConsoleOnly = false, SuppressChat = false, Hidden = false)]
public void CmdMine(CCitadelPlayerController caller, params string[] args) { }

One attribute creates all three forms at once: /mycmd, !mycmd and dw_mycmd. The handler can take a nullable controller for console use, and typed arguments (string, int, float, enum, params, rawArgs). Throw CommandException for a usage error.

[ChatCommand] and [ConCommand] are gone

As of Deadworks v0.5, [ChatCommand] is a compile error and the attribute's own message says "Commands with this attribute are no longer registered". Everything is [Command]. v0.5 also added a real permissions system (dw_role_grant <name> admin), though both our gamemodes still gate on their own SteamID set.

The ones that matter, by what you are trying to do

Make the map quiet

ConVarTypeDefaultEffect
citadel_trooper_spawn_enabledbooltrue"set to false to prevent any troopers from spawning"
citadel_npc_spawn_enabledbooltrue"set to false to prevent any NPC from spawning"
citadel_trooper_use_ziplinesbooltrueTroopers riding ziplines
ai_disabledboolfalseGlobal AI off. Everything you spawn becomes inert scenery that can still be punched.
citadel_powerup_spawn_enabledbooltrueThe map's own powerup camps
citadel_dev_boss_backdoor_protection_disabledboolfalse"Set to Disable Backdoor Protection for Bosses"
ai_disabled is the right answer for an inert objective

A spawned NPC keeps attacking players. The temptation is to fight it per-entity; the global switch is simpler and safe in a gamemode that spawns no other NPCs, and it leaves the thing punchable, which is what an objective needs.

Control the economy

ConVarTypeDefaultEffect
citadel_player_starting_goldint32600"Initial gold for players." Set 0 if your mode hands out its own.
citadel_item_sell_price_ratiofloat320.51.0 is a full refund on sells. One cvar, so the shop's numbers and yours can never disagree.
citadel_allow_purchasing_anywhereboolfalse"If enabled, you can purchase upgrades anywhere". This is how you gate a shop phase.
citadel_hero_demo_unlock_flex_slotsboolfalse"Do we start flex slots unlocked"
citadel_tick_gold_start_timefloat32480Passive income starts at 8 minutes of match time
citadel_tick_gold_payout_for_lowestfloat320.025Passive payout share
citadel_tick_gold_payout_for_second_lowestfloat320.015Passive payout share
The mystery souls, and where they came from

Souls started appearing out of nowhere partway through BattleBox matches. The cause was citadel_tick_gold_start_time: the game pays passive tick gold on a timer of its own, starting at 480 seconds, which lands somewhere in round three. Push the start time out of reach and zero both payout shares, and your mode's economy is the only thing handing out souls:

ConVar.Find("citadel_tick_gold_start_time")?.SetFloat(999999f);
ConVar.Find("citadel_tick_gold_payout_for_lowest")?.SetFloat(0f);
ConVar.Find("citadel_tick_gold_payout_for_second_lowest")?.SetFloat(0f);

Gate the shop with citadel_allow_purchasing_anywhere rather than refusing purchases in code. The engine then simply will not let them buy, so there is no way to end up with an item and no charge, or a half-finished transaction.

Shape a match

ConVarTypeDefaultEffect
citadel_allow_duplicate_heroesboolfalse"If enabled, heroes can be selected by multiple players". Essential for small custom modes.
citadel_start_players_on_ziplinebooltrueSet false or everyone starts on a zipline instead of where you put them
citadel_spawn_practice_botsboolfalseBots. Needs a changelevel to take effect.
citadel_spawn_practice_bots_countint321How many
sv_cheatsboolfalseKeep it on, or the engine teleports players who fly above the vanilla height limit back down
Bot spawning is not a command

citadel_add_bot and bot_add do not exist in Deadlock. The working method is two cvars and a map reload:

ConVar.Find("citadel_spawn_practice_bots")?.SetInt(1);
ConVar.Find("citadel_spawn_practice_bots_count")?.SetInt(1);
Server.ExecuteCommand($"changelevel {Server.MapName}");

Note also that bots do not work on large custom maps: navmesh generation runs out of memory.

Server hygiene

ConVarTypeDefaultEffect
sv_hibernate_when_emptybooltrue"Puts the server into extremely low CPU usage mode when no clients connected". Our lobby launches set it to 0, which is why an abandoned lobby burns a core at full tick until something kills it. If you turn it off, you need a reaper.
sv_max_unreliable_delta_sizeint324096"Maximum allowable entity delta size over unreliable delivery." Setting it to 1 is the known fix for the Bedwars join freeze.

Powerup camp timings

Both override cvars are -1 by default, meaning "use the normal behaviour", and the normal interval is 300 seconds. That is why a freshly placed spawner looks empty for five minutes. The engine's own descriptions:

ConVarDefaultDescription, verbatim
citadel_powerup_initial_spawn_time_override-1"If positive, override initial spawn time (in seconds) for all powerup camps"
citadel_powerup_spawn_interval_override-1"If positive, override initial spawn interval (in seconds) for all powerup camps"

Note the words all powerup camps. These are global, so they also speed up any camps the map ships. See the warning at the bottom of Death and respawn.

Respawn

Nineteen cvars, with the full ramp model and how to pin it flat, on its own page: Death, respawn and revive.

Dumping them yourself

The dump in the KB has 3,030 entries with type, default, min, max and description, tab separated:

# find one, with its real default and the engine's own description
grep -P "^citadel_player_override_spawn_time\t" 06_server/cvars_dump_raw.txt

# every cvar in a family, formatted
grep -i "spawn_time" 06_server/cvars_dump_raw.txt \
  | awk -F'\t' '{printf "%-52s %-9s %s\n", $1, $2, $3}'

# 895 citadel_* cvars grouped by what they are for
less 06_server/cvars_by_category.md
Check your plugin against the dump once

It takes a minute and it found a dead line that had been there for weeks. Pull every cvar name your plugin sets and grep each one:

grep -o 'ConVar.Find("[^"]*"' YourPlugin.cs | cut -d'"' -f2 | sort -u \
  | while read c; do grep -qP "^\Q$c\E\t" cvars_dump_raw.txt || echo "MISSING: $c"; done