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
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}");
| ConCommand | Signature or effect |
|---|---|
citadel_enable_fast_cooldowns | Enables fast cooldowns |
citadel_disable_fast_cooldowns | Disable fast cooldowns |
citadel_unlock_flex_slots | <team number>, or omit it for both teams |
citadel_powerup_spawn_enabled | This one is a bool cvar, but our plugin drives it through ExecuteCommand alongside the two override timings, which also works |
citadel_spawn_trooper_zipline | Spawn a trooper on a zipline |
modifier_dump_list | Lists modifiers |
ent_create / ent_fire / ent_remove | See Entities |
modifier_create | See 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.
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
| ConVar | Type | Default | Effect |
|---|---|---|---|
citadel_trooper_spawn_enabled | bool | true | "set to false to prevent any troopers from spawning" |
citadel_npc_spawn_enabled | bool | true | "set to false to prevent any NPC from spawning" |
citadel_trooper_use_ziplines | bool | true | Troopers riding ziplines |
ai_disabled | bool | false | Global AI off. Everything you spawn becomes inert scenery that can still be punched. |
citadel_powerup_spawn_enabled | bool | true | The map's own powerup camps |
citadel_dev_boss_backdoor_protection_disabled | bool | false | "Set to Disable Backdoor Protection for Bosses" |
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
| ConVar | Type | Default | Effect |
|---|---|---|---|
citadel_player_starting_gold | int32 | 600 | "Initial gold for players." Set 0 if your mode hands out its own. |
citadel_item_sell_price_ratio | float32 | 0.5 | 1.0 is a full refund on sells. One cvar, so the shop's numbers and yours can never disagree. |
citadel_allow_purchasing_anywhere | bool | false | "If enabled, you can purchase upgrades anywhere". This is how you gate a shop phase. |
citadel_hero_demo_unlock_flex_slots | bool | false | "Do we start flex slots unlocked" |
citadel_tick_gold_start_time | float32 | 480 | Passive income starts at 8 minutes of match time |
citadel_tick_gold_payout_for_lowest | float32 | 0.025 | Passive payout share |
citadel_tick_gold_payout_for_second_lowest | float32 | 0.015 | Passive payout share |
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
| ConVar | Type | Default | Effect |
|---|---|---|---|
citadel_allow_duplicate_heroes | bool | false | "If enabled, heroes can be selected by multiple players". Essential for small custom modes. |
citadel_start_players_on_zipline | bool | true | Set false or everyone starts on a zipline instead of where you put them |
citadel_spawn_practice_bots | bool | false | Bots. Needs a changelevel to take effect. |
citadel_spawn_practice_bots_count | int32 | 1 | How many |
sv_cheats | bool | false | Keep it on, or the engine teleports players who fly above the vanilla height limit back down |
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
| ConVar | Type | Default | Effect |
|---|---|---|---|
sv_hibernate_when_empty | bool | true | "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_size | int32 | 4096 | "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:
| ConVar | Default | Description, 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
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