Search docs
Search modules and symbols
Modules

Docs / API / Modules / plugin

Plugin

Platform

@s2script/sdk/plugin

Load-window authoring: hook (game-event catalog: hook.on / hook.onPre), previous(), pluginId(), publish / use, onOutput, createScope, Scope. The artifact is export function OnPluginStart (plus optional named publics).

Import

import { CtxEvents } from "@s2script/sdk/plugin";

Author-time types ship in the npm package; the engine injects the runtime at plugin load. Add @s2script/sdk to your plugin's dependencies.

API reference

Generated from the shipped type definitions.

interface
CtxEvents

Game-event subscriptions on this plugin's load-scope (PluginContext.events).

on(name: string, handler: (ev: GameEvent) => void): void

Subscribe to a fired game event (post-phase). The GameEvent is valid only synchronously.

// plugins/disabled/nextmap/src/plugin.ts:133
ctx.events.on("round_end", () => console.log("round ended"));
onPre(name: string, handler: (ev: GameEvent) => HookResultValue | void): void
Pre-hook a game event: return a HookResultValue (Handled/Stop suppress the client broadcast).
interface
CtxClients

Handlers fire for clients that connect AFTER Active. To cover already-connected clients, seed explicitly in OnPluginStart: for (const c of Clients.all()) { … } — there is no framework replay (replaying onConnect for pre-existing clients would fire auth/ban/reservation logic out of its real order).

onConnect(handler: (client: Client) => void | Promise<void>): void
A client began connecting (pre-auth).
onPutInServer(handler: (client: Client) => void | Promise<void>): void
A client's entity was put in the server (ClientPutInServer).
onActive(handler: (client: Client) => void | Promise<void>): void
A client became fully active (in-game, receiving snapshots).
onFullyConnect(handler: (client: Client) => void | Promise<void>): void
A client finished authenticating (Steam ticket validated).
onDisconnect(handler: (client: Client) => void): void
A client disconnected.
onSettingsChanged(handler: (client: Client) => void): void
A client's convars/settings changed (ClientSettingsChanged).
onVoice(handler: (client: Client) => void): void
A client sent a voice packet (per-frame while speaking).
onCookiesCached(handler: (client: Client) => void): void
A client's persisted cookies finished loading and are now readable.
onSay(handler: (slot: number, text: string, teamonly: boolean) => HookResultValue | void): void

A client sent chat: return a HookResultValue to suppress it.

teamonly
team-channel say.
onRunCmd(handler: (cmd: UserCmdView, info: { slot: number }) => HookResultValue | void): void
Per-tick usercmd hook (SM OnPlayerRunCmd): read/modify UserCmdView; return Handled to block the tick.
interface
CtxEntities

Entity lifecycle / I/O subscriptions on this plugin's load-scope (PluginContext.entities).

onCreate(className: string, handler: (entity: EntityRef | null, className: string) => void): void

An entity of className was created (not yet spawned).

className
match, or "*" for all.
onSpawn(className: string, handler: (entity: EntityRef | null, className: string) => void): void
An entity of className spawned (post-DispatchSpawn).
onDelete(className: string, handler: (entity: EntityRef | null, className: string) => void): void
An entity of className is being deleted; the ref goes stale right after.
onOutput(classname: string, output: string, handler: (ev: OutputEvent) => HookResultValue | void): void
Hook a named entity output (FireOutputInternal); return a HookResultValue to suppress it.
interface
CtxServer

Per-frame + map/precache hooks on this plugin's load-scope (PluginContext.server).

onGameFrame(fn: () => void, opts?: { priority?: "high" | "normal" | "low" | "monitor"; phase?: "pre" | "post" }): void
Run fn every game frame. phase picks WHERE in the frame it runs and defaults to "pre" (before simulation). Use "post" when the work must land after the engine's own per-frame writes — re-asserting a netvar the engine re-derives during simulation is overwritten if written in "pre", because the derivation happens after and the outgoing snapshot carries the engine's value.
onMapStart(handler: (mapName: string) => void): void
A new map became live; mapName is the BSP name.
onPrecache(handler: (pc: PrecacheContext) => void): void
Precache window — register models/sounds to precache for the current map.
interface
CtxCommands

Console/chat command registration on this plugin's load-scope (PluginContext.commands).

register(name: string, handler: CommandHandler): void
Register a public command (any client may run it).
registerServer(name: string, handler: CommandHandler): void
Register a server-only command (console/rcon, not client-runnable).
registerAdmin(name: string, flags: number, handler: CommandHandler): void
Register an admin command gated by flags (an ADMFLAG bitmask; fail-safe default-deny).
onClientCommand(name: string, handler: (slot: number, argString: string) => HookResultValue | void): void
Observe an existing CLIENT command by name — SourceMod's AddCommandListener. For engine-owned commands (player_ping, jointeam, drop), which register cannot claim. Observe-by-default: the engine still handles it unless the handler returns >= HookResult.Handled. Unsubscribed automatically when the plugin unloads.
interface
CtxTranslations

The phrase files this plugin uses (PluginContext.translations). Nothing is loaded automatically — a plugin declares what it needs, the same rule SourceMod's LoadTranslations enforces. The build reads this call to work out which keys cmd.replyT and Translations.translate will accept, so a key from a file you did not load is a compile error.

load(...names: string[]): void

