@s2script/sdk 0.4.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/entity.d.ts CHANGED
@@ -2,21 +2,30 @@
2
2
  * @s2script/entity — author-time type stubs for the injected entity API.
3
3
  * NO runtime code: the engine injects the implementation at load time.
4
4
  */
5
- import type { HookResultValue } from "./events";
6
5
 
7
6
  /**
8
- * A serial-gated handle to a live entity. Wraps the `__s2_ent_ref_*` natives; the raw
9
- * entity pointer never crosses to JS. All accessors degrade safely (return null/false)
10
- * when the entity slot has been reused or the ops table is absent.
7
+ * A host-liveness-gated handle to a live entity. `id` is a HOST-MINTED monotonic liveness
8
+ * id — liveness is decided by the host's books (fed by engine create/delete notifications,
9
+ * cleared at map transition), NEVER by reading the entity's own memory. Every access
10
+ * re-resolves: books first, then identity-slot validation, instance last. A stale ref
11
+ * degrades to null/false — including across a changelevel.
12
+ *
13
+ * The framework mints every `EntityRef`; plugin code never constructs one (the constructor
14
+ * is intentionally not part of the public surface — a hand-built ref is the "raw ref across
15
+ * time" footgun). Obtain refs from the engine (events, `findByClass`, `readHandle`, …).
11
16
  */
12
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). */
13
20
  readonly index: number;
14
- readonly serial: number;
21
+ /** The host-minted liveness id for this ref (books key). Not the raw engine serial. */
22
+ readonly id: number;
15
23
  /** This entity's targetname (`CEntityIdentity::m_name`) — e.g. a map trigger's `"map_start"`. `""` if
16
24
  * the entity has no targetname; `null` if the ref is stale/invalid. */
17
25
  readonly name: string | null;
18
- constructor(index: number, serial: number);
19
- /** True iff the slot's current serial still matches the captured serial. */
26
+ /** @internal The host mints refs; this is not part of the public API surface. */
27
+ private constructor();
28
+ /** True iff the host's books say live AND the identity slot still matches. */
20
29
  isValid(): boolean;
21
30
  /** Read an i32 at `offset` bytes into the entity, or null if the ref is stale. */
22
31
  readInt32(offset: number): number | null;
