Game Events
Game events are a legacy Source 2 mechanism and Valve has been retiring them over time - some events no longer fire in current CS2 builds. Prefer Game Hooks or Core Events when an equivalent exists.
SwiftlyS2 generates a strongly-typed interface for every CS2 game event, exposed through the plugin-scoped Core.GameEvent service (APIIGameEventService).
Firing Events
Fire broadcasts to every player, FireToPlayer targets a slot, FireToServer stays server-side. Each has an async counterpart (FireAsync, FireToPlayerAsync, FireToServerAsync) for off-main-thread use - the sync overloads are thread-unsafe.
// To everyone, with an optional configure callback
Core.GameEvent.Fire<EventShowSurvivalRespawnStatus>(@event =>
{
@event.LocToken = "survival_respawn_status";
@event.Duration = 5;
});
// To one player slot
Core.GameEvent.FireToPlayer<EventShowSurvivalRespawnStatus>(0, @event =>
{
@event.LocToken = "private_status";
});
// Server-side only
Core.GameEvent.FireToServer<EventShowSurvivalRespawnStatus>();
// Off the main thread
await Core.GameEvent.FireAsync<EventShowSurvivalRespawnStatus>();The event instance passed to a configure callback is only valid for that callback's duration - don't stash and read it later.
Hooking Events
Register hooks by attribute or manually through the service, in Pre or Post mode. Returning HookResult.Stop from a pre-hook cancels the event.
[GameEventHandler(HookMode.Pre)]
public HookResult OnPlayerDeathPre(EventPlayerDeath @event)
{
Console.WriteLine($"victim={@event.UserId}, attacker={@event.Attacker}");
return HookResult.Continue;
}
[GameEventHandler(HookMode.Post)]
public HookResult OnPlayerDeathPost(EventPlayerDeath @event)
{
Console.WriteLine($"headshot={@event.Headshot}, weapon={@event.Weapon}");
return HookResult.Continue;
}Drop every hook registered for an event type at once:
Core.GameEvent.UnhookPre<EventPlayerDeath>();
Core.GameEvent.UnhookPost<EventPlayerDeath>();Reading Event Data
Every generated event implements APIIGameEvent`1, giving you Accessor (APIIGameEventAccessor) and a settable DontBroadcast flag. Recognized fields get typed properties directly on the event - for EventPlayerDeath: int UserId/Attacker/Assister, string Weapon, bool Headshot, float Distance, int ActualDmgHealth, plus IPlayer? convenience wrappers (UserIdPlayer, AttackerPlayer, AssisterPlayer) resolved through the player manager:
[GameEventHandler(HookMode.Post)]
private HookResult OnPlayerDeath(EventPlayerDeath @event)
{
if (@event.AttackerPlayer is { } attacker && @event.UserIdPlayer is { } victim)
{
Console.WriteLine($"{attacker.Name} killed {victim.Name} with {@event.Weapon}");
}
return HookResult.Continue;
}Fall back to Accessor for fields without a generated property:
string weapon = @event.Accessor.GetString("weapon");
int attackerSlot = @event.Accessor.GetPlayerSlot("attacker");See APIIGameEventAccessor for the full set of typed getters/setters (bool, int32, uint64, float, string, entity, entity index, player slot) plus read-only GetPlayer/GetPlayerController/GetPlayerPawn/IsReliable()/IsLocal().
Check whether a slot is listening for an event with APIIGameEventService.IsListeningToEvent - by event name ("player_death") or the typed IsListeningToEvent<EventPlayerDeath>(playerId) overload.