@s2script/sdk 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/events.d.ts CHANGED
@@ -3,18 +3,27 @@
3
3
  /** A live game-event accessor. Valid ONLY during the synchronous handler — read fields before any `await`;
4
4
  * a stashed GameEvent used later reads defaults. The raw engine event never crosses to JS. */
5
5
  export declare class GameEvent {
6
+ /** The event's name (e.g. `"player_death"`). */
6
7
  readonly name: string;
8
+ /** Read an integer field. `0` if absent or non-integer. */
7
9
  getInt(key: string): number;
10
+ /** Read a float field. `0` if absent or non-float. */
8
11
  getFloat(key: string): number;
12
+ /** Read a boolean field. `false` if absent. */
9
13
  getBool(key: string): boolean;
14
+ /** Read a string field. `""` if absent. */
10
15
  getString(key: string): string;
11
16
  /** A 64-bit field as a decimal string (SM-parity, wire-safe). */
12
17
  getUint64(key: string): string;
13
18
  /** A player field (e.g. "userid"/"attacker") as a 0-based slot, or -1 if absent. Resolve with Player.fromSlot. */
14
19
  getPlayerSlot(key: string): number;
20
+ /** Overwrite an integer field on the live event (valid only in the synchronous handler). */
15
21
  setInt(key: string, value: number): void;
22
+ /** Overwrite a float field on the live event (valid only in the synchronous handler). */
16
23
  setFloat(key: string, value: number): void;
24
+ /** Overwrite a boolean field on the live event (valid only in the synchronous handler). */
17
25
  setBool(key: string, value: boolean): void;
26
+ /** Overwrite a string field on the live event (valid only in the synchronous handler). */
18
27
  setString(key: string, value: string): void;
19
28
  /** Set a 64-bit field from a decimal string (SM-parity, wire-safe). */
20
29
  setUint64(key: string, value: string): void;
@@ -22,9 +31,26 @@ export declare class GameEvent {
22
31
 
23
32
  /** Collapsed pre-hook result. Return from an `onPre` handler; `Handled`/`Stop` suppress the client
24
33
  * broadcast (server still processes). Returning nothing = `Continue`. */
25
- export declare const HookResult: { readonly Continue: 0; readonly Changed: 1; readonly Handled: 2; readonly Stop: 3 };
34
+ export declare const HookResult: {
35
+ /** No change — proceed normally. Equivalent to returning nothing. */
36
+ readonly Continue: 0;
37
+ /** Fields were modified in place; proceed with the changes (the client broadcast still happens). */
38
+ readonly Changed: 1;
39
+ /** Suppress the client broadcast (the server still processes the event). */
40
+ readonly Handled: 2;
41
+ /** Suppress the client broadcast AND stop running lower-priority handlers. The collapse takes the MAX. */
42
+ readonly Stop: 3;
43
+ };
44
+ /** The numeric union an `onPre` handler may return — one of the {@link HookResult} values. */
26
45
  export type HookResultValue = 0 | 1 | 2 | 3;
27
46
 
47
+ /**
48
+ * Fire and target game events from JS.
49
+ * @example
50
+ * import { Events } from "@s2script/sdk/events";
51
+ * // plugins/playercommands/src/plugin.ts:78 — announce a name change
52
+ * Events.fire("player_changename", { userid: p.userId, oldname, newname });
53
+ */
28
54
  export declare const Events: {
29
55
  /** Fire a game event. Returns the engine FireEvent result. */
30
56
  fire(name: string, fields?: Record<string, number | string | boolean | bigint>, dontBroadcast?: boolean): boolean;
package/globals.d.ts CHANGED
@@ -3,7 +3,12 @@
3
3
  // typecheck gate as a root file so plugins that use these globals WITHOUT importing type-check against
4
4
  // the real sandbox — NOT lib.dom (the sandbox has no window/document/etc.).
5
5
 
6
- /** The engine-injected console (a subset of the browser/node console). */
6
+ /**
7
+ * The engine-injected console (a subset of the browser/node console). Global — no import needed.
8
+ * @example
9
+ * // plugins/antiflood/src/plugin.ts:48 — write a line to the server console/log
10
+ * console.log("[antiflood] onLoad — chat flood protection active");
11
+ */
7
12
  declare const console: {
8
13
  log(...data: any[]): void;
9
14
  error(...data: any[]): void;
package/http.d.ts CHANGED
@@ -1,11 +1,42 @@
1
1
  /** @s2script/http — async HTTP. NO runtime code (injected as __s2pkg_http). */
2
- export interface FetchOptions { method?: string; headers?: Record<string, string>; body?: string; timeoutMs?: number; }
2
+
3
+ /** Options for {@link fetch}. */
4
+ export interface FetchOptions {
5
+ /** HTTP method (e.g. `"GET"`, `"POST"`). @defaultValue `"GET"` */
6
+ method?: string;
7
+ /** Request headers as a name→value map. */
8
+ headers?: Record<string, string>;
9
+ /** Request body (already-serialized string; set your own `content-type`). */
10
+ body?: string;
11
+ /** Abort the request after this many milliseconds. @defaultValue no timeout */
12
+ timeoutMs?: number;
13
+ }
14
+
15
+ /** The response from a {@link fetch} call — a copied snapshot (no live socket). */
3
16
  export interface Response {
4
- readonly status: number; readonly ok: boolean; readonly statusText: string;
17
+ /** HTTP status code (e.g. `200`, `404`). */
18
+ readonly status: number;
19
+ /** True iff {@link Response.status} is in the 2xx range. */
20
+ readonly ok: boolean;
21
+ /** HTTP status reason phrase (e.g. `"Not Found"`). */
22
+ readonly statusText: string;
23
+ /** Response headers as a lower-cased name→value map. */
5
24
  readonly headers: Record<string, string>;
25
+ /** The response body decoded as text. */
6
26
  text(): string;
27
+ /** Parse the response body as JSON into `T` (throws on invalid JSON). */
7
28
  json<T = unknown>(): T;
8
29
  }
9
- /** Perform an HTTP request off the game thread. Rejects on a network error / timeout; an HTTP
10
- * status (incl. 4xx/5xx) RESOLVES with ok=false. */
30
+
31
+ /**
32
+ * Perform an HTTP request off the game thread.
33
+ * @param url - Absolute `http(s)://` URL.
34
+ * @param options - Method, headers, body, and timeout (see {@link FetchOptions}).
35
+ * @returns Resolves for ANY HTTP response — a 4xx/5xx resolves with `ok=false`, it does not reject.
36
+ * @throws Rejects only on a network error or timeout.
37
+ * @example
38
+ * import { fetch } from "@s2script/sdk/http";
39
+ * const r = await fetch("https://httpbin.org/get", { timeoutMs: 15000 });
40
+ * if (r.ok) console.log(r.json<{ url: string }>().url);
41
+ */
11
42
  export declare function fetch(url: string, options?: FetchOptions): Promise<Response>;
package/interfaces.d.ts CHANGED
@@ -8,6 +8,13 @@
8
8
  * events to every plugin subscribed to this interface via its `on(event, …)`.
9
9
  */
10
10
  export interface PublishHandle {
11
- /** Emit a forwarded event to all consumers subscribed via `interface.on(event, …)`. */
11
+ /**
12
+ * Emit a forwarded event to all consumers subscribed via `interface.on(event, …)`. The payload is
13
+ * structured-copied across the context boundary (JSON, EntityRef-aware — never a live reference).
14
+ * @example
15
+ * import type { PublishHandle } from "@s2script/sdk/interfaces";
16
+ * // plugins/zones/src/plugin.ts:30 — notify consumers a zone was created
17
+ * iface.emit("created", { zone: z.name, min: z.min, max: z.max, tags: z.tags });
18
+ */
12
19
  emit(event: string, payload: unknown): void;
13
20
  }
package/math.d.ts CHANGED
@@ -3,23 +3,37 @@
3
3
  * NO runtime code: the engine injects the implementation (core prelude) at load time.
4
4
  */
5
5
 
6
- /** A 3-component vector value (a copied snapshot; never a live pointer). */
6
+ /**
7
+ * A 3-component vector value (a copied snapshot; never a live pointer).
8
+ * @example
9
+ * import { Vector } from "@s2script/sdk/math";
10
+ * const start = new Vector(0, 0, 100);
11
+ * console.log(start.length());
12
+ */
7
13
  export declare class Vector {
14
+ /** The X component. */
8
15
  x: number;
16
+ /** The Y component. */
9
17
  y: number;
18
+ /** The Z component. */
10
19
  z: number;
11
20
  constructor(x: number, y: number, z: number);
12
21
  /** Euclidean magnitude — e.g. speed from a velocity vector. */
13
22
  length(): number;
23
+ /** Format as `"Vector(x, y, z)"` (for logging). */
14
24
  toString(): string;
15
25
  }
16
26
 
17
27
  /** A Source 2 Euler angle value (x=pitch, y=yaw, z=roll), a copied snapshot. */
18
28
  export declare class QAngle {
29
+ /** Pitch, in degrees. */
19
30
  x: number;
31
+ /** Yaw, in degrees. */
20
32
  y: number;
33
+ /** Roll, in degrees. */
21
34
  z: number;
22
35
  constructor(x: number, y: number, z: number);
36
+ /** Format as `"QAngle(x, y, z)"` (for logging). */
23
37
  toString(): string;
24
38
  }
25
39
 
package/menu.d.ts CHANGED
@@ -1,12 +1,62 @@
1
1
  /** @s2script/menu — interactive player menus (chat + center backends). NO runtime code (injected at load). */
2
- export declare const enum MenuStyle { Chat = "chat", Center = "center" }
3
- export declare const enum MenuCancelReason { Exit = 0, Timeout = 1, Disconnect = 2, NewMenu = 3 }
4
2
 
5
- export interface MenuSelectEvent { readonly slot: number; readonly item: number; readonly info: string; readonly display: string; }
6
- export interface MenuCancelEvent { readonly slot: number; readonly reason: MenuCancelReason; }
3
+ /** The display backend a {@link Menu} renders through — the value of {@link Menu.style}. */
4
+ export declare const enum MenuStyle {
5
+ /** Chat-log backend: numbered lines printed to chat, picked by typing the number. */
6
+ Chat = "chat",
7
+ /** CS2 center-screen HTML backend: W/S move the cursor, E selects; re-painted each tick. */
8
+ Center = "center",
9
+ }
10
+ /** Why a menu closed without a selection — the {@link MenuCancelEvent.reason}. */
11
+ export declare const enum MenuCancelReason {
12
+ /** The player chose the Exit control (or `close`/`cancel` was called). */
13
+ Exit = 0,
14
+ /** The `display` seconds elapsed with no selection. */
15
+ Timeout = 1,
16
+ /** The player left the server. */
17
+ Disconnect = 2,
18
+ /** A newer menu was shown to the same slot, superseding this one. */
19
+ NewMenu = 3,
20
+ }
21
+
22
+ /** Payload for {@link Menu.onSelect} — the item a player picked. */
23
+ export interface MenuSelectEvent {
24
+ /** 0-based player slot that made the selection. */
25
+ readonly slot: number;
26
+ /** 0-based index of the chosen item within the menu's item list. */
27
+ readonly item: number;
28
+ /** The `info` string the item was added with — the machine key, returned to your handler. */
29
+ readonly info: string;
30
+ /** The visible label the item was added with. */
31
+ readonly display: string;
32
+ }
33
+ /** Payload for {@link Menu.onCancel} — a menu that closed without a selection. */
34
+ export interface MenuCancelEvent {
35
+ /** 0-based player slot whose menu closed. */
36
+ readonly slot: number;
37
+ /** Why it closed (see {@link MenuCancelReason}). */
38
+ readonly reason: MenuCancelReason;
39
+ }
7
40
 
41
+ /**
42
+ * A slot-based interactive menu. Add items, wire {@link Menu.onSelect}/{@link Menu.onCancel},
43
+ * then {@link Menu.display} to a player slot. The model paginates automatically (7 items per chat
44
+ * page) and picks a renderer by {@link Menu.style}, falling back to Chat when the style's renderer
45
+ * is unregistered. Only one menu is active per slot; displaying a new one supersedes the old
46
+ * ({@link MenuCancelReason.NewMenu}).
47
+ * @example
48
+ * import { Menu, MenuStyle } from "@s2script/sdk/menu";
49
+ * const m = new Menu("s2script Menu Demo");
50
+ * m.style = MenuStyle.Center;
51
+ * m.freezePlayer = true; // freeze movement while the WASD menu is open (nav still works)
52
+ * m.addItem("hp", "Heal to 100");
53
+ * m.addItem("noclip", "Toggle Noclip");
54
+ * m.onSelect(e => { console.log(`picked ${e.info} (slot ${e.slot})`); });
55
+ * m.display(slot, 30);
56
+ */
8
57
  export declare class Menu {
9
58
  constructor(title?: string);
59
+ /** Heading shown above the item list. */
10
60
  title: string;
11
61
  /** Which registered renderer to use. Falls back to Chat if the style's renderer is unregistered. */
12
62
  style: MenuStyle;
@@ -18,7 +68,9 @@ export declare class Menu {
18
68
  freezePlayer: boolean;
19
69
  /** (info, display) — `info` is returned to onSelect; `display` is shown. */
20
70
  addItem(info: string, display: string, opts?: { disabled?: boolean }): void;
71
+ /** Register the callback fired when a player picks an item. Replaces any previous handler. */
21
72
  onSelect(handler: (e: MenuSelectEvent) => void): void;
73
+ /** Register the callback fired when the menu closes without a selection (see {@link MenuCancelReason}). Replaces any previous handler. */
22
74
  onCancel(handler: (e: MenuCancelEvent) => void): void;
23
75
  /** Show to a 0-based slot for `seconds` (0 = until selection/cancel/disconnect). */
24
76
  display(slot: number, seconds?: number): void;
@@ -28,11 +80,44 @@ export declare class Menu {
28
80
  static registerRenderer(name: string, renderer: MenuRenderer): void;
29
81
  }
30
82
 
83
+ /** A live display of one menu to one slot, passed to a {@link MenuRenderer}. Owns the page/cursor state and exposes the input verbs a renderer drives. */
31
84
  export interface MenuSession {
85
+ /** 0-based player slot this session is displayed to. */
32
86
  readonly slot: number;
87
+ /** The resolved snapshot the renderer paints: heading, per-line state, and 0-based page position. */
33
88
  view(): { title: string; lines: MenuLine[]; page: number; pageCount: number; exit: boolean };
89
+ /** Chat idiom: apply a number-key press against the current view (1..7 select, 8=Back, 9=Next, 0=Exit). */
34
90
  pickNumber(n: number): void;
35
- moveUp(): void; moveDown(): void; confirm(): void; cancel(): void;
91
+ /** Center idiom: move the cursor up over the current page's nav targets (items + Back/Next/Exit), wrapping. */
92
+ moveUp(): void;
93
+ /** Center idiom: move the cursor down over the current page's nav targets, wrapping. */
94
+ moveDown(): void;
95
+ /** Center idiom: activate the current cursor target (select an item, paginate, or exit). */
96
+ confirm(): void;
97
+ /** Close this session with an {@link MenuCancelReason.Exit}. */
98
+ cancel(): void;
99
+ }
100
+ /** One rendered line of a {@link MenuSession} view. */
101
+ export interface MenuLine {
102
+ /** The line's visible text. */
103
+ text: string;
104
+ /** The chat number key ("1".."0") that picks this line, or null for an unselectable/disabled line. */
105
+ key: string | null;
106
+ /** True if this line is a selectable item. */
107
+ selectable: boolean;
108
+ /** True if the center cursor is currently on this line (the renderer highlights it). */
109
+ cursor?: boolean;
110
+ /** For a control line, which control it is: `"back"`, `"next"`, or `"exit"` (absent on item lines). */
111
+ control?: string;
112
+ /** For an item line, its 0-based index into the menu's item list (absent on control lines). */
113
+ index?: number;
114
+ }
115
+ /** A display backend for {@link Menu} — registered via {@link Menu.registerRenderer}, driven with a {@link MenuSession}. */
116
+ export interface MenuRenderer {
117
+ /** Show the session for the first time (the menu just opened). */
118
+ open(session: MenuSession): void;
119
+ /** Re-paint the session after a page/cursor change. */
120
+ update(session: MenuSession): void;
121
+ /** Tear down the display for `slot` (the menu closed). */
122
+ close(slot: number): void;
36
123
  }
37
- export interface MenuLine { text: string; key: string | null; selectable: boolean; cursor?: boolean; control?: string; index?: number; }
38
- export interface MenuRenderer { open(session: MenuSession): void; update(session: MenuSession): void; close(slot: number): void; }
package/net.d.ts CHANGED
@@ -1,19 +1,43 @@
1
1
  /** @s2script/net — engine-generic raw TCP + UDP sockets (binary), off the game thread. */
2
+
3
+ /** A connected TCP client socket — a per-plugin handle over a copied byte stream (no live socket crosses to JS). */
2
4
  export interface TcpSocket {
5
+ /** Send bytes; a `string` is sent as its UTF-8 encoding. */
3
6
  send(data: Uint8Array | string): void;
7
+ /** Register a handler invoked for each inbound chunk of bytes. */
4
8
  onData(handler: (bytes: Uint8Array) => void): void;
9
+ /** Register a handler for the peer closing the connection. */
5
10
  onClose(handler: () => void): void;
11
+ /** Register a handler for a transport error; `err` is the error text. */
6
12
  onError(handler: (err: string) => void): void;
13
+ /** Close the connection. */
7
14
  close(): void;
8
15
  }
16
+ /** A bound UDP socket — a per-plugin handle for connectionless datagrams. */
9
17
  export interface UdpSocket {
18
+ /** Send a datagram to `host:port`; a `string` is sent as its UTF-8 encoding. */
10
19
  sendTo(host: string, port: number, data: Uint8Array | string): void;
20
+ /** Register a handler invoked for each inbound datagram; `from` is the sender's address. */
11
21
  onMessage(handler: (from: { host: string; port: number }, bytes: Uint8Array) => void): void;
22
+ /** Close the socket. */
12
23
  close(): void;
13
24
  }
25
+ /** Entry point for opening raw TCP + UDP sockets. */
14
26
  export declare const Net: {
15
- /** Connect a TCP client. Rejects on connect failure. */
27
+ /**
28
+ * Connect a TCP client to `host:port` off the game thread.
29
+ * @returns Resolves on connect with the live {@link TcpSocket} handle.
30
+ * @throws Rejects on connect failure (refused, unreachable, DNS error).
31
+ * @example
32
+ * import { Net } from "@s2script/sdk/net";
33
+ * const sock = await Net.connectTcp("127.0.0.1", 9000);
34
+ * sock.onData((bytes) => console.log("got " + bytes.length + " bytes"));
35
+ * sock.send("hello");
36
+ */
16
37
  connectTcp(host: string, port: number): Promise<TcpSocket>;
17
- /** Bind a UDP socket on an ephemeral local port. */
38
+ /**
39
+ * Bind a UDP socket on an ephemeral local port.
40
+ * @returns Resolves with the bound {@link UdpSocket} handle.
41
+ */
18
42
  udp(): Promise<UdpSocket>;
19
43
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@s2script/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "s2script SDK — the builtin capability types (imported as @s2script/sdk/entity, …) plus the `s2s` CLI for building Source 2 / CS2 plugins.",
5
5
  "type": "module",
6
6
  "bin": {
package/plugin.d.ts CHANGED
@@ -16,8 +16,16 @@ import type { TopMenuItem } from "./topmenu";
16
16
  import type { PrecacheContext } from "./sound";
17
17
  import type { UserCmdView } from "./usercmd";
18
18
 
19
+ /** Game-event subscriptions on this plugin's load-scope ({@link PluginContext.events}). */
19
20
  export interface CtxEvents {
21
+ /**
22
+ * Subscribe to a fired game event (post-phase). The {@link GameEvent} is valid only synchronously.
23
+ * @example
24
+ * // plugins/disabled/nextmap/src/plugin.ts:133
25
+ * ctx.events.on("round_end", () => console.log("round ended"));
26
+ */
20
27
  on(name: string, handler: (ev: GameEvent) => void): void;
28
+ /** Pre-hook a game event: return a {@link HookResultValue} (`Handled`/`Stop` suppress the client broadcast). */
21
29
  onPre(name: string, handler: (ev: GameEvent) => HookResultValue | void): void;
22
30
  }
23
31
  /**
@@ -27,37 +35,73 @@ export interface CtxEvents {
27
35
  * out of its real order).
28
36
  */
29
37
  export interface CtxClients {
38
+ /** A client began connecting (pre-auth). */
30
39
  onConnect(handler: (client: Client) => void | Promise<void>): void;
40
+ /** A client's entity was put in the server (`ClientPutInServer`). */
31
41
  onPutInServer(handler: (client: Client) => void | Promise<void>): void;
42
+ /** A client became fully active (in-game, receiving snapshots). */
32
43
  onActive(handler: (client: Client) => void | Promise<void>): void;
44
+ /** A client finished authenticating (Steam ticket validated). */
33
45
  onFullyConnect(handler: (client: Client) => void | Promise<void>): void;
46
+ /** A client disconnected. */
34
47
  onDisconnect(handler: (client: Client) => void): void;
48
+ /** A client's convars/settings changed (`ClientSettingsChanged`). */
35
49
  onSettingsChanged(handler: (client: Client) => void): void;
50
+ /** A client sent a voice packet (per-frame while speaking). */
36
51
  onVoice(handler: (client: Client) => void): void;
52
+ /** A client's persisted cookies finished loading and are now readable. */
37
53
  onCookiesCached(handler: (client: Client) => void): void;
54
+ /** A client sent chat: return a {@link HookResultValue} to suppress it. @param teamonly - team-channel say. */
38
55
  onSay(handler: (slot: number, text: string, teamonly: boolean) => HookResultValue | void): void;
56
+ /** Per-tick usercmd hook (SM `OnPlayerRunCmd`): read/modify {@link UserCmdView}; return `Handled` to block the tick. */
39
57
  onRunCmd(handler: (cmd: UserCmdView, info: { slot: number }) => HookResultValue | void): void;
40
58
  }
59
+ /** Entity lifecycle + damage subscriptions on this plugin's load-scope ({@link PluginContext.entities}). */
41
60
  export interface CtxEntities {
61
+ /** An entity of `className` was created (not yet spawned). @param className - match, or `"*"` for all. */
42
62
  onCreate(className: string, handler: (entity: EntityRef | null, className: string) => void): void;
63
+ /** An entity of `className` spawned (post-`DispatchSpawn`). */
43
64
  onSpawn(className: string, handler: (entity: EntityRef | null, className: string) => void): void;
65
+ /** An entity of `className` is being deleted; the ref goes stale right after. */
44
66
  onDelete(className: string, handler: (entity: EntityRef | null, className: string) => void): void;
67
+ /** Hook a named entity output (`FireOutputInternal`); return a {@link HookResultValue} to suppress it. */
45
68
  onOutput(classname: string, output: string, handler: (ev: OutputEvent) => HookResultValue | void): void;
69
+ /**
70
+ * Damage pre-hook (SDKHooks-equivalent): read/modify {@link DamageInfo}; return `Handled` semantics via `info`.
71
+ * @example
72
+ * // plugins/basecommands/src/plugin.ts:81 — halve incoming damage
73
+ * ctx.entities.onDamage((info) => { info.damage = info.damage / 2; });
74
+ */
46
75
  onDamage(handler: (info: DamageInfo) => HookResultValue | void): void;
47
76
  }
77
+ /** Per-frame + map/precache hooks on this plugin's load-scope ({@link PluginContext.server}). */
48
78
  export interface CtxServer {
79
+ /** Run `fn` every game frame. @param opts - `priority` orders it within the frame (`monitor` runs last, read-only). */
49
80
  onGameFrame(fn: () => void, opts?: { priority?: "high" | "normal" | "low" | "monitor" }): void;
81
+ /** A new map became live; `mapName` is the BSP name. */
50
82
  onMapStart(handler: (mapName: string) => void): void;
83
+ /** Precache window — register models/sounds to precache for the current map. */
51
84
  onPrecache(handler: (pc: PrecacheContext) => void): void;
52
85
  }
86
+ /** Console/chat command registration on this plugin's load-scope ({@link PluginContext.commands}). */
53
87
  export interface CtxCommands {
88
+ /** Register a public command (any client may run it). */
54
89
  register(name: string, handler: (cmd: CommandInvocation) => void): void;
90
+ /** Register a server-only command (console/rcon, not client-runnable). */
55
91
  registerServer(name: string, handler: (cmd: CommandInvocation) => void): void;
92
+ /** Register an admin command gated by `flags` (an `ADMFLAG` bitmask; fail-safe default-deny). */
56
93
  registerAdmin(name: string, flags: number, handler: (cmd: CommandInvocation) => void): void;
57
94
  }
58
- export interface CtxConfig { onChange(handler: (cfg: Config) => void): void; }
95
+ /** Config live-reload subscription on this plugin's load-scope ({@link PluginContext.config}). */
96
+ export interface CtxConfig {
97
+ /** Fires when the plugin's config file is re-materialized on disk; re-read values inside. */
98
+ onChange(handler: (cfg: Config) => void): void;
99
+ }
100
+ /** TopMenu (adminmenu) contribution on this plugin's load-scope ({@link PluginContext.topmenu}). */
59
101
  export interface CtxTopMenu {
102
+ /** Add (or reuse) a top-level menu category. */
60
103
  addCategory(name: string): void;
104
+ /** Add an item under an existing category. */
61
105
  addItem(category: string, item: TopMenuItem): void;
62
106
  }
63
107
 
@@ -67,29 +111,49 @@ export type InterfaceHandle<T extends object> = T & {
67
111
  on(event: string, handler: (payload: any) => void): void;
68
112
  };
69
113
 
114
+ /**
115
+ * A disposable bundle of subscriptions ({@link PluginContext.createScope}). Registering through a scope
116
+ * lets you drop the whole group at once with {@link Scope.clear} without unloading the plugin.
117
+ */
70
118
  export interface Scope {
119
+ /** Game-event subscriptions bound to this scope. */
71
120
  readonly events: CtxEvents;
121
+ /** Client-lifecycle subscriptions bound to this scope. */
72
122
  readonly clients: CtxClients;
123
+ /** Entity/damage subscriptions bound to this scope. */
73
124
  readonly entities: CtxEntities;
125
+ /** Per-frame/map subscriptions bound to this scope. */
74
126
  readonly server: CtxServer;
75
127
  /** Remove every subscription this scope holds; the scope stays usable (re-register on next open). */
76
128
  clear(): void;
77
129
  /** clear() + permanently retire the scope. Idempotent. */
78
130
  dispose(): void;
131
+ /** True once {@link Scope.dispose} has run. */
79
132
  readonly disposed: boolean;
80
133
  }
81
134
 
135
+ /**
136
+ * The load-scoped context passed to a plugin's {@link PluginFactory}. Its sub-objects register every
137
+ * subscription; all registration is load-window only (buffered, armed at Active).
138
+ */
82
139
  export interface PluginContext {
83
140
  /** This plugin's id (manifest `id`). */
84
141
  readonly id: string;
85
142
  /** The revived hot-reload handoff (the previous instance's `state()` return), or undefined. */
86
143
  readonly previous: unknown;
144
+ /** Game-event subscriptions ({@link CtxEvents}). */
87
145
  readonly events: CtxEvents;
146
+ /** Client-lifecycle subscriptions ({@link CtxClients}). */
88
147
  readonly clients: CtxClients;
148
+ /** Entity/damage subscriptions ({@link CtxEntities}). */
89
149
  readonly entities: CtxEntities;
150
+ /** Per-frame/map/precache subscriptions ({@link CtxServer}). */
90
151
  readonly server: CtxServer;
152
+ /** Console/chat command registration ({@link CtxCommands}). */
91
153
  readonly commands: CtxCommands;
154
+ /** Config live-reload subscription ({@link CtxConfig}). */
92
155
  readonly config: CtxConfig;
156
+ /** TopMenu (adminmenu) contribution ({@link CtxTopMenu}). */
93
157
  readonly topmenu: CtxTopMenu;
94
158
  /** Publish this plugin's manifest-declared interface. Buffered; goes live at Active. */
95
159
  publish<T extends object>(name: string, impl: T): PublishHandle;
@@ -101,12 +165,31 @@ export interface PluginContext {
101
165
  createScope(): Scope;
102
166
  }
103
167
 
168
+ /** Optional lifecycle hooks a {@link PluginFactory} may return to participate in unload + hot-reload. */
104
169
  export interface PluginHooks {
105
170
  /** Best-effort cleanup at unload (the ledger remains the teardown authority). */
106
171
  onUnload?(): void;
107
172
  /** Hot-reload handoff capture; JSON-serialized (EntityRef-aware) and revived as the next instance's ctx.previous. */
108
173
  state?(): unknown;
109
174
  }
175
+ /**
176
+ * A plugin's body: called once at load with the {@link PluginContext}, optionally returning
177
+ * {@link PluginHooks}. May be async (the load waits for it to settle).
178
+ */
110
179
  export type PluginFactory = (ctx: PluginContext) => void | PluginHooks | Promise<void | PluginHooks>;
111
- export interface PluginDefinition { readonly __s2plugin: 1; }
180
+ /** The opaque artifact {@link plugin} returns; a module must `export default` it to be a valid plugin. */
181
+ export interface PluginDefinition {
182
+ /** Brand tag proving this object came from {@link plugin} (host-checked at load). */
183
+ readonly __s2plugin: 1;
184
+ }
185
+ /**
186
+ * Define a plugin from its factory. `export default` the result — the host calls the factory once at load.
187
+ * @example
188
+ * import { plugin } from "@s2script/sdk/plugin";
189
+ * // examples/greeter-plugin/src/plugin.ts:8
190
+ * export default plugin((ctx) => {
191
+ * const handle = ctx.publish("@demo/greeter", impl);
192
+ * ctx.server.onGameFrame(() => handle.emit("greeted", { slot: 0 }));
193
+ * });
194
+ */
112
195
  export declare function plugin(factory: PluginFactory): PluginDefinition;
package/plugins.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  /** @s2script/plugins — runtime plugin management (the SM `sm plugins` backend). NO runtime code (injected). */
2
+
3
+ /** A snapshot of one plugin's identity and lifecycle state, as returned by {@link Plugins.list}. */
2
4
  export interface PluginInfo {
3
5
  /** the plugin id (from its manifest). */
4
6
  readonly id: string;
@@ -15,6 +17,13 @@ export interface PluginInfo {
15
17
  */
16
18
  readonly state: "running" | "loading" | "waiting" | "failed" | "unloaded";
17
19
  }
20
+ /**
21
+ * Enumerate and drive the plugin set at runtime (list / load / unload / reload).
22
+ * @example
23
+ * import { Plugins } from "@s2script/sdk/plugins";
24
+ * // plugins/basecommands/src/plugin.ts:126 — sm plugins list
25
+ * for (const p of Plugins.list()) cmd.reply(p.id + " — " + p.state);
26
+ */
18
27
  export declare const Plugins: {
19
28
  /** Every loaded/unloaded plugin. */
20
29
  list(): PluginInfo[];
package/server.d.ts CHANGED
@@ -1,4 +1,12 @@
1
1
  /** @s2script/server — server control (run console commands, query map validity). NO runtime code (injected at load). */
2
+
3
+ /**
4
+ * Server console + globals: run commands, register/read/set cvars, and query map/time state.
5
+ * @example
6
+ * import { Server } from "@s2script/sdk/server";
7
+ * // plugins/basecommands/src/plugin.ts:46 — sm_map
8
+ * Server.command("changelevel " + map);
9
+ */
2
10
  export declare const Server: {
3
11
  /** Run `cmd` at the server console (queued; executes next frame). */
4
12
  command(cmd: string): void;
package/sound.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /** @s2script/sound — engine-generic sound: emit a named SoundEvent + register custom precache paths. */
2
2
  import type { EntityRef } from "./entity";
3
3
 
4
+ /** Options for {@link Sound.emit} — the source entity, recipient slots, and volume. */
4
5
  export interface SoundEmitOptions {
5
6
  /** Source entity (serial-gated; a stale ref emits nothing and returns 0). Omitted -> worldspawn
6
7
  * (a global/2D sound). */
@@ -22,6 +23,14 @@ export interface PrecacheContext {
22
23
  add(path: string): boolean;
23
24
  }
24
25
 
26
+ /**
27
+ * Engine-generic sound entry point — play a named SoundEvent to some or all clients.
28
+ * @example
29
+ * import { Sound } from "@s2script/sdk/sound";
30
+ * // global 2D broadcast to every valid client (worldspawn source):
31
+ * const guid = Sound.emit("MyPack.Ping");
32
+ * if (guid === 0) console.log("emit failed (unresolved name / stale source)");
33
+ */
25
34
  export declare const Sound: {
26
35
  /** Play a named SoundEvent (the engine resolves name->hash; built-in soundevents need no
27
36
  * precache). Returns the engine sound GUID (nonzero) or 0 on failure (unresolved engine
package/timers.d.ts CHANGED
@@ -3,7 +3,13 @@
3
3
  * NO runtime code: the engine injects the implementation at load time.
4
4
  */
5
5
 
6
- /** Await a delay of `ms` milliseconds before continuing. */
6
+ /**
7
+ * Await a delay of `ms` milliseconds before continuing. Tick-integrated (resumes on a game frame).
8
+ * @example
9
+ * import { delay } from "@s2script/sdk/timers";
10
+ * // plugins/funcommands/src/plugin.ts:77 — sm_freeze auto-unfreeze after `secs`
11
+ * delay(secs * 1000).then(() => { const q = Player.fromSlot(slot); if (q && q.pawn) q.pawn.moveType = WALK; });
12
+ */
7
13
  export declare function delay(ms: number): Promise<void>;
8
14
  /** Yield to the next microtick. */
9
15
  export declare function nextTick(): Promise<void>;
package/topmenu.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  /** @s2script/topmenu — the extensible admin/top menu registry. NO runtime code (injected at load). */
2
+
3
+ /** A single registered top-menu entry (an admin command surfaced in the adminmenu). */
2
4
  export interface TopMenuItem {
3
5
  /** Plugin-namespaced unique id, e.g. "playercommands:slap". A duplicate id replaces. */
4
6
  id: string;
@@ -9,10 +11,23 @@ export interface TopMenuItem {
9
11
  /** Runs in THIS plugin's context when an admin selects the item; receives the admin's 0-based slot. */
10
12
  onSelect: (adminSlot: number) => void;
11
13
  }
14
+ /** A handler-free copy of the whole registry — what a menu renderer reads to build the display. */
12
15
  export interface TopMenuSnapshot {
16
+ /** All category names, in registration order. */
13
17
  categories: string[];
18
+ /** Every registered item's metadata (no `onSelect`), tagged with its category. */
14
19
  items: { id: string; category: string; name: string; flags: number }[];
15
20
  }
21
+ /**
22
+ * The shared admin/top-menu registry — renderers read {@link TopMenu.snapshot} and dispatch picks
23
+ * through {@link TopMenu.select}; items are contributed by plugins via their own ctx.topmenu.
24
+ * @example
25
+ * import { TopMenu } from "@s2script/sdk/topmenu";
26
+ * const snap = TopMenu.snapshot();
27
+ * for (const cat of snap.categories) renderCategory(cat);
28
+ * // when the admin picks an item id, dispatch it back to its owner plugin:
29
+ * m.onSelect(e => { TopMenu.select(e.info, slot); });
30
+ */
16
31
  export declare const TopMenu: {
17
32
  /** All categories + item metadata (no handlers) — the adminmenu renderer reads this. */
18
33
  snapshot(): TopMenuSnapshot;
package/trace.d.ts CHANGED
@@ -57,6 +57,14 @@ export interface TraceOptions {
57
57
  ignoreEntity?: EntityRef;
58
58
  }
59
59
 
60
+ /**
61
+ * Ray-tracing entry point — `line`/`ray`/`hull` traces over the Source 2 physics scene.
62
+ * @example
63
+ * import { Trace } from "@s2script/sdk/trace";
64
+ * import { Vector } from "@s2script/sdk/math";
65
+ * const down = Trace.line(new Vector(o.x, o.y, o.z + 500), new Vector(o.x, o.y, o.z - 100));
66
+ * if (down.didHit) console.log(down.fraction, down.endPos);
67
+ */
60
68
  export declare const Trace: {
61
69
  /** A line trace from `start` to `end`. */
62
70
  line(start: Vector, end: Vector, opts?: TraceOptions): TraceHit;