Modifiers
A modifier is the game's own buff, debuff and state marker system. Stuns, silences, shields, speed boosts, "is in the shop", "is carrying the idol" and the thing that makes a team change actually network are all modifiers. Applying one is a single call, and most of what goes wrong is in the four failure modes at the bottom of this page.
Adding a modifier
The full signature from the official docs:
// every argument after the name is optional
entity.AddModifier("modifier_citadel_stunned", KeyValues3? kv, caster, ability, team);
In practice there are two shapes worth using.
Plain, with your own timer for the duration
// This is the shape to prefer. It needs no extra type, and the duration is exactly
// what you asked for whatever the modifier's own default happens to be.
pawn.AddModifier("modifier_citadel_cursed");
Timer.Once(3000.Milliseconds(), () => {
try {
var p = Players.FromSlot(slot)?.GetHeroPawn()?.As<CCitadelPlayerPawn>();
if (p != null && p.IsValid) p.RemoveModifier("modifier_citadel_cursed");
} catch { }
});
Re-reading the pawn from the controller inside the timer rather than capturing it is deliberate. Writing to a pawn handle captured earlier is how a Bedwars player once ended up stuck out of the world with no log line to show for it.
With a duration keyvalue
using var kv = new KeyValues3();
kv.SetFloat("duration", 3.0f);
pawn.AddModifier("modifier_citadel_knockdown", kv);
duration is confirmed on modifier_citadel_knockdown. Others want different
fields (power, a target, an ability reference), and a modifier that does not read your
keyvalue will use its own default without complaining. If the length matters, own it with a timer
and treat the keyvalue as a nicety.
Removing a modifier
entity.RemoveModifier("modifier_citadel_stunned"); // by name
entity.RemoveModifier(modifierRef); // by reference
Removing a modifier that is not applied is harmless. That is what makes the timer pattern above safe: if the modifier expired on its own first, your removal is a no-op.
Reading what is applied
entity.ModifierProp.HasModifier("modifier_citadel_cursed"); // bool
entity.ModifierProp.Modifiers; // enumerate them all
entity.ModifierProp.SetModifierState(EModifierState.X, true);
AddModifier returning without throwing is not the same as the modifier
landing. A name the engine does not recognise can simply do nothing at all. If your gamemode depends
on the modifier being on, check HasModifier immediately afterwards and log the answer.
Otherwise your log will cheerfully claim a state that was never applied.
Catching a modifier as it is applied
This hook is more useful than it looks, and it is how BattleBox detects a punch without classifying a single melee. A punchable powerup grants a modifier the instant it breaks, so the modifier arriving is the signal that a punch landed.
public override HookResult OnAddModifier(AddModifierEvent args) {
string name = args.ModifierVData.Name ?? "";
var owner = args.ModifierProperty.Owner; // who it is landing on
// return HookResult.Stop to REFUSE it, so the buff is never actually gained
return HookResult.Continue;
}
A powerup pickup does not grant one modifier. It grants four, and only one of them means anything:
gun_powerup_pickup/near_rejuv // hidden proximity aura, radius 300
gun_powerup_pickup/near_rejuv/target_near_item // the same aura, per target
gun_powerup_pickup/modifier_check_nearby_player_parry
gun_powerup_pickup/gun_powerup_pickup // THE REWARD, only when it breaks
The aura fires roughly every 15 milliseconds just from standing nearby. Returning
Stop for all of them created a grant and refuse loop. The reward is the one where the
text before the slash equals the text after it, so that is the only shape to match:
int sl = name.IndexOf('/');
if (sl > 0 && name.IndexOf('/', sl + 1) < 0) {
string a = name.Substring(0, sl), b = name.Substring(sl + 1);
if (a == b && a.EndsWith("_powerup_pickup")) { /* a punch landed */ }
}
From the console, without a plugin
modifier_create modifier_citadel_disarmed
// with a source path, a target and parameters
modifier_create upgrade_capacitor/modifier_weapon_capacitor_slow player {"duration" "5"}
Useful for finding out whether a modifier does what you think before you write any code. Also
worth knowing: modifier_dump_list is a real cvar and will list them.
The four ways this fails quietly
| Symptom | Cause | What to do |
|---|---|---|
| Nothing happens, no error | The name does not exist in this build | HasModifier straight after. A typo is indistinguishable from a no-op otherwise. |
| It lasts the wrong length of time | The modifier ignored your duration | Enforce it with Timer.Once plus RemoveModifier. |
| It is applied, nothing visible changes | You picked a _tracker or _desc name | Those are UI-only. See the naming rules below. |
| It fires thousands of times | You hooked an aura, not an event | Match the exact modifier name you want, never a prefix. |
Naming rules that save you a test
_buildupis the pre-cast indicator, not the effect. Applying it does nothing useful._trackerand_descare UI-only. They are what the HUD reads, not gameplay._selfagainst_enemyis who receives it. Picking the wrong one applies the effect to the wrong side of the interaction.
The ones worth knowing
Grouped by what you would reach for them for. Every name is in the full list below.
Stopping a player doing things
| Name | Effect |
|---|---|
modifier_citadel_stunned | Classic stun. Blocks movement and actions. |
modifier_citadel_silenced | Blocks abilities. The game's own display name is "Silenced". |
modifier_citadel_disarmed | Blocks weapon attacks. Display name "Disarmed". Proven in a community Manhunt mode. |
modifier_citadel_cursed | Display name "Cursed". BattleBox puts this on a revived player for three seconds. |
modifier_citadel_root | Stops movement but allows attacking, unlike stunned. |
modifier_citadel_pre_match_wait | Valve's own pre-match freeze. The right tool for a countdown phase. |
modifier_citadel_dazed | Daze. |
modifier_citadel_knockdown | Knockdown. Confirmed to read a duration keyvalue. |
modifier_citadel_kelvin_frozen | Kelvin dome freeze. Very strong, may include invulnerability. |
modifier_citadel_muted | Blocks voice chat. |
Defence
| Name | Effect |
|---|---|
modifier_citadel_metal_skin | Near invulnerability. |
modifier_citadel_barrier | Shield. |
modifier_citadel_tech_shield | Spirit shield. Display name "Spirit Shield". |
modifier_citadel_damage_reduction | Generic percentage reduction. |
modifier_citadel_reflect_shield | Reflect. |
modifier_citadel_crystal_shield | Shield, Ice source. |
Movement and stats
| Name | Effect |
|---|---|
modifier_citadel_speed_boost | Speed up. |
modifier_citadel_lineslow / _slowtarget | Slow. |
modifier_citadel_damageboost | Damage up. |
modifier_citadel_rapid_fire | Fire rate up. |
modifier_citadel_bullet_flurry | Bullet flurry. |
modifier_citadel_dodging | Dodge state. |
State markers you can read instead of computing
These are the cheap way to know something about a player without a per-tick distance check.
| Name | Means |
|---|---|
modifier_citadel_in_shop | They are in the shop. |
modifier_citadel_in_fountain | They are in the regen zone. |
modifier_citadel_in_hideout_zone | They are in the Hideout map zone. |
modifier_citadel_holding_golden_idol | They are carrying the urn. |
modifier_citadel_revealed_on_map | They are visible to enemies on the minimap. |
modifier_citadel_sprint_start / _sprint_end | Sprint state transitions. |
The one that is not really a modifier
pawn.AddModifier("citadel_change_team") is the only known safe way to move a live
player between engine teams 2 and 3, and each application flips the pawn between
them rather than setting a side. Writing pawn.TeamNum instead changes the number
server-side and never tells the clients, which makes bullets pass through enemies. Full detail in
Recipes.
All 125 names
Scanned out of the install on 2026-04-21. This is a string scan of the game files, so it is complete for names that appear as literal strings and may miss any that are constructed at runtime. A name being here means it exists in the build; it does not promise it does something useful when applied cold.
The scan only covered modifier_citadel_*. Two other families exist and both matter:
citadel_change_team (no modifier_ prefix at all), and the
modifier_citadel_powerup_* rewards that punchable powerups grant, namely
_powerup_movement, _powerup_survival, _powerup_gun and
_powerup_casting. Item modifiers use modifier_item_* and
upgrade_* namespaces instead.
Verifying a name yourself
The game's own localisation file is the last word on whether a status exists and what it is called in the UI:
grep -n "modifier_citadel_cursed" \
09_research_log/github_clones/GameTracking-Deadlock/game/citadel/resource/\
localization/citadel_attributes/citadel_attributes_english.txt
// 88: "modifier_citadel_cursed" "Cursed"
A name in citadel_attributes_english.txt has a player-visible status. A name that
only turns up in a binary string scan exists but may be internal.