@s2script/sdk 0.5.0 → 0.6.0

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/entity.d.ts CHANGED
@@ -15,6 +15,8 @@
15
15
  * time" footgun). Obtain refs from the engine (events, `findByClass`, `readHandle`, …).
16
16
  */
17
17
  export declare class EntityRef {
18
+ /** This entity's slot index in the game entity system. Paired with the host-minted
19
+ * {@link EntityRef.id} for slot validation (books first, then identity-slot match). */
18
20
  readonly index: number;
19
21
  /** The host-minted liveness id for this ref (books key). Not the raw engine serial. */
20
22
  readonly id: number;
@@ -80,21 +82,33 @@ export declare class EntityRef {
80
82
  /** Write an int32 at the end of a pointer chain (each hop deref'd, liveness-gated at the root). Returns
81
83
  * false on a stale ref or a null hop. Used to clear a flag on a pointer-referenced sub-object. */
82
84
  writeInt32Via(pathOffs: number[], finalOff: number, value: number): boolean;
85
+ /** Read an i8 (sign-extended) at the end of a pointer chain (each hop deref'd, liveness-gated at the root); null if the root is stale or any hop is null. */
83
86
  readInt8Via(pathOffs: number[], finalOff: number): number | null;
87
+ /** Read an i16 (sign-extended) through a pointer chain; null if the root is stale or any hop is null. */
84
88
  readInt16Via(pathOffs: number[], finalOff: number): number | null;
89
+ /** Read a u8 through a pointer chain; null if the root is stale or any hop is null. */
85
90
  readUInt8Via(pathOffs: number[], finalOff: number): number | null;
91
+ /** Read a u16 through a pointer chain; null if the root is stale or any hop is null. */
86
92
  readUInt16Via(pathOffs: number[], finalOff: number): number | null;
93
+ /** Read a u32 through a pointer chain; null if the root is stale or any hop is null. */
87
94
  readUInt32Via(pathOffs: number[], finalOff: number): number | null;
95
+ /** Read an f32 through a pointer chain; null if the root is stale or any hop is null. */
88
96
  readFloat32Via(pathOffs: number[], finalOff: number): number | null;
97
+ /** Read a bool through a pointer chain; null if the root is stale or any hop is null. */
89
98
  readBoolVia(pathOffs: number[], finalOff: number): boolean | null;
99
+ /** Read a u64 as a BigInt through a pointer chain; null if the root is stale or any hop is null. */
90
100
  readUInt64Via(pathOffs: number[], finalOff: number): bigint | null;
101
+ /** Read an i64 as a BigInt through a pointer chain; null if the root is stale or any hop is null. */
91
102
  readInt64Via(pathOffs: number[], finalOff: number): bigint | null;
103
+ /** Decode a `CEntityHandle` at the end of a pointer chain into a liveness-gated {@link EntityRef}; null if the root is stale, any hop is null, or the handle is dead. */
92
104
  readHandleVia(pathOffs: number[], finalOff: number): EntityRef | null;
93
105
  /** Write a scalar through a pointer chain (write mirror of `read*Via`). Liveness-gated at the root;
94
106
  * returns false on a stale ref, an unresolved hop, or a bad offset/kind. Does NOT notifyStateChanged —
95
107
  * the caller decides (many sub-object fields, e.g. the fire gate, are server-authoritative).
96
108
  * (`writeInt32Via` is declared above alongside `readInt32Via`.) */
97
109
  writeFloat32Via(pathOffs: number[], finalOff: number, value: number): boolean;
110
+ /** Write a bool through a pointer chain (write mirror of {@link EntityRef.readBoolVia}; shares the
111
+ * no-notify semantics on {@link EntityRef.writeFloat32Via}). False on a stale ref, an unresolved hop, or a bad offset/kind. */
98
112
  writeBoolVia(pathOffs: number[], finalOff: number, value: boolean): boolean;
99
113
  /** Read a `CEntityHandle` at `offset`, decode it, and return a live `EntityRef` — or null if stale/invalid. */
100
114
  readHandle(offset: number): EntityRef | null;
@@ -138,7 +152,12 @@ export type EntityKeyValueMap = { [key: string]: string | number | boolean };
138
152
  /** Create a new entity by class name. WITHOUT keyvalues: create only — set fields, then call
139
153
  * `.spawn()`. WITH keyvalues: create + DispatchSpawn(keyvalues) in one call — a non-null result is
140
154
  * a LIVE, SPAWNED entity (on spawn failure the entity is removed and null returned). The created
141
- * entity is game-world-owned (NOT auto-removed on plugin unload) — the plugin owns cleanup. */
155
+ * entity is game-world-owned (NOT auto-removed on plugin unload) — the plugin owns cleanup.
156
+ * @example
157
+ * import { createEntity } from "@s2script/sdk/entity";
158
+ * const wt = createEntity("point_worldtext", { message: "s2-ekv-proof", fullbright: true });
159
+ * if (wt) wt.remove();
160
+ */
142
161
  export declare function createEntity(className: string, keyvalues?: EntityKeyValueMap): EntityRef | null;
143
162
 
144
163
  /** The payload delivered to an `Entity.onOutput` handler. */
@@ -155,6 +174,13 @@ export interface OutputEvent {
155
174
  delay: number;
156
175
  }
157
176
 
177
+ /**
178
+ * Entry point for entity lookup by class name.
179
+ * @example
180
+ * import { Entity } from "@s2script/sdk/entity";
181
+ * const triggers = Entity.findByClass("trigger_multiple");
182
+ * for (const t of triggers) console.log(t.index, t.name);
183
+ */
158
184
  export declare const Entity: {
159
185
  /** Find every entity whose designer-name (class) exactly matches `className`. Returns liveness-gated refs. */
160
186
  findByClass(className: string): EntityRef[];
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,7 @@
1
1
  {
2
2
  "name": "@s2script/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
+ "license": "MIT OR Apache-2.0",
4
5
  "description": "s2script SDK — the builtin capability types (imported as @s2script/sdk/entity, …) plus the `s2s` CLI for building Source 2 / CS2 plugins.",
5
6
  "type": "module",
6
7
  "bin": {
@@ -27,7 +28,9 @@
27
28
  "files": [
28
29
  "dist",
29
30
  "*.d.ts",
30
- "README.md"
31
+ "README.md",
32
+ "LICENSE-MIT",
33
+ "LICENSE-APACHE"
31
34
  ],
32
35
  "exports": {
33
36
  "./admin": {
@@ -124,6 +127,6 @@
124
127
  },
125
128
  "repository": {
126
129
  "type": "git",
127
- "url": "https://github.com/GabeHirakawa/s2script.git"
130
+ "url": "https://github.com/s2script/s2script.git"
128
131
  }
129
132
  }
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;