Load translations/<name>.phrases.json for each name, in the order given. Order is significant: translate takes the first hit within each of its two passes (the client's language, then English), so list your own set before any shared one if you want to be able to override a shared phrase.

ctx.translations.load("basecomm", "common");
interface
CtxConfig

Config live-reload subscription on this plugin's load-scope (PluginContext.config).

onChange(handler: (cfg: Config) => void): void
Fires when the plugin's config file is re-materialized on disk; re-read values inside.
interface
CtxTopMenu

TopMenu (adminmenu) contribution on this plugin's load-scope (PluginContext.topmenu).

addCategory(name: string): void
Add (or reuse) a top-level menu category.
addItem(category: string, item: TopMenuItem): void
Add an item under an existing category.
type
InterfaceHandle

A producer-backed inter-plugin interface: its methods, plus forward subscriptions.

interface
Scope

A disposable bundle of subscriptions (PluginContext.createScope). Registering through a scope lets you drop the whole group at once with Scope.clear without unloading the plugin.

readonly events: CtxEvents
Game-event subscriptions bound to this scope.
readonly clients: CtxClients
Client-lifecycle subscriptions bound to this scope.
readonly entities: CtxEntities
Entity lifecycle / I/O subscriptions bound to this scope.
readonly server: CtxServer
Per-frame/map subscriptions bound to this scope.
clear(): void
Remove every subscription this scope holds; the scope stays usable (re-register on next open).
dispose(): void
clear() + permanently retire the scope. Idempotent.
readonly disposed: boolean
True once Scope.dispose has run.
interface
PluginContext

The load-scoped context the host builds for one plugin load. Public authoring uses hook / command / named publics instead of receiving this object. Scope reuses the same subscription namespaces.

readonly id: string
This plugin's id (manifest id).
readonly previous: unknown
The revived hot-reload handoff (the previous instance's state() return), or undefined.
readonly events: CtxEvents
Game-event subscriptions (CtxEvents).
readonly clients: CtxClients
Client-lifecycle subscriptions (CtxClients).
readonly entities: CtxEntities
Entity lifecycle / I/O subscriptions (CtxEntities).
readonly server: CtxServer
Per-frame/map/precache subscriptions (CtxServer).
readonly commands: CtxCommands
Console/chat command registration (CtxCommands).
readonly config: CtxConfig
Config live-reload subscription (CtxConfig).
readonly translations: CtxTranslations
Phrase files this plugin uses (CtxTranslations).
readonly topmenu: CtxTopMenu
TopMenu (adminmenu) contribution (CtxTopMenu).
publish(name: string, impl: T): PublishHandle
Publish this plugin's manifest-declared interface. Buffered; goes live at Active.
use(name: string): InterfaceHandle<T>
Resolve a HARD dep (must be in pluginDependencies). Immediate — the proxy is callable during OnPluginStart.
tryUse(name: string): InterfaceHandle<T> | null
Resolve an OPTIONAL dep (must be in optionalPluginDependencies); null while unpublished.
createScope(): Scope
Allocate a disposable subscription scope (load-window only — the capability originates at load).
interface
PluginHooks

Optional lifecycle hooks a plugin may return to participate in unload + hot-reload. Public authoring uses export function OnPluginEnd / OnPluginState instead.

onUnload(): void
Best-effort cleanup at unload (the ledger remains the teardown authority).
state(): unknown
Hot-reload handoff capture; JSON-serialized (EntityRef-aware) and revived as the next instance's previous.
const
hook

Game-event catalog. Load-window only — throws after settle (same window as command). hook.on is post-fire; hook.onPre is pre (Handled/Stop suppress the client broadcast). The GameEvent is valid only synchronously.

on(name: string, handler: (ev: GameEvent) => void): void
Post-phase game-event subscribe. The GameEvent is valid only synchronously.
onPre(name: string, handler: (ev: GameEvent) => HookResultValue | void): void
Pre-phase game-event subscribe. Return HookResultValue Handled/Stop to suppress the client broadcast.
fn
onOutput(classname: string, output: string, handler: (ev: OutputEvent) => HookResultValue | void): void

Load-window entity I/O subscribe (SourceMod HookEntityOutput). Keyed by (classname, output) at the native mux — use "*" for either side. Throws after settle.

fn
previous(): unknown

The revived hot-reload handoff (the previous instance's OnPluginState return), or undefined. Load-window only.

fn
pluginId(): string

This plugin's id (manifest id). Load-window only.

fn
createScope(): Scope

Allocate a disposable subscription scope. Load-window only — same contract as PluginContext.createScope.

fn
publish(name: string, impl: T): PublishHandle

Publish this plugin's manifest-declared interface. Load-window only (buffered, armed at Active). Same contract as PluginContext.publish.

fn
use(name: string): InterfaceHandle<T>

Resolve a HARD dep (must be in pluginDependencies). Load-window only. Same contract as PluginContext.use. Prefer import { greet } from "@demo/greeter" for the producer-as-import form; use() remains the explicit load-window form and the optional-dep path.

fn
tryUse(name: string): InterfaceHandle<T> | null

Resolve an OPTIONAL dep (must be in optionalPluginDependencies); null while unpublished. Load-window only. Same contract as PluginContext.tryUse.

const
topmenu

Load-window TopMenu contribution. Same contract as PluginContext.topmenu. Throws after settle.

const
translations

Load-window phrase-file declaration. Same contract as PluginContext.translations. Throws after settle. s2s build / sync-phrase-types.mjs collect translations.load(...) the same way they collect ctx.translations.load(...).

Back to modules All packages

s2script — Source 2 plugin framework

GitHub