Skip to main content

Game Events

Namespace: DeadworksManaged.Api

Listen to and create Source 2 game events.

GameEventHandlerAttribute

Auto-register a method as a game event handler:

[GameEventHandler("player_hero_changed")]
public HookResult OnPlayerHeroChanged(PlayerHeroChangedEvent args)
{
var pawn = args.Userid?.As<CCitadelPlayerPawn>();
if (pawn != null)
{
// Do stuff
}
return HookResult.Continue;
}

Typed Event Classes

For every event in the game's .gameevents schema, the SDK source-generates a typed class named after the event in PascalCase (player_hero_changedPlayerHeroChangedEvent, player_deathPlayerDeathEvent), with one property per field. Your handler can take either the typed class or the untyped GameEvent (with GetString/GetInt/GetFloat/GetBool/GetPlayerPawn/GetPlayerController/GetEHandle accessors).

Fields typed player_controller_and_pawn in the schema (e.g. player_death.userid/attacker) generate split propertiesUseridController/UseridPawn, AttackerController/AttackerPawn — there is no single Userid property on those events. Fields typed player_pawn (like player_hero_changed.userid) generate a single pawn-typed property.

You can also subscribe dynamically without an attribute via GameEvents.AddListener(name, handler) / GameEvents.RemoveListener(...).

GameEventWriter

Wraps a newly created game event for setting fields and firing.

var ev = GameEvents.Create("my_custom_event");
if (ev != null)
{
ev.SetInt("player_slot", 0);
ev.SetString("action", "test");
ev.Fire();
// After firing, the event is owned by the engine — do not use it again
}

Methods

MethodReturnsDescription
SetString(string key, string value)GameEventWriterSet string field (chainable)
SetInt(string key, int value)GameEventWriterSet int field (chainable)
SetFloat(string key, float value)GameEventWriterSet float field (chainable)
SetBool(string key, bool value)GameEventWriterSet bool field (chainable)
Fire(bool dontBroadcast)boolFires the event. Returns success. After firing, owned by engine — must not be used

Common Game Events

Event NameTypical fieldsNotes
player_deathuserid, attackerFires on hero death
player_spawnuseridSee warning below — fires before the pawn is fully materialized on the first spawn
player_hero_changeduseridMore reliable than player_spawn for post-hero-select logic
player_used_abilityplayer, caster, abilityname, annotationFires for every ability activation including shots (citadel_weapon_*) and melee (ability_melee_*)
ability_addeduserid, ability (ehandle)Fires when an ability/item is granted — use this to detect item purchases
game_state_changedgame_state_newFires at major match transitions, including end-of-match

The full list of Source 2 events shipped by Deadlock is available at SteamTracking-Deadlock/resource/core.gameevents and game.gameevents. These files list every event name and its field schema.

Common Patterns

Detecting a melee attack

[GameEventHandler("player_used_ability")]
public HookResult OnAbility(GameEvent ev)
{
var name = ev.GetString("abilityname", "");
if (!name.StartsWith("ability_melee")) return HookResult.Continue;

// annotation tells heavy vs light melee (event keys are case-sensitive)
var kind = ev.GetString("annotation", ""); // "heavy_melee" or "light_melee"
Console.WriteLine($"Melee attack: {kind}");
return HookResult.Continue;
}

You could also use OnAbilityAttempt with InputButton.Weapon1, but player_used_ability fires only on successful activation and gives you the heavy/light distinction for free.

Detecting a weapon shot

Same event, different prefix check:

if (name.StartsWith("citadel_weapon_")) { /* shot fired */ }

Detecting an item purchase

[GameEventHandler("ability_added")]
public HookResult OnAbilityAdded(GameEvent ev)
{
var pawn = ev.GetPlayerPawn("userid")?.As<CCitadelPlayerPawn>();
if (pawn == null) return HookResult.Continue;

// ability_added fires both for ability unlocks and for item purchases.
// Walk the pawn's abilities on the next tick to see what's actually there.
Timer.Once(1.Seconds(), () =>
{
foreach (var ab in pawn.AbilityComponent.Abilities)
if (ab.IsItem && ab.AbilityName == "upgrade_unstoppable")
Console.WriteLine("Bought Unstoppable!");
});
return HookResult.Continue;
}

Detecting match end

The cleanest signal is game_state_changed.

player_spawn Is Racy on First Spawn

The player_spawn event fires before the pawn is fully populated the first time a player connects. Subsequent spawns are fine. If your handler dereferences hero data (GetHeroPawn, AbilityComponent, etc.), either:

  • Subscribe to player_hero_changed instead — it fires after hero data is ready, and
  • Use Timer.NextTick or a short delay to re-check the pawn before touching it.
[GameEventHandler("player_hero_changed")]
public HookResult OnPlayerHeroChanged(PlayerHeroChangedEvent args)
{
var pawn = args.Userid?.As<CCitadelPlayerPawn>();
if (pawn == null) return HookResult.Continue;
// safe to inspect pawn's hero-specific state here
return HookResult.Continue;
}

See Also

  • Players — Player pawn and controller access