@@ -68,31 +77,45 @@ export declare class EntityRef {
68
77
  * is null. */
69
78
  readFloatsChain(ptrOffs: number[], finalOff: number, count: number): number[] | null;
70
79
  /** Follow a pointer chain (each an offset), then read a scalar at `finalOff`. null if the root is stale or any
71
- * hop is null. `readHandleVia` decodes a handle field → a serial-gated EntityRef; vectors use readFloatsChain. */
80
+ * hop is null. `readHandleVia` decodes a handle field → a liveness-gated EntityRef; vectors use readFloatsChain. */
72
81
  readInt32Via(pathOffs: number[], finalOff: number): number | null;
73
- /** Write an int32 at the end of a pointer chain (each hop deref'd, serial-gated at the root). Returns
82
+ /** Write an int32 at the end of a pointer chain (each hop deref'd, liveness-gated at the root). Returns
74
83
  * false on a stale ref or a null hop. Used to clear a flag on a pointer-referenced sub-object. */
75
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. */
76
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. */
77
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. */
78
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. */
79
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. */
80
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. */
81
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. */
82
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. */
83
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. */
84
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. */
85
104
  readHandleVia(pathOffs: number[], finalOff: number): EntityRef | null;
86
- /** Write a scalar through a pointer chain (write mirror of `read*Via`). Serial-gated at the root;
105
+ /** Write a scalar through a pointer chain (write mirror of `read*Via`). Liveness-gated at the root;
87
106
  * returns false on a stale ref, an unresolved hop, or a bad offset/kind. Does NOT notifyStateChanged —
88
107
  * the caller decides (many sub-object fields, e.g. the fire gate, are server-authoritative).
89
108
  * (`writeInt32Via` is declared above alongside `readInt32Via`.) */
90
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. */
91
112
  writeBoolVia(pathOffs: number[], finalOff: number, value: boolean): boolean;
92
113
  /** Read a `CEntityHandle` at `offset`, decode it, and return a live `EntityRef` — or null if stale/invalid. */
93
114
  readHandle(offset: number): EntityRef | null;
94
115
  /** Notify the engine that the field at `offset` changed (triggers network replication). No-op if stale. */
95
116
  notifyStateChanged(offset: number): void;
117
+ /** Raw identity-slot flags (engine m_flags), or null when stale/unavailable. Bit meanings are game-specific. */
118
+ identityFlags(): number | null;
96
119
  /** DispatchSpawn this created entity. With keyvalues, the entity's own Spawn() parses them (the
97
120
  * SourceMod DispatchKeyValue / CSSharp DispatchSpawn(kv) mechanism). Returns false if stale,
98
121
  * unresolved, or the keyvalue map is rejected (non-finite number, unsupported value type, empty
@@ -109,7 +132,7 @@ export declare class EntityRef {
109
132
  * `trigger_multiple` needs a model to build the physics volume that fires touch. Returns false
110
133
  * if the op is unavailable or the ref is stale. */
111
134
  setModel(name: string): boolean;
112
- /** Read a CUtlVector<CHandle> at (ptrOffs chain -> vectorOff) as live serial-gated EntityRefs.
135
+ /** Read a CUtlVector<CHandle> at (ptrOffs chain -> vectorOff) as live liveness-gated EntityRefs.
113
136
  * Follows the pointer chain, reads count@+0 / elements@+8, caps at maxCount. [] if stale/unresolved. */
114
137
  readHandleVector(ptrOffs: number[], vectorOff: number, maxCount?: number): EntityRef[];
115
138
  /** Fire an entity input (e.g. "Kill"/"Ignite"/"SetHealth"/"Enable"/"Open"/"FireUser1"/"AddOutput")
@@ -129,7 +152,12 @@ export type EntityKeyValueMap = { [key: string]: string | number | boolean };
129
152
  /** Create a new entity by class name. WITHOUT keyvalues: create only — set fields, then call
130
153
  * `.spawn()`. WITH keyvalues: create + DispatchSpawn(keyvalues) in one call — a non-null result is
131
154
  * a LIVE, SPAWNED entity (on spawn failure the entity is removed and null returned). The created
132
- * 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
+ */
133
161
  export declare function createEntity(className: string, keyvalues?: EntityKeyValueMap): EntityRef | null;
134
162
 
135
163
  /** The payload delivered to an `Entity.onOutput` handler. */
@@ -147,30 +175,13 @@ export interface OutputEvent {
147
175
  }
148
176
 
149
177
  /**
150
- * Hook Source 2 entity outputs (`func_button`→`OnPressed`, `trigger_multiple`→`OnStartTouch`,
151
- * `logic_relay`→`OnTrigger`, …) via a `FireOutputInternal` detour. `classname`/`output` accept `"*"`
152
- * wildcards. The handler runs SYNCHRONOUSLY (may block): returning a `HookResultValue >= Handled`
153
- * (2/3) SUPPRESSES the output — the original `FireOutputInternal` call is skipped.
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);
154
183
  */
155
184
  export declare const Entity: {
156
- onOutput(classname: string, output: string, handler: (ev: OutputEvent) => HookResultValue | void): void;
157
- /**
158
- * Fire when the engine CREATES an entity of `className` (`"*"` = all) — earliest hook; the entity is
159
- * barely constructed, schema fields may be zero/default. The handler receives the serial-gated
160
- * `entity` (may be `null`) plus its `className`. Prefer `onSpawn` to read fields.
161
- */
162
- onCreate(className: string, handler: (entity: EntityRef | null, className: string) => void): void;
163
- /**
164
- * Fire after the engine SPAWNS an entity of `className` (`"*"` = all) — `Spawn()` has run, so schema
165
- * fields/keyvalues are populated. The useful hook for reading state.
166
- */
167
- onSpawn(className: string, handler: (entity: EntityRef | null, className: string) => void): void;
168
- /**
169
- * Fire as the engine DELETES an entity of `className` (`"*"` = all). The entity is still readable
170
- * during the synchronous handler; a stashed ref reads `null` once the slot is freed (serial gate),
171
- * never garbage.
172
- */
173
- onDelete(className: string, handler: (entity: EntityRef | null, className: string) => void): void;
174
- /** Find every entity whose designer-name (class) exactly matches `className`. Returns serial-gated refs. */
185
+ /** Find every entity whose designer-name (class) exactly matches `className`. Returns liveness-gated refs. */
175
186
  findByClass(className: string): EntityRef[];
176
187
  };
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,23 +31,27 @@ 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
- /** Subscribe to a game event by name. The handler runs synchronously when the event fires. */
30
- on(name: string, handler: (ev: GameEvent) => void): void;
31
- /**
32
- * Unsubscribe from a game event by name.
33
- *
34
- * **Note:** `off` removes ALL of the calling plugin's handlers for the given event name —
35
- * handler identity is NOT compared. This matches the engine-op semantics: the mux removes
36
- * every subscription the current plugin holds for `name` in one call. Avoid double-subscribing
37
- * the same name with different handlers if you need selective removal.
38
- */
39
- off(name: string, handler: (ev: GameEvent) => void): void;
40
- /** Pre-hook: runs before the event broadcasts; may read+modify `ev` and return a HookResult to block. */
41
- onPre(name: string, handler: (ev: GameEvent) => HookResultValue | void): void;
42
55
  /** Fire a game event. Returns the engine FireEvent result. */
43
56
  fire(name: string, fields?: Record<string, number | string | boolean | bigint>, dontBroadcast?: boolean): boolean;
44
57
  /** Fire a game event to ONE client (SourceMod FireToClient). Same field type-inference as `fire`. Returns false on miss. */
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
@@ -4,33 +4,17 @@
4
4
  */
5
5
 
6
6
  /**
7
- * Handle returned by {@link publishInterface}: lets the producer emit forwarded
7
+ * Handle returned by `ctx.publish(name, impl)`: lets the producer emit forwarded
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
  }
14
-
15
- /**
16
- * Publish a typed inter-plugin interface under `name`. `impl`'s methods become the
17
- * natives consumers call (`interface.method(...)`); the returned handle's `emit` fans
18
- * forwarded events out to consumers' `on(event, …)` subscriptions.
19
- *
20
- * The interface's VERSION is injected by the host from this plugin's manifest
21
- * `publishes` map — never passed here, and never written in TypeScript source.
22
- * Publishing a name the manifest does not declare is refused at load.
23
- *
24
- * `impl` is generic over `object` rather than `Record<string, Function>` so that a
25
- * producer can bind it to its contract — `const impl: Zones = {…}` — which is what
26
- * actually proves the implementation matches the published `.d.ts`. A TypeScript
27
- * `interface` has no implicit index signature (only a `type` alias does), so a
28
- * `Record<…>` parameter would reject every interface-typed contract. The host
29
- * enumerates `impl`'s own function properties; non-function properties are ignored.
30
- *
31
- * Auto-ledgered: the interface is withdrawn (and hard-dep consumers degraded) on unload.
32
- */
33
- export declare function publishInterface<T extends object>(
34
- name: string,
35
- impl: T,
36
- ): PublishHandle;
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.4.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": {
@@ -14,7 +14,9 @@
14
14
  "dependencies": {
15
15
  "esbuild": "^0.25.0",
16
16
  "adm-zip": "^0.5.16",
17
- "typescript": "^5.6.0"
17
+ "typescript": "^5.6.0",
18
+ "eslint": "^10.7.0",
19
+ "@s2script/eslint-plugin": "0.2.0"
18
20
  },
19
21
  "devDependencies": {
20
22
  "@types/adm-zip": "^0.5.7"
@@ -64,9 +66,6 @@
64
66
  "./events": {
65
67
  "types": "./events.d.ts"
66
68
  },
67
- "./frame": {
68
- "types": "./frame.d.ts"
69
- },
70
69
  "./http": {
71
70
  "types": "./http.d.ts"
72
71
  },
@@ -82,6 +81,9 @@
82
81
  "./net": {
83
82
  "types": "./net.d.ts"
84
83
  },
84
+ "./plugin": {
85
+ "types": "./plugin.d.ts"
86
+ },
85
87
  "./plugins": {
86
88
  "types": "./plugins.d.ts"
87
89
